Tmpe: Updates to mod incompatibility checker

Created on 9 Jan 2020  路  19Comments  路  Source: CitiesSkylinesMods/TMPE

I'm pondering making some additional changes to mod incompatibility checker _after_ LABS gets updated to v11, so probably part of v11.1 release:

  • Definitions should specify criticality of the incompatibility:

    • Critical - guaranteed to break TM:PE in a bad way

    • Major - likely to break TM:PE but in a recoverable way (diable conflicting mod an things return to normal)

    • Minor - causes some problems but user can choose to ignore

  • Might be better to have a class that defines the incompatible mods, rather than txt file
  • Critical conflicts will always be checked, regardless of user settings
  • Distinction between major/minor is possibly irrelevant but just putting the idea out there for feedback

Thoughts?

COMPATIBILITY EXTERNAL adjustments required in-progress

Most helpful comment

I'd rather suggest using the main module's GUID instead of the version (as you already do for duplicate detection). FullName is not that great, better go with Name and GUID.

All 19 comments

Moving to code is a good idea. Syntax checks, and severity enum will also be checked by the compiler.
When you display "severity" to the user, they might not pay attention to what it means and will only care whether this run on their game + mods setup? And if you allow them to click ignore the conflicts, many will ignore (and suffer) without remembering the cause.

I was thinking the severity would override user choice to varying degrees.

Example:

  • Minor - respects user settings in mod options, eg. they can choose to bypass checking
  • Major - will always check and warn, but they can choose to ignore
  • Critical - will demand the conflict be resolved and game restart before continue

Another thing I'd like to add: Detect if multiple TMPE assemblies in the app domain. For example, user switches from LABS to STABLE while game is running = likely to suffer settings loss as game doesn't know which assembly is active. Would be useful to check for that and warn user maybe with button to exit to desktop.

Is it possible to check if there are multiple assemblies in RAM? (cc: @dymanoid)

Good idea, you can get list of all assemblies loaded to current AppDomain using AppDomain.CurrentDomain.GetAssemblies() :)

I assume Assembly.FullName will be sufficient to check whether there are multiple TMPE in RAM?

FullName is string which also contains version of assembly, so you would get false negative result when you compare across different build versions. I would try to find TrafficManager with other version than caller: Assembly.GetName().Name && Assembly.GetName().Version

I'd rather suggest using the main module's GUID instead of the version (as you already do for duplicate detection). FullName is not that great, better go with Name and GUID.

Info 28.7546777: Something went wrong while checking incompatible mods - see main game log for details.

Incompatible mod detector is failing completely when one or more mods were not instantiated correctly on game init(missing dependencies or other problem) - probably needs try...catch or additional check.

I have a try..catch around whole thing currently so the errors probably aren't great. I'll try and improve that for the v11.2 release as it might help track down some broken mods. I should at least be outputting the mod path or something as that would give some way to find mod even if it's not loading in to game properly.

Getting there....

Info 21.5842923: Assembly: TrafficManager v1.0.6877 in MOD ERROR
Info 21.5849921: Assembly: TrafficManager v1.0.6665 in MOD ERROR
Info 21.5856742: Assembly: TrafficManager v1.0.7080 in MOD ERROR
Info 21.5863450: Assembly: TrafficManager v1.0.7338 in MOD ERROR
Info 21.5870234: Assembly: TrafficManager v11.1.0 in MOD ERROR
Info 21.5882154: Assembly: TrafficManager v11.1.0 in \TrafficManager

Only the last mod (local build) was enabled.

I tried using asm.Location but it was empty for all mods. So then, seeing as I was going to need PluginInfo for the mods anyway (eg. to unsubscribe and other stuff), I tried Singleton<PluginManager>.instance.FindPluginInfo(asm); but it only works for enabled assemblies!

The parent folder (eg. workshop id in most cases) will be useful for logging and maybe also to display in whatever dialog I concoct.

I also, for TM:PE versions prior to 11.1.0, want to try and query the Version field or, if not present, the Name field/property on the TrafficManagerMod class - being able to show versions or name older mods or their name (which sometimes contains version) will be useful for disambiguation. I suspect I can do that via reflection, but haven't quite worked out how to yet.

Being able to get a date (either of build if such a thing exists, or file last changed on disk) would also be most useful.

Hrm, I can iterate Singleton<PluginManager>.instance.GetPluginsInfo() and create a dictionary of <Guid, PluginInfo> then I can cross-reference that with asm.ManifestModule.ModuleVersionId to link an assembly to a PluginInfo where possible.

But what to do if two PluginInfo.userModInstance.GetType().Assembly.ManifestModule.ModuleVersionId gives same Guid value? (Eg. someone uploads an old TM:PE to workshop without changing anything, there's now two mods with same Guid?)

Just dumping some info about old versions TM:PE TrafficManagerMod:

Version + game version available as far back as 1.7.1 (1.7.0 was released as version 1.7.1 by mistake) in 2016. Game version info, or lack thereof, will make any obsolete versions very obvious:

public static readonly string Version = "1.7.1";

public static readonly uint GameVersion = 155313168u;
public static readonly uint GameVersionA = 1u;
public static readonly uint GameVersionB = 5u;
public static readonly uint GameVersionC = 0u;
public static readonly uint GameVersionBuild = 4u;

.Version appears somewhere in .Name as far back as v1.9.0 in 2017:

public string Name => "Traffic Manager: President Edition [" + Version + "]";

Occasionally .Version has other stuff in it - how to parse in to a Version instance for easy comparison?

public static readonly string Version = "1.10.15-stable";

LABS vs. STABLE introduced in 10.21:

public static readonly string Version = "10.21";

#if LABS
        public string Branch => "LABS";
#elif DEBUG
        public string Branch => "DEBUG";
#else
        public string Branch => "STABLE";
#endif

Is this a viable way to clean -whatever from a string version number prior to using Version.TryParse()?

string clean = Regex.Replace(strVersion, "[^0-9\.]", string.Empty);

Or this from StackOverflow:

string clean = Regex.Match(strVersion, @"[0-9]+(?:\.[0-9]+)+").Value;

Which goes in to:

if (Version.TryParse(clean, out var version)) {
  // version  extracted
}
else {
  // source doesn't have a match
}

Just accidentally did a hot load after instinctively building project upon some changes ...and the assembly checker noticed the zombie!

Info 18.4499741: Assembly: TrafficManager v11.1.0 in MOD ERROR
Info 18.4506880: Assembly: TrafficManager v1.0.7338 in MOD ERROR
Info 18.4513620: Assembly: TrafficManager v1.0.7080 in MOD ERROR
Info 18.4520180: Assembly: TrafficManager v1.0.6665 in MOD ERROR
Info 18.4526769: Assembly: TrafficManager v1.0.6877 in MOD ERROR
Info 18.4536098: Assembly: TrafficManager v11.1.0 in \TrafficManager

// hot reload

Info 261.4814958: Assembly: TrafficManager v11.1.0 in \TrafficManager
Info 261.4823702: Assembly: TrafficManager v11.1.0 in MOD ERROR
Info 261.4830772: Assembly: TrafficManager v1.0.7338 in MOD ERROR
Info 261.4837739: Assembly: TrafficManager v1.0.7080 in MOD ERROR
Info 261.4844594: Assembly: TrafficManager v1.0.6665 in MOD ERROR
Info 261.4851739: Assembly: TrafficManager v1.0.6877 in MOD ERROR
Info 261.4863544: Assembly: TrafficManager v11.1.0 in MOD ERROR

Note 3 x 11.1.0 in second set of results. I just wish I could accurately get paths. Still working on it...

Also it looks like the game uses the assembly that's first in the list, assuming that's how hotloading works (it puts the newly detected assembly at top of list). I changed assembly version and did a build to trigger hotload at main menu:

Info 45.4962332: TM:PE 11.1.1 LABS designed for Cities: Skylines 1.12.3
...
Info 45.5013651: Assembly: TrafficManager v11.1.1 in \TrafficManager
Info 45.5022161: Assembly: TrafficManager v11.1.0 in MOD ERROR
Info 45.5028974: Assembly: TrafficManager v1.0.7338 in MOD ERROR
Info 45.5035862: Assembly: TrafficManager v1.0.7080 in MOD ERROR
Info 45.5042713: Assembly: TrafficManager v1.0.6665 in MOD ERROR
Info 45.5049646: Assembly: TrafficManager v1.0.6877 in MOD ERROR
Info 45.5060715: Assembly: TrafficManager v11.1.0 in MOD ERROR

So I'm wondering now if we can fake a hotload to choose which version of TM:PE is active. That would mean we could switch between LABS and STABLE at will - have both subscribed, and so long as only one is enabled it can get added to top of assemblues list...?

@krzychu124 @dymanoid do you think this would be viable thing to do?

So long as only one version of the mod is enabled, I can get it's Guid and scan to see if it's at top of asseblies list and, if not, fake a hotload to trick the game in to reloading it and putting it top of list = it's now the active assembly.

Finally got reflection mostly working:

Info 21.3403873: -- TrafficManager v11.1.0 LABS - aubergine
Info 21.3410013: -- TrafficManager v1.0.7338 STABLE - krzychu - should be v11.0
Info 21.3418188: -- TrafficManager v10.20 OBSOLETE - LinuxFan
Info 21.3424263: -- TrafficManager v1.10.6 OBSOLETE - tmhardie (alpha/debug)
Info 21.3430252: -- TrafficManager v1.10.12 OBSOLETE - 591857426 (fixed for industry dlc)
Info 21.3439599: -- TrafficManager v11.1.1 LABS - Local dev build

Not sure why 11.0 STABLE is being reported as v1.0.7338 STABLE (reverting to assembly version because it had some problem reading the TrafficManagerMod.Version member.

Now reliably getting versions and I've added special case for v10.20 to treat it as STABLE for now (manually annotated for clarity):

Info 21.3293601: Assembly: TrafficManager v11.1.0 LABS    -> aubergine
Info 21.3302959: Assembly: TrafficManager v11.0 STABLE    -> krzychu
Info 21.3309732: Assembly: TrafficManager v10.20 STABLE    -> linuxfan
Info 21.3316470: Assembly: TrafficManager v1.10.6 OBSOLETE    -> rogue upload
Info 21.3323342: Assembly: TrafficManager v1.10.12 OBSOLETE    -> rogue upload
Info 21.3333248: Assembly: TrafficManager v11.1.1 LABS    -> local dev build

Edit: Multiple things we can do now:

  • In UI, we can order assemblies by version
  • We can show which assemblies are obsolete based on their target game version

Should make it much easier for us to auto-purge dead versions (excluding most recent version), and make it easier for user to decide which verions to disable (assuming we can fake hotload if remaining single active version is not at top of asaseblies list).

The "hot load" won't help for old versions if they were already loaded. They will still remain in the process and might still process events causing unexpected behavior. There is no way to get rid of "dead instances" but to restart the game.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aubergine10 picture aubergine10  路  4Comments

blackeva8 picture blackeva8  路  5Comments

aubergine10 picture aubergine10  路  6Comments

aubergine10 picture aubergine10  路  5Comments

kianzarrin picture kianzarrin  路  4Comments