Skip to content

Game.Modding.ModManager

Assembly: Game (approx.; classes located in the game's managed assembly)
Namespace: Game.Modding

Type: class

Base: System.Object (implements IEnumerable, IEnumerable, IDisposable)

Summary: ModManager discovers, registers, initializes, and disposes game mods and UI modules for Cities: Skylines 2. It enumerates mod ExecutableAsset entries, loads mod assemblies, instantiates IMod-derived types (without running constructors via FormatterServices.GetUninitializedObject), invokes IMod lifecycle callbacks (OnLoad / OnDispose), registers UI modules with the UI system, reports progress and errors via NotificationSystem, and can mark the game as requiring a restart when enabled mods change. It uses logging and PerformanceCounter timing around heavy operations. The class also contains the nested ModInfo type which represents a single mod asset and manages loading, state, and instances for that mod.


Fields

  • private const string kBurstSuffix = "_win_x86_64"
    Provides the platform-specific suffix used to identify burst-compiled assemblies (constant).

  • private static ILog log
    Logger instance for mod-related messages; configured with UI error display disabled.

  • private readonly List<ModInfo> m_ModsInfos
    Internal list holding ModInfo entries discovered/registered by the manager.

  • private bool m_Disabled
    True when mod loading is disabled (ModManager created in disabled mode).

  • private bool m_Initialized
    Tracks whether Initialize(UpdateSystem) has completed successfully. Exposed via isInitialized.

  • private bool m_IsInProgress
    Guard flag to avoid concurrent initialization runs.

{{ YOUR_INFO }} - Notes: - The manager uses PerformanceCounter wrappers for timing Register/Initialize/Dispose operations and writes timings to log. - The NotificationSystem is used to show mod loading progress, per-mod errors/warnings, and restart-required prompts.

Properties

  • public bool isInitialized { get; }
    Returns whether Initialize(UpdateSystem) has completed. (Backed by m_Initialized)

{{ YOUR_INFO }} - Typical use: check isInitialized before adding UI modules dynamically or requiring a restart.

  • public bool restartRequired { get; private set; }
    Indicates whether a restart is required because mods were enabled/disabled after initialization.

{{ YOUR_INFO }} - When set to true, RequireRestart() has pushed a notification. The property is writable only by the ModManager (private set).

Constructors

  • private ModManager()
    Private default ctor. The code supplies it but primary external construction uses the bool overload.

{{ YOUR_INFO }} - Exists to prevent accidental parameterless construction from external code.

  • public ModManager(bool disabled)
    Public constructor. If disabled == false, pushes a "ModsLoading" NotificationSystem progress indicator while mods are subsequently initialized.

{{ YOUR_INFO }} - When created with disabled = true, the manager will not push progress notifications nor initialize mods. This is used by the GameManager when mods should be suppressed.

Methods

  • public IEnumerator<ModInfo> GetEnumerator()
    Returns enumerator over registered ModInfo entries (m_ModsInfos).

  • IEnumerator IEnumerable.GetEnumerator()
    Non-generic enumerator implementation calling the generic overload.

{{ YOUR_INFO }} - Use foreach to iterate registered mods: foreach (var mi in gameManager.modManager) { ... }

  • public static bool AreModsEnabled()
    Static helper that checks if GameManager.instance?.modManager?.ListModsEnabled() contains any entries.

  • public static string[] GetModsEnabled()
    Static helper returning GameManager.instance?.modManager?.ListModsEnabled() (or null).

  • public string[] ListModsEnabled()
    Returns names of loaded mods plus registered UI module names. Filters m_ModsInfos for loaded assets and concatenates with UIModuleAsset names from AssetDatabase.

{{ YOUR_INFO }} - Useful for displaying enabled mods in external UIs or telemetry.

  • public void Initialize(UpdateSystem updateSystem)
    Main initialization sequence:
  • Guards against disabled, already-initialized, or in-progress states.
  • Pushes a "Initializing mods" notification.
  • Calls RegisterMods() to discover ExecutableAsset mod entries.
  • Calls InitializeMods(updateSystem) to load mod assemblies and call IMod.OnLoad.
  • Marks m_Initialized and pushes final mod-loading notifications.
  • If any mod failed to load or produced warnings, creates per-mod notifications with dialogs offering to open the mod page or disable the mod.
  • On exception logs error and pops a failure notification.

{{ YOUR_INFO }} - The method is resilient: individual mod failures result in mod disposal and logged errors but do not abort the entire initialization loop. - After successful initialization, UI modules are also registered and the UI system is updated via GameManager.instance.userInterface.appBindings.

  • private void RegisterMods()
    Populates m_ModsInfos by calling ExecutableAsset.GetModAssets(typeof(IMod)) and wrapping each returned ExecutableAsset in a ModInfo. Errors during registration are logged but do not stop the process.

  • private void InitializeMods(UpdateSystem updateSystem)
    Iterates m_ModsInfos and calls ModInfo.Load(updateSystem) for each. Wrapping load with PerformanceCounter timing and disposing a mod on exception. Calls InitializeUIModules() after loading mods.

  • private void InitializeUIModules()
    Discovers UIModuleAsset entries from AssetDatabase, registers their host locations with UIManager.defaultUISystem (under "ui-mods"), and calls GameManager.instance.userInterface.appBindings.AddActiveUIModLocation with the module CUI paths. Logs registration.

  • public void AddUIModule(UIModuleAsset uiModule)
    If already initialized, registers a single UIModuleAsset host location and adds it as an active UI mod (used for dynamic addition).

  • public void RemoveUIModule(UIModuleAsset uiModule)
    If initialized, removes the UI host location and active CUI path for a module.

  • public void Dispose()
    Disposes all registered mods by calling ModInfo.Dispose() for each entry (timed with PerformanceCounter) and clears m_ModsInfos. Logs dispose timings and any errors per-mod.

  • public void RequireRestart()
    Marks restartRequired and pushes a "RestartRequired" NotificationSystem entry with a confirmation dialog. If the user confirms, GameManager.QuitGame() is called. If already set or not initialized, does nothing.

{{ YOUR_INFO }} - This method is called when enabling/disabling of mods requires a game restart; the notification provides Yes/No options.

  • public bool TryGetExecutableAsset(IMod mod, out ExecutableAsset asset)
    Attempts to find the ExecutableAsset that produced the given IMod instance by scanning m_ModsInfos and their instances collections.

  • public bool TryGetExecutableAsset(Assembly assembly, out ExecutableAsset asset)
    Attempts to find the ExecutableAsset whose loaded assembly equals the provided assembly.

{{ YOUR_INFO }} - Both TryGetExecutableAsset overloads return false and null asset if not found.

  • Nested type: public class ModInfo
  • Summary:
    • Represents a single mod ExecutableAsset.
    • Tracks state (enum State), created IMod instances (m_Instances), load errors, and exposes information about validity, load status, bursting, names, etc.
  • Major members (selected):
    • Fields/props:
    • private readonly List<IMod> m_Instances — internal list of IMod instances (created via FormatterServices.GetUninitializedObject).
    • public IReadOnlyList<IMod> instances => m_Instances
    • public ExecutableAsset asset { get; private set; }
    • public bool isValid — true iff asset.isMod && asset.isEnabled && asset.isUnique
    • public bool isLoaded => asset.isLoaded
    • public bool isBursted => asset.isBursted
    • public string name => asset.fullName
    • public string assemblyFullName => asset.assembly.FullName
    • public State state { get; private set; } — enum values include Unknown, Loaded, Disposed, IsNotModWarning, IsNotUniqueWarning, GeneralError, MissedDependenciesError, LoadAssemblyError, LoadAssemblyReferenceError
    • public string loadError { get; private set; }
    • Constructor:
    • public ModInfo(ExecutableAsset asset) — wraps the given asset.
    • Methods:
    • public void Preload(Assembly[] assemblies) — placeholder for preloading (empty body in current code).
    • public void Load(UpdateSystem updateSystem) — performs the load workflow:
      • Checks preconditions: only run when in Unknown state, asset.isEnabled, asset.isRequired.
      • Validates asset.isMod/isReference and uniqueness and dependencies; sets appropriate error state and messages.
      • Calls asset.LoadAssembly(AfterLoadAssembly, out uniqueAsset) to load the mod assembly; replaces asset with uniqueAsset.
      • Finds types derived from IMod in the loaded assembly and allocates uninitialized instances for each type, adding them to m_Instances.
      • Calls OnLoad(updateSystem) to invoke IMod.OnLoad for each instance.
      • Sets state = Loaded.
      • Catches and maps exceptions to state and loadError and re-throws them.
    • private static void AfterLoadAssembly(Assembly assembly) — registers additional types with TypeManager and marks the serializer system dirty.
    • private void OnLoad(UpdateSystem updateSystem) — invokes OnLoad on each IMod instance.
    • private void OnDispose() / public void Dispose() — invokes OnDispose on each IMod instance, clears the instances list, and sets state = Disposed.

{{ YOUR_INFO }} - Important ModInfo behavior: - Dependencies are checked via asset.canBeLoaded; missing references are reported into loadError (joined list of missing reference names). - Assembly types derived from IMod are located using reflection helpers (GetTypesDerivedFrom()) and instances are created without invoking constructors; the mod system expects the mod's own code to perform initialization in IMod.OnLoad. - After assembly load, TypeManager.InitializeAdditionalTypes and SerializerSystem.SetDirty() are called so ECS/serialization recognizes new types.

Usage Example

// Example: Initialize mods during game startup
var modManager = new ModManager(disabled: false);
if (!modManager.isInitialized)
{
    // updateSystem is provided by the game engine; pass it in
    modManager.Initialize(updateSystem);
}

// Iterate through registered mods
foreach (var modInfo in modManager)
{
    Console.WriteLine($"Mod: {modInfo.name}, State: {modInfo.state}");
}

// When shutting down or reloading mods
modManager.Dispose();

// Example: Require a restart when enabling/disabling mods at runtime
modManager.RequireRestart();

{{ YOUR_INFO }} - Notes for modders writing mods (IMod implementers): - Mod assemblies are discovered through ExecutableAsset entries. Ensure your asset metadata flags (isMod, isUnique, isEnabled, isRequired) are set appropriately. - Implement IMod.OnLoad(UpdateSystem) to perform runtime initialization; OnDispose() will be called during unloading. - If your mod exposes UI modules, provide UIModuleAsset entries; the ModManager will register them under "ui-mods" so the UI can load CUI content. - If a mod fails to load, the ModManager records a textual loadError (stack trace or missing refs) and shows notifications that let players view the mod page or disable the mod. - Threading / safety: - Initialization is intended to be called from the main game thread with the UpdateSystem instance. - ModManager uses Unity/ECS APIs (TypeManager, World.DefaultGameObjectInjectionWorld) when loading assemblies; those operations require the engine to be in an appropriate state.