Reloaded-Project / Reloaded.Hooks

Advanced native function hooks for x86, x64. Welcome to the next level!
GNU Lesser General Public License v3.0
216 stars 33 forks source link

There is not enough guide for using the library without using third-party tools #26

Open Albeoris opened 5 months ago

Albeoris commented 5 months ago

Hi! This is both a request to improve the documentation and an attempt to verify that my understanding of the process.

Please correct me if I'm wrong. To hook a native call in a C++ game, I need:

  1. Launcher: Write a C# application that will launch the game process in the Suspended state and inject a C++ DLL into it, wait for initialization to complete and resume the process. 1.1. To start a process in the Suspended state, use WinAPI CreateProcess with the appropriate flag. 1.2. To load the library, use WinAPI CreateThread(loadLibraryWPtr, pathPtr) 1.3. To allocate memory in a remote process and pass the path to the loaded library, use Kernel32.VirtualAllocEx 1.4. Reloaded.Injector is not suitable for this task, since it cannot inject itself into processes in the Suspended State. 1.5. To bypass the restrictions of Steam, which may refuse to enable the overlay if the original launcher has been replaced, register the custom launcher registration as Debugger in the Windows registry for the original .exe.

  2. Bootstrap: Write a native C++ DLL that will be injected by the launcher to the game process and then rise the .NET Runtime. 2.1 x86 for x86 games, x64 for x64 games 2.2. This is a simple example: Reloaded.Core.Bootstrap 2.3. This is a advanced example of DllMain which raises .NET Runtime in a separate thread, thereby avoiding deadlock: Reloaded.Mod.Loader.Bootstrapper

  3. .NET Mod: Write a managed .NET8+ DLL that will be loaded from async thread in DllMain and will use Reloaded.Hooks 3.1 Good example of how to call it from DllMain: Reloaded.Mod.Loader.Bootstrapper 3.2 RTFM: GettingStarted

Am I understanding everything correctly? Or are there already easier ways to do this?

P.S. Just in case, I’ll explain why, with lack of knowledge about the wonderful world of C++, I write a launcher myself, rather than using Reloaded-II:

It's all about delivering the mod to the end player: I would like to have one .exe file or .zip archive that needs to be launched/unpacked into the game, after which launching the game on Steam will launch a modified version of the game without performing any additional actions.

At the same time, I want to remain compatible with the latest version of all libraries (which makes it seem like a bad idea to ship Reloaded with the mod in the same archive).

At the same time, I want not to break compatibility with other mods that the player already has installed.

As far as I know, you are currently developing Reloaded-3. Perhaps these comments will give you some ideas.

In my ideal world, Reloaded could package itself and selected mods into a self-contained .exe file, which when run, would check for Reloaded in the game folder. If it exists, the mod will add it to it. If it is not there, it will unpack it from the archive or offer to download a newer version if there is a network. Then it will launch the game. And each subsequent launch through this .exe file will launch the game with the mod forcibly enabled (and the rest that are included in Reloaded, if any) without displaying the Reloaded window.

Sewer56 commented 5 months ago

I wasn't feeling well late yesterday, so sorry for the delay. Happened to forget about this too, in the morning.

Anyway, yeah, you pretty much got the jist of what it takes to load .NET Code in a native process from scratch. Simplifying this process is one of the reasons Reloaded-II exists, even though it has far outgrown its original purpose. Some notes below.

It's all about delivering the mod to the end player: I would like to have one .exe file or .zip archive that needs to be launched/unpacked into the game, after which launching the game on Steam will launch a modified version of the game without performing any additional actions.

You might find it easier to just deploy a portable version of Reloaded-II. For example, the Persona CEP is doing this. I've done something like this for a friend before.

Here's the source code and files for a self-contained installer that deploys Reloaded-II in a portable fashion to FlatOut2. You should be able to get this working for your purposes without requiring major source code changes.

R2 for FlatOut2.zip

Over there, I faked a bit of the process, as I actually included repacked game files for better load times. Of course, that's normally a no-no, but this wasn't a public release.

Anyway, you should be able to get this adjusted for your game with the following steps

  1. Throw a pre-configured game entry in .Reloaded-II/Apps 1.1. With the mods you want enabled already. 1.2. Note you can set a relative path; e.g. "AppLocation": "../../../FlatOut2.exe", 1.3. Pre-enable any mods you want enabled in the App config too. 1.4. Set DontInject: true. This is a recently added flag that launches an EXE without DLL Injection from launcher. This lets the game to reboot from Steam, so Steam Input works, and ASI Loader will kick in to load your code.
  2. Throw mods to include out the box in .Reloaded-II/Mods
  3. Adjust Program.cs in installer to instead install Reloaded-II to user's selected game folder. 3.1 Replace all mentions of FlatOut 2 FOJ Edition with full path to user's game folder.
  4. Copy ASI Loader, Launcher Start Script and Reloaded Bootstrapper to game folder 4.1 This gets your mods to automatically kick in when launched from Steam. 4.2 For ASI Loader, deploy it from R2, and grab the Reloaded.Mod.Loader.Bootstrapper.asi and whatever DLL ASI Loader spills out to game folder. 4.3 Launcher start script is just Run Mod Manager.bat.

User would just run Setup.exe, select their game folder, and the rest would be automated. End result would look something like:

image

Reloaded stuff in a subfolder, a .bat to launch Reloaded, ASI Loader, Reloaded Bootstrapper, and plain game files.

User can open Mod Manager from the .bat file. Clicking Launch Application from R2 will be equivalent to just normally running the EXE if DontInject is set to true. And ASI Loader will handle loading the bootstrapper, and thus R2.


Edge Cases

Sewer56 commented 5 months ago

Ah, I forgot. If you use the GameFinder library, you also will be able to auto locate the user's steam game. So you could in fact fully automate an install.

Albeoris commented 5 months ago

Brilliant! Thank you very much for the detailed explanation!

Sewer56 commented 5 months ago

No worries :p

Albeoris commented 5 months ago

which requires the game to be launched by Steam

This is how we solved this problem for FF9: SteamFix

Sewer56 commented 5 months ago

This is how we solved this problem for FF9:

Looks like you're stubbing a binary. Yeah, that's another way to handle it, albeit more game specific.