The thread/post might start off very basic and eventually grow to cover all aspects of the subject.
Note, this guide WILL be highly technical and advanced to users not familiar with binary formats and hex editors.
While i will do my utmost to keep it as user friendly as possible. I will not resort to hand holding in this guide.
If you don't understand something here fully i highly advise you to educate yourself on that using a combination of Google and asking questions before you venture into actually changing something and saving/using it.
This guide assumes you have extracted the game archives and have a powerful hex editor installed.
The basic guide to extract the game files can be found here: viewtopic.php?f=172&t=189533
A powerful hex editor, 010 Editor, can be found here (free trial): http://www.sweetscape.com/download/
I will not blame you if you extend that trial period, not even when you do it indefinitely, through a simple registry edit. But i will not go into further details on that. I just wanted to let you know that those 30 days can be extended that's all.
This guide will be written with 010 Editor used as the editor of choice. All code/scripts/templates are based on 010 Editor and as such i highly recommend you download a copy or you will not be able to follow along entirely.
Also, i would like to apologize for the large resolution of my screen in advance.
This guide was written for the game version that can be seen in the threads title/subject, but generally should apply to later versions of the game as well unless specifically changed.
You can find out what's been changed by checking the respective patch guides here: http://eurotrucksimulator2.com/mod_tools.php
------------------------------ END HEADER ------------------------------
Editing Game Files Using A Hex Editor.

Just setting the mood...
So you want to be a haxxor? Well, no... You want to be able to change things at their core rather than through some heavy, clunk, difficult to use program. Right?
In most people's minds (at least those i have met) a hex editor is as much black magic as Chinese is to someone who doesn't speak it. Most people associate hex editors with the dark arts.
But that isn't the case, not at all.
Everything looks daunting when you first look at it and you have no prior experience with what you see.

Three TOBJ files open in 010 Editor. (Click for full size)
Familiarizing yourself with the core concepts.
Let's do a crash course in digitally stored data before we move on. Starting with some of the basic data types.
Code: Select all
Names Bits Value range Num values Comment
------------------------------------------------------------------------------------------------------------------------------------------
char, byte 8 -128 to +127 256 Signed Byte or character
uchar, ubyte 8 0 to 255 256 Unsigned Byte or character.
short, int16 16 −32,768 to +32,767 65,536 Signed 16 bit integer
ushort, uint16, WORD 16 0 to 65535 65,536 Unsigned 16 bit integer
int, int32, long 32 −2,147,483,648 to +2,147,483,647 4,294,967,295 Signed 32 bit integer. (Most common for reasons) *
uint, uint32, ulong, DWORD 32 0 to 4,294,967,295 4,294,967,295 Unsigned 32 bit integer. (Also common)
int64, quad, __int64 64 -4,294,967,296 to 4,294,967,295 1.8446744e+19 Signed 64 bit integer.
uint64, uquad, QWORD 64 0 to 1.8446744e+19 1.8446744e+19 Unsigned 64 bit integer.
hfloat 16 https://en.wikipedia.org/wiki/Floating_point Half precision floating point number.
float 32 https://en.wikipedia.org/wiki/Floating_point Floating point number.
double 64 https://en.wikipedia.org/wiki/Floating_point Double precision floating point number.
string -- *Technically* Infinity An array of char. **
wstring -- *Technically* Infinity An array of wchar. ***
* - 32 bit integers are common because they fit neatly into memory chunks. Without getting too technical, if your data structure fits neatly into 4 byte
aligned chunks it's faster to access the data than if you had a mix of bytes, shorts and ints or quads.
** - To make a string you generally say "everything until the next null (0x00) byte or char" or alternatively "N chars" where N is a value read before the string.
Strings usually come LAST in a file for the alignment reason stated above but they don't have to and if they come before such numerical data as above they are
usually padded with null (0x00) bytes to make the numerical data align neatly but even that isn't necessary. It's just that programmers and those designing file
formats are usually very OCD about these things. ;)
Strings can be length prefixed or their length is stored somewhere else.
Strings that are length prefixed ALWAYS have the length of the string as a value right before it. The value can be in byte, short or int form.
Yes, technically the length prefix can also be in quad form but who the F uses strings longer than 4.2 billion characters?
*** - wchar is two chars in a pair. Read about unicode if you want to know more: https://en.wikipedia.org/wiki/Unicode
Not commonly used in ETS 2.
So if you have a programming language with data types, you should be able to copy-paste those type definitions straight into your hex editor (with minor editing) to read the data.
And the other way around holds true too. You could technically copy the data types from the hex editor to a programming language of choice that uses data types and use them in code.
We will revisit this later on when we start looking at templates and scripts.
Another thing you should be aware of is the difference between different bases. In this case base 2 (binary), base 10 (decimal) and base 16 (hexadecimal)
Hex (hexadecimal) editors generally show the contents of files bytes using hexadecimal numbers. As each byte is in the range 0-255 it's a perfect opportunity to use Hexadecimal.
Hexadecimal (as you should know, if not click here) ranges from 0, 1, 2, ..., 9, A, B, C, D, E, F.
It's as if you kept counting after 9 using the alphabet, only counting back at zero after you counted to the letter F.
So, instead of counting 1, 2, 3, ..., 8, 9, 10...
We count 1, 2, 3, ..., D, E, F, 10!
10 in hexadecimal is 16 in decimal. Got it? Cool!
How do we make the distinction between decimal and hexadecimal notation? It would get very confusing if i put 10 and actually meant 16, right?
There are many different ways. For the sake of simplicity i will ALWAYS use "0x" to represent hexadecimal numbers. If you want to know other ways of representing hex numbers then look them up!
Code: Select all
0x10 = 16 (1 * 16 + 0)
0x80 = 128 (8 * 16 + 0)
0xFF = 255 (15 * 16 + 15)
0x42 = 66 (4 * 16 + 2)
And for the heck of it...
0x1337 = 4919 (1 * 4096 + 3 * 256 + 3 * 16 + 7
With this newfound knowledge (assuming you didn't have it before that is) take a look at the previous screenshot again.
Can you tell what part of the files are numerical data and what parts of the files are strings? It's always a good idea when looking at new files to make the distinction between what makes up a string and what makes up binary data.
A brief introduction to reverse engineering file formats.
This is the process i tend to follow when trying to figure out a file format, distinguishing strings from binary data.
To help you make this distinction, i have highlighted the string in green and the binary data in red.

Numerical data vs string data.
If you want to follow along here, open the the file /vehicle/truck/upgrade/paintjob/paintjob_010.tobj from the game extracts. Doing so might make it easier for you to understand the process.
Then select the bytes highlighted and press CTRL+B (add bookmark), select the background (and foreground) color(s) and give it a descriptive name.
Moving on, as you can see the string here is very telling. It's the path of the DDS texture file that this TOBJ file is describing. This path is a Linux style path using forward slashes and it assumes the game/zip/scs archive is the "root" directory.
Hence, if this was inside a mod archive, the path is relevant only to the file structure inside the mod archive.
Thus, with this knowledge we KNOW what the string is most likely used for. That's why i highlighted it in a green tone to classify it as KNOWN.
One thing you should have noticed here too is, the string doesn't have a terminating null byte. That is, the string ends in 2E 64 64 73 or ".dds" and doesn't have any more bytes after it.
From the data types above, you should now know that strings are either null terminated or length prefixed... somewhere. And in this case we can assume the string is length prefixed due to a lacking null terminator.
This simplifies reverse engineering the binary data somewhat, because we can find this value and hence also mark it known when we do.
To start, we should find out how long the string is... There are at least 2 user friendly ways to do this.
1) Select the bytes of the string and look in the lower right corner of your editor.

Measure string length by selecting it.
2) Alternatively, since we bookmarked it, check your bookmarks.

Or check the size of your bookmark to see how long the string is.
Personally, i prefer the first way in this case because you get (at a glance) both the size in base 10 (decimal) and base 16 (hexadecimal) notation.
So the string is 48 (0x30) characters long.
Let's search for the length value in the data!
Remember what i said about length prefixed strings in that code segment above? Strings are either length prefixed, with their length right before the string. Or the length is defined elsewhere.
Since we can clearly tell the 4 bytes before the string are all null (0x00) bytes we can safely assume the string isn't length prefixed and the value is stored elsewhere.
So since the string isn't length prefixed... Remember what i said about 4 byte alignment? With this knowledge we should always assume the length of the string is defined as a 32 bit (4 byte wide) integer first. Only when that fails do we look for other value sizes.
Alas, let's search for a 32 bit integer with the value '48'. Press CTRL+F to search for data.

Search for data...
Even though we could use a signed integer here. Select Unsigned Int because we won't be looking for strings with a negative size anyways.
Type in the value we are looking for and press the text that reads All
You should then get this...

... and you shall find!
But how can we be sure that what we found really is the length of the string? It could just be coincidence, right?
Even so, sometimes you find more than one result... How can we tell the results apart?
Easy! We just repeat the process on many different TOBJ files!

Verifying what you have found.
Cool huh?
We can now bookmark that too because we are certain that we have found what we were looking for. But to do so, we need to remove the old red bookmark as you cannot place a bookmark on top of another bookmark.

Bookmarked path string length.
So what about the other data? How do we know what that does?
Generally, without a lot of researching you don't! So in the interest of not having you read a whole book on every subject there is to know about textures, 3D modeling and so on, let me just tell you what i know. Which isn't a lot to be honest because i don't care about the other data.

A template with coloring
Let me explain...
Green = Version number of the TOBJ file. This stays the same on all TOBJ files i have come across.
Red = Unknown data that never changes and is always null bytes, hence i cannot decipher it even if i wanted to.
Orange = Data that changes depending on several factors. Which i will get to later.
Blue = Make a guess...
Black = The path of the dds file this TOBJ file describes.
As you can tell, everything in the data (which from this point on we can call a header, because that's what it is) neatly aligns on 4 byte boundaries.
Hence it's probably safe to assume that all data in the header is stored in 4 byte chunks.
Now, let me explain the data marked in orange. I don't actually know what it does but i know it matters a lot to how the texture is displayed and used in game.
However to explain this i must first make a brief introduction to bit flags.
Remember that i talked about base 2 as the third alternative besides base 10 and base 16?
An integer stored in a binary format is actually stored as a sequence of bits. Just like counting in decimal where we add 1 to the left once we reached number 9 and just like hexadecimal where we add 1 to the left when we reach number F. In binary we add 1 to the left when we reach 1!
Hence, to count to ten in binary...
Code: Select all
Decimal Binary
00 0000
01 0001
02 0010
03 0011
04 0100
05 0101
06 0110
07 0111
08 1000
09 1001
10 1010
Imagine you had 8 different machines all doing various tasks. You have 8 switches to turn each one of them on and off... well it's the same thing here!
The game possibly has up to 160 different "machines" (settings) to toggle between on textures.
Some of these toggles are known by name thanks to the Wiki guide on SCS Blender tools.
But i haven't taken the time to map those settings to various flags in the TOBJ file format and i will explain why later.Settings of TOBJ file are:
U Repeat - repeat texture in U direction.
V Repeat - repeat texture in V direction.
TS Normal - tangent space normal for the texture.
No MIP Maps - don't use MIP Maps for the texture.
No Compress - don't use compression on texture.
If we lay out the binary data of all bytes marked in orange shown in the last picture we get this...
Code: Select all
00000001 00000000 00000010 00000000
00000010 00000000 00000011 00000011
00000011 00000000 00000010 00000010
00000000 00000001 00000000 00000000
00000000 00000001 00000000 00000000

A switchboard with on/off switches
I must put emphasis on 'might be' here. Again, i don't know because i haven't checked.
All i know is, the settings must be right for the model and texture used. Hence i ALWAYS copy an existing TOBJ file for the type of texture i am going to work with and only change the path.
It is a very common mistake among modders who use TOBJ files not suited for the texture they put in and then experience weird glitches and possibly even crashes. Especially in OpenGL mode.
The values DO change and they ARE important so make sure you use the right TOBJ file. And again, to know that you should copy an existing file that corresponds to what you are working on.
Of course, any of these 4 byte groups might actually be 32 bit integers too! Just FYI.
And you don't really have to worry about the things you don't know about. If you goal is to change the path inside a TOBJ file then you have already accomplished this goal! Just change the path and make sure the string size integer is updated, save the file and off you go!
Using Templates in 010 Editor
Templates and scripts are very powerful features of 010 Editor. They allow you to organize the data in a file after the specified value types.
For example, here's a template that is usable in 010 editor for TOBJ files:
Code: Select all
//--------------------------------------
//--- 010 Editor v5.0 Binary Template
//
// File: TOBJ.bt
// Author: Cadde
// Revision: 1.1
// Purpose: For ETS 2 TOBJ files.
//--------------------------------------
typedef struct {
uint32 version <bgcolor=cDkGreen>;
uint32 un2 <fgcolor=cRed, bgcolor=cDkRed, hidden=true>;
uint32 un3 <fgcolor=cRed, bgcolor=cDkRed, hidden=true>;
uint32 un4 <fgcolor=cRed, bgcolor=cDkRed, hidden=true>;
uint32 un5 <fgcolor=cRed, bgcolor=cDkRed, hidden=true>;
uint32 un6 <fgcolor=cBlack, bgcolor=0x0080FF>;
uint32 un7 <fgcolor=cBlack, bgcolor=0x0080FF>;
uint32 un8 <fgcolor=cBlack, bgcolor=0x0080FF>;
uint32 un9 <fgcolor=cBlack, bgcolor=0x0080FF>;
uint32 un10 <fgcolor=cBlack, bgcolor=0x0080FF>;
uint32 fileNameLength <fgcolor=cWhite, bgcolor=cBlue>;
uint32 un11 <fgcolor=cRed, bgcolor=cDkRed, hidden=true>;
if (fileNameLength > 0)
char path[fileNameLength] <fgcolor=cWhite, bgcolor=cBlack>;
} TOBJ <read = TOBJRead, write = TOBJWrite>;
string TOBJRead( TOBJ &tobj ) {
if (exists(tobj.path))
return tobj.path;
else
return "";
}
void TOBJWrite( TOBJ &tobj, string s ) {
local uint fileNameOffset = FileSize() - tobj.fileNameLength;
DeleteBytes(fileNameOffset, tobj.fileNameLength);
WriteBytes(s, fileNameOffset, Strlen(s));
tobj.fileNameLength = Strlen(s);
FileSave();
RunTemplate();
}
TOBJ tobj;
Lines starting with double forward slashes '//' are comments. They are ignored by the template engine and let us describe the template.
Code: Select all
typedef struct {
...
} NAME <options>
Compare headers to the name, address, zip code, country of a parcel/box with some extra details about the contents of the box.
Code: Select all
Name: Some Guy
Address: Some street 69
Zip: 1234 56 Some city
Country: Zimbabwe
Weight: 12.3 kg
Contents: 123 pcs of chocolate bars
Handling: Must be stored at temperatures below 20 degrees C.
So, the struct one defines is usually a header and contains the fields the application or "machinery" needs to handle the data (payload) that comes after it.
In the digital world, most things are boxed not just once but several times over. You put a box of data inside another box of data.
For example, your mod archive is a rack of boxes (files) that in turn has their own boxes of data specified using various kinds of structures.
In the case of our TOBJ file, this structure is rather simple. Consisting of...
Code: Select all
Version as a 32 bit unsigned integer.
Nine unknown instances of 32 bit unsigned integers.
FileNameLength as a 32 bit unsigned integer.
One unknown instance of a 32 bit unsigned integer.
I also want to add that the stuff at the end between '<' and '>' tags are optional settings for each field which decorates the data in the hex editor with colors and hides the fields from the template results that i don't care about since they will always be unknown to me.
Then follows two functions...
Code: Select all
string TOBJRead( TOBJ &tobj ) {
if (exists(tobj.path))
return tobj.path;
else
return "";
}
The '&tobj' parameter is a reference to what has been loaded before (i will get to that too) and is the type of structure we defined above. We can access the various named fields of that structure as you can see in the code snippet.
Basically, the function checks if 'tobj.path' has been set and returns the value of that if it is set. If it isn't set it returns an empty string.
The function is called when the above structure is being read. (as the structure options tells it to)
The read function has a corresponding write function too!
Code: Select all
void TOBJWrite( TOBJ &tobj, string s ) {
local uint fileNameOffset = FileSize() - tobj.fileNameLength;
DeleteBytes(fileNameOffset, tobj.fileNameLength);
WriteBytes(s, fileNameOffset, Strlen(s));
tobj.fileNameLength = Strlen(s);
FileSave();
RunTemplate();
}
Then it works with that 's' argument to reconstruct the TOBJ payload... First we determine the position in the file where the DDS file path starts. This is simple subtractive math.
Code: Select all
FileSize = 96
FileNameLength = 48 (but can change as you know)
FileNameOffset = FileSize - FileNameLength
48 = 96 - 48
After that we call 'DeleteBytes(offset, numBytesToDelete)' which removes all the data that the DDS path makes up in our file.
And then we call 'WriteBytes(buffer, offset, numBytesToWrite)' which adds the contents of the string 's' to the file. As i said before, strings are arrays of chars and chars are actually bytes. Hence we write bytes back into the file and those bytes are stored in the 's' input parameter of the function.
When those bytes have been written we change the 'fileNameLength' field of our referenced TOBJ structure to 'Strlen(s)' which in of itself is a function that counts the number of characters there are in the input string 's'.
To wrap it all up, we save the file with the 'FileSave()' call and run the template on the file again with 'RunTemplate()'.
The reason for this is that when you change the contents of a file via a template, 010 Editor doesn't seem to recognize the change or at the very least ignores it. Pressing File->Save doesn't save the file because the editor doesn't know the file has been changed. I don't know if this is intended behavior of 010 editor or if it's a bug. I don't care, i simply run that function and it writes the file because why else would you want to modify the file in the first place?
And i cause a re-run on the template because otherwise the user would have to do so manually.
And finally, there's a single "mysterious" line...
Code: Select all
TOBJ tobj;
Which gives us this!

A template with coloring
You might be asking "will i have to create my own templates now?"
No, you don't have to. I am merely showing you that you CAN if you have the energy to learn basic C programming skills or you already know how. There's extensive documentation on the subject on the 010 Editor webpage.
In any other case, i will provide templates for common SCS Software file formats to the best of my knowledge when asked. But not all formats are as simple as the TOBJ format to work with.
For now, you can use the template i have posted above in full. Simply click "Templates -> New template" and paste the code i gave you above into the .bt (Binary Template) file. Save it as "TOBJ.bt" and you will be able to use it.
Using the TOBJ template from above
Right, you have loaded the above template into your hex editor and you have pretty colors... "What do i do now?"
Double click here...

Type in the new path you want to use and press ENTER / RETURN and the templates TOBJWrite function takes care of the rest for you!
And this is where the whole procedure links in with why the file name is part of the "header" structure. The write function is called when you edit the value of the TOBJ structure as shown above. It passes a reference of the TOBJ structure to the write function and the 's' parameters is the contents of the text box you change after you double click the value.
To make it work in any other way i would have to provide a cumbersome script for this editing capability which would only make it harder to edit TOBJ files for no good reason.
And that is why the path is part of the "header" structure and how the reference to the header structure is passed to the write function.
---------------------------------------------------------------------
That is it for the guide for now, i will expand on the guide based on feedback from the readers.
No feedback? No more guide contents!
Please see the following posts for more templates / scripts.