It depends on which aspect you want to understand. For the actual dll injection where you make the the original binary load your new dll, there are a couple of ways of doing it. You can patch the original with an extra dll import (I believe OpenRCT2 did this), but then you need to ship a patched original binary. You can make your dll pretend to be a dll already loaded by the original and then forward the function calls it makes to the actual original as well as patching in your own code when the dll loads (I believe GenTool does this). Finally you can have some kind of loader process that starts the target exe, pauses it immediately and then patches in a dll load command in memory before resuming the exe.
This final option is what Thyme uses, a small launcher program is also compiled along side the dll to do the startup and force the load of the dll. To get this information I basically did a lot of googling and consolidated information from different sources. Fortunately the work is done now, so in general any contributors to thyme won't need to know this stuff.
Regarding the actual reimplementation, mostly what you need to know are C++ to write the implementations and some disassembler and ASM knowledge to examine the original binary to find and study the original functions. If you were to purchase or otherwise acquire a commercial version of IDA it can also generate pseudo C code to make understanding a function easier.
Once you know the address in the binary that the original function lives at and you have your reimplemented version, there are functions in thyme that run when the dll loads that you can add to that take the original address and a function pointer and patch the original function to jump straight into your new function at run time so when the original code tries to call say the open file function, it will actually jump into your open file function.
There are also special functions that go the other way. Say there is a complex function you haven't figured out yet that gets called everywhere, you could use its address to create a function pointer to it to call it where its needed in your own code. Similarly you can access global variables that are shared between functions in the same way, by making a pointer or reference to them.
I would suggest reading some tutorials on writing code in x86 assembly to get an idea of how a program is structured after it has been compiled and probably grab at least the free version of IDA and load the ZH binary. When you load it, it will probably only reliably find winmain, I've done a fair amount of work at my end identifying functions in the game which makes it look a little less daunting and confusing. For BFME games, I recommend you load no-cd versions that have had safedisc stripped out, otherwise disassemblers have a hard time working out what is what due to how the protection worked. Its always the game.dat file that is the actual exe BTW, bfme.exe or whatever its called is just a loader. I suspect the unofficial patches that ship new game.dat files to not required the mini cd images are no-cd exes that have had the safedisc removed, so they will probably be fine.
- NDC likes this