Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Albeoris opened this issue Jun 8, 2024 · 6 comments

Comments

@Albeoris
Copy link

Albeoris commented Jun 8, 2024

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
Copy link
Member

Sewer56 commented Jun 8, 2024

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.

  • Reloaded.Injector can be made to support the use case, I in fact might have even added that patch to the 2.0.0 branch, but I did deprecate it for the Rust WIP library.

  • To get your code running in the original process, you also can use a stub DLL, a.k.a. the ASI Loader approach. A third method for some super rare cases is hard patching the EXE to add a loader dependency.

    Although I don't like modifying the game folder at all, they are a necessary evil for the 'it just works' experience, as some end users, may for example rely on Steam Input for controller rebinding, which requires the game to be launched by Steam.

  • There's also some edge cases once you actually get your loader inside the game. One example is Steam DRM Wrapper. You have to delay loading mods because game may be encrypted on boot. Another is forced game reboot by Steam, if not launched by Steam; in which case your DLL Injected code is lost.

  • There's also a bunch of extra things to do inside your loader too. For example, AssemblyLoadContexts, to isolate mods so they can use their own dependencies. Spawning a console, creating dumps on crashes, etc.

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

  • You must run the Reloaded Launcher after setup, because it needs to update the loader paths in AppData/Roaming/Reloaded-Mod-Loader-II/ReloadedII.json, otherwise the bootstrapper won't find the loader. Unless you want to write a minimal file yourself, that would work too.

  • You might want to skip deploying ASI Loader if one's already present. AsiLoaderDeployer.cs from R2's source may help.

@Sewer56
Copy link
Member

Sewer56 commented Jun 8, 2024

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
Copy link
Author

Brilliant! Thank you very much for the detailed explanation!

@Sewer56
Copy link
Member

Sewer56 commented Jun 13, 2024

No worries :p

@Albeoris
Copy link
Author

which requires the game to be launched by Steam

This is how we solved this problem for FF9:
SteamFix

@Sewer56
Copy link
Member

Sewer56 commented Jun 14, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants