This documentation is intended to anyone who wants to learn or wants to read the Ark Save file Directly.
This file is a Binary File generated by UE4's Save World Function, the first part is a "Ark File Header" which contains important information about the file, after this we have to read something called the Name Table which contains a basic table which will be the backbone for our reading or the file.
To understand all of this you need a basic knowledge of binary files, C++, and Number representation on a file (Float, Int32, UInt32, etc)
We are going to concentrate in describing save file Version 9, there's ark savefiles from version 5 but that is not well documented.
The basic flow to read the data of the Ark save file is as follows:
- Read the Ark File Header Object
- Read the Binary Data Names Object
- Read the Name Table Object
- Read the Embeded Binary Data Object
- Read the Unknown Data Object
- Read the Game Objects Array
- For each of the Game Objects read the Properties Object
Ark File Header Object[]
This is the start of the .ark savefile, which is a header containing some important information.
Name | Type | Length | Description |
---|---|---|---|
⬤ Save Version | Int16 | 2 bytes | The savefile version number, on this documentation we are describing version 09 |
⬤ Binary Data Offset | Int32 | 4 bytes | This is the file offset in which the Embedded Binary Data starts |
⬤ Unknown | Int32 | 4 bytes | This seems to be unused data (all test read 0) |
⬤ Name Table Offset | Int32 | 4 bytes | This is the file offset in which the Name Table Data starts |
⬤ Properties Block Offset | Int32 | 4 bytes | This is the file offset in which the Properties are stored |
⬤ Game Time | Float | 4 bytes | This section stores the in-game time |
⬤ Save Count | Int32 | 4 bytes | This section stores the amount of times the save file has been saved |
Binary Data Names Object[]
To read this table you need to read the String Data into the Object as many times as it was specified on the Array Length
Name | Type | Length | Description |
---|---|---|---|
Array length | Int32 | 2 bytes | This tells us how many Strings are contained in this Array |
String Data | String | Unknown bytes | The size of these objects is unknown as data is read on the fly |
Name Table Object[]
This table is crucial to keep reading the rest of the save file as it contains a lot of references needed to be able to understand the save file.
To read this Table you need to move your read cursor on the file to the Name Table Offset you got from the Ark File Header, do not forget to save the current cursor position as you need to go back to this position once you are done reading this data.
Name | Type | Length | Description |
---|---|---|---|
Array length | Int32 | 4 bytes | This tells us how many Strings are contained in this Array |
String Data | String | Unknown bytes | The size of these objects is unknown as data is read on the fly |
Embeded Binary Data Object[]
The use of this data is unknown, we will read an array of arrays, containing Int32 numbers. First we will read the Array Length and do a loop that many times, on each of these loops we will:
Read the next 4 bytes as Int32 and loop that many times reading each time another Int32 value into the SubArray.
Name | Type | Length | Description |
---|---|---|---|
Array Length | Int32 | 4 bytes | This tells us how many Arrays are contained in this Array |
SubArray Length | Int32 | 4 bytes | This tells us how many Strings are contained in this Array |
Int32 Data | Int32 | SubArray Length * 4 bytes | Int32 numbers on the array |
Unknown Data Object[]
This is some unknown data object, we need to read it correctly in case Ark uses it at some point
Name | Type | Length | Description |
---|---|---|---|
Array Length | Int32 | 4 bytes | This tells us how many Arrays are contained in this Array |
Unknown Number | Int32 | 4 bytes | This is an unknown number which appears to be some kind of flags |
Unknown Object Count | Int32 | 4 bytes | This is an unknown number which appears to tells us an object count |
String Data | String | Unknown bytes | Some Unknown String Data |
Game Objects Array[]
This is the main object which contains all the game objects
Name | Type | Length | Description |
---|---|---|---|
Array length | Int32 | 4 bytes | This tells us how many Game Objects are contained in this Array |
Game Object | Game Object | Unknown bytes | This is a Game Object which needs to be read accordingly. |
Game Object[]
Name | Type | Length | Description |
---|---|---|---|
GUID | UInt128 | 16 Bytes | The GUID of the object, It appears to not be unique |
Class Name | Ark Class Object | 8 Bytes | The class name of the object |
Item | Boolean Object | 4 Bytes | This defines if the object is an item |
Class Names Array Length | Int32 | 4 Bytes | The amount of Ark Class Objects contained on this array |
Class Name | Ark Class Object | 8 Bytes | This data Repeats itself the Amount specified in Class Names Array Length |
From Data file | Boolean Object | 4 Bytes | A boolean that tells if it's from Data File (Unknown meaning) |
Data File Index | Int32 | 4 Bytes | The Index of the Data file (Unknown meaning) |
Location Data Available | Boolean Object | 4 Bytes | This specifies if there's location data available, only if this is true the Next 24 bytes exist, otherwise the information does not exist and should be skipped |
Position Data | Location Data Object | 24 Bytes | This data is only available if the previous Location Data Available was true, otherwise we should skip reading these 24 bytes. |
Properties Offset | Int32 | 4 Bytes | This tells us the Offset of the Properties on which to start reading the properties of the object. |
Unknown Integer | Int32 | 4 Bytes | Unknown integer which appeaars to always be 0 (It may be simply a 4 byte long NUL) |
Properties Object[]
This is one of the most "fun" parts to read data from. To create this object we will fill it with an array of the objects we read from the file, first we need to sum the Ark File Header Object's Properties Block Offset with the Game Object's Properties Offset and begin reading from there, until we reach a Class Name named "None"
Name | Type | Length | Description |
---|---|---|---|
Class Name | Ark Class Object | 8 Bytes | Class name of the property if the Result is "None" we need to stop reading at the end of this property. |
Properties Content | Property Object | Unknown Bytes | If the Previous Class Name was not "None" then we begin to read the contents of the property accordingly |
Unknown Integer | Int32 | 4 Bytes | Unknown integer which appeaars to always be 0 (It may be simply a 4 byte long NUL) |
Extra Data | Extra Data Object | Unknown Bytes | Sometimes there's extra data at the end of the properties, you should save this data because it may contain important information. |
Property Object[]
After reading the property's Class Name you need to read the data according to it's classification:
BoolProperty Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | PropertyBoolean | 1 Bytes | The boolean Value |
ByteProperty Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read, Funny enough this sometimes does not tell us the exact size of bytes to read. |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | UInt8 Or Ark Class Object Or Raw Bytes | 1, 8 or Unknown Bytes | If Length is 1 read as UInt8, If Length is 8 then Read as a Ark Class Object followed with a value of another Ark Class Object, If the Length is different read as Raw Bytes. |
FloatProperty Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | Float | 4 Bytes | The Float Value |
DoubleProperty Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | Double | 8 Bytes | The Double Value |
IntProperty Object[]
This is read the same as Int32Property Object
Int8Property Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | Int8 | 1 Byte | The Signed Int8 Value |
UInt8Property Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | UInt8 | 1 Byte | The Unsigned UInt8 Value |
Int16Property Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | Int16 | 2 Bytes | The Signed Int16 Value |
UInt16Property Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | UInt16 | 2 Bytes | The Unsigned UInt16 Value |
Int32Property Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | Int32 | 4 Bytes | The Signed Int32 Value |
UInt32Property Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | UInt32 | 4 Bytes | The Unsigned UInt32 Value |
Int64Property Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | Int64 | 8 Bytes | The Signed Int64 Value |
UInt64Property Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | UInt64 | 8 Bytes | The Unsigned UInt64 Value |
NameProperty Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
Value | Ark Class Object | 8 Bytes | The Ark Class Name Object |
ObjectProperty Object[]
Name | Type | Length | Description |
---|---|---|---|
Length | Int32 | 4 Bytes | Length in bytes of the data to read |
Index | Int32 | 4 Bytes | Index of the data read (Used on some properties that repeat themselves multiple times) |
ID | Int32 | 4 Bytes | This is an ID of some sort (Unknown use) |
Value | Int32 Or Ark Class Object | 4 or 8 Bytes | If the Length is 8 then Read the Int32 value, if it's 12 read the Ark Class Object as Value |
StructProperty Object[]
ArrayProperty Object[]
PropertyText Object[]
StrProperty Object[]
Ark Class Object[]
When reading an Ark Class Object you need to read always 8 bytes, and when an index is defined (more than 0) you need to append that index at the name of the class, so if you have your Name Table Object and in position #72 you have PrimalItem_WeaponEmptyCryopod_C (Note: There's no position 0 on the Name Table Object) it means that if your index = 1 you need to append _0 at the end making it PrimalItem_WeaponEmptyCryopod_C_0 and if your index=135 then you need to append _134.
Name | Type | Length | Description |
---|---|---|---|
ID | Int32 | 4 Bytes | The corresponding ID on Name Table Object |
Index | Int32 | 4 Bytes | The index of this Name, the game takes the Names and represents them with a _INDEX at the end, if this number is 0 we do not append a _0 to the name, we leave the original name as is, if we find a 1 or more in this value we need to start counting from 0, which means that Index=1 is Append=_0 |
Data Types[]
Below are the data types and how to read them accordingly.
String[]
Name | Type | Length | Description |
---|---|---|---|
String Length | Int32 | 4 bytes | This tells us how many bytes to read |
String Data | String | String Length bytes | Read this data as a string, keep in mind the last character will be a 0x00 NUL character. |
Bool[]
Read 32Bit (4 Byte) In which the least significant bit indicates true
Double[]
Read 64Bit (8 Byte) Double Number
Float[]
Read 32Bit (4 Byte) Float Number
Int8[]
Read 8Bit (1 Byte) Signed Integer
UInt8[]
Read 8Bit (1 Byte) Unsigned Integer
Int16[]
Read 16Bit (2 Byte) Signed Integer
UInt16[]
Read 16Bit (2 Byte) Unsigned Integer
Int32[]
Read 32Bit (4 Byte) Signed Integer
UInt32[]
Read 32Bit (4 Byte) Unsigned Integer
Int64[]
Read 64Bit (8 Byte) Signed Integer
UInt64[]
Read 64Bit (8 Byte) Unsigned Integer
PropertyBoolean[]
Read 8Bit (1 Byte) In which the least significant bit indicates true.