Game Dev

Steam DRM for Unity 3D Games

DRM in Steam is provided by a wrapper that you can apply to your game’s compiled executable. This wrapper checks to make sure that the game is running under an authenticated instance of Steam. Unfortunately the Steam wrapper doesn’t support .Net applications such as Unity. In the FAQS of Steamworks.NET C# wrapper for steamworks API you can read:[a]

Does the Steam DRM wrapper work with Unity or XNA/Monogame?
No it doesn’t. Unfortunately the Steam DRM wrapper does not play well with .NET applications such as Unity and your application will likely fail to start if it’s wrapped. Even if it does succeed and work for you it will often fail on other computers.

The Steamworks documentation recommends using SteamAPI.RestartAppIfNecessary() for DRM checking. SteamAPI.RestartAppIfNecessary() returns true if the application is launched directly by double clicking the game executable file. In this case your application should exit and the game will be launched by Steam if the client is installed on your system and you own the game. This is a very basic DRM check for your game, which ensures that you can’t launch the game standalone outside of the Steam client. Another way to make sure that the user owns the game is by checking that the SteamApps.BIsSubscribed() method returns true.

This is not a very strong form of protection but it works if you just pay attention to a big vulnerability flaw: SteamAPI.RestartAppIfNecessary() and SteamApps.BIsSubscribed() will always return false if a steam_appid.txt file containing the Steam application identifier of the game is found inside the game’s root folder. This file allows you to develop and test without launching your game through the Steam client. Just removing the steam_appid.txt file when building the production game depot isn’t enough. Anyone could create a text file containing the game’s appid and put it in the game root folder. Then, if the game doesn’t make any extra check, it could be very easy to run the game even if you don’t own it: you just need to have the Steam client opened when launching the game executable, so that the game can connect and communicate with it via the Steamworks API. The RestartAppIfNecessary and BIsSubscribed methods will return false, the application won’t exit after startup and the Steam client won’t make any additional check while communicating with the game.

You can make some tests with the Unity games in your Steam library:

  • Search for the game installation folder in your file system and double click the game executable. As expected, the game will exit just after launch, than the Steam client will start and launch the game.
  • Now take the steam application id of the game. To get it just create a desktop link for the game. In the properties of this link you will find a URL in the form steam://rungameid/appid containing the application id.
  • Inside the installation folder of your game, create a steam_appid.txt file containing the application id.
  • Now, with the Steam client opened, launch the game executable. If the game just checks the return value of  SteamAPI.RestartAppIfNecessary() or SteamApps.BIsSubscribed() without any additional check, the game will start standalone, even if you don’t own it.

I’ve done this test with some of the Unity games in my Steam library and a lot of them failed it. One of my games actually finds the steam_appid.txt file and deletes it during startup, but if I mark the file as read only, the deletion fails and the game starts standalone.

Here are some advices for a stronger DRM check at startup:

  • Before calling SteamAPI.RestartAppIfNecessary(), check if steam_appid.txt exists and try to delete it. If you cannot delete the file, just quit the application.
  • Use SteamApps.BIsSubscribedApp(appID) instead of SteamApps.BIsSubscribed(). This method won’t be affected by the steam_appid.txt file.

Here is some example code:

The above method will skip DRM checking during development when Application.isEditor is true. When the application is launched standalone it will detect and delete steam_appid.txt or just quit if the file can’t be deleted.

Checking if SteamAPI has been initialized

As PercyPea says in a comment below, there is another check that is important to add during game startup. If your startup code is based on the SteamManager.cs provided by the Steamworks.NET Unity package, you can see that when the Steam API initialization fails, the code provided simply returns without quitting the application:

One of the reasons for SteamAPI.Init() returning false is that Steam is not installed in your system. Make sure to execute an Application.Quit() when Init returns false:

Notes

  1. Steamworks.NET FAQS []

11 thoughts on “Steam DRM for Unity 3D Games

  1. PercyPea

    I could be wrong on this, but also it seems that if you copy your Unity3D game onto a system that doesn’t have Steam installed at all, the game will run. SteamManager.cs by default just seems to log an error that SteamAPI_Init() fails, but doesn’t then quit the Application, so the game itself can continue to run. (Albeit without any steam functionality)

    Reply
    1. Giuseppe Post author

      Yes, you are right. If Steam is not installed, SteamAPI.RestartAppIfNecessary and SteamAPI.Init() will both return false. If you don’t modify the SteamManager.cs base code, your game will run. I added an Application.Quit() on my version of the SteamManager but forgot to mention it here. I just added a section about it at the end of this article. Thanks!

      Reply
  2. Matej Vanco

    Hi, I’ve got my project in Unity and I actually use SteamAPI… My problem is:
    When I add your code, and start scene, everything works fine, but when I load another scene and switch back to the first scene, my project crashes. Do you have some ideas how to solve it? Big thank you.

    Reply
    1. Giuseppe Post author

      I’ve no idea about what’s going on in your project when you reload the first scene. You should do some debug to find out what’s causing the crash. One advice I can give you – since the first time you open the scene, the code runs fine – is to make sure that the DRM check is executed only once during the application startup. You can use a loading scene, some flag or a singleton to implement this behavior.

      Reply
  3. Will

    My title uses the steam remote storage API to read/write save files. My thought is this would provide some protection since accessing local/remote save data requires the Steam application to serve as an intermediary. Would this be effective?

    Reply
    1. Giuseppe Post author

      I don’t think so. If you launch the exe with the steam_appid.txt file in the same folder, the game will launch normally and will use the steamworks API to connect to the Steam backend. This means that the game will be able to load and save remote data using the steamworks API.
      The problem is that if you don’t make any chek on startup, Steam won’t make any check on its own, and just putting a steam_appid.txt file in the exe folder will be enough to run the game with full Steam integration.
      Bear in mind that even if you implement some check, this kind of DRM is so weak that it will be broken in minutes if some hacker wants to.

      Reply
  4. Domarius

    Thanks for writing this, it’s indispensable to me. Seems that this takes care of anything the casual user could do and the only path from here is to resort to actual hacking, which is much less of an issue unless you’re a big target. Should be plenty for small time devs like me. Much appreciated.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *