modwright 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +35 -0
- package/README.md +212 -0
- package/assets/claude/agents/mw-explore.md +46 -0
- package/assets/claude/agents/mw-fixture.md +33 -0
- package/assets/claude/agents/mw-rule-author.md +51 -0
- package/assets/claude/agents/mw-scaffold-author.md +48 -0
- package/assets/claude/skills/mw-author.md +59 -0
- package/assets/claude/skills/mw-new-item.md +51 -0
- package/assets/claude/skills/mw-new-spell.md +50 -0
- package/assets/claude/skills/mw-scaffold.md +59 -0
- package/assets/claude/skills/mw-ship.md +57 -0
- package/assets/claude/skills/mw-validate.md +51 -0
- package/assets/claude/skills/mw-verify.md +59 -0
- package/assets/nivalisnights/extract_resources.py +177 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
- package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
- package/bridges/bg3-se/README.md +99 -0
- package/bridges/bg3-se/bridge.json +68 -0
- package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
- package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
- package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
- package/bridges/cp2077-cet/README.md +165 -0
- package/bridges/cp2077-cet/bridge.json +53 -0
- package/dist/core/bridge/arm.js +175 -0
- package/dist/core/bridge/cet-console.js +26 -0
- package/dist/core/bridge/cet-log.js +362 -0
- package/dist/core/bridge/index.js +11 -0
- package/dist/core/bridge/locate.js +35 -0
- package/dist/core/bridge/paths.js +34 -0
- package/dist/core/bridge/pending.js +89 -0
- package/dist/core/bridge/poll.js +155 -0
- package/dist/core/bridge/stage.js +100 -0
- package/dist/core/bridge/status.js +97 -0
- package/dist/core/bridge/types.js +145 -0
- package/dist/core/bridge/validate.js +107 -0
- package/dist/core/build/index.js +6 -0
- package/dist/core/build/manifest.js +47 -0
- package/dist/core/build/paths.js +32 -0
- package/dist/core/build/run.js +342 -0
- package/dist/core/build/stage.js +265 -0
- package/dist/core/build/steps/bg3.js +129 -0
- package/dist/core/build/steps/core.js +195 -0
- package/dist/core/build/steps/cyberpunk.js +95 -0
- package/dist/core/build/steps/listing.js +35 -0
- package/dist/core/build/types.js +13 -0
- package/dist/core/build/zip.js +148 -0
- package/dist/core/compat/check.js +113 -0
- package/dist/core/compat/pe.js +220 -0
- package/dist/core/compat/types.js +1 -0
- package/dist/core/compat/version.js +70 -0
- package/dist/core/deploy/guards.js +292 -0
- package/dist/core/deploy/index.js +5 -0
- package/dist/core/deploy/plan.js +696 -0
- package/dist/core/deploy/reload-apply.js +361 -0
- package/dist/core/deploy/reload.js +358 -0
- package/dist/core/deploy/sources.js +169 -0
- package/dist/core/deploy/types.js +1 -0
- package/dist/core/epic.js +54 -0
- package/dist/core/fsutil.js +191 -0
- package/dist/core/home.js +70 -0
- package/dist/core/knowledge/facts.js +229 -0
- package/dist/core/knowledge/index.js +4 -0
- package/dist/core/knowledge/search.js +112 -0
- package/dist/core/knowledge/store.js +123 -0
- package/dist/core/knowledge/types.js +1 -0
- package/dist/core/ledger/check.js +42 -0
- package/dist/core/ledger/hooks.js +80 -0
- package/dist/core/ledger/index.js +7 -0
- package/dist/core/ledger/query.js +17 -0
- package/dist/core/ledger/record.js +84 -0
- package/dist/core/ledger/status.js +53 -0
- package/dist/core/ledger/store.js +98 -0
- package/dist/core/ledger/types.js +108 -0
- package/dist/core/logs/index.js +4 -0
- package/dist/core/logs/registry.js +50 -0
- package/dist/core/logs/scan.js +195 -0
- package/dist/core/logs/triage.js +160 -0
- package/dist/core/logs/types.js +1 -0
- package/dist/core/lsx.js +10 -0
- package/dist/core/lz4.js +66 -0
- package/dist/core/mods.js +229 -0
- package/dist/core/process.js +49 -0
- package/dist/core/project/build-steps.js +83 -0
- package/dist/core/project/describe.js +74 -0
- package/dist/core/project/index.js +5 -0
- package/dist/core/project/load.js +146 -0
- package/dist/core/project/schema.js +264 -0
- package/dist/core/project/types.js +3 -0
- package/dist/core/prompts.js +72 -0
- package/dist/core/registry.js +35 -0
- package/dist/core/safety/backup.js +206 -0
- package/dist/core/safety/execute.js +188 -0
- package/dist/core/safety/index.js +5 -0
- package/dist/core/safety/plan.js +122 -0
- package/dist/core/safety/restore.js +172 -0
- package/dist/core/safety/types.js +1 -0
- package/dist/core/scaffold/bootstrap.js +7 -0
- package/dist/core/scaffold/catalog.js +28 -0
- package/dist/core/scaffold/claude-assets.js +271 -0
- package/dist/core/scaffold/collisions.js +64 -0
- package/dist/core/scaffold/emit.js +356 -0
- package/dist/core/scaffold/engine.js +680 -0
- package/dist/core/scaffold/ids.js +78 -0
- package/dist/core/scaffold/index.js +10 -0
- package/dist/core/scaffold/jsonpath.js +121 -0
- package/dist/core/scaffold/load.js +81 -0
- package/dist/core/scaffold/render.js +85 -0
- package/dist/core/scaffold/sequence.js +396 -0
- package/dist/core/scaffold/types.js +309 -0
- package/dist/core/steam.js +63 -0
- package/dist/core/surfaceutil.js +36 -0
- package/dist/core/testplan/check.js +67 -0
- package/dist/core/testplan/index.js +5 -0
- package/dist/core/testplan/load.js +100 -0
- package/dist/core/testplan/registry.js +317 -0
- package/dist/core/testplan/run.js +389 -0
- package/dist/core/testplan/types.js +82 -0
- package/dist/core/text/converters/bg3.js +50 -0
- package/dist/core/text/converters/cyberpunk.js +36 -0
- package/dist/core/text/converters/index.js +261 -0
- package/dist/core/text/diff/cr2w.js +180 -0
- package/dist/core/text/diff/index.js +16 -0
- package/dist/core/text/diff/loca.js +43 -0
- package/dist/core/text/diff/lsx.js +126 -0
- package/dist/core/text/handles.js +86 -0
- package/dist/core/text/index.js +6 -0
- package/dist/core/text/json.js +46 -0
- package/dist/core/text/templates/cache.js +71 -0
- package/dist/core/text/templates/index.js +3 -0
- package/dist/core/text/templates/library.js +245 -0
- package/dist/core/text/templates/types.js +51 -0
- package/dist/core/text/toolchain.js +16 -0
- package/dist/core/text/types.js +1 -0
- package/dist/core/text/validate.js +149 -0
- package/dist/core/text/xml.js +58 -0
- package/dist/core/toolchain/datafetch.js +151 -0
- package/dist/core/toolchain/env.js +63 -0
- package/dist/core/toolchain/features.js +170 -0
- package/dist/core/toolchain/index.js +8 -0
- package/dist/core/toolchain/install.js +191 -0
- package/dist/core/toolchain/locate.js +275 -0
- package/dist/core/toolchain/remember.js +84 -0
- package/dist/core/toolchain/run.js +318 -0
- package/dist/core/toolchain/specs.js +351 -0
- package/dist/core/toolchain/types.js +1 -0
- package/dist/core/types.js +1 -0
- package/dist/core/userconfig.js +66 -0
- package/dist/core/validate/context.js +139 -0
- package/dist/core/validate/index.js +5 -0
- package/dist/core/validate/registry.js +58 -0
- package/dist/core/validate/run.js +131 -0
- package/dist/core/validate/suppressions.js +91 -0
- package/dist/core/validate/types.js +2 -0
- package/dist/core/yamledit.js +51 -0
- package/dist/index.js +389 -0
- package/dist/server.js +1914 -0
- package/dist/surfaces/baldursgate3/compat.js +164 -0
- package/dist/surfaces/baldursgate3/identity.js +110 -0
- package/dist/surfaces/baldursgate3/index/build.js +331 -0
- package/dist/surfaces/baldursgate3/index/index.js +17 -0
- package/dist/surfaces/baldursgate3/index/info.js +40 -0
- package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
- package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
- package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
- package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
- package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
- package/dist/surfaces/baldursgate3/index/queries.js +327 -0
- package/dist/surfaces/baldursgate3/index/schema.js +62 -0
- package/dist/surfaces/baldursgate3/index.js +3 -0
- package/dist/surfaces/baldursgate3/logs.js +278 -0
- package/dist/surfaces/baldursgate3/modsettings.js +115 -0
- package/dist/surfaces/baldursgate3/pak.js +153 -0
- package/dist/surfaces/baldursgate3/settings.js +49 -0
- package/dist/surfaces/baldursgate3/surface.js +232 -0
- package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
- package/dist/surfaces/baldursgate3/validators/files.js +476 -0
- package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
- package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
- package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
- package/dist/surfaces/baldursgate3/validators/story.js +790 -0
- package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
- package/dist/surfaces/cyberpunk2077/compat.js +223 -0
- package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
- package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
- package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
- package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
- package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
- package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
- package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
- package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
- package/dist/surfaces/cyberpunk2077/index.js +3 -0
- package/dist/surfaces/cyberpunk2077/logs.js +742 -0
- package/dist/surfaces/cyberpunk2077/paths.js +12 -0
- package/dist/surfaces/cyberpunk2077/surface.js +218 -0
- package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
- package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
- package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
- package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
- package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
- package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
- package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
- package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
- package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
- package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
- package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
- package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
- package/dist/surfaces/eldenring/binders.js +111 -0
- package/dist/surfaces/eldenring/compat.js +166 -0
- package/dist/surfaces/eldenring/dcx.js +49 -0
- package/dist/surfaces/eldenring/index.js +13 -0
- package/dist/surfaces/eldenring/loaders.js +132 -0
- package/dist/surfaces/eldenring/logs.js +67 -0
- package/dist/surfaces/eldenring/paramdef.js +256 -0
- package/dist/surfaces/eldenring/params.js +129 -0
- package/dist/surfaces/eldenring/surface.js +121 -0
- package/dist/surfaces/eldenring/tools.js +52 -0
- package/dist/surfaces/eldenring/validators/binders.js +193 -0
- package/dist/surfaces/eldenring/validators/packages.js +159 -0
- package/dist/surfaces/eldenring/validators/params.js +334 -0
- package/dist/surfaces/eldenring/validators/profiles.js +287 -0
- package/dist/surfaces/index.js +34 -0
- package/dist/surfaces/nivalisnights/compat.js +229 -0
- package/dist/surfaces/nivalisnights/index/build.js +310 -0
- package/dist/surfaces/nivalisnights/index/index.js +5 -0
- package/dist/surfaces/nivalisnights/index/queries.js +231 -0
- package/dist/surfaces/nivalisnights/index.js +4 -0
- package/dist/surfaces/nivalisnights/logs.js +473 -0
- package/dist/surfaces/nivalisnights/surface.js +195 -0
- package/dist/surfaces/skyrimse/archives.js +229 -0
- package/dist/surfaces/skyrimse/compat.js +163 -0
- package/dist/surfaces/skyrimse/index.js +12 -0
- package/dist/surfaces/skyrimse/loadorder.js +174 -0
- package/dist/surfaces/skyrimse/logs.js +241 -0
- package/dist/surfaces/skyrimse/plugins.js +149 -0
- package/dist/surfaces/skyrimse/records.js +94 -0
- package/dist/surfaces/skyrimse/surface.js +240 -0
- package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
- package/dist/surfaces/skyrimse/validators/archives.js +110 -0
- package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
- package/dist/surfaces/skyrimse/validators/records.js +117 -0
- package/dist/surfaces/stardewvalley/compat.js +190 -0
- package/dist/surfaces/stardewvalley/config.js +42 -0
- package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
- package/dist/surfaces/stardewvalley/index/index.js +229 -0
- package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
- package/dist/surfaces/stardewvalley/index.js +12 -0
- package/dist/surfaces/stardewvalley/logs.js +307 -0
- package/dist/surfaces/stardewvalley/mods.js +182 -0
- package/dist/surfaces/stardewvalley/surface.js +244 -0
- package/dist/surfaces/stardewvalley/validators/content.js +321 -0
- package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
- package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
- package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
- package/dist/surfaces/subnautica2/compat.js +138 -0
- package/dist/surfaces/subnautica2/index.js +5 -0
- package/dist/surfaces/subnautica2/logs.js +129 -0
- package/dist/surfaces/subnautica2/paks.js +173 -0
- package/dist/surfaces/subnautica2/surface.js +151 -0
- package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
- package/dist/surfaces/valheim/compat.js +171 -0
- package/dist/surfaces/valheim/index/index.js +301 -0
- package/dist/surfaces/valheim/index/tables.js +103 -0
- package/dist/surfaces/valheim/index.js +11 -0
- package/dist/surfaces/valheim/logs.js +197 -0
- package/dist/surfaces/valheim/packaging.js +131 -0
- package/dist/surfaces/valheim/plugins.js +270 -0
- package/dist/surfaces/valheim/references.js +121 -0
- package/dist/surfaces/valheim/surface.js +223 -0
- package/dist/surfaces/valheim/validators/packaging.js +218 -0
- package/dist/surfaces/valheim/validators/plugins.js +287 -0
- package/dist/surfaces/valheim/validators/references.js +169 -0
- package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
- package/knowledge/baldursgate3/audio.voice.yaml +182 -0
- package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
- package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
- package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
- package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
- package/knowledge/baldursgate3/crash.causes.yaml +90 -0
- package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
- package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
- package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
- package/knowledge/baldursgate3/dialogue.yaml +122 -0
- package/knowledge/baldursgate3/feats.classes.yaml +164 -0
- package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
- package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
- package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
- package/knowledge/baldursgate3/journal.quests.yaml +214 -0
- package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
- package/knowledge/baldursgate3/localization.handles.yaml +92 -0
- package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
- package/knowledge/baldursgate3/origins.companions.yaml +144 -0
- package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
- package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
- package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
- package/knowledge/baldursgate3/process.launch.yaml +34 -0
- package/knowledge/baldursgate3/progressions.yaml +92 -0
- package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
- package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
- package/knowledge/baldursgate3/races.progression.yaml +228 -0
- package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
- package/knowledge/baldursgate3/scriptextender.yaml +252 -0
- package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
- package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
- package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
- package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
- package/knowledge/baldursgate3/stats.spells.yaml +293 -0
- package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
- package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
- package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
- package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
- package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
- package/knowledge/baldursgate3/ui.icons.yaml +412 -0
- package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
- package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
- package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
- package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
- package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
- package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
- package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
- package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
- package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
- package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
- package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
- package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
- package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
- package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
- package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
- package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
- package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
- package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
- package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
- package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
- package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
- package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
- package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
- package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
- package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
- package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
- package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
- package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
- package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
- package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
- package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
- package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
- package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
- package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
- package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
- package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
- package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
- package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
- package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
- package/knowledge/cyberpunk2077/player.control.yaml +918 -0
- package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
- package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
- package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
- package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
- package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
- package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
- package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
- package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
- package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
- package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
- package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
- package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
- package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
- package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
- package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
- package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
- package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
- package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
- package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
- package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
- package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
- package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
- package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
- package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
- package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
- package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
- package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
- package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
- package/knowledge/eldenring/formats.binders.yaml +237 -0
- package/knowledge/eldenring/launcher.me3.yaml +230 -0
- package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
- package/knowledge/eldenring/params.paramdef.yaml +149 -0
- package/knowledge/eldenring/params.regulation.yaml +142 -0
- package/knowledge/eldenring/saves.online.yaml +118 -0
- package/knowledge/eldenring/toolchain.cli.yaml +256 -0
- package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
- package/knowledge/skyrimse/archives.bsa.yaml +212 -0
- package/knowledge/skyrimse/install.detection.yaml +41 -0
- package/knowledge/skyrimse/loadorder.files.yaml +242 -0
- package/knowledge/skyrimse/plugins.esl.yaml +189 -0
- package/knowledge/skyrimse/plugins.format.yaml +224 -0
- package/knowledge/skyrimse/skse.runtime.yaml +283 -0
- package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
- package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
- package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
- package/knowledge/stardewvalley/game.versions.yaml +202 -0
- package/knowledge/stardewvalley/install.layout.yaml +277 -0
- package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
- package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
- package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
- package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
- package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
- package/knowledge/subnautica2/game.build.yaml +331 -0
- package/knowledge/subnautica2/modding.state.yaml +210 -0
- package/knowledge/subnautica2/ue5.containers.yaml +72 -0
- package/knowledge/subnautica2/ue5.paks.yaml +203 -0
- package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
- package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
- package/knowledge/valheim/bepinex.loader.yaml +247 -0
- package/knowledge/valheim/bepinex.plugins.yaml +251 -0
- package/knowledge/valheim/game.versions.yaml +321 -0
- package/knowledge/valheim/install.layout.yaml +216 -0
- package/knowledge/valheim/jotunn.library.yaml +334 -0
- package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
- package/knowledge/valheim/toolchain.build.yaml +111 -0
- package/package.json +29 -0
- package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
- package/scaffolds/baldursgate3/new-item.yaml +264 -0
- package/scaffolds/baldursgate3/new-mod.yaml +174 -0
- package/scaffolds/baldursgate3/new-passive.yaml +100 -0
- package/scaffolds/baldursgate3/new-project.yaml +118 -0
- package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
- package/scaffolds/baldursgate3/new-spell.yaml +102 -0
- package/scaffolds/baldursgate3/new-status.yaml +119 -0
- package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
- package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
- package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
- package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
- package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
- package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
- package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
- package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
- package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
- package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
- package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
- package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
- package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
- package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
- package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
- package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
- package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
- package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
- package/scaffolds/stardewvalley/new-project.yaml +184 -0
- package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
- package/scripts/mw-tool.mjs +184 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export const BASE_MODULES = new Set([
|
|
2
|
+
"gustav",
|
|
3
|
+
"gustavdev",
|
|
4
|
+
"gustavx",
|
|
5
|
+
"shared",
|
|
6
|
+
"shareddev",
|
|
7
|
+
"honour",
|
|
8
|
+
"honourx",
|
|
9
|
+
"engine",
|
|
10
|
+
"mainui",
|
|
11
|
+
"modbrowser",
|
|
12
|
+
"fw3",
|
|
13
|
+
"dicesets",
|
|
14
|
+
]);
|
|
15
|
+
export function isBaseModule(name) {
|
|
16
|
+
return BASE_MODULES.has(name.toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
export function parseModSettings(xml) {
|
|
19
|
+
const withoutComments = xml.replace(/<!--[\s\S]*?-->/g, "");
|
|
20
|
+
const entries = [];
|
|
21
|
+
let index = 0;
|
|
22
|
+
for (const node of withoutComments.matchAll(/<node\s+id="ModuleShortDesc">([\s\S]*?)<\/node>/g)) {
|
|
23
|
+
const body = node[1] ?? "";
|
|
24
|
+
const name = attr(body, "Name");
|
|
25
|
+
if (!name)
|
|
26
|
+
continue;
|
|
27
|
+
const entry = {
|
|
28
|
+
name,
|
|
29
|
+
uuid: attr(body, "UUID") ?? "",
|
|
30
|
+
index: index++,
|
|
31
|
+
};
|
|
32
|
+
const folder = attr(body, "Folder");
|
|
33
|
+
if (folder !== undefined)
|
|
34
|
+
entry.folder = folder;
|
|
35
|
+
const version = attr(body, "Version64") ?? attr(body, "Version");
|
|
36
|
+
if (version !== undefined)
|
|
37
|
+
entry.version = version;
|
|
38
|
+
entries.push(entry);
|
|
39
|
+
}
|
|
40
|
+
return entries;
|
|
41
|
+
}
|
|
42
|
+
export function diffModSettings(before, after) {
|
|
43
|
+
const beforeEntries = parseModSettings(before);
|
|
44
|
+
const afterEntries = parseModSettings(after);
|
|
45
|
+
const beforeKeys = new Map(beforeEntries.map((e) => [keyOf(e), e]));
|
|
46
|
+
const afterKeys = new Map(afterEntries.map((e) => [keyOf(e), e]));
|
|
47
|
+
const removed = beforeEntries.filter((e) => !afterKeys.has(keyOf(e)));
|
|
48
|
+
const added = afterEntries.filter((e) => !beforeKeys.has(keyOf(e)));
|
|
49
|
+
const commonBefore = beforeEntries.map(keyOf).filter((k) => afterKeys.has(k));
|
|
50
|
+
const commonAfter = afterEntries.map(keyOf).filter((k) => beforeKeys.has(k));
|
|
51
|
+
const reordered = commonBefore.some((key, i) => commonAfter[i] !== key);
|
|
52
|
+
const userModsBefore = beforeEntries.filter((e) => !isBaseModule(e.name));
|
|
53
|
+
const userModsAfter = afterEntries.filter((e) => !isBaseModule(e.name));
|
|
54
|
+
const baseGameOnlyLeft = userModsBefore.length > 0 && userModsAfter.length === 0;
|
|
55
|
+
return { removed, added, reordered, baseGameOnlyLeft };
|
|
56
|
+
}
|
|
57
|
+
export function describeModSettingsDiff(diff) {
|
|
58
|
+
const removedUser = diff.removed.filter((e) => !isBaseModule(e.name));
|
|
59
|
+
const removedBase = diff.removed.filter((e) => isBaseModule(e.name));
|
|
60
|
+
const addedUser = diff.added.filter((e) => !isBaseModule(e.name));
|
|
61
|
+
const sentences = [];
|
|
62
|
+
if (removedUser.length > 0) {
|
|
63
|
+
sentences.push(`The game dropped ${count(removedUser.length, "user mod")} from modsettings.lsx on ` +
|
|
64
|
+
`launch: ${list(removedUser)}${removedBase.length > 0
|
|
65
|
+
? `, along with ${count(removedBase.length, "base-game module")}`
|
|
66
|
+
: ""}.`);
|
|
67
|
+
}
|
|
68
|
+
else if (removedBase.length > 0) {
|
|
69
|
+
sentences.push(`The game dropped ${count(removedBase.length, "base-game module")} from ` +
|
|
70
|
+
`modsettings.lsx on launch (${list(removedBase)}) but left every user mod in place.`);
|
|
71
|
+
}
|
|
72
|
+
else if (addedUser.length > 0 && diff.reordered) {
|
|
73
|
+
sentences.push(`Nothing was dropped from modsettings.lsx: ${count(addedUser.length, "user mod")} was ` +
|
|
74
|
+
`added (${list(addedUser)}) and the remaining entries changed order.`);
|
|
75
|
+
}
|
|
76
|
+
else if (addedUser.length > 0) {
|
|
77
|
+
sentences.push(`Nothing was dropped from modsettings.lsx; ${count(addedUser.length, "user mod")} was ` +
|
|
78
|
+
`added (${list(addedUser)}).`);
|
|
79
|
+
}
|
|
80
|
+
else if (diff.reordered) {
|
|
81
|
+
sentences.push("Nothing was dropped from modsettings.lsx, but the entries came back in a different " +
|
|
82
|
+
"order than they went in.");
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
sentences.push("modsettings.lsx came back unchanged: same entries, same order.");
|
|
86
|
+
}
|
|
87
|
+
if (diff.baseGameOnlyLeft) {
|
|
88
|
+
sentences.push("Only base-game modules are left, so the game reset the profile to a clean one rather " +
|
|
89
|
+
"than dropping a single entry.");
|
|
90
|
+
}
|
|
91
|
+
if (removedUser.length > 0 || diff.baseGameOnlyLeft || diff.reordered) {
|
|
92
|
+
sentences.push("An order that reverts itself usually means a missing or renamed pak; the crash guard " +
|
|
93
|
+
"can also disable a mod while it stays listed.");
|
|
94
|
+
}
|
|
95
|
+
return sentences.join(" ");
|
|
96
|
+
}
|
|
97
|
+
function attr(body, id) {
|
|
98
|
+
const match = body.match(new RegExp(`<attribute\\s+id="${id}"[^>]*?value="([^"]*)"`));
|
|
99
|
+
return match?.[1];
|
|
100
|
+
}
|
|
101
|
+
function keyOf(entry) {
|
|
102
|
+
return entry.uuid
|
|
103
|
+
? `uuid:${entry.uuid.toLowerCase()}`
|
|
104
|
+
: `name:${entry.name.toLowerCase()}/${(entry.folder ?? "").toLowerCase()}`;
|
|
105
|
+
}
|
|
106
|
+
function count(n, noun) {
|
|
107
|
+
return `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
108
|
+
}
|
|
109
|
+
function list(entries) {
|
|
110
|
+
const shown = entries
|
|
111
|
+
.slice(0, 3)
|
|
112
|
+
.map((e) => (e.uuid ? `${e.name} (${e.uuid})` : `${e.name} (no UUID)`));
|
|
113
|
+
const rest = entries.length - shown.length;
|
|
114
|
+
return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
|
|
115
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { inflateSync } from "node:zlib";
|
|
3
|
+
import { decodeLz4Block } from "../../core/lz4.js";
|
|
4
|
+
import { parseMetaLsxModuleInfo } from "../../core/lsx.js";
|
|
5
|
+
const MAGIC = 0x4b50534c;
|
|
6
|
+
const HEADER_SIZE = 40;
|
|
7
|
+
const ENTRY_SIZE = 272;
|
|
8
|
+
const SOLID_FLAG = 0x04;
|
|
9
|
+
const MAX_FILE_LIST_BYTES = 64 * 1024 * 1024;
|
|
10
|
+
export const DEFAULT_MAX_ENTRY_BYTES = 64 * 1024 * 1024;
|
|
11
|
+
export const META_LSX_MAX_BYTES = 4 * 1024 * 1024;
|
|
12
|
+
const DELETION_MARKER = 0xbeefdeadbeef;
|
|
13
|
+
const META_LSX_RE = /^Mods\/([^/]+)\/meta\.lsx$/i;
|
|
14
|
+
function methodFromFlags(entryFlags) {
|
|
15
|
+
switch (entryFlags & 0x0f) {
|
|
16
|
+
case 0:
|
|
17
|
+
return "none";
|
|
18
|
+
case 1:
|
|
19
|
+
return "zlib";
|
|
20
|
+
case 2:
|
|
21
|
+
return "lz4";
|
|
22
|
+
case 3:
|
|
23
|
+
return "zstd";
|
|
24
|
+
default:
|
|
25
|
+
return "unknown";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function readPakListing(pakPath) {
|
|
29
|
+
const handle = await fs.open(pakPath, "r");
|
|
30
|
+
try {
|
|
31
|
+
const header = Buffer.alloc(HEADER_SIZE);
|
|
32
|
+
const headerRead = await handle.read(header, 0, HEADER_SIZE, 0);
|
|
33
|
+
if (headerRead.bytesRead < HEADER_SIZE) {
|
|
34
|
+
throw new Error(`${pakPath}: too short to contain an LSPK header (got ${headerRead.bytesRead} of ${HEADER_SIZE} bytes)`);
|
|
35
|
+
}
|
|
36
|
+
if (header.readUInt32LE(0) !== MAGIC) {
|
|
37
|
+
throw new Error(`${pakPath}: not an LSPK package (missing "LSPK" magic at offset 0)`);
|
|
38
|
+
}
|
|
39
|
+
const version = header.readUInt32LE(4);
|
|
40
|
+
if (version !== 18) {
|
|
41
|
+
throw new Error(`${pakPath}: unsupported LSPK version v${version} — only v18 is supported. v15/v16 use a 64-bit-offset ` +
|
|
42
|
+
`"FileEntry15" struct, a different layout from v18's "FileEntry18" (Norbyte/lslib master, ` +
|
|
43
|
+
`LSLib/LS/PackageReader.cs, PackageReader.ReadInternal's version switch), so this reader refuses ` +
|
|
44
|
+
`rather than misreading them.`);
|
|
45
|
+
}
|
|
46
|
+
const fileListOffset = Number(header.readBigUInt64LE(8));
|
|
47
|
+
const flags = header.readUInt8(20);
|
|
48
|
+
const numParts = header.readUInt16LE(38);
|
|
49
|
+
if ((flags & SOLID_FLAG) !== 0) {
|
|
50
|
+
throw new Error(`${pakPath}: this is a "solid" archive (PackageFlags.Solid) — every file is one shared LZ4 frame instead of ` +
|
|
51
|
+
`independently-offset entries, which this reader does not implement.`);
|
|
52
|
+
}
|
|
53
|
+
const listHeader = Buffer.alloc(8);
|
|
54
|
+
const listHeaderRead = await handle.read(listHeader, 0, 8, fileListOffset);
|
|
55
|
+
if (listHeaderRead.bytesRead < 8) {
|
|
56
|
+
throw new Error(`${pakPath}: truncated file list header at offset ${fileListOffset}`);
|
|
57
|
+
}
|
|
58
|
+
const numFiles = listHeader.readInt32LE(0);
|
|
59
|
+
const compressedSize = listHeader.readInt32LE(4);
|
|
60
|
+
if (numFiles < 0 || compressedSize < 0) {
|
|
61
|
+
throw new Error(`${pakPath}: malformed file list header at offset ${fileListOffset} (numFiles=${numFiles}, compressedSize=${compressedSize})`);
|
|
62
|
+
}
|
|
63
|
+
const entryTableSize = numFiles * ENTRY_SIZE;
|
|
64
|
+
if (compressedSize > MAX_FILE_LIST_BYTES || entryTableSize > MAX_FILE_LIST_BYTES) {
|
|
65
|
+
throw new Error(`${pakPath}: implausible file list (numFiles=${numFiles} => ${entryTableSize} entry-table bytes, ` +
|
|
66
|
+
`compressedSize=${compressedSize}); this reader refuses anything over ${MAX_FILE_LIST_BYTES} bytes rather than ` +
|
|
67
|
+
`allocating from a corrupt or hostile header.`);
|
|
68
|
+
}
|
|
69
|
+
const compressed = Buffer.alloc(compressedSize);
|
|
70
|
+
const listBodyRead = await handle.read(compressed, 0, compressedSize, fileListOffset + 8);
|
|
71
|
+
if (listBodyRead.bytesRead < compressedSize) {
|
|
72
|
+
throw new Error(`${pakPath}: truncated file list (expected ${compressedSize} compressed bytes, got ${listBodyRead.bytesRead})`);
|
|
73
|
+
}
|
|
74
|
+
const decompressed = Buffer.from(decodeLz4Block(compressed, entryTableSize));
|
|
75
|
+
const entries = [];
|
|
76
|
+
for (let i = 0; i < numFiles; i++) {
|
|
77
|
+
const base = i * ENTRY_SIZE;
|
|
78
|
+
const nameBuf = decompressed.subarray(base, base + 256);
|
|
79
|
+
let nul = nameBuf.indexOf(0);
|
|
80
|
+
if (nul === -1)
|
|
81
|
+
nul = 256;
|
|
82
|
+
const name = nameBuf.toString("utf8", 0, nul);
|
|
83
|
+
const offsetInFile1 = decompressed.readUInt32LE(base + 256);
|
|
84
|
+
const offsetInFile2 = decompressed.readUInt16LE(base + 260);
|
|
85
|
+
const part = decompressed.readUInt8(base + 262);
|
|
86
|
+
const entryFlags = decompressed.readUInt8(base + 263);
|
|
87
|
+
const sizeOnDisk = decompressed.readUInt32LE(base + 264);
|
|
88
|
+
const uncompressedSize = decompressed.readUInt32LE(base + 268);
|
|
89
|
+
const offset = offsetInFile1 + offsetInFile2 * 2 ** 32;
|
|
90
|
+
if (offset === DELETION_MARKER)
|
|
91
|
+
continue;
|
|
92
|
+
entries.push({ name, offset, sizeOnDisk, uncompressedSize, part, method: methodFromFlags(entryFlags) });
|
|
93
|
+
}
|
|
94
|
+
return { version, flags, numParts, entries };
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
await handle.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function readPakEntry(pakPath, entry, maxBytes = DEFAULT_MAX_ENTRY_BYTES) {
|
|
101
|
+
if (entry.part !== 0) {
|
|
102
|
+
throw new Error(`${pakPath}: entry "${entry.name}" lives in archive part ${entry.part}, not part 0 (the pak file itself) — ` +
|
|
103
|
+
`reading additional archive parts is not supported, and this reader does not guess the part filename.`);
|
|
104
|
+
}
|
|
105
|
+
if (entry.sizeOnDisk > maxBytes || entry.uncompressedSize > maxBytes) {
|
|
106
|
+
throw new Error(`${pakPath}: entry "${entry.name}" declares ${entry.sizeOnDisk} stored / ${entry.uncompressedSize} decompressed bytes, ` +
|
|
107
|
+
`over this read's ${maxBytes}-byte cap — refusing rather than allocating from a size read straight out of the pak.`);
|
|
108
|
+
}
|
|
109
|
+
const handle = await fs.open(pakPath, "r");
|
|
110
|
+
try {
|
|
111
|
+
const raw = Buffer.alloc(entry.sizeOnDisk);
|
|
112
|
+
if (entry.sizeOnDisk > 0) {
|
|
113
|
+
const read = await handle.read(raw, 0, entry.sizeOnDisk, entry.offset);
|
|
114
|
+
if (read.bytesRead < entry.sizeOnDisk) {
|
|
115
|
+
throw new Error(`${pakPath}: truncated entry "${entry.name}" (expected ${entry.sizeOnDisk} bytes at offset ${entry.offset}, got ${read.bytesRead})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
switch (entry.method) {
|
|
119
|
+
case "none":
|
|
120
|
+
return raw;
|
|
121
|
+
case "zlib":
|
|
122
|
+
return inflateSync(raw, { maxOutputLength: maxBytes });
|
|
123
|
+
case "lz4":
|
|
124
|
+
return Buffer.from(decodeLz4Block(raw, entry.uncompressedSize));
|
|
125
|
+
case "zstd":
|
|
126
|
+
throw new Error(`${pakPath}: entry "${entry.name}" uses zstd compression, which this reader does not support.`);
|
|
127
|
+
default:
|
|
128
|
+
throw new Error(`${pakPath}: entry "${entry.name}" has an unrecognized compression method.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
await handle.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export async function readPakModuleInfos(pakPath) {
|
|
136
|
+
const listing = await readPakListing(pakPath);
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const entry of listing.entries) {
|
|
139
|
+
if (!META_LSX_RE.test(entry.name))
|
|
140
|
+
continue;
|
|
141
|
+
const buf = await readPakEntry(pakPath, entry, META_LSX_MAX_BYTES);
|
|
142
|
+
const info = parseMetaLsxModuleInfo(buf.toString("utf8"));
|
|
143
|
+
out.push({
|
|
144
|
+
pakPath,
|
|
145
|
+
entryName: entry.name,
|
|
146
|
+
folder: info?.folder,
|
|
147
|
+
name: info?.name,
|
|
148
|
+
uuid: info?.uuid,
|
|
149
|
+
version64: info?.version64,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { documents } from "../../core/fsutil.js";
|
|
4
|
+
export function scriptExtenderSettingsFile(install) {
|
|
5
|
+
return path.join(install.root, "bin", "ScriptExtenderSettings.json");
|
|
6
|
+
}
|
|
7
|
+
export function readScriptExtenderSettings(install) {
|
|
8
|
+
const file = scriptExtenderSettingsFile(install);
|
|
9
|
+
let raw;
|
|
10
|
+
try {
|
|
11
|
+
raw = JSON.parse(readFileSync(file, "utf8"));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return { present: false, file };
|
|
15
|
+
}
|
|
16
|
+
const bool = (key) => (typeof raw[key] === "boolean" ? raw[key] : undefined);
|
|
17
|
+
const settings = { present: true, file };
|
|
18
|
+
if (typeof raw.LogDirectory === "string" && raw.LogDirectory.trim())
|
|
19
|
+
settings.logDirectory = raw.LogDirectory;
|
|
20
|
+
const enableLogging = bool("EnableLogging");
|
|
21
|
+
if (enableLogging !== undefined)
|
|
22
|
+
settings.enableLogging = enableLogging;
|
|
23
|
+
const logRuntime = bool("LogRuntime");
|
|
24
|
+
if (logRuntime !== undefined)
|
|
25
|
+
settings.logRuntime = logRuntime;
|
|
26
|
+
const logCompile = bool("LogCompile");
|
|
27
|
+
if (logCompile !== undefined)
|
|
28
|
+
settings.logCompile = logCompile;
|
|
29
|
+
const logFailedCompile = bool("LogFailedCompile");
|
|
30
|
+
if (logFailedCompile !== undefined)
|
|
31
|
+
settings.logFailedCompile = logFailedCompile;
|
|
32
|
+
const createConsole = bool("CreateConsole");
|
|
33
|
+
if (createConsole !== undefined)
|
|
34
|
+
settings.createConsole = createConsole;
|
|
35
|
+
const developerMode = bool("DeveloperMode");
|
|
36
|
+
if (developerMode !== undefined)
|
|
37
|
+
settings.developerMode = developerMode;
|
|
38
|
+
return settings;
|
|
39
|
+
}
|
|
40
|
+
export function scriptExtenderLogDirs(install) {
|
|
41
|
+
const settings = readScriptExtenderSettings(install);
|
|
42
|
+
const dirs = [];
|
|
43
|
+
if (settings.logDirectory)
|
|
44
|
+
dirs.push(settings.logDirectory);
|
|
45
|
+
const fallback = documents("OsirisLogs");
|
|
46
|
+
if (!dirs.some((d) => path.resolve(d).toLowerCase() === path.resolve(fallback).toLowerCase()))
|
|
47
|
+
dirs.push(fallback);
|
|
48
|
+
return dirs;
|
|
49
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { baldursgate3Compat } from "./compat.js";
|
|
3
|
+
import { localAppData, pathExists, readTextIfExists } from "../../core/fsutil.js";
|
|
4
|
+
import { findInSteam } from "../../core/steam.js";
|
|
5
|
+
import { dedupeByRoot, logs, roots } from "../../core/surfaceutil.js";
|
|
6
|
+
import { bg3Index } from "./index/index.js";
|
|
7
|
+
import { scanModIdentities as scanBg3ModIdentities } from "./identity.js";
|
|
8
|
+
import { SIGNATURES } from "./logs.js";
|
|
9
|
+
import { describeModSettingsDiff, diffModSettings, parseModSettings } from "./modsettings.js";
|
|
10
|
+
import { BRIDGE_VALIDATORS } from "./validators/bridge.js";
|
|
11
|
+
import { FILE_VALIDATORS } from "./validators/files.js";
|
|
12
|
+
import { ITEM_VALIDATORS } from "./validators/items.js";
|
|
13
|
+
import { PROGRESSION_VALIDATORS } from "./validators/progression.js";
|
|
14
|
+
import { STATS_VALIDATORS } from "./validators/stats.js";
|
|
15
|
+
import { STORY_VALIDATORS } from "./validators/story.js";
|
|
16
|
+
import { TEMPLATE_VALIDATORS } from "./validators/templates.js";
|
|
17
|
+
import { readScriptExtenderSettings, scriptExtenderLogDirs } from "./settings.js";
|
|
18
|
+
const LARIAN = localAppData("Larian Studios", "Baldur's Gate 3");
|
|
19
|
+
const BASE_MODULES = new Set([
|
|
20
|
+
"gustav",
|
|
21
|
+
"gustavdev",
|
|
22
|
+
"gustavx",
|
|
23
|
+
"shared",
|
|
24
|
+
"shareddev",
|
|
25
|
+
"honour",
|
|
26
|
+
"honourx",
|
|
27
|
+
"engine",
|
|
28
|
+
"mainui",
|
|
29
|
+
"modbrowser",
|
|
30
|
+
"fw3",
|
|
31
|
+
"dicesets",
|
|
32
|
+
]);
|
|
33
|
+
const MODSETTINGS = path.join(LARIAN, "PlayerProfiles", "Public", "modsettings.lsx");
|
|
34
|
+
const PAKS_ROOT = path.join(LARIAN, "Mods");
|
|
35
|
+
const NON_STEAM_CANDIDATES = [
|
|
36
|
+
"C:/GOG Games/Baldurs Gate 3",
|
|
37
|
+
"C:/Program Files (x86)/GOG Galaxy/Games/Baldurs Gate 3",
|
|
38
|
+
];
|
|
39
|
+
export const baldursgate3 = {
|
|
40
|
+
id: "baldursgate3",
|
|
41
|
+
name: "Baldur's Gate 3",
|
|
42
|
+
aliases: ["bg3", "baldurs gate 3", "baldur's gate 3"],
|
|
43
|
+
summary: "Larian Divinity engine. .pak mods live under %LOCALAPPDATA%/Larian Studios, " +
|
|
44
|
+
"with an explicit load order in modsettings.lsx and optional Script Extender support.",
|
|
45
|
+
async detect() {
|
|
46
|
+
const installs = [];
|
|
47
|
+
const override = process.env.MODWRIGHT_BG3_ROOT;
|
|
48
|
+
if (override && (await pathExists(override))) {
|
|
49
|
+
installs.push({ gameId: "baldursgate3", root: override, source: "env" });
|
|
50
|
+
}
|
|
51
|
+
for (const root of await findInSteam(["Baldurs Gate 3"])) {
|
|
52
|
+
installs.push({ gameId: "baldursgate3", root, source: "steam" });
|
|
53
|
+
}
|
|
54
|
+
for (const candidate of NON_STEAM_CANDIDATES) {
|
|
55
|
+
if (await pathExists(candidate)) {
|
|
56
|
+
installs.push({ gameId: "baldursgate3", root: candidate, source: "gog" });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return dedupeByRoot(installs);
|
|
60
|
+
},
|
|
61
|
+
modRoots(install) {
|
|
62
|
+
return roots([
|
|
63
|
+
{
|
|
64
|
+
id: "paks",
|
|
65
|
+
label: "Mods (.pak)",
|
|
66
|
+
path: PAKS_ROOT,
|
|
67
|
+
layout: "file",
|
|
68
|
+
extensions: [".pak"],
|
|
69
|
+
ordered: true,
|
|
70
|
+
conflictScope: "opaque",
|
|
71
|
+
notes: "The real mod folder — under the user profile, NOT the game install. " +
|
|
72
|
+
"A .pak here still does nothing until it is listed in modsettings.lsx.",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "loose",
|
|
76
|
+
label: "Loose file overrides (Data/)",
|
|
77
|
+
path: path.join(install.root, "Data"),
|
|
78
|
+
layout: "flat",
|
|
79
|
+
conflictScope: "merged",
|
|
80
|
+
notes: "Unpacked overrides shipped as loose files. These win over .pak content " +
|
|
81
|
+
"and are the usual cause of a mod that 'cannot be uninstalled'.",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "scriptextender",
|
|
85
|
+
label: "Script Extender data",
|
|
86
|
+
path: path.join(LARIAN, "Script Extender"),
|
|
87
|
+
layout: "directory",
|
|
88
|
+
conflictScope: "isolated",
|
|
89
|
+
notes: "BG3SE config and per-mod extender data. Requires DWrite.dll in bin/.",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "nativemods",
|
|
93
|
+
label: "Native mod loader plugins",
|
|
94
|
+
path: path.join(install.root, "bin", "NativeMods"),
|
|
95
|
+
layout: "file",
|
|
96
|
+
extensions: [".dll"],
|
|
97
|
+
conflictScope: "isolated",
|
|
98
|
+
notes: "Native .dll plugins loaded by the BG3 Native Mod Loader.",
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
},
|
|
102
|
+
loadOrderFile() {
|
|
103
|
+
return MODSETTINGS;
|
|
104
|
+
},
|
|
105
|
+
diffLoadOrder(before, after) {
|
|
106
|
+
const diff = diffModSettings(before, after);
|
|
107
|
+
return {
|
|
108
|
+
removed: diff.removed.map((entry) => entry.name),
|
|
109
|
+
added: diff.added.map((entry) => entry.name),
|
|
110
|
+
reordered: diff.reordered,
|
|
111
|
+
description: describeModSettingsDiff(diff),
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
logSignatures: SIGNATURES,
|
|
115
|
+
index: bg3Index,
|
|
116
|
+
validators: [
|
|
117
|
+
...ITEM_VALIDATORS,
|
|
118
|
+
...PROGRESSION_VALIDATORS,
|
|
119
|
+
...STORY_VALIDATORS,
|
|
120
|
+
...STATS_VALIDATORS,
|
|
121
|
+
...FILE_VALIDATORS,
|
|
122
|
+
...BRIDGE_VALIDATORS,
|
|
123
|
+
...TEMPLATE_VALIDATORS,
|
|
124
|
+
],
|
|
125
|
+
async loadOrder() {
|
|
126
|
+
const xml = await readTextIfExists(MODSETTINGS);
|
|
127
|
+
if (!xml)
|
|
128
|
+
return [];
|
|
129
|
+
return parseModSettings(xml).map((entry) => ({
|
|
130
|
+
index: entry.index,
|
|
131
|
+
name: entry.name,
|
|
132
|
+
enabled: true,
|
|
133
|
+
uuid: entry.uuid || undefined,
|
|
134
|
+
folder: entry.folder,
|
|
135
|
+
baseGame: BASE_MODULES.has(entry.name.toLowerCase()),
|
|
136
|
+
}));
|
|
137
|
+
},
|
|
138
|
+
async scanModIdentities(install) {
|
|
139
|
+
return scanBg3ModIdentities(PAKS_ROOT, path.join(install.root, "Data"));
|
|
140
|
+
},
|
|
141
|
+
logs(install) {
|
|
142
|
+
const seDirs = scriptExtenderLogDirs(install);
|
|
143
|
+
return logs([
|
|
144
|
+
{
|
|
145
|
+
id: "extender",
|
|
146
|
+
label: "Script Extender console log",
|
|
147
|
+
candidates: seDirs.map((dir) => path.join(dir, "Extender Runtime *.log")),
|
|
148
|
+
rootId: "scriptextender",
|
|
149
|
+
notes: "Lua errors and module load messages from BG3SE, written as " +
|
|
150
|
+
"'Extender Runtime <date>.log' under LogDirectory only when LogRuntime is true " +
|
|
151
|
+
"in bin/ScriptExtenderSettings.json. SE runs fine without writing any log at all.",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "osiris",
|
|
155
|
+
label: "Osiris log",
|
|
156
|
+
candidates: seDirs.map((dir) => path.join(dir, "Osiris*.log")),
|
|
157
|
+
notes: "Story rule evaluation and compile errors, written as 'Osiris Runtime <date>.log' " +
|
|
158
|
+
"under LogDirectory when EnableLogging is true (timestamp in the name is UTC). " +
|
|
159
|
+
"Grows to hundreds of MB in one session; only its tail is ever scanned. 'Symbol not " +
|
|
160
|
+
"found in story' and missing DB_/PROC_/QRY_ prefixes are the classic failures.",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "network",
|
|
164
|
+
label: "Game network log",
|
|
165
|
+
candidates: [path.join(install.root, "bin", "network.*.log")],
|
|
166
|
+
notes: "First read on any crash: shows whether a level transition started before the game died. " +
|
|
167
|
+
"One file per launch, ISO timestamp in the name; the newest wins.",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "crash",
|
|
171
|
+
label: "Crash dumps",
|
|
172
|
+
candidates: [path.join(install.root, "bin", "CrashDump*.dmp")],
|
|
173
|
+
binary: true,
|
|
174
|
+
notes: "Timestamps corroborate the network log; the dumps themselves are not parsed.",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "gamelog",
|
|
178
|
+
label: "Game log",
|
|
179
|
+
candidates: [path.join(install.root, "bin", "gold.*.log")],
|
|
180
|
+
notes: "Larian's general client log (Discord init, client thread start/end, cloud save " +
|
|
181
|
+
"sync), one file per launch beside network.*.log. Same line format as the network log.",
|
|
182
|
+
},
|
|
183
|
+
]);
|
|
184
|
+
},
|
|
185
|
+
async explainMissing(install) {
|
|
186
|
+
const settings = readScriptExtenderSettings(install);
|
|
187
|
+
if (!settings.present) {
|
|
188
|
+
const reason = "Script Extender does not appear to be installed or configured: " +
|
|
189
|
+
`${settings.file} was not found.`;
|
|
190
|
+
return { extender: reason, osiris: reason };
|
|
191
|
+
}
|
|
192
|
+
const logDir = scriptExtenderLogDirs(install)[0];
|
|
193
|
+
const reasons = {};
|
|
194
|
+
reasons.extender =
|
|
195
|
+
settings.logRuntime === false
|
|
196
|
+
? `LogRuntime is false in ${settings.file}, so Script Extender never writes its console ` +
|
|
197
|
+
`log ('Extender Runtime <date>.log'); set it to true and relaunch. LogDirectory ` +
|
|
198
|
+
`resolves to "${logDir}".`
|
|
199
|
+
: `LogRuntime is enabled in ${settings.file} and LogDirectory resolves to "${logDir}", ` +
|
|
200
|
+
"but no Extender Runtime log was found there — has the game been launched since?";
|
|
201
|
+
reasons.osiris =
|
|
202
|
+
settings.enableLogging === false
|
|
203
|
+
? `EnableLogging is false in ${settings.file}, so Osiris never writes its runtime log ` +
|
|
204
|
+
`('Osiris Runtime <date>.log'); set it to true and relaunch. LogDirectory resolves to ` +
|
|
205
|
+
`"${logDir}".`
|
|
206
|
+
: `EnableLogging is enabled in ${settings.file} and LogDirectory resolves to "${logDir}", ` +
|
|
207
|
+
"but no Osiris Runtime log was found there — has the game been launched since?";
|
|
208
|
+
return reasons;
|
|
209
|
+
},
|
|
210
|
+
toolNeeds: [
|
|
211
|
+
{ feature: "index", tools: ["divine"], note: "build_index extracts the vanilla paks and converts .lsf/.loca with LSLib's divine." },
|
|
212
|
+
{ feature: "build", tools: ["divine"], note: "The divine-* steps (lsx to lsf, loca, create-package) and verifying a .pak's listing." },
|
|
213
|
+
],
|
|
214
|
+
processNames: ["bg3_dx11.exe", "bg3.exe"],
|
|
215
|
+
compat: baldursgate3Compat,
|
|
216
|
+
guidance: "Load order entries identify mods by UUID and module Name, neither of which has to " +
|
|
217
|
+
"match the .pak filename on disk — ModWright now reads meta.lsx from inside every " +
|
|
218
|
+
"installed pak (and every loose Data/Mods/*/meta.lsx) to match entries to their real " +
|
|
219
|
+
"identity, rather than guessing from filenames. Two or more paks can carry the same " +
|
|
220
|
+
"UUID under different filenames (a hand-built copy and a mod.io re-download, say); " +
|
|
221
|
+
"which copy the game then loads is not predictable from outside (observed in game: " +
|
|
222
|
+
"the load order's Folder attribute named a copy whose content did not load, and " +
|
|
223
|
+
"a plain launch never rewrites that file); load_order's crossCheck and find_conflicts's " +
|
|
224
|
+
"sharedIdentities list every copy, and Ext.Mod.GetMod(uuid).Info.Directory at the " +
|
|
225
|
+
"Script Extender console is the live answer. " +
|
|
226
|
+
"Two failure modes dominate. First, a .pak dropped into Mods/ without a matching " +
|
|
227
|
+
"ModuleShortDesc entry in modsettings.lsx is inert — always cross-check the load order " +
|
|
228
|
+
"against the installed paks. Second, mod managers (the in-game one, Vortex, BG3 Mod " +
|
|
229
|
+
"Manager) rewrite modsettings.lsx and silently drop entries whose UUIDs they cannot " +
|
|
230
|
+
"match, so an order that 'reverts itself' " +
|
|
231
|
+
"usually means a missing or renamed pak rather than a corrupt file.",
|
|
232
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { bridgeNeverShipValidator } from "../../../core/bridge/index.js";
|
|
2
|
+
export const bridgeNeverShipsValidator = bridgeNeverShipValidator({
|
|
3
|
+
id: "bg3.history.bridge-never-ships",
|
|
4
|
+
game: "baldursgate3",
|
|
5
|
+
evidence: ["ModWright research"],
|
|
6
|
+
});
|
|
7
|
+
export const BRIDGE_VALIDATORS = [bridgeNeverShipsValidator];
|