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,164 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { readPeFileVersion } from "../../core/compat/pe.js";
|
|
3
|
+
import { isDirectory, localAppData, pathExists, readTextIfExists, resolveCandidate } from "../../core/fsutil.js";
|
|
4
|
+
import { promises as fs } from "node:fs";
|
|
5
|
+
import { scriptExtenderLogDirs } from "./settings.js";
|
|
6
|
+
function larianDir() {
|
|
7
|
+
return localAppData("Larian Studios", "Baldur's Gate 3");
|
|
8
|
+
}
|
|
9
|
+
const SE_BUILD_RE = /BG3Ext\s+v(\d+)/i;
|
|
10
|
+
const SE_GAME_VERSION_RE = /Game version\s+v?([\d.]+)/i;
|
|
11
|
+
async function detectComponents(install) {
|
|
12
|
+
const gameExe = path.join(install.root, "bin", "bg3_dx11.exe");
|
|
13
|
+
const gameInstalled = await pathExists(gameExe);
|
|
14
|
+
let gameVersion = await readPeFileVersion(gameExe);
|
|
15
|
+
let gameSource = gameVersion ? "bin/bg3_dx11.exe (PE FileVersion)" : undefined;
|
|
16
|
+
const dwrite = path.join(install.root, "bin", "DWrite.dll");
|
|
17
|
+
const dwriteInstalled = await pathExists(dwrite);
|
|
18
|
+
const larian = larianDir();
|
|
19
|
+
const seLogCandidates = scriptExtenderLogDirs(install).map((dir) => path.join(dir, "Extender Runtime *.log"));
|
|
20
|
+
const seLogHit = await resolveCandidate(seLogCandidates);
|
|
21
|
+
const seLog = seLogHit ? await readTextIfExists(seLogHit.path) : undefined;
|
|
22
|
+
let extenderVersion;
|
|
23
|
+
let extenderSource;
|
|
24
|
+
if (seLog) {
|
|
25
|
+
const buildMatch = seLog.match(SE_BUILD_RE);
|
|
26
|
+
if (buildMatch?.[1]) {
|
|
27
|
+
extenderVersion = buildMatch[1];
|
|
28
|
+
extenderSource = `${path.basename(seLogHit.path)} ("BG3Ext vNN" startup line, heuristic — format confirmed on one sample, see module comment)`;
|
|
29
|
+
}
|
|
30
|
+
if (!gameVersion) {
|
|
31
|
+
const gameMatch = seLog.match(SE_GAME_VERSION_RE);
|
|
32
|
+
if (gameMatch?.[1]) {
|
|
33
|
+
gameVersion = gameMatch[1];
|
|
34
|
+
gameSource = `${path.basename(seLogHit.path)} ("Game version" startup line, heuristic)`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!extenderVersion && dwriteInstalled) {
|
|
39
|
+
const cached = await newestCachedExtenderBuild(updaterCacheDir());
|
|
40
|
+
if (cached) {
|
|
41
|
+
const dllVersion = await readPeFileVersion(cached.dll);
|
|
42
|
+
const major = dllVersion?.split(".")[0];
|
|
43
|
+
if (major && major !== "0") {
|
|
44
|
+
extenderVersion = major;
|
|
45
|
+
extenderSource =
|
|
46
|
+
`BG3ScriptExtender updater cache, newest cached build ${cached.folder} ` +
|
|
47
|
+
"(BG3ScriptExtender.dll PE FileVersion; heuristic — the loader uses the newest build " +
|
|
48
|
+
"the game version accepts, which is normally this one)";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const components = [
|
|
53
|
+
{
|
|
54
|
+
id: "game",
|
|
55
|
+
label: "Baldur's Gate 3",
|
|
56
|
+
kind: "game",
|
|
57
|
+
installed: gameInstalled,
|
|
58
|
+
version: gameVersion,
|
|
59
|
+
source: gameSource,
|
|
60
|
+
path: gameExe,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "scriptextender",
|
|
64
|
+
label: "Script Extender (BG3SE)",
|
|
65
|
+
kind: "loader",
|
|
66
|
+
installed: dwriteInstalled,
|
|
67
|
+
version: extenderVersion,
|
|
68
|
+
source: extenderSource,
|
|
69
|
+
path: dwrite,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
const nativeModsDir = path.join(install.root, "bin", "NativeMods");
|
|
73
|
+
components.push({
|
|
74
|
+
id: "nativemodloader",
|
|
75
|
+
label: "Native Mod Loader",
|
|
76
|
+
kind: "loader",
|
|
77
|
+
installed: await isDirectory(nativeModsDir),
|
|
78
|
+
path: nativeModsDir,
|
|
79
|
+
});
|
|
80
|
+
const modsDir = path.join(larian, "Mods");
|
|
81
|
+
const pakNames = await listPakBasenames(modsDir);
|
|
82
|
+
components.push({
|
|
83
|
+
id: "mcm",
|
|
84
|
+
label: "Mod Configuration Menu",
|
|
85
|
+
kind: "framework",
|
|
86
|
+
installed: pakNames.some((n) => /\b(mod configuration menu|bg3[\s_-]?mcm|\bmcm\b)/i.test(n)),
|
|
87
|
+
source: "Mods/*.pak filename match only — no manifest is read, so this can miss a renamed file or false-positive on an unrelated name.",
|
|
88
|
+
path: modsDir,
|
|
89
|
+
});
|
|
90
|
+
components.push({
|
|
91
|
+
id: "compatibilityframework",
|
|
92
|
+
label: "Compatibility Framework",
|
|
93
|
+
kind: "framework",
|
|
94
|
+
installed: pakNames.some((n) => /compatibility framework/i.test(n)),
|
|
95
|
+
source: "Mods/*.pak filename match only — no manifest is read, so this can miss a renamed file or false-positive on an unrelated name.",
|
|
96
|
+
path: modsDir,
|
|
97
|
+
});
|
|
98
|
+
return components;
|
|
99
|
+
}
|
|
100
|
+
function updaterCacheDir() {
|
|
101
|
+
return localAppData("BG3ScriptExtender", "ScriptExtender");
|
|
102
|
+
}
|
|
103
|
+
const CACHED_BUILD_RE = /^(\d+)\.\d+\.\d+\.\d+_[0-9a-f]{16,}$/i;
|
|
104
|
+
export async function newestCachedExtenderBuild(cacheDir) {
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
entries = (await fs.readdir(cacheDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const builds = entries
|
|
113
|
+
.map((folder) => ({ folder, major: Number(CACHED_BUILD_RE.exec(folder)?.[1] ?? NaN) }))
|
|
114
|
+
.filter((b) => Number.isFinite(b.major))
|
|
115
|
+
.sort((a, b) => b.major - a.major);
|
|
116
|
+
for (const build of builds) {
|
|
117
|
+
const dll = path.join(cacheDir, build.folder, "BG3ScriptExtender.dll");
|
|
118
|
+
if (await pathExists(dll))
|
|
119
|
+
return { ...build, dll };
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
async function listPakBasenames(dir) {
|
|
124
|
+
try {
|
|
125
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
126
|
+
return entries
|
|
127
|
+
.filter((e) => e.isFile() && e.name.toLowerCase().endsWith(".pak"))
|
|
128
|
+
.map((e) => e.name);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const RULES = [
|
|
135
|
+
{
|
|
136
|
+
id: "bg3-se-latest-patch-only",
|
|
137
|
+
game: "baldursgate3",
|
|
138
|
+
component: "scriptextender",
|
|
139
|
+
requires: [],
|
|
140
|
+
advisory: true,
|
|
141
|
+
severity: "info",
|
|
142
|
+
note: "Script Extender only officially supports the game's current patch. After a BG3 update, " +
|
|
143
|
+
"check the latest bg3se Updater Release notes before assuming a mod (rather than a stale SE) broke.",
|
|
144
|
+
source: "https://github.com/Norbyte/bg3se/blob/master/TROUBLESHOOTING.md",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "bg3-se-mcm-patch7-interop",
|
|
148
|
+
game: "baldursgate3",
|
|
149
|
+
component: "scriptextender",
|
|
150
|
+
requires: [{ component: "mcm" }, { component: "game" }],
|
|
151
|
+
advisory: true,
|
|
152
|
+
versionGate: "game",
|
|
153
|
+
severity: "warn",
|
|
154
|
+
undeterminedSeverity: "info",
|
|
155
|
+
note: "Script Extender and the Mod Configuration Menu are known to interfere with each other as of " +
|
|
156
|
+
"BG3 Patch 7, with no fixed timeline from either project. If MCM's window will not open, this " +
|
|
157
|
+
"is the first thing to check rather than the mod itself.",
|
|
158
|
+
source: "https://github.com/Norbyte/bg3se/issues/462",
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
export const baldursgate3Compat = {
|
|
162
|
+
detectComponents,
|
|
163
|
+
rules: RULES,
|
|
164
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { readTextIfExists } from "../../core/fsutil.js";
|
|
4
|
+
import { parseMetaLsxModuleInfo } from "../../core/lsx.js";
|
|
5
|
+
import { readPakModuleInfos } from "./pak.js";
|
|
6
|
+
const pakInfoCache = new Map();
|
|
7
|
+
function cachedPakModuleInfos(pakPath, size, mtimeMs) {
|
|
8
|
+
const key = JSON.stringify([pakPath, size, mtimeMs]);
|
|
9
|
+
let cached = pakInfoCache.get(key);
|
|
10
|
+
if (!cached) {
|
|
11
|
+
cached = readPakModuleInfos(pakPath);
|
|
12
|
+
pakInfoCache.set(key, cached);
|
|
13
|
+
cached.catch(() => pakInfoCache.delete(key));
|
|
14
|
+
}
|
|
15
|
+
return cached;
|
|
16
|
+
}
|
|
17
|
+
export async function scanPaksIdentities(paksRootPath) {
|
|
18
|
+
const copies = [];
|
|
19
|
+
const unreadable = [];
|
|
20
|
+
let entries;
|
|
21
|
+
try {
|
|
22
|
+
entries = await fs.readdir(paksRootPath);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { copies, unreadable };
|
|
26
|
+
}
|
|
27
|
+
for (const entryName of entries) {
|
|
28
|
+
if (path.extname(entryName).toLowerCase() !== ".pak")
|
|
29
|
+
continue;
|
|
30
|
+
const pakPath = path.join(paksRootPath, entryName);
|
|
31
|
+
let mtime;
|
|
32
|
+
let stat;
|
|
33
|
+
try {
|
|
34
|
+
stat = await fs.stat(pakPath);
|
|
35
|
+
mtime = stat.mtime.toISOString();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
let infos;
|
|
41
|
+
try {
|
|
42
|
+
infos = await cachedPakModuleInfos(pakPath, stat.size, stat.mtimeMs);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
unreadable.push({ path: pakPath, error: error.message });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const info of infos) {
|
|
49
|
+
if (!info.folder && !info.uuid)
|
|
50
|
+
continue;
|
|
51
|
+
copies.push({
|
|
52
|
+
path: pakPath,
|
|
53
|
+
rootId: "paks",
|
|
54
|
+
folder: info.folder ?? path.basename(pakPath, path.extname(pakPath)),
|
|
55
|
+
name: info.name,
|
|
56
|
+
uuid: info.uuid,
|
|
57
|
+
modifiedAt: mtime,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { copies, unreadable };
|
|
62
|
+
}
|
|
63
|
+
export async function scanLooseIdentities(looseRootPath) {
|
|
64
|
+
const copies = [];
|
|
65
|
+
const unreadable = [];
|
|
66
|
+
const modsDir = path.join(looseRootPath, "Mods");
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = await fs.readdir(modsDir, { withFileTypes: true });
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return { copies, unreadable };
|
|
73
|
+
}
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (!entry.isDirectory())
|
|
76
|
+
continue;
|
|
77
|
+
const metaPath = path.join(modsDir, entry.name, "meta.lsx");
|
|
78
|
+
let stat;
|
|
79
|
+
try {
|
|
80
|
+
stat = await fs.stat(metaPath);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const text = await readTextIfExists(metaPath);
|
|
86
|
+
if (!text) {
|
|
87
|
+
unreadable.push({ path: metaPath, error: "could not read meta.lsx" });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const info = parseMetaLsxModuleInfo(text);
|
|
91
|
+
if (!info || (!info.folder && !info.uuid))
|
|
92
|
+
continue;
|
|
93
|
+
copies.push({
|
|
94
|
+
path: metaPath,
|
|
95
|
+
rootId: "loose",
|
|
96
|
+
folder: info.folder ?? entry.name,
|
|
97
|
+
name: info.name,
|
|
98
|
+
uuid: info.uuid,
|
|
99
|
+
modifiedAt: stat.mtime.toISOString(),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return { copies, unreadable };
|
|
103
|
+
}
|
|
104
|
+
export async function scanModIdentities(paksRootPath, looseRootPath) {
|
|
105
|
+
const [paks, loose] = await Promise.all([scanPaksIdentities(paksRootPath), scanLooseIdentities(looseRootPath)]);
|
|
106
|
+
return {
|
|
107
|
+
copies: [...paks.copies, ...loose.copies],
|
|
108
|
+
unreadable: [...paks.unreadable, ...loose.unreadable],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { pathExists, walk } from "../../../core/fsutil.js";
|
|
4
|
+
import { defaultCacheDir } from "../../../core/knowledge/store.js";
|
|
5
|
+
import { assertAbsolute, locateTool, runTool, toNativePath, TOOL_SPECS } from "../../../core/toolchain/index.js";
|
|
6
|
+
import { info } from "./info.js";
|
|
7
|
+
import { parseLoca } from "./parsers/loca.js";
|
|
8
|
+
import { parseLsx, walkLsxNodes } from "./parsers/lsx.js";
|
|
9
|
+
import { parseStats } from "./parsers/stats.js";
|
|
10
|
+
import { parseTreasureTable } from "./parsers/treasuretable.js";
|
|
11
|
+
import { createSchema } from "./schema.js";
|
|
12
|
+
export const VANILLA_PAKS = ["Shared.pak", "Gustav.pak", "GustavX.pak", "Localization/English.pak"];
|
|
13
|
+
const STATS_PATTERN = /\/Stats\/Generated\/Data\/[^/]+\.txt$/i;
|
|
14
|
+
const TREASURE_PATTERN = /\/Stats\/Generated\/TreasureTable\.txt$/i;
|
|
15
|
+
const LSX_PATTERNS = [
|
|
16
|
+
/\/Lists\/[^/]+\.lsx$/i,
|
|
17
|
+
/\/Progressions\/[^/]+\.lsx$/i,
|
|
18
|
+
/\/ClassDescriptions\/[^/]+\.lsx$/i,
|
|
19
|
+
/\/Tags\/[^/]+\.lsx$/i,
|
|
20
|
+
/\/RootTemplates\/[^/]+\.lsx$/i,
|
|
21
|
+
/\/GUI\/[^/]+\.lsx$/i,
|
|
22
|
+
/\/ActionResourceDefinitions\/[^/]+\.lsx$/i,
|
|
23
|
+
/\/(Content\/)?Assets\/.*\.lsx$/i,
|
|
24
|
+
];
|
|
25
|
+
const LOCA_XML_PATTERN = /(?:^|\/)Localization\/[^/]+\/[^/]+\.xml$/i;
|
|
26
|
+
const EXTRACT_EXPRESSION = "^(Public/[^/]+/(Stats/Generated|Lists|Progressions|ClassDescriptions|Tags|RootTemplates|GUI|ActionResourceDefinitions)/.*" +
|
|
27
|
+
"|Public/[^/]+/(Content/)?Assets/.*\\.lsf" +
|
|
28
|
+
"|(.*/)?Localization/.*)$";
|
|
29
|
+
const INDEXED_NODE_IDS = new Set([
|
|
30
|
+
"SpellList",
|
|
31
|
+
"Progression",
|
|
32
|
+
"ClassDescription",
|
|
33
|
+
"Tag",
|
|
34
|
+
"Tags",
|
|
35
|
+
"GameObjects",
|
|
36
|
+
"IconUV",
|
|
37
|
+
"TextureAtlasPath",
|
|
38
|
+
"ActionResourceDefinition",
|
|
39
|
+
"Resource",
|
|
40
|
+
]);
|
|
41
|
+
function uuidFor(node) {
|
|
42
|
+
return node.attributes.UUID?.value ?? node.attributes.ClassUUID?.value ??
|
|
43
|
+
(node.id === "GameObjects" ? node.attributes.MapKey?.value : undefined) ??
|
|
44
|
+
(node.id === "Resource" ? node.attributes.ID?.value : undefined);
|
|
45
|
+
}
|
|
46
|
+
function nameFor(node) {
|
|
47
|
+
return node.attributes.Name?.value ?? (node.id === "IconUV" ? node.attributes.MapKey?.value : undefined);
|
|
48
|
+
}
|
|
49
|
+
async function collectFiles(root, patterns) {
|
|
50
|
+
if (!(await pathExists(root)))
|
|
51
|
+
return [];
|
|
52
|
+
const walked = await walk(root, { limit: 500_000 });
|
|
53
|
+
return walked.files.filter((f) => patterns.some((p) => p.test(f))).map((rel) => ({ abs: path.join(root, rel), rel }));
|
|
54
|
+
}
|
|
55
|
+
async function collectLsxFiles(extractDir, convertedDir) {
|
|
56
|
+
const [native, converted] = await Promise.all([
|
|
57
|
+
collectFiles(extractDir, LSX_PATTERNS),
|
|
58
|
+
collectFiles(convertedDir, LSX_PATTERNS),
|
|
59
|
+
]);
|
|
60
|
+
const byRel = new Map();
|
|
61
|
+
for (const file of native)
|
|
62
|
+
byRel.set(file.rel, file);
|
|
63
|
+
for (const file of converted)
|
|
64
|
+
byRel.set(file.rel, file);
|
|
65
|
+
return [...byRel.values()];
|
|
66
|
+
}
|
|
67
|
+
function insertInBatches(store, table, columns, rows, step, progress, batchSize = 5000) {
|
|
68
|
+
if (rows.length === 0) {
|
|
69
|
+
progress?.({ step, done: 0, total: 0 });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (let i = 0; i < rows.length; i += batchSize) {
|
|
73
|
+
const batch = rows.slice(i, i + batchSize);
|
|
74
|
+
store.transaction(() => {
|
|
75
|
+
store.insertMany(table, columns, batch);
|
|
76
|
+
});
|
|
77
|
+
progress?.({ step, done: Math.min(i + batchSize, rows.length), total: rows.length });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function insertParsedFiles(store, sourceId, files, progress) {
|
|
81
|
+
let nextStatId = (Number(store.get(`SELECT COALESCE(MAX(id), 0) AS m FROM stat`)?.m) || 0) + 1;
|
|
82
|
+
const statRows = [];
|
|
83
|
+
const fieldRows = [];
|
|
84
|
+
const ftsRows = [];
|
|
85
|
+
progress?.({ step: "parse-stats", total: files.statFiles.length, note: sourceId });
|
|
86
|
+
for (const { abs, rel } of files.statFiles) {
|
|
87
|
+
const text = await fs.readFile(abs, "utf8");
|
|
88
|
+
for (const entry of parseStats(text, rel)) {
|
|
89
|
+
const id = nextStatId++;
|
|
90
|
+
statRows.push([id, entry.name, entry.type, entry.using ?? null, sourceId, entry.file, entry.line]);
|
|
91
|
+
for (const [field, value] of Object.entries(entry.fields)) {
|
|
92
|
+
fieldRows.push([id, field, value]);
|
|
93
|
+
ftsRows.push([entry.name, field, value]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
insertInBatches(store, "stat", ["id", "name", "type", '"using"', "source_id", "file", "line"], statRows, "insert-stat", progress);
|
|
98
|
+
insertInBatches(store, "stat_field", ["stat_id", "field", "value"], fieldRows, "insert-stat-field", progress);
|
|
99
|
+
insertInBatches(store, "stat_field_fts", ["name", "field", "value"], ftsRows, "insert-stat-field-fts", progress);
|
|
100
|
+
const nodeRows = [];
|
|
101
|
+
progress?.({ step: "parse-lsx", total: files.lsxFiles.length, note: sourceId });
|
|
102
|
+
for (const { abs, rel } of files.lsxFiles) {
|
|
103
|
+
const text = await fs.readFile(abs, "utf8");
|
|
104
|
+
let doc;
|
|
105
|
+
try {
|
|
106
|
+
doc = parseLsx(text, rel);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
progress?.({ step: "parse-lsx-error", note: `${rel}: ${err instanceof Error ? err.message : String(err)}` });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
for (const region of doc.regions) {
|
|
113
|
+
walkLsxNodes(region.root, (node, breadcrumb) => {
|
|
114
|
+
if (!INDEXED_NODE_IDS.has(node.id))
|
|
115
|
+
return;
|
|
116
|
+
nodeRows.push([
|
|
117
|
+
sourceId,
|
|
118
|
+
rel,
|
|
119
|
+
region.id,
|
|
120
|
+
node.id,
|
|
121
|
+
breadcrumb,
|
|
122
|
+
uuidFor(node) ?? null,
|
|
123
|
+
nameFor(node) ?? null,
|
|
124
|
+
JSON.stringify(node.attributes),
|
|
125
|
+
]);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
progress?.({ step: "parse-treasure", total: files.treasureFiles.length, note: sourceId });
|
|
130
|
+
for (const { abs, rel } of files.treasureFiles) {
|
|
131
|
+
const text = await fs.readFile(abs, "utf8");
|
|
132
|
+
for (const entry of parseTreasureTable(text, rel)) {
|
|
133
|
+
nodeRows.push([
|
|
134
|
+
sourceId,
|
|
135
|
+
rel,
|
|
136
|
+
entry.treasuretable,
|
|
137
|
+
"TreasureCategory",
|
|
138
|
+
`TreasureTable/${entry.treasuretable}/subtable-${entry.subtableIndex}`,
|
|
139
|
+
null,
|
|
140
|
+
entry.category,
|
|
141
|
+
JSON.stringify({
|
|
142
|
+
Category: { type: "LSString", value: entry.category },
|
|
143
|
+
Args: { type: "LSString", value: entry.args },
|
|
144
|
+
CanMerge: { type: "bool", value: entry.canMerge ? "True" : "False" },
|
|
145
|
+
SubtableIndex: { type: "int32", value: String(entry.subtableIndex) },
|
|
146
|
+
}),
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
insertInBatches(store, "node", ["source_id", "file", "region", "node_id", "path", "uuid", "name", "attrs_json"], nodeRows, "insert-node", progress);
|
|
151
|
+
const locaByHandle = new Map();
|
|
152
|
+
progress?.({ step: "parse-loca", total: files.locaFiles.length, note: sourceId });
|
|
153
|
+
for (const { abs, rel } of files.locaFiles) {
|
|
154
|
+
const text = await fs.readFile(abs, "utf8");
|
|
155
|
+
let entries;
|
|
156
|
+
try {
|
|
157
|
+
entries = parseLoca(text, rel);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
progress?.({ step: "parse-loca-error", note: `${rel}: ${err instanceof Error ? err.message : String(err)}` });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
for (const entry of entries)
|
|
164
|
+
locaByHandle.set(entry.handle, { version: entry.version, text: entry.text });
|
|
165
|
+
}
|
|
166
|
+
const locaRows = [];
|
|
167
|
+
const locaFtsRows = [];
|
|
168
|
+
for (const [handle, { version, text }] of locaByHandle) {
|
|
169
|
+
locaRows.push([handle, version, text]);
|
|
170
|
+
locaFtsRows.push([handle, text]);
|
|
171
|
+
}
|
|
172
|
+
insertInBatches(store, "loca", ["handle", "version", "text"], locaRows, "insert-loca", progress);
|
|
173
|
+
insertInBatches(store, "loca_fts", ["handle", "text"], locaFtsRows, "insert-loca-fts", progress);
|
|
174
|
+
return { statCount: statRows.length, nodeCount: nodeRows.length, locaCount: locaRows.length };
|
|
175
|
+
}
|
|
176
|
+
export async function indexFromDirectory(dir, store, progress) {
|
|
177
|
+
createSchema(store);
|
|
178
|
+
const absDir = path.resolve(dir);
|
|
179
|
+
const sourceId = `dir:${absDir}`;
|
|
180
|
+
store.run(`INSERT OR REPLACE INTO source (id, kind, path, mtime) VALUES (?, ?, ?, ?)`, [
|
|
181
|
+
sourceId,
|
|
182
|
+
"directory",
|
|
183
|
+
absDir,
|
|
184
|
+
new Date().toISOString(),
|
|
185
|
+
]);
|
|
186
|
+
progress?.({ step: "walk", note: absDir });
|
|
187
|
+
const files = {
|
|
188
|
+
statFiles: await collectFiles(absDir, [STATS_PATTERN]),
|
|
189
|
+
lsxFiles: await collectFiles(absDir, LSX_PATTERNS),
|
|
190
|
+
locaFiles: await collectFiles(absDir, [LOCA_XML_PATTERN]),
|
|
191
|
+
treasureFiles: await collectFiles(absDir, [TREASURE_PATTERN]),
|
|
192
|
+
};
|
|
193
|
+
const counts = await insertParsedFiles(store, sourceId, files, progress);
|
|
194
|
+
return { sourceId, ...counts };
|
|
195
|
+
}
|
|
196
|
+
export async function buildIndex(install, store, options = {}) {
|
|
197
|
+
createSchema(store);
|
|
198
|
+
const progress = options.progress;
|
|
199
|
+
const divineSpec = TOOL_SPECS.find((spec) => spec.id === "divine");
|
|
200
|
+
if (!divineSpec)
|
|
201
|
+
throw new Error("No toolchain spec registered for 'divine'.");
|
|
202
|
+
const divine = options.locate
|
|
203
|
+
? await options.locate("divine")
|
|
204
|
+
: await locateTool(divineSpec, { projectOverrides: options.toolOverrides });
|
|
205
|
+
if (!divine.found || !divine.path) {
|
|
206
|
+
throw new Error(`Cannot build the BG3 vanilla index: ${divine.hint ?? "divine.exe (LSLib) was not found."}`);
|
|
207
|
+
}
|
|
208
|
+
const cacheDir = defaultCacheDir();
|
|
209
|
+
const only = options.only;
|
|
210
|
+
const paks = VANILLA_PAKS.filter((pakName) => {
|
|
211
|
+
if (!only || only.length === 0)
|
|
212
|
+
return true;
|
|
213
|
+
const base = pakName.replace(/\.pak$/i, "");
|
|
214
|
+
return only.some((o) => o.toLowerCase() === base.toLowerCase());
|
|
215
|
+
});
|
|
216
|
+
const sources = [];
|
|
217
|
+
let statTotal = 0;
|
|
218
|
+
let nodeTotal = 0;
|
|
219
|
+
let locaTotal = 0;
|
|
220
|
+
for (const pakName of paks) {
|
|
221
|
+
const pakBase = pakName.replace(/\.pak$/i, "");
|
|
222
|
+
const pakPath = path.join(install.root, "Data", pakName);
|
|
223
|
+
if (!(await pathExists(pakPath))) {
|
|
224
|
+
progress?.({ step: "pak-missing", note: pakPath });
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const extractDir = path.join(cacheDir, "baldursgate3", "extract", pakBase);
|
|
228
|
+
if (options.force || !(await pathExists(extractDir))) {
|
|
229
|
+
const pakAbs = path.resolve(pakPath);
|
|
230
|
+
const extractAbs = path.resolve(extractDir);
|
|
231
|
+
assertAbsolute([pakAbs, extractAbs]);
|
|
232
|
+
progress?.({ step: "extract", note: pakName });
|
|
233
|
+
const result = await runTool(divine, {
|
|
234
|
+
args: [
|
|
235
|
+
"-g", "bg3", "-a", "extract-package",
|
|
236
|
+
"-s", toNativePath(pakAbs), "-d", toNativePath(extractAbs),
|
|
237
|
+
"-x", EXTRACT_EXPRESSION, "--use-regex",
|
|
238
|
+
],
|
|
239
|
+
expectOutputs: [extractAbs],
|
|
240
|
+
timeoutMs: 10 * 60_000,
|
|
241
|
+
});
|
|
242
|
+
if (!result.ok) {
|
|
243
|
+
throw new Error(`divine extract-package failed for ${pakName}: ${result.error?.message ?? "unknown error"}` +
|
|
244
|
+
(result.error?.hint ? ` (${result.error.hint})` : ""));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
progress?.({ step: "extract-cached", note: extractDir });
|
|
249
|
+
}
|
|
250
|
+
const convertedDir = path.join(cacheDir, "baldursgate3", "converted", pakBase);
|
|
251
|
+
if (options.force || !(await pathExists(convertedDir))) {
|
|
252
|
+
const extractAbs = path.resolve(extractDir);
|
|
253
|
+
const convertedAbs = path.resolve(convertedDir);
|
|
254
|
+
assertAbsolute([extractAbs, convertedAbs]);
|
|
255
|
+
const extractWalk = await walk(extractAbs, { limit: 200_000 });
|
|
256
|
+
const hasLsf = extractWalk.files.some((f) => /\.lsf$/i.test(f));
|
|
257
|
+
await fs.mkdir(convertedAbs, { recursive: true });
|
|
258
|
+
progress?.({ step: hasLsf ? "convert-resources" : "convert-resources-skipped", note: pakName });
|
|
259
|
+
const converted = hasLsf ? await runTool(divine, {
|
|
260
|
+
args: [
|
|
261
|
+
"-g",
|
|
262
|
+
"bg3",
|
|
263
|
+
"-a",
|
|
264
|
+
"convert-resources",
|
|
265
|
+
"-i",
|
|
266
|
+
"lsf",
|
|
267
|
+
"-o",
|
|
268
|
+
"lsx",
|
|
269
|
+
"-s",
|
|
270
|
+
toNativePath(extractAbs),
|
|
271
|
+
"-d",
|
|
272
|
+
toNativePath(convertedAbs),
|
|
273
|
+
],
|
|
274
|
+
expectOutputs: [convertedAbs],
|
|
275
|
+
timeoutMs: 20 * 60_000,
|
|
276
|
+
}) : { ok: true, error: undefined };
|
|
277
|
+
if (!converted.ok) {
|
|
278
|
+
throw new Error(`divine convert-resources failed for ${pakName}: ${converted.error?.message ?? "unknown error"}`);
|
|
279
|
+
}
|
|
280
|
+
const locaWalk = await walk(extractAbs, { limit: 200_000 });
|
|
281
|
+
const locaFiles = locaWalk.files.filter((f) => /\.loca$/i.test(f));
|
|
282
|
+
for (const rel of locaFiles) {
|
|
283
|
+
const src = path.resolve(extractAbs, rel);
|
|
284
|
+
const dst = path.resolve(convertedAbs, rel.replace(/\.loca$/i, ".xml"));
|
|
285
|
+
assertAbsolute([src, dst]);
|
|
286
|
+
progress?.({ step: "convert-loca", note: rel });
|
|
287
|
+
const locaResult = await runTool(divine, {
|
|
288
|
+
args: ["-g", "bg3", "-a", "convert-loca", "-s", toNativePath(src), "-d", toNativePath(dst)],
|
|
289
|
+
expectOutputs: [dst],
|
|
290
|
+
timeoutMs: 60_000,
|
|
291
|
+
});
|
|
292
|
+
if (!locaResult.ok) {
|
|
293
|
+
throw new Error(`divine convert-loca failed for ${rel}: ${locaResult.error?.message ?? "unknown error"}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
progress?.({ step: "convert-cached", note: convertedDir });
|
|
299
|
+
}
|
|
300
|
+
const files = {
|
|
301
|
+
statFiles: await collectFiles(extractDir, [STATS_PATTERN]),
|
|
302
|
+
lsxFiles: await collectLsxFiles(extractDir, convertedDir),
|
|
303
|
+
locaFiles: await collectFiles(convertedDir, [LOCA_XML_PATTERN]),
|
|
304
|
+
treasureFiles: await collectFiles(extractDir, [TREASURE_PATTERN]),
|
|
305
|
+
};
|
|
306
|
+
const sourceId = `pak:${pakName}`;
|
|
307
|
+
const mtime = (await fs.stat(pakPath)).mtime.toISOString();
|
|
308
|
+
store.run(`INSERT OR REPLACE INTO source (id, kind, path, mtime) VALUES (?, ?, ?, ?)`, [
|
|
309
|
+
sourceId,
|
|
310
|
+
"pak",
|
|
311
|
+
path.resolve(pakPath),
|
|
312
|
+
mtime,
|
|
313
|
+
]);
|
|
314
|
+
sources.push(pakName);
|
|
315
|
+
const counts = await insertParsedFiles(store, sourceId, files, progress);
|
|
316
|
+
statTotal += counts.statCount;
|
|
317
|
+
nodeTotal += counts.nodeCount;
|
|
318
|
+
locaTotal += counts.locaCount;
|
|
319
|
+
}
|
|
320
|
+
store.transaction(() => {
|
|
321
|
+
store.run(`INSERT OR REPLACE INTO meta (key, value) VALUES ('builtAt', ?)`, [new Date().toISOString()]);
|
|
322
|
+
if (install.version) {
|
|
323
|
+
store.run(`INSERT OR REPLACE INTO meta (key, value) VALUES ('gameVersion', ?)`, [install.version]);
|
|
324
|
+
}
|
|
325
|
+
store.run(`INSERT OR REPLACE INTO meta (key, value) VALUES ('sources', ?)`, [JSON.stringify(sources)]);
|
|
326
|
+
});
|
|
327
|
+
if (store.file)
|
|
328
|
+
await store.save();
|
|
329
|
+
progress?.({ step: "done", note: `${statTotal} stats, ${nodeTotal} nodes, ${locaTotal} loca entries` });
|
|
330
|
+
return info(install);
|
|
331
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { buildIndex } from "./build.js";
|
|
2
|
+
import { info } from "./info.js";
|
|
3
|
+
import { query, queryKinds } from "./queries.js";
|
|
4
|
+
export const bg3Index = {
|
|
5
|
+
info,
|
|
6
|
+
build: buildIndex,
|
|
7
|
+
queryKinds,
|
|
8
|
+
query,
|
|
9
|
+
};
|
|
10
|
+
export { buildIndex, indexFromDirectory, VANILLA_PAKS } from "./build.js";
|
|
11
|
+
export { info } from "./info.js";
|
|
12
|
+
export { query, queryKinds } from "./queries.js";
|
|
13
|
+
export { createSchema } from "./schema.js";
|
|
14
|
+
export { parseStats } from "./parsers/stats.js";
|
|
15
|
+
export { parseLsx, walkLsxNodes } from "./parsers/lsx.js";
|
|
16
|
+
export { parseLoca } from "./parsers/loca.js";
|
|
17
|
+
export { parseTreasureTable } from "./parsers/treasuretable.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { defaultCacheDir, indexFile, IndexStore } from "../../../core/knowledge/store.js";
|
|
3
|
+
export async function info(_install) {
|
|
4
|
+
const file = indexFile(defaultCacheDir(), "baldursgate3");
|
|
5
|
+
const result = { game: "baldursgate3", file, exists: false };
|
|
6
|
+
let stat;
|
|
7
|
+
try {
|
|
8
|
+
stat = await fs.stat(file);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
result.exists = true;
|
|
14
|
+
result.sizeBytes = stat.size;
|
|
15
|
+
let store;
|
|
16
|
+
try {
|
|
17
|
+
store = await IndexStore.open(file);
|
|
18
|
+
const builtAt = store.get(`SELECT value FROM meta WHERE key = 'builtAt'`)?.value;
|
|
19
|
+
const gameVersion = store.get(`SELECT value FROM meta WHERE key = 'gameVersion'`)?.value;
|
|
20
|
+
const sourcesRaw = store.get(`SELECT value FROM meta WHERE key = 'sources'`)?.value;
|
|
21
|
+
if (builtAt !== undefined)
|
|
22
|
+
result.builtAt = String(builtAt);
|
|
23
|
+
if (gameVersion !== undefined)
|
|
24
|
+
result.gameVersion = String(gameVersion);
|
|
25
|
+
if (sourcesRaw !== undefined) {
|
|
26
|
+
try {
|
|
27
|
+
result.sources = JSON.parse(String(sourcesRaw));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
result.tables = store.tableCounts();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
store?.close();
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|