Valheim Reversing Game File
How I reversed the character save file, and created a cheat tool for multiplayer servers.
DISCLAIMER: This was done purely for research and academic reasons, as an educational proof of concept, do not use it to cause harm or for ilegal purposes.
Recently a friend of mine offered me this game which has just been launched, and I played it online with my friends on a dedicated server for some days.
I then realized the game character would keep the items between multiplayer servers, also stats like life and stamina did not change when moving from server to server, or even from local to server, that caught my attention and made me think about how to exploit that.
Finding the character file
So the first thing I did was to look for where the game saved the character files. This was trivial, as I already knew that the worlds were saved in C:\Users\MY_USER\AppData\LocalLow\IronGate\Valheim\Worlds
as I've been syncing worlds between local and dedicated servers for a while, and just looking at the folder Valheim
we can see the following folders inside:
- worlds
- characters
Opening the characters
folder reveals all the characters I've created in-game.
Opening the file on VS Code reveals some gibberish, so I scrolled through it for a while looking for readable text, until I found this:
Bjørnm��������!�����H��B���A�/�B�"�FGP_Bonemass����g������SurtlingCore
������B����������������������� ArrowFired������B���������������w�o����Igvar
PickaxeBronze�����bC���������������m������BjørnShieldBronzeBuckler���LCC����������������m������Bjørn AxeBronze������C���������������m������Bjørn
CookedMeat������B�����������������������
FishCooked������B�����������������������BowHuntsman������B��������������m������BjørnHammer������B���������������m������Bjørn
CapeTrollHide���L��C��������������m������Bjørn
ArrowFlintd������B���������������w�o����Igvar
ArrowFlintd������B���������������w�o����IgvarArrowBronzed������B��������������#�1�����Mike LitorisArmorBronzeChest����ȹD�������������m������BjørnArmorBronzeLegs���s߀D��������������m������Bjørn
Cloudberry-������B�����������������������BeltStrength������B�����������������������
CookedLoxMeat������B�����������������������NeckTailGrilled
������B�����������������������HelmetTrollLeather���V�JD��������������m������Bjørn ArrowFired������B��������������d����
Here I noticed the name of my character Bjørn
, some items that I had on my inventory at the time, and more gibberish, which made it clear that I'm looking at a binary file.
Loading the character file
Since it's a binary file, the next step was to load it on a hex editor. For a quick preview I used VS Code plugin "Hex Editor" but then moved to HexFiend (on MacOS) and Frhed (on Windows).
Looking at the same data on a hex editor:
Understanding the file
Trial and Error
The first approach was to change the items names, this would make the character file corrupted, as I later discovered that the string size of the item name appears before the item name, so changing just the item name to a different string length would **corrupt the file. This means that the character would spawn naked with no items and that could not move.**
Besides corrupting the file, some bugs occurred while doing this process, which was also pretty interesting to see. For example, the camera popping out of the character when some overflows would occur.
Reversing the file
In this stage I realized I needed to better understand the binary data, this was done during several hours, by looking at the file, line by line, trying to make sense of it.
There are basically three approaches for this phase:
- Backup the character file, load the game, do one action (like move on item on the inventory, or change one quantity), close the game to force the update of the character file, and then compare both files (backup and new one)
- Stare at the data and try to extrapolate possibilities
- Change one byte on the file, load the game and try to see any difference (I do not recommend this approached as the odds are against you, although I was able to use this method to discover how to make the character invincible)
1. Isolate Actions and analyze the changes to the file
This is the best approach to quickly understand the file, the steps for this approach are the following:
- With the game closed, make a copy of the character file
- Load the game and make one change (preferably without moving the character)
- Close the game, this makes the game update the character file
- Look at the data and compare both files
The easiest way to see the differences between the files is just to do a diff
, HexFiend supports this feature, and on Windows I used VBinDiff which is a great tool btw.
After doing this for several times I noticed the first byte of the file kept changing as you can see in the image below.
The image above was taken when changing between equipped powers, from "Eikthyr", notice it has a length of 7
characters, to "Bonemass" which has a string length of 8
.
Every time the file is saved, some bytes change, I believe it's impossible to narrow it down to changing only one or two bytes, but even if 10 bytes change this method is still doable.
So, on the image we see that the first byte changed from 0xA4
to 0xA5
, which led me to believe that this byte was related to file size.
Also it would be pretty common to start a binary file with the number of bytes to read, usually an uint32 or uint64.
We can see the first 4 bytes are A5 02 40 00
, which would represent 4194981
in little endian. This decimal number is very close to the file size, just off by some bytes. After checking several character files I noticed this is a pattern, it seems to be the number of bytes in the file minus some number of header bytes (not sure exactly).
This method allowed me to find certain things like:
- power cooldown bytes
- item level
- x,y item position in inventory
2. Stare at the data and try to understand it
Sometimes from just looking at the data, analyzing what data comes before or after a byte, converting it to a decimal value can be enough to understand what it could mean.
For example:
... |<-- G -->| |H||<--- I --->||<--- J --->| ...
I r o n N a i l s
... 1F 00 00 00 09 49 72 6F 6E 4E 61 69 6C 73 0A 00 00 00 ...
The byte (H) before the ASCII string, is the decimal value of the exact length of the Item name, and the uint32 after (J) has the exact quantity I had in the inventory. Also as this is the first item in the item region of the file, I noticed the uint32 (G) corresponded to the total number of items I had on my inventory.
Note: The bytes are in little endian format.
3. Changing one byte at a time
This method consists in changing one byte close to other regions (like the item name) and seeing how it affects the game.
After finding out the region in the character file that represent the character life and stamina, I decided to play with it to see if I could increase it.
After some frustrating tries, where I would increase the values and nothing would happen on the game, I decided to go "all-in" on the changes and changed all the bytes to 0xff
. To my surprise, when I loaded the game, the character had no life and no stamina, but the game seemed kind of broken, because I could not attack or sprint. So I repeated the process but now only for the life bytes, and when I loaded the game again, the character had no life, but could not be damaged and everything worked fine.
Why did this happen?
I believe that the region I was playing with, stores the current value of life and stamina, but the max value is generated based on the food items you have equipped. Changing this values to the max uint32 size, means that on the next operation they might overflow and become negative, for that reason the character cannot be killed. Although, doing the same for the stamina breaks the game.
File Conclusions
I've seen three different file sizes, 1kb file upon character creation, 4MB file and 8MB file. Still don't have a clue of the reason for the file growing from 4MB to 8MB as 90% the file seem to be zeros.
From my analysis the file is divided on the following regions:
- Header
- Huge chunks of mostly zeros, no idea why (90% of the file is in this region)
- Map markers
- Character Stats
- Inventory
- Discovered items and recipes
- Food Inventory
1. Header
This file size: 8.398.158 bytes
06 25 80 00 21 00 00 00 00 00 00 00 1A 00 00 00 C5 00 00 00 7C 08 00 00 02 00 00 00
E6 D9 A0 BC FF FF FF FF 01 13 8B 42 45 5E FA 11 42 1A 2C 07 45 01 2E D7 51 41 F2 68
9A 42 6A 12 A0 C3 01 BF 8E 64 45 CD B3 F0 41 D6 0F 08 45 6E 81 5B 45 D9 24 FD 41 C2
69 F7 44 01 6C 02 40 00 04 00 00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00
06 25 80 00
: 8398086 dec
8398158 - 8398086 = 72 bytes
Just know that the first uint32 is related to file size.
2. Chunks of mostly zeros
00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00010101 01010101
01010101 01010101 01000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000
3. Map Markers Region
Started at 0x400060 in this specific file
. . . . . . . . . $ e n e m y _ e i k t h y r . ¯ . C .
00 00 00 00 17 00 00 00 0E 24 65 6E 65 6D 79 5F 65 69 6B 74 68 79 72 08 AF 84 43 03
. . B ´ k  à . . . . . . ` ý .  . . . . . . Ò Ã . . .
9F 9A 42 B4 6B C2 C3 09 00 00 00 00 00 60 FD 86 C2 00 00 00 00 A0 86 D2 C3 00 00 00
. . . r i v e r . j ! Â . . . . @ . Â . . . . . . . B l
00 00 05 72 69 76 65 72 00 6A 21 C2 00 00 00 00 40 8B 20 C2 00 00 00 00 00 0C 42 6C
a c k f o r e s t . . D . . . . è À . Ã . . . . . .
61 63 6B 20 66 6F 72 65 73 74 20 96 1A 44 00 00 00 00 E8 C0 8C C3 00 00 00 00 00 0E
c o p p e r d e p o s i t X . Ë Ã . . . . ø \ ? Ä . . .
63 6F 70 70 65 72 20 64 65 70 6F 73 69 74 58 00 CB C3 00 00 00 00 F8 5C 3F C4 00 00
. . . . f o r s a k e n a l t a r . o E Ã . . . . . í ....
00 00 00 0E 66 6F 72 73 61 6B 65 6E 20 61 6C 74 61 72 A0 6F 45 C3 00 00 00 00 00 ED ....
Did not dedicate any time to this region, but I'm assuming it has the coordinates and text for each marker.
$enemy_eikthyr
must refer to the world variable of where this boss is located.
4. Character Stats and Inventory Region
<-- prev |A| |<--- B --->||<--- C --->| |<-- D -->|
B J ø ø R N
00 00 00 06 42 6A C3 B8 72 6E 6D 1E F7 D1 00 00 00 00 00 01 1F 20 00 00 18 00 00 00
|<-- E -->| |<-- F -->| |<-- G -->| |<-- H -->| |I|| J||<--- K --->|
G P _ E i k t h y r
05 03 05 43 05 03 05 43 C3 C3 0C 43 00 63 64 82 45 0A 47 50 5F 45 69 6B 74 68 79 72
|<-- L -->| |<-- M -->| |<-- N -->| |O||<--- P --->||<--- Q --->||<-- R
I r o n N a i l s
00 00 00 00 67 00 00 00 1F 00 00 00 09 49 72 6F 6E 4E 61 69 6C 73 0A 00 00 00 00 00
-->||<-- X -->||<-- Y -->||S||T| |<--- U --->|| next item -->
C8 42 02 00 00 00 03 00 00 00 00 01 00 00 00 00 00 00 00 6D 1E F7 D1 00 00 00 00 xx
File uses Little Endian format, keep that in mind when reading more than one byte.
-
A. Player Name length: Example "bjørn", due to special character has an extra byte, 0x06 --> 06 dec
-
B. Player Name - variable in size (utf8)
-
C. No clue...
-
D. No clue... but is always
18 00 00 00
-
E. Life max value
-
F. Related to life, seems to be a timer or percentage?
-
G. Stamina max value
-
H. Related to stamina, seems to be a timer or percentage?
-
I. No clue...
-
J. Length of the power name, example: for "GP_Eikthyr" --> 0x0A --> 10 dec
-
K. Name of the Power, example: "GP_Eikthyr", variable length! Note: if
C
is 0,D
is 0 andE
does not exist -
L. Cooldown timer of the power, microseconds? not sure yet!
-
M. No clue... but it's always
67 00 00 00
-
N. Number of items in inventory, example: 0x1f --> 31 dec
-
O. Length of the upcoming item name, example: "IronNails" --> 0x09 --> 09 dec
-
P. Item name, example: "IronNails", variable length!
-
Q. Item quantity, uint32
-
R. Item durability
-
S. No clue...
-
T. Item level
-
U. No clue...
-
X. X coordinate of item in inventory
-
Y. Y coordinate of item in inventory
Notes:
- From the end of
B
toJ
it's always 35 bytes - From
L[0]
toO
, it's 12 bytes
5. Discovered Items and Recipes Region
<-- prev item region (last item)-->||A||<--- B --->||C| |<--- D
$ i t e m _ a x e _ s t
00 00 00 00 00 00 00 00 00 00 00 AC 00 00 00 0F 24 69 74 65 6D 5F 61 78 65 5F 73 74
--->||E| |<--- F --->|| next item (pattern repeats) -->
o n e $ i t e m _ c l u b $ i t
6F 6E 65 0A 24 69 74 65 6D 5F 63 6C 75 62 0C 24 69 74 ...
- A. No clue...
- B. Number of entries (items or other entries)
- C. Size of entry string
- D. Entry string
- E. Size of entry string
- F. Entry string
6. Food Items Region
<--- prev recipes and achievements region (last entry) ||A| |<--- B
$ t u t o r i a l _ w i s h b o n e _ t e x t B e a r
24 74 75 74 6F 72 69 61 6C 5F 77 69 73 68 62 6F 6E 65 5F 74 65 78 74 07 42 65 61 72
--->||C| |<--- D --->||<--- E
d 1 0 H a i r 1 4
64 31 30 06 48 61 69 72 31 34 9F 1F 6B 3F 9F 1F 6B 3F 9F 1F 6B 3F BB 60 3C 3F 81 D2
--->||<-- F -->||G||<--- H --->|
C o o k e d L o x M e a t
13 3F BD DC D4 3E 00 00 00 00 03 00 00 00 0D 43 6F 6F 6B 65 64 4C 6F 78 4D 65 61 74
|<-- I -->| |<-- J -->| |K ||<--- L --->||<-- M -->||<-- N -->||O|
C o o k e d M e a t
70 83 69 41 AC 6D 05 41 0A 43 6F 6F 6B 65 64 4D 65 61 74 00 45 84 41 80 67 46 41 0A
|<--- P --->||<-- Q -->||<-- R -->||<-- S -->||<-- T -->||<--
F i s h C o o k e d
46 69 73 68 43 6F 6F 6B 65 64 67 1A 95 41 E6 A9 25 41 02 00 00 00 0F 00 00 00 0B 00
U -->||<--- Continues until the file ends .............
00 00 D3 C8 22 41 00 00 80 3F 65 00 00 00 D1 40 94 40 00 00 00 00 64 00 00 00 DC D0
- A. Upcoming String Size
- B. Beard selected for the character
- C. Upcoming String Size
- D. Hair selected for the character
- E. No clue...
- F. Number of food items equipped
- G. String size of first food item
- H. Food item name
- I. Health modifier (not sure if it is summed, but think so) - modifying this value will affect character stats region (E)
- J. Stamina modifier (not sure if it is summed, but think so) - modifying this value will affect character stats region (G)
- K. String size of second food item
- L. Food item name
- M. same as I
- N. same as J
- O. String size of third food item
- P. Food item name
- Q. same as I
- R. same as J
- S. No clue...
- T. No clue...
- U. No clue...
Valheim Multiplayer Cheat Tool
I took this opportunity to experiment with Lorca framework, so this tool was done using GO and Lorca framework (webview) for frontend of the desktop app.
You can check the tool on github: valheim-imacheater
In this video you can see the tool in use:
Feel free to reach me if you would like to contribute to the reversing of the file.