0x0ade / FEZMod-Legacy

Kinda dead FEZ mod. Check the linked issue comment for further info.
https://github.com/0x0ade/FEZMod-Legacy/issues/5#issuecomment-341082111
MIT License
23 stars 2 forks source link

FEZMod as a modloader #26

Open ghost opened 8 years ago

ghost commented 8 years ago

I think that it would be nice to turn FEZMod into a modloader. The modloader would just patch the game, adding MEF (or other extensibility framework) support, while FEZMod itself (editor, speedrun...) would be in an addons folder for exemple.

I don't know if you will find that useful and how much time it would take to code this, but it would prevent patching the game at each update.

0x0ade commented 8 years ago

FEZMod already adds a minimalistic modloader layer. It already passes on hooked method calls to the mods (modules; f.e. InitializeMenu in .Speedrun).

Feel free to take a look at the abstract module class on engine and game level. The version seen in FezEngine.Mod.mm is just a stripped down version of what once was in FEZ.Mod.mm, but as FezEngine.dll can't refer to FEZ.exe, it's split in two.

MonoMod (the patcher) already looks for all ".mm.dll" mods.
The FEZMod "addons" (FEZMod.Speedrun, FEZMod.Editor) aren't hard-coded in FEZMod.Core. The only hard / deep dependency is the one to FEZMod.Core itself (FEZ.Mod.mm and FezEngine.Mod.mm), but I don't see that as an issue yet. What would a FEZ mod be without access to the modloader? Using MEF / dependency injection would be possible, but then what about the interface definitions? Should every mod keep a copy?

MEF looks interesting and reminds me of FEZ's ServiceHelper. As FEZ itself already uses ServiceHelper to resolve component dependencies, I'll keep using that for components.

Finally,

but it would prevent patching the game at each update.

Bad news: That's harder to do now than before. Hotswapping was actually possible before (as long as the hooked methods didn't change), but ever since MonoMod adds new types to the patched assembly (i.e. FEZ.exe, FezEngine.dll), making MonoMod.JIT obsolete, thus improving performance and making MonoMod easier to use, patching the game at each update is required for at least FEZMod.Core.

I could try to make FEZMod support non-".mm.dll" mods and make FEZMod.Editor be the first one as it hooks the least FEZ stuff (almost none). I could also try to hook all methods FEZMod.Core hooks currently, but use MEF to refer to FEZMod instead. The largest negative point I'd imagine with using MEF instead of FEZMod directly is performance, which is partially screwed up by FEZMod itself already. I'm at least currently trying to learn C# / .NET performance patterns / reuse the ones I've learnt from Java as I'm becoming more confident in C#. (FEZMod and MonoMod are my first ever C# project)

ghost commented 8 years ago

Okay, since all the classes of .mm.dll libraries are injected into FEZMod, MEF isn't needed — and given your arguments, it isn't a good idea... >.< I am closing this issue

0x0ade commented 8 years ago

I'd like to keep this issue going as a general "modloader discussion" issue. The way FEZMod acts as modloader isn't optimal (as in: I've already written a better implementation in my previous Java game engine project thing and would like to recreate it for FEZMod) - any constructive feedback is appreciated.

ghost commented 8 years ago

For MEF performance issues : please tell me if I am wrong, but it may only impact performance a little when instantiating classes, as this library uses System.Reflection.

since MonoMod adds new types to the patched assembly

I don't understand this sentence, especially why it would prevent just-in-time modding.

0x0ade commented 8 years ago

Reflection is always slower than direct access. If possible, System.Reflection.Emit should be used. Still, reflection is a powerful tool and I'm abusing it in places like the TAS mod (FEZMod.Speedrun) or XmlHelper. See Renaud's blog post about reflection performance: http://theinstructionlimit.com/fast-net-reflection Luckily, ReflectionHelper is included in Common.dll and already used by FEZ itself and FEZMod where possible.


About the "adds new types" sentence: Before the change in MonoMod I've linked to (and some further changes), MonoMod only carried code over that's in hooked types - exactly what you'd expect. One of the problems I had with this: Hooked code can call mod code; Mod code can't call added hooked code.

Let's say I'm adding InitializeMenu to FEZMod. Hooked code can find it, mod code can find it, everything's fine. (Actually, FEZ crashed calling it from MenuBase - more later in this wall of text.)

Let's say I'm adding Save to GameLevelManager. Everything patched into FEZ.exe, FezEngine.dll etc. can call the Save method without any problem - MonoMod changes the referenced assembly from [FEZ.Mod.mm] to [FEZ]. Now I'd like to call GameLevelManager.Save from the editor mod. "Sure," you'd say, "it calls GameLevelManager.Save.'

Uh. It calls GameLevelManager.Save and it doesn't work™. You wonder why, run monodis or any other IL disassembly tool and see: [FEZ.Mod.mm]GameLevelManager.Save Then you disassemble [FEZ.Mod.mm]GameLevelManager.Save and see it refers to [FEZ.Mod.mm]GameLevelManager's level data. And it's always null - [FEZ]GameLevelManager is where the fun's at.

Then you remember: "Oh, right, MonoMod doesn't fix the references in the non-hooked types! How would I fix that?" Patch these methods / types just-in-time OR patch the new types into FEZ.exe and others.

I went with MonoMod.JIT first. It worked... only on Linux, as always. And let's not ignore the performance hit and dependency issue - running MonoMod while the game is running, halting the game for a few seconds, just to fix a reference issue? And what if I delete / update MonoMod?

Recently I've hit other issues (MenuBase not finding mod methods because it's the first ever private type that's made public for FEZMod via hacks), making me say "screw this, gonna patch all the new types into the resulting assembly". And it works well... except that FezEngine.exe can't refer to FEZ.exe, and thus some FEZ.Mod.mm stuff had to move to FezEngine.Mod.mm.

Examples for ugly workarounds to the [.mm] issue: FEZMod changing MemoryContentManager behavior, GameLevelManagerHelper calling new methods in GameLevelManager Basically FEZMod calls existing FEZ methods, hooked wait for special strings. It's performant, but just a workaround.

Finally, it doesn't prevent just-in-time patching, and it's probably going to be used for non-.mm mods. It's obsoleted, deprecated, not in use and currently not maintained with currently no plans to further use it nor maintain it, but it works on one platform (Mono: Linux, Mac OSX, Mono for Windows) and could be fixed for .NET (Windows, .NET Core).

ghost commented 8 years ago

Thank you for your precision. I understand all now :) I'll look for the link you gave me.

And couldn't you use FEZ's ReflectionHelper in MonoMod ? Do you have the rights ? It is compatible with Mono.Cecil ?

0x0ade commented 8 years ago

I'm using it in MonoMod.JIT already: https://github.com/0x0ade/MonoMod/blob/master/MonoMod/JIT/ReflectionHelper.cs#L9

I'm using the word "stolen" as I don't see any license for the version on theinstructionlimit, but have credited @renaudbedard for it. If he has got any complaints with me using it, I'll remove it.