What will we do?:
-Decompile Valheim game code
-Read hex binary file
-Understand game save logic
-Remove fog of minimap
What we won't do?
-create hacks / cheats
What we need:
-Valheim game
-Basic understanding of hex structures
-A brain
We will split the tutorial in simple steps to get started:
1) Backup game files to safe location
2) Run game in offline mode, create new character and world.
3) Decompile game
4) Analyze game code
5) Disect file structures
6) Patch your game data
1) Backup game files to safe location
You need to backup your game data if you have any:
C:\Users\<yourusername>\AppData\LocalLow\IronGate\ Valheim
Copy the folder to a safe location to prevent game data loose if you do sth wrong.
C:\Users\<yourusername>\AppData\LocalLow\IronGate\ Valheim
Copy the folder to a safe location to prevent game data loose if you do sth wrong.
2) Run game in offline mode, create new character and world
To run your game in offline mode you have to disconnect from internet, this will prevent steam cloud to override your data files.
Now go ahead create a new character and a new world and wait until game stops loading and you spawn with your character in the world.
If your game works fine you should see a minimap on the right that looks something like this:
And expanded like this:
Our objective in this tutorial is to remove that fog in the map.
Now close your game and proceed to next step.
Now go ahead create a new character and a new world and wait until game stops loading and you spawn with your character in the world.
If your game works fine you should see a minimap on the right that looks something like this:
And expanded like this:
Our objective in this tutorial is to remove that fog in the map.
Now close your game and proceed to next step.
This sounds like a lot of work but it isn't, the game is made in unity c# with ilspy you get the game code without any work.
Open ILSpy then file open and go to
C:\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\ Managed
and search for a file named assembly_valheim.dll, click open and your game code should now be decompiled.
Make sure to click on the + next to {} to browse game classes.
Open ILSpy then file open and go to
C:\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\ Managed
and search for a file named assembly_valheim.dll, click open and your game code should now be decompiled.
Make sure to click on the + next to {} to browse game classes.
We did nothing yet but create our start point for this tutorial. Time to look thorugh game code to find what we want.
Remember our objective?, display minimap without fog, so first find the minimap in those clases.
Luckily for us a game class named minimap is in the code!
Lets take a look at whats inside:
For those who still don't know in the left we got game classes and in the right decompiled code. Looking through the code we find an interesting variable named m_explored that is a bool array.
Its a start.
We need to know where is used in order to understand map exploring logic.
Right click in the variable and click analyze, in the bottom we should see some results.
If you have to guess without looking you can say that:
-Reset -> reset explored flag
-Explore -> explore map and set flag
-isExplored -> check if vector is explored
-getMapData -> load map from file?
-setMapData -> set map data from array?
-start -> create bool array
Double click in any analyze function to get to the code
Reset
We guess right it reset explore state
Explore
With x,y we set flag to explored.
Maybe this is a bit tricky to understand but its a well known strategy to convert a 2d coordinate system to 1d.
explored is a 1d that represents game tiles.
Here is an example to understand what i mean with this:
Given any world coordinates in this case 16,7 we can transform those coordinates to 1d coordinates using the following formula:
x + y * worldWidth, and we can store our world data inside a 1d array like this: -> 1 1 1 1 1 1 0 0 0 1 1 0 0 0 1 1 1 1 1 1
or rearranging our data
1 1 1 1 1
1 0 0 0 1
1 0 0 0 1
1 1 1 1 1
World dimension: 5 tiles x 4 tiles, where 1 and 0 represent world data tiles or state or whatever you need.
This information will be useful ahead.
Get map data and set map data looks like this:
Looking though the code we can guess that getmap data writes something called zpackage with some data, and set map data reads binary data or sth.
So we need to know who saves this data to our game data files. We know that get should obtain last data so again we can use our analyze strategy with that function
We find out who uses this function and we get this:
Dead end that tell me nothing, but we can follow SetMapData function clicking it.
We set some value with the data but no file yet, dead end again?, not so much we found the class that contains player profile, so we check what that class can do for us.
Remember our objective?, display minimap without fog, so first find the minimap in those clases.
Luckily for us a game class named minimap is in the code!
Lets take a look at whats inside:
For those who still don't know in the left we got game classes and in the right decompiled code. Looking through the code we find an interesting variable named m_explored that is a bool array.
Its a start.
We need to know where is used in order to understand map exploring logic.
Right click in the variable and click analyze, in the bottom we should see some results.
If you have to guess without looking you can say that:
-Reset -> reset explored flag
-Explore -> explore map and set flag
-isExplored -> check if vector is explored
-getMapData -> load map from file?
-setMapData -> set map data from array?
-start -> create bool array
Double click in any analyze function to get to the code
Reset
We guess right it reset explore state
Explore
With x,y we set flag to explored.
Maybe this is a bit tricky to understand but its a well known strategy to convert a 2d coordinate system to 1d.
explored is a 1d that represents game tiles.
Here is an example to understand what i mean with this:
Given any world coordinates in this case 16,7 we can transform those coordinates to 1d coordinates using the following formula:
x + y * worldWidth, and we can store our world data inside a 1d array like this: -> 1 1 1 1 1 1 0 0 0 1 1 0 0 0 1 1 1 1 1 1
or rearranging our data
1 1 1 1 1
1 0 0 0 1
1 0 0 0 1
1 1 1 1 1
World dimension: 5 tiles x 4 tiles, where 1 and 0 represent world data tiles or state or whatever you need.
This information will be useful ahead.
Get map data and set map data looks like this:
Looking though the code we can guess that getmap data writes something called zpackage with some data, and set map data reads binary data or sth.
So we need to know who saves this data to our game data files. We know that get should obtain last data so again we can use our analyze strategy with that function
We find out who uses this function and we get this:
Dead end that tell me nothing, but we can follow SetMapData function clicking it.
We set some value with the data but no file yet, dead end again?, not so much we found the class that contains player profile, so we check what that class can do for us.
Click PlayerProfile class on the left, looking through the code we found something interesting:
loadPlayerDataFromDisk
This function is super simple, we load a file from disk in path characters/<name>.fch
If you didn't read any binary file yet, don't worry i will explain this steps now:
Go to
C:\Users\<yourusername>\AppData\LocalLow\IronGate\ Valheim\characters
and you should see a file named <character_name>.fch
Open HXD and drop that file in there, you should get something like this:
Following the read file logic we read first 4 bytes, (those selected in blue), and store it in count (our data size).
We can check that this is correct if we put our cursor at the end of our 4 bytes, then we press CTRL + G and enter the first 4 bytes in reverse order.
In our example our data size is: 51 01 40 00 -> READ FROM RIGHT TO LEFT -> 00 40 01 51
This is due to endianess: https://en.wikipedia.org/wiki/Endianness
I won't explain but basically you can read binary data in two ways, human way (from left to right) and machine way (from right to left)
Now following the code we should read 4 bytes again and read that data
We can see that last array of bytes has same length as count2 and its the end of the file, so we now have to know what those two are.
If we make a guess first data is player profile data and last 40 bytes is file checksum.
Moving on, we know that data contains player profile, so we need to edit our map data in order to reach our goal.
Time to go back to our loadPlayerFromDisk function
This could be a mess but trust me, it isnt a big deal. If you remember from previous step, first 4 bytes is data size, so we skip that bytes, now following this function we know that:
-> version type int (4 bytes)
-> x4 int values for player stats (16 bytes)
-> worldCount type int (4 bytes)
--- worldData Structure ---
Remember our getMap data and set map data?, is time to use them.
loadPlayerDataFromDisk
This function is super simple, we load a file from disk in path characters/<name>.fch
If you didn't read any binary file yet, don't worry i will explain this steps now:
Go to
C:\Users\<yourusername>\AppData\LocalLow\IronGate\ Valheim\characters
and you should see a file named <character_name>.fch
Open HXD and drop that file in there, you should get something like this:
Following the read file logic we read first 4 bytes, (those selected in blue), and store it in count (our data size).
We can check that this is correct if we put our cursor at the end of our 4 bytes, then we press CTRL + G and enter the first 4 bytes in reverse order.
In our example our data size is: 51 01 40 00 -> READ FROM RIGHT TO LEFT -> 00 40 01 51
This is due to endianess: https://en.wikipedia.org/wiki/Endianness
I won't explain but basically you can read binary data in two ways, human way (from left to right) and machine way (from right to left)
Now following the code we should read 4 bytes again and read that data
We can see that last array of bytes has same length as count2 and its the end of the file, so we now have to know what those two are.
If we make a guess first data is player profile data and last 40 bytes is file checksum.
Moving on, we know that data contains player profile, so we need to edit our map data in order to reach our goal.
Time to go back to our loadPlayerFromDisk function
This could be a mess but trust me, it isnt a big deal. If you remember from previous step, first 4 bytes is data size, so we skip that bytes, now following this function we know that:
-> version type int (4 bytes)
-> x4 int values for player stats (16 bytes)
-> worldCount type int (4 bytes)
--- worldData Structure ---
->world_key type long (8 bytes)Now we know where our map data starts in our file, is time to do something with it, but first we need to know how this data is handled.
-> have custom spawnpoint type byte (1 byte)
-> spawnPoint data (custom data value vector3 = x3 int values => 12 bytes)
-> haveLogout point (1 byte)
-> logoutVector (12 bytes)
-> haveDeathPoint (1 byte)
-> deathPointVector (12 bytes)
-> homePoint (12 bytes)
-> haveMapData (1 byte)
-> mapData --> (custom data value byteArray)-> size (4 bytes)
-> data (size bytes)
Remember our getMap data and set map data?, is time to use them.
Now we need to change map data from the start of m_explored data to the end, this should be easy -> simple math is 00 08 00 00 -> 2048 decimal,
array len -> 2048 * 2048 (is a 2d mapped to 1d array)
2048*2048 -> 0x400000 (hex notation)
Make sure your pointer position is after 00 08 00 00, and then press CTRL + E and set that length
Once selected we need to replace data, so press edit fill selection, in patterns just 01 and press ok.
You will get something like this:
Press CTRL + S to save your changes to the file.
Note: if you have multiple worlds (worldCount > 1) then you will have to find your target world or edit multiple worlds. Its recommended to create a new character and new world to keeps things simple.
Now its time to test this:
Open again valheim in offline mode to prevent steam overlapping your file.
Load your world and...
Note: file checksum is unverified yet, if is implemented in a future, you will need to generate checksum of new data.
Now whats next?
-> Use this information to create saved game parser and editor
->Why to stop there?, find a way to add items to your inventory editing this file
-> Find a way to activate cheats in game
->Why to stop there?, find a way to add items to your inventory editing this file
-> Find a way to activate cheats in game
Game Optimization
If you are reading this your game should freeze every time the world is saved in a server or local, that is because devs did things simple.
If you have read this post you will notice that character file is almost 90% of exploring data. Not only that but all world exploring state is saved in the same file, this means that if you play with a friend in a world then you have your own world and play in two or more worlds, the game file would save that world state data in a single file. And when loading/saving all file data is readed, and loaded into memory, including not loaded worlds
So how can devs optimize this file:
-Split world exploration data in multiple files or save in same file with index and load only the one you are using. (any of both strategies support world with different sizes, but actual game file structure won't)
-Since exploration state is a boolean (explored/not explored), you can save up to 8 regions in a single byte, using 1 bit per region. This will reduce file size 8 times.
-Compress data when u can.
If you have read this post you will notice that character file is almost 90% of exploring data. Not only that but all world exploring state is saved in the same file, this means that if you play with a friend in a world then you have your own world and play in two or more worlds, the game file would save that world state data in a single file. And when loading/saving all file data is readed, and loaded into memory, including not loaded worlds
So how can devs optimize this file:
-Split world exploration data in multiple files or save in same file with index and load only the one you are using. (any of both strategies support world with different sizes, but actual game file structure won't)
-Since exploration state is a boolean (explored/not explored), you can save up to 8 regions in a single byte, using 1 bit per region. This will reduce file size 8 times.
-Compress data when u can.