User:Egor/Steam

There are two parts to Steam support in thcrap: SteamDRM bypass and Steam integration.


 * SteamDRM bypass is used so that the user can use the Steam versions of the games with thcrap, just as well as DRM-free versions.
 * Steam integration allows Steam to recognize that you're playing a given game when you run it through thcrap, regardless of whether the game exe is actually from Steam.

SteamDRM bypass
First thing to note is that sometimes we call it "cracking", because that's what it literally is. The reason why it's needed is because SteamDRM includes two completely useless features that just make it harder for tools like thcrap to work: Thcrap normally just patches the exe when it starts, but we can't do that with the steam version, because the data isn't decrypted yet. Instead we apply patches in stages: first we patch the SteamDRM stub in order to notify thcrap when the main program becomes decrypted, and then when that happens, we apply patches like we do normally. Now, because we modify the stub in the process of doing this, the stub's integrity check starts failing so we have to patch it out too.
 * the encryption
 * the integrity check

Example implementation: 1 2

As you can see, we can actually do most of the job before the steam version is even released. This is because the way SteamDRM applies to the game is deterministic enough that we can predict all the offsets using the original EXE's size. This doesn't always work, because occasionally steam updates the stub. The integrated SteamDRMP.dll gets updated even less often than the stub itself. I've mostly automated the process of generating the binhacks necessary for this.

I'm not particularly happy with the way the patching process works, because it violates an basic assumption of thcrap: a single game+version combination represents a single set of binhacks/breakpoints, no matter how many corresponding hashes there are in versions.js. The way it works right now, is that regardless of whether it's a steam version or not, thcrap attempts to apply the steam bypass, and if it doesn't work, it skips straight to the game binhacks. Ideally we'd have a "v1.00a-steam" version that would have the bypass, and the regular that wouldn't. The problem is that currently the only way to do it is to copy-paste everything else. We'd only want to keep the differences in the -steam version.

One scary consequence of keeping everything in one file is the fact that we might have to potentially deal with two versions of SteamDRM at once. In Janurary 2021, th11, th12 and th12 (yes, twice) were reuploaded on Steam, and each time a new exe was generated. Thankfully the protection hasn't been updated during the 6 months that passed since the original release on Steam, so all we had to do was add them to versions.js.

Note that this is a low-priority feature. If it becomes impossible to do in the future, we'll just tell people to download the original exe and overwrite the steam one. In fact, you should do it anyway, since that's what most of the dev team uses. There is zero downside to it, especially considering the following section.

Steam Integration
As side effect of the above, the games become completely disconnected from steam. In order to have stuff like overlay and play time tracking, we have to redo the integration with steam client. Fortunately it only involves loading the steam_api.dll and calling one function. See thcrap/src/steam.cpp for the implementation. The only caveat is that we need to know the steam app id for it to work. This is also stored in the game-specific .js file: example.

Notably, this works even if the exe you're using is from a Comiket release. This allows you to, for example, use thcrap with other mods which don't work with Steam and at the time still be able to use Steam overlay. Or to just avoid using the steam exe.

You can disable steam integration by either
 * not installing steam in the first place
 * removing steam_api.dll from thcrap's folder (it will be redownloaded on every update though)
 * setting "steam_appid" in your runconfig to an empty string