Skip to content

How it works

Zetrith edited this page Jan 19, 2023 · 8 revisions

The mod's goal is to rewrite the game's assemblies at runtime during the game's startup, potentially applying patches useful to other mods and then swapping the assemblies in place without disrupting the starting sequence too much.

Rewriting and patching is handled using Mono.Cecil. Prepatcher orchestrates the last step.
In short, it abuses ReflectionOnly assemblies to hide and duplicate code and restart the game in-place. The longer explanation is below.

Constraints

  • The game process shouldn't need to restart as this isn't user friendly
  • The earliest entry point for a mod in RimWorld is during Mod instance creation. This happens after all game AND mod assemblies are loaded.

Startup timeline

(As assemblies/types are duplicated during runtime I'm going to denote them by (1) and (2))

  • RimWorld process starts
  • Unity loads Assembly-CSharp(1), caches RW's Unity component types: Verse.Root_Entry(1), Verse.Root_Play(1), etc.
  • Control is handed over to RW
  • Root_Entry.Start(1) runs, creates a new thread and starts loading game data there:
    • Assemblies(1) for ALL mods are loaded into memory
    • Mod types(1) are instantiated, their constructors run:
      • The Harmony mod(1) runs, patches VersionControl.DrawInfoInCorner
      • Prepatcher(1) runs:
        • Empties Verse.Root_*.Start(1), Verse.Root.OnGUI(1) using Harmony
        • Patches Verse.Root_*.Update(1)
        • Starts creating Assembly-CSharp(2) using Cecil and applies all patches
        • Serializes the Cecil assemblies into byte[]
        • Sets all mod assemblies(1), Assembly-CSharp(1) and internal MonoMod assemblies(1) as ReflectionOnly (this effectively hides them)
        • Loads Assembly-CSharp(2) and mod assemblies(2) (all transitive dependents of rewritten assemblies need to be reloaded)
        • Aborts the loading thread
  • The patched Verse.Root_Entry.Update(1) runs (on the main thread):
    • Destroys RW's Unity components(1)
    • Creates RW's Unity components(2)
  • Root_Entry.Start(2) runs, RimWorld code loads all data again
    • Harmony(2) runs
    • Prepatcher(2) runs, detects that it's (2) and doesn't do anything anymore
    • All other mods(2) init and run as if nothing happened
  • The only place where anything (1) is referenced after duplication is in Unity's internal caches (second starting step)
  • When a map is loaded:
    • Verse.Root_Play.Start(1) runs but it's empty
    • Verse.Root_Play.Update(1) runs
      • It's patched to do the same thing as Verse.Root_Entry.Update(1) (i.e. transfer control to (2) code)
Clone this wiki locally