drok / Harmony-CitiesSkylines

Harmony 2.x assembly provider mod for Cities: Skylines
Other
13 stars 3 forks source link

HarmonyLib dependent must not be an exported type #9

Closed drok closed 3 years ago

drok commented 3 years ago

Classes that use HarmonyLib features should not be public, because this causes ReflectionTypeLoadException errors and TypeLoadException errors.

Failure mechanism

The game loads mods in 3 phases:

  1. Scan all assemblies, note their assemblyName and directory.
  2. Load mod assemblies one mod at a time, starting with the dependency tree leaves.
  3. Instantiate enabled assemblies, and OnEnable() them
  4. further use.

The Harmony Mod assemblies may be loaded at phase 2, but some time after the mod that uses them. This should not matter, because no mods are instantiated until all the assemblies are loaded. If a dependent assembly is a perfect match (ie, matching public key token and culture), it is "borrowed" from the mod that supplies it, to load it into the mod that requires it. The borrowing only happens once, because once loaded into memory, it is used for all mods that need it. If two mods need the same dependency, that dependency will appear on the assembly list of the mod that happened the need it first, and no longer on the assembly list of the supplying mod.

If the match is not perfect (ie, either public key token or culture don't match), then the game cannot load the needed assembly as a leaf in the dependency tree of the mod that needs it. Instead, it would be loaded as a leaf in the dependency tree of the mod that supplies it. The effect is that by the time phase 3 starts, all needed assemblies are loaded.

However, if a mod with the dependency exposes this dependency through a public class (type), then the public class must be resolved when the game AddAssembly due to GetExportedTypes() - where it searches for an IUserMod type. Since the signed dependency is not yet loaded, GetExportedTypes() throws a TypeLoadException, and the IUserMod class is not found, and will not be instantiated later at phase 3.

In short, to avoid dependency order problems like this, the depended types must be inside internal or private types, so they do not need to be Resolved/loaded at AddAssembly() time.

Practical effects

This bug would cause affected mods to crash with TypeLoadException and be unloadable if a Harmony mod is not subscribed.

The expected behaviour is that mods should not crash, should load, and should either:

How to test if your mod is affected

  1. First Time User Scenario (user forgot to also subscribe the dependency Harmony):
    1. Subscribe both your mod and Harmony.
    2. delete the Harmony mod folder from the workshop folder.
    3. Start the game
    4. Observe crash logged in output_log.txt.
  2. User of local mods. (Epic Games players; content creators who require a stable, un-updated set of mods for production work; Players who run the game offline, on airplanes, for example; generally new players who are unaware of the Harmony workings):
    1. Subscribe your mod.
    2. Copy your mod to local %LOCALAPPDATA%. Do not copy the Harmony folder locally.
    3. Start the game with --noWorkshop.
    4. Observe crash logged in output_log.txt.
  3. Player who switches from boformer's Harmony to the redesigned (this) Harmony (users who want to access the Harmony Report to diagnose their mod collection issue):
    1. Subscribe your mod and boformer's Harmony.
    2. Unsubscribe boformer's Harmony
    3. Subscribe Harmony (redesigned)
    4. Start the game normally.
    5. Observe crash logged in output_log.txt.

Workarounds

Since this bug is effectively a load-ordering bug, it can be worked around by ensuring Harmony is subscribed and loaded before the affected mod.

  1. If used with local mods, Harmony should be in a folder with a name alphabetically earlier than the affected mod's (eg. 000-Harmony-should-load-first). Local mods are loaded in alphabetical order by folder name.
  2. If used with Harmony redesigned, the affected mods should be subscribed after Harmony is subscribed. Workshop items are loaded in the order they were subscribed.

Solutions

See the drok/PopulationDemographics@63d2bd9 and drok/PopulationDemographics@708c894 commits for potential ways to fix this issue.

drok commented 3 years ago

Mods that currently contain this bug:

(partial list, there may be others I'm unaware of)

drok commented 3 years ago

Issue is detected and reported on the Harmony Report as of 6d05eff

ghost commented 3 years ago

Population Demographics and Building Usage have been updated to gracefully handle missing Harmony mod.

drok commented 3 years ago

Population Demographics and Building Usage have been updated to gracefully handle missing Harmony mod.

Thank you for your quick fix, cheers!