Pushing Valheim's Limits

I recently started playing the Viking survival game Valheim, released by Iron Gate Studios on February 2nd. Since release, it has surpassed 5 and a half million sales. It's a ton of fun, and I definitely think it's deserving of the explosion of attention it's gotten. A friend and I started a dedicated server and started playing pretty consistently, getting pretty close to endgame. At one point, he and I both died while exploring a swamp biome far away from our base, and we wanted to see if there was a way to fly back up there and get our stuff back with admin commands or something similar. Those commands existed, but it turned out that there was no way for either of us to access them on a server, even with admin privileges.


This got me thinking about how I could change that, because I knew that this game was made with Unity; something I have a decent amount of experience with. I also know that, because Unity games are powered by C#, their code should be able to be easily decompiled using a tool such as dnSpy or something similar, due to the way C# is compiled (C# -> IL/Intermediate Language -> native (executable) binary). Intermediate Language, as the name suggests, is not quite as close to machine language as, say, x86 assembly. .NET binaries are not compiled to machine code until they are actually executed, meaning what is stored on your machine until you start up the game is IL. Because IL isn't as "obfuscated" from a human perspective as machine code would be, it can be easily converted back into C#. So I started poking around Valheim's binaries using dnSpy, seeing what I could get out of the game.


Dev Commands

It turns out that enabling the built-in developer commands for use in multiplayer wasn't hard. Not only that, but I was able to enable them no matter what, even if I wasn't an admin.

The first thing that caught my eye when I opened dnSpy was a class called Console.


Console.cs in dnSpy


I quickly started looking around in this class, and I found the first thing I was looking for.

In the Console class, there is a method called InputText. It was clear that this was the method responsible for handling all Console input, and for dealing with the behaviors necessary depending on what commands were entered.


The InputText method

It didn't take too much looking around to find what I was really here for. Scrolling down a bit, I could see the text "devcommands" encased in an if-statement:




At first glance, this was all I needed. However, looking deeper, I realized that nothing of value was actually happening here - the boolean 'm_cheat' was simply being toggled, and the fact that cheats were just turned on/off was being logged using (what I assumed was) a logging class called Gogan. But there was nothing here that would be preventing me from enabling the dev commands in multiplayer; it was just toggling a variable. So I knew what I was really looking for would be somewhere else.


I kept scrolling down, and eventually, I came across all of the developer commands themselves:


The meaty bits

This definitely has what I'm looking for. Specifically, in the if-statement at the top, there are two conditions that are crucial here. Number one, ZNet.instance.IsServer() and number two, this.IsCheatsEnabled(). The first one would definitely cause me problems, because obviously, neither me or my friend are the server, we are just admins on the server. This condition would mean that dev commands simply cannot be used on a dedicated server, because with a dedicated server, no player is the server. The server is the server, but it is not a player. This is contrasted with a non-dedicated server, where one player is the host (server), and the rest are clients. Also, I needed to know what exactly caused this.IsCheatsEnabled() to return true. As I soon found out, this.IsCheatsEnabled() had the same condition:



Knowing this, I only had to do two simple things to (hopefully) gain access to the dev commands in multiplayer: remove the ZNet.instance.IsServer() requirement in InputText, and remove it in IsCheatsEnabled also. I was easily able to do that using dnSpy.


Some of dnSpy's extremely useful features, Edit Method highlighted


Using 'Edit Method', I removed the IsServer() requirement in IsCheatsEnabled:



and I removed it in InputText also:



So now, if all is working as intended, the game should only make sure we are 1. in an active server, 2. acting on the local player (our client & not someone else's), and 3. that we have enabled cheats (and that's all, no IsServer() checking). Opening up the game, joining the server, and running the devcommands console command, we get this result: 



This looks promising, but lets see if actually running a command does anything...



Looks like it worked! I can now spawn things, change the time of day, teleport, and change the weather (for example) on any server, regardless of admin status.


So, there are a few important things to be learned about Valheim from this. The biggest being, the servers are entirely (or almost entirely) client-authoritative. Basically, the server leaves all authenticity checking up to the client, and anything the client says is okay gets allowed by the server. It is well known that this approach leaves the door open to a lot of hacks such as this and other illegitimate behavior, but given the 10 player server limit, the overall co-op nature of the game, and the fact that all servers require a password to join, this was probably more of a design decision than a flaw and I doubt it will change anytime soon. Regardless, this was fun to explore, and it only scratches the surface of crazy things I was able to allow myself to do in multiplayer. I may write up some of the others, such as enabling debugmode (flying, instakilling, free build), giving myself full access to all wards (equivalent to cupboards in Rust; they prevent other players from building in your area), allowing myself to build in dungeons, creating a "bomb" hotkey that destroys/kills all objects within ~20 feet of me, and more in the future. Thanks for reading!

Copyright © 2014-2021 Iota