This is a nice approach. Another good approach is to patch the binary to load a DLL and then have that DLL overwrite function pointers or arbitrary bytes in the parent binary at runtime. Compared to just editing the binary directly, this has several benefits: it makes it easy to reimplement functions in C; you can document your assembly changes much better; patches by different people can be composed fairly easily.
Compared to your approach: this can be legally better (you can distribute a patcher and a DLL rather than a full binary); you don’t need to do the fixing up of the disassembly.
The “thinker” mod for Sid Meier’s Alpha Centauri is a good implementation of this. https://github.com/induktio/thinker
There’s also the PRACX and OpenSMACX projects doing similar things.
Indeed — in this case I mainly just wanted to be able to understand and instrument the binary better for RE purposes, but if I were planning on distributing some kind of augmentation then this is the way to go. I’ve used runtime hooking-based enhancements for certain games before, and it’s sometimes amazing the extent to which things can be enhanced. Entire ecosystems have upon occasion emerged from such tools.
A typical approach would be to have a “launcher” which spawns the original with CreateProcess using the SUSPENDED flag, then uses Read/WriteProcessMemory (and/or CreateRemoteThread) to inject some kind of shellcode — which, as you say, might choose to load the rest of itself by simply calling LoadLibrary. Nowadays, libraries like EasyHook make this sort of thing easier than ever.