Game.Modding.ModManager
Assembly: Game (approx.; classes located in the game's managed assembly)
Namespace: Game.Modding
Type: class
Base: System.Object (implements IEnumerable
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.isUniquepublic 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, LoadAssemblyReferenceErrorpublic 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
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.