Core Loading System
Estimated reading time: 4 minutesLoading Flow Overview
Mod loading starts when SC2DataManager.startInit() calls ModLoader.loadMod(), with the following stages:
- Read Mod zip files from the configured sources
- Run
scriptFileList_inject_earlyandscriptFileList_earlyloadand related load triggers - Register Mods with Addons
- Rebuild the
tw-storydatanode - Run
scriptFileList_preload - Start SugarCube2 as usual
Loading Sources and Priority
ModLoader loads Mods from four sources, in this order:
Override rule: If the same Mod exists in multiple sources, the later-loaded one wins. So remote overrides local, IndexedDB overrides remote and local.
modList.json Format
modList.json is a JSON array of Mod zip paths. Its usage differs by source:
Local (insert2html)
When using insert2html.js, pass a modList.json that lists Mod zips relative to the file's location. Mods are embedded into the game HTML as local type.
Paths are resolved relative to the directory containing modList.json.
Remote (RemoteLoader)
When the game is served via a web server, RemoteLoader fetches modList.json from the same directory as the HTML and loads listed Mods via fetch. Paths must be valid for fetch:
Override Priority
If the same Mod name exists across sources: remote overrides local, and IndexedDB overrides both.
Detailed Loading Steps
Full Mod loading flow (21 steps):
Phase 1: Read and Inject
1. Load Mods in order: local → remote → localStorage → IndexDB. Call DependenceChecker.checkFor() for dependency checking (see Dependency Checking).
2. Use ModZipReader to read each Mod's boot.json and parse its structure.
3. Call initModInjectEarlyLoadInDomScript() to inject all scriptFileList_inject_early JS files into HTML; the browser executes them as normal <script> tags. Mods should perform their own initialization here.
The inject_early stage supports only synchronous operations; async operations are not awaited.
During this, loaded Mods can register the ModLoadControllerCallback.canLoadThisMod hook to decide whether later Mods may load (ModLoaderGui uses this for safe mode).
4. Fire these hooks to notify all Mods that the current Mod has loaded:
AddonPluginHookPoint.afterInjectEarlyLoadModLoadControllerCallback.afterModLoadAddonPluginHookPoint.afterModLoad
These hooks support async and will wait for promises to resolve.
Phase 2: EarlyLoad Execution
5. Call initModEarlyLoadScript() to run all scriptFileList_earlyload scripts. Execution uses JsPreloader.JsRunner(), which wraps code as (async () => { return ${jsCode} })() and waits for completion.
Because of the injected return, only the first line or an immediately-invoked closure from the first line runs. It is recommended to structure earlyload scripts as an IIFE async closure.
6. During initModEarlyLoadScript(), tryInitWaitingLazyLoadMod() is called repeatedly to pick up any lazily-loaded Mods. Encrypted Mods use this to decrypt and inject Mods in earlyload.
7. Lazily loaded Mods read their zip, then their scriptFileList_inject_early and scriptFileList_earlyload run, and canLoadThisMod is triggered.
8. After all Mod JS has loaded and run, fire AddonPluginHookPoint.afterEarlyLoad.
Phase 3: Addon Registration
9. Call registerMod2Addon() to register Mods that declare addonPlugin in boot.json with their Addon Mods.
Addon Mods must register themselves via AddonPluginManager.registerAddonPlugin before this (during EarlyLoad or earlier).
10. Addon Mods receive registration via AddonPluginHookPointExMustImplement.registerMod and can record or act accordingly.
11. Fire AddonPluginHookPoint.afterRegisterMod2Addon.
12. At this point, Mod JS loading is complete.
Phase 4: Data Merging
13. Fire AddonPluginHookPoint.beforePatchModToGame.
14. Merge all Mods' styleFileList, scriptFileList, and tweeFileList into tw-storydata and rebuild the node.
15. Fire AddonPluginHookPoint.afterPatchModToGame. TweeReplacer, ReplacePatch, and similar Mods run their replacements here.
Phase 5: Preload and Start
16. ModLoader.loadMod() finishes and returns SC2 code.
17. SC2 code calls JsPreloader.startLoad().
18. Run scriptFileList_preload scripts.
19. Fire AddonPluginHookPoint.afterPreload.
20. Fire ModLoadControllerCallback.ModLoaderLoadEnd—the last hook in the load process. Mods can do final setup here before SC2 starts.
21. Mod loading completes, ModLoader is ready, and SugarCube2 runs normally. All further ModLoader actions are triggered by SugarCube2.
Mod Data Merge Rules
During merging:
- Mods are loaded in list order; later Mods override earlier ones for the same Passage name.
- Identical CSS/JS files across Mods are concatenated, not overwritten.
- Inter-Mod merge results are computed first, then the result overrides the original game's Passage/JS/CSS.
