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,241 @@
|
|
|
1
|
+
const PLUGIN_MANAGER = "https://github.com/ianpatt/skse64/blob/master/skse64/PluginManager.cpp";
|
|
2
|
+
const PAPYRUS_SAMPLE = "a captured Papyrus.0.log";
|
|
3
|
+
const SKSE_PLUGIN_PREFIX = String.raw `^\s*plugin (?<dll>\S.*?) \((?<dataVersion>[0-9A-Fa-f]{8}) (?<plugin>.+?) (?<pluginVersion>[0-9A-Fa-f]{8})\) `;
|
|
4
|
+
const SKSE_HANDLE_SUFFIX = String.raw `(?: (?<errCode>-?\d+))? \(handle \d+\)\s*$`;
|
|
5
|
+
function skse(reason) {
|
|
6
|
+
return new RegExp(SKSE_PLUGIN_PREFIX + reason + SKSE_HANDLE_SUFFIX);
|
|
7
|
+
}
|
|
8
|
+
const SKSE = [
|
|
9
|
+
{
|
|
10
|
+
id: "skyrimse.skse.plugin-loaded",
|
|
11
|
+
game: "skyrimse",
|
|
12
|
+
sources: ["skse"],
|
|
13
|
+
severity: "info",
|
|
14
|
+
category: "success",
|
|
15
|
+
pattern: skse("loaded correctly"),
|
|
16
|
+
explain: "An SKSE plugin loaded; the roll-call of these lines is the authoritative list of SKSE plugins live this run.",
|
|
17
|
+
seenIn: [PLUGIN_MANAGER],
|
|
18
|
+
extract: ["dll", "plugin", "pluginVersion"],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "skyrimse.skse.plugin-incompatible-runtime",
|
|
22
|
+
game: "skyrimse",
|
|
23
|
+
sources: ["skse"],
|
|
24
|
+
severity: "error",
|
|
25
|
+
category: "version",
|
|
26
|
+
pattern: skse("disabled, incompatible with current version of the game"),
|
|
27
|
+
explain: "The plugin's declared compatible runtime versions do not include the running game; the commonest failure after a game update.",
|
|
28
|
+
hint: "Update the plugin to a build for this runtime, or downgrade the game to the version the plugin declares.",
|
|
29
|
+
seenIn: [PLUGIN_MANAGER],
|
|
30
|
+
extract: ["dll", "plugin"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "skyrimse.skse.address-library-missing",
|
|
34
|
+
game: "skyrimse",
|
|
35
|
+
sources: ["skse"],
|
|
36
|
+
severity: "error",
|
|
37
|
+
category: "version",
|
|
38
|
+
pattern: skse("disabled, address library needs to be updated"),
|
|
39
|
+
explain: "The plugin is version-independent through the Address Library, but no versionlib-*.bin for this runtime is installed under Data/SKSE/Plugins.",
|
|
40
|
+
hint: "Install the Address Library for SKSE Plugins release matching the game runtime; the file is versionlib-<runtime>.bin.",
|
|
41
|
+
seenIn: [PLUGIN_MANAGER],
|
|
42
|
+
extract: ["dll", "plugin"],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "skyrimse.skse.address-library-recompile",
|
|
46
|
+
game: "skyrimse",
|
|
47
|
+
sources: ["skse"],
|
|
48
|
+
severity: "error",
|
|
49
|
+
category: "version",
|
|
50
|
+
pattern: skse("must be recompiled for new address library"),
|
|
51
|
+
explain: "The plugin declares the post-AE Address Library but not the version-5 format flag and predates the 1.7.99 format change; only its author can rebuild it.",
|
|
52
|
+
hint: "Look for an updated build of the plugin; nothing on the install side fixes this.",
|
|
53
|
+
seenIn: [PLUGIN_MANAGER],
|
|
54
|
+
extract: ["dll", "plugin"],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "skyrimse.skse.plugin-load-failed",
|
|
58
|
+
game: "skyrimse",
|
|
59
|
+
sources: ["skse"],
|
|
60
|
+
severity: "error",
|
|
61
|
+
category: "load",
|
|
62
|
+
pattern: skse("couldn't load plugin"),
|
|
63
|
+
explain: "Windows refused to load the DLL; the errCode is GetLastError, which separates a missing dependency (126/193 shapes) from a broken binary.",
|
|
64
|
+
hint: "Check the plugin's own requirements (a Visual C++ runtime, another DLL beside it) and that the file is not blocked or truncated.",
|
|
65
|
+
seenIn: [PLUGIN_MANAGER],
|
|
66
|
+
extract: ["dll", "plugin", "errCode"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "skyrimse.skse.plugin-refused",
|
|
70
|
+
game: "skyrimse",
|
|
71
|
+
sources: ["skse"],
|
|
72
|
+
severity: "error",
|
|
73
|
+
category: "load",
|
|
74
|
+
pattern: skse("reported as incompatible during (?<phase>load|query)"),
|
|
75
|
+
explain: "The plugin's own load function returned false; it refused this environment itself. The 'query' wording dates the install to SKSE 2.0.x.",
|
|
76
|
+
hint: "The plugin's own log or message box says why; read it next.",
|
|
77
|
+
seenIn: [PLUGIN_MANAGER],
|
|
78
|
+
extract: ["dll", "plugin", "phase"],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "skyrimse.skse.plugin-no-version-data",
|
|
82
|
+
game: "skyrimse",
|
|
83
|
+
sources: ["skse"],
|
|
84
|
+
severity: "warn",
|
|
85
|
+
category: "load",
|
|
86
|
+
pattern: skse("no version data"),
|
|
87
|
+
explain: "The DLL exports no SKSEPlugin_Version; SKSE logs it without treating it as an error, but the plugin is not running.",
|
|
88
|
+
hint: "A DLL in Data/SKSE/Plugins that is not an SKSE plugin at all, or one built against the pre-AE API; check what it is.",
|
|
89
|
+
seenIn: [PLUGIN_MANAGER],
|
|
90
|
+
extract: ["dll", "plugin"],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "skyrimse.skse.plugin-disabled-other",
|
|
94
|
+
game: "skyrimse",
|
|
95
|
+
sources: ["skse"],
|
|
96
|
+
severity: "error",
|
|
97
|
+
category: "load",
|
|
98
|
+
pattern: skse("(?<reason>disabled, (?:bad version data|no name specified|unsupported version independence method|only compatible with versions earlier than 1\\.6\\.629|requires newer script extender|fatal error occurred while (?:checking plugin compatibility|loading plugin|querying plugin))|LE plugin cannot be used with SE|does not appear to be an SKSE plugin)"),
|
|
99
|
+
explain: "SKSE disabled the plugin for the reason quoted; each reason maps to one check in PluginManager.cpp.",
|
|
100
|
+
seenIn: [PLUGIN_MANAGER],
|
|
101
|
+
extract: ["dll", "plugin", "reason"],
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
const SKSE_LOADER = [
|
|
105
|
+
{
|
|
106
|
+
id: "skyrimse.skse-loader.runtime",
|
|
107
|
+
game: "skyrimse",
|
|
108
|
+
sources: ["skse-loader"],
|
|
109
|
+
severity: "info",
|
|
110
|
+
category: "version",
|
|
111
|
+
pattern: /^\s*(?:skse64 loader (?<skse>\d[\w.]*)(?: \((?:release|steam)\))?|runtime: initialize \(version = (?<runtime>\d+\.\d+\.\d+))/i,
|
|
112
|
+
explain: "The SKSE build and the game runtime actually in play; the anchor for every compatibility check.",
|
|
113
|
+
seenIn: ["ModWright research (unverified: tolerant pattern)"],
|
|
114
|
+
extract: ["skse", "runtime"],
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
const PAPYRUS_PREFIX = String.raw `^\[\d{2}/\d{2}/\d{4} - \d{2}:\d{2}:\d{2}(?:AM|PM)\] `;
|
|
118
|
+
function papyrus(body) {
|
|
119
|
+
return new RegExp(PAPYRUS_PREFIX + body);
|
|
120
|
+
}
|
|
121
|
+
const PAPYRUS = [
|
|
122
|
+
{
|
|
123
|
+
id: "skyrimse.papyrus.log-opened",
|
|
124
|
+
game: "skyrimse",
|
|
125
|
+
sources: ["papyrus"],
|
|
126
|
+
severity: "info",
|
|
127
|
+
category: "load",
|
|
128
|
+
pattern: papyrus(String.raw `Papyrus log opened \(PC\)`),
|
|
129
|
+
explain: "A new game session's script log begins here; the run boundary for scoping.",
|
|
130
|
+
seenIn: [PAPYRUS_SAMPLE],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "skyrimse.papyrus.cannot-open-store",
|
|
134
|
+
game: "skyrimse",
|
|
135
|
+
sources: ["papyrus"],
|
|
136
|
+
severity: "error",
|
|
137
|
+
category: "resource",
|
|
138
|
+
pattern: papyrus(String.raw `Cannot open store for class "(?<script>[^"]+)", missing file\?`),
|
|
139
|
+
explain: "A script the save or a form references is no longer installed (its .pex is gone); the most diagnostic Papyrus line, written without a severity prefix.",
|
|
140
|
+
hint: "Reinstall the mod that shipped that script, or accept that the save carries a stale reference.",
|
|
141
|
+
seenIn: [PAPYRUS_SAMPLE],
|
|
142
|
+
extract: ["script"],
|
|
143
|
+
systemImpact: false,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "skyrimse.papyrus.bind-base-type-mismatch",
|
|
147
|
+
game: "skyrimse",
|
|
148
|
+
sources: ["papyrus"],
|
|
149
|
+
severity: "error",
|
|
150
|
+
category: "script",
|
|
151
|
+
pattern: papyrus(String.raw `Error: Unable to bind script (?<script>\S+) to (?<form>.+?) \((?<formId>[0-9A-Fa-f]{8})\) because their base types do not match`),
|
|
152
|
+
explain: "A script is attached to a form of the wrong base type; a mismatched attach in one mod's plugin.",
|
|
153
|
+
seenIn: [PAPYRUS_SAMPLE],
|
|
154
|
+
extract: ["script", "form", "formId"],
|
|
155
|
+
systemImpact: false,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "skyrimse.papyrus.stale-property",
|
|
159
|
+
game: "skyrimse",
|
|
160
|
+
sources: ["papyrus"],
|
|
161
|
+
severity: "warn",
|
|
162
|
+
category: "script",
|
|
163
|
+
pattern: papyrus(String.raw `Warning: Property (?<property>\S+) on script (?<script>\S+) attached to (?<form>.+?) \((?<formId>[0-9A-Fa-f]{8})\) cannot be initialized because the script no longer contains that property`),
|
|
164
|
+
explain: "The save was made against an older version of the script; the property it recorded no longer exists.",
|
|
165
|
+
seenIn: [PAPYRUS_SAMPLE],
|
|
166
|
+
extract: ["property", "script", "formId"],
|
|
167
|
+
systemImpact: false,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "skyrimse.papyrus.none-object-call",
|
|
171
|
+
game: "skyrimse",
|
|
172
|
+
sources: ["papyrus"],
|
|
173
|
+
severity: "error",
|
|
174
|
+
category: "script",
|
|
175
|
+
pattern: papyrus(String.raw `Error: Cannot call (?<function>[\w.]+)\(\) on a None object, aborting function call`),
|
|
176
|
+
explain: "The commonest Papyrus runtime fault: a script called a function on an unset object.",
|
|
177
|
+
seenIn: [PAPYRUS_SAMPLE],
|
|
178
|
+
extract: ["function"],
|
|
179
|
+
systemImpact: false,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "skyrimse.papyrus.plugin-not-loaded",
|
|
183
|
+
game: "skyrimse",
|
|
184
|
+
sources: ["papyrus"],
|
|
185
|
+
severity: "error",
|
|
186
|
+
category: "order",
|
|
187
|
+
pattern: papyrus(String.raw `Error: File "(?<plugin>[^"]+)" does not exist or is not currently loaded\.`),
|
|
188
|
+
explain: "A script reached for a plugin that is not in the load order; the runtime face of a missing master.",
|
|
189
|
+
hint: "Enable the named plugin in Plugins.txt or remove the mod whose script expects it.",
|
|
190
|
+
seenIn: [PAPYRUS_SAMPLE],
|
|
191
|
+
extract: ["plugin"],
|
|
192
|
+
systemImpact: false,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "skyrimse.papyrus.save-script-drift",
|
|
196
|
+
game: "skyrimse",
|
|
197
|
+
sources: ["papyrus"],
|
|
198
|
+
severity: "warn",
|
|
199
|
+
category: "script",
|
|
200
|
+
pattern: papyrus(String.raw `Warning: Function (?<function>[\w.]+) in stack frame \d+ in stack \d+ (?:differs from|doesn't exist in) the in-game resource files - using version from save`),
|
|
201
|
+
explain: "The save carries a compiled function that no longer matches the installed script; save-baked script drift.",
|
|
202
|
+
seenIn: [PAPYRUS_SAMPLE],
|
|
203
|
+
extract: ["function"],
|
|
204
|
+
systemImpact: false,
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
const CRASH = [
|
|
208
|
+
{
|
|
209
|
+
id: "skyrimse.crash.unhandled-exception",
|
|
210
|
+
game: "skyrimse",
|
|
211
|
+
sources: ["crash"],
|
|
212
|
+
severity: "error",
|
|
213
|
+
category: "crash",
|
|
214
|
+
pattern: /^Unhandled exception "(?<exception>EXCEPTION_[A-Z_]+)" at 0x(?<address>[0-9A-Fa-f]+)\s+(?<module>\S+)\+(?<rva>[0-9A-Fa-f]+)/,
|
|
215
|
+
explain: "The crash itself: the exception and the module the faulting address falls in (CrashLoggerSSE format).",
|
|
216
|
+
hint: "The module after the address is the first suspect; the SKSE PLUGINS section below it lists what was loaded.",
|
|
217
|
+
seenIn: ["https://github.com/alandtse/CrashLoggerSSE/blob/main/src/main.cpp"],
|
|
218
|
+
extract: ["exception", "module", "rva"],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "skyrimse.crash.format-crashlogger",
|
|
222
|
+
game: "skyrimse",
|
|
223
|
+
sources: ["crash"],
|
|
224
|
+
severity: "info",
|
|
225
|
+
category: "crash",
|
|
226
|
+
pattern: /^SKSE PLUGINS:\s*$/,
|
|
227
|
+
explain: "A CrashLoggerSSE-format log (its plugin inventory section).",
|
|
228
|
+
seenIn: ["https://github.com/alandtse/CrashLoggerSSE/blob/main/src/main.cpp"],
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: "skyrimse.crash.format-trainwreck",
|
|
232
|
+
game: "skyrimse",
|
|
233
|
+
sources: ["crash"],
|
|
234
|
+
severity: "info",
|
|
235
|
+
category: "crash",
|
|
236
|
+
pattern: /^SCRIPT EXTENDER PLUGINS:\s*$/,
|
|
237
|
+
explain: "A Trainwreck-format log (its plugin inventory section).",
|
|
238
|
+
seenIn: ["https://github.com/Phostwood/crash-analyzer/blob/main/TestLogTrainwreck1.txt"],
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
export const SIGNATURES = [...SKSE_LOADER, ...SKSE, ...PAPYRUS, ...CRASH];
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export const TES4_FLAG_MASTER = 0x0000_0001;
|
|
4
|
+
export const TES4_FLAG_LOCALIZED = 0x0000_0080;
|
|
5
|
+
export const TES4_FLAG_LIGHT = 0x0000_0200;
|
|
6
|
+
export const TES4_FLAG_IGNORED = 0x0000_1000;
|
|
7
|
+
export const RECORD_FLAG_COMPRESSED = 0x0004_0000;
|
|
8
|
+
export const FORM_VERSION_SSE = 44;
|
|
9
|
+
export const FORM_VERSION_LE = 43;
|
|
10
|
+
export const VALID_HEADER_VERSIONS_SSE = [0.94, 1.7, 1.71];
|
|
11
|
+
export const HEADER_VERSION_EXPANDED_LIGHT_RANGE = 1.71;
|
|
12
|
+
export const MASTER_LIMIT_SSE = 253;
|
|
13
|
+
export const RECORD_HEADER_LENGTH = 24;
|
|
14
|
+
export const SUBRECORD_HEADER_LENGTH = 6;
|
|
15
|
+
export class PluginHeaderError extends Error {
|
|
16
|
+
reason;
|
|
17
|
+
constructor(message, reason) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.reason = reason;
|
|
20
|
+
this.name = "PluginHeaderError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const WINDOWS_1252 = new TextDecoder("windows-1252");
|
|
24
|
+
export function decodeZString(bytes) {
|
|
25
|
+
const end = bytes.length > 0 && bytes[bytes.length - 1] === 0 ? bytes.length - 1 : bytes.length;
|
|
26
|
+
return WINDOWS_1252.decode(bytes.subarray(0, end));
|
|
27
|
+
}
|
|
28
|
+
function ascii4(view, offset) {
|
|
29
|
+
return String.fromCharCode(view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2), view.getUint8(offset + 3));
|
|
30
|
+
}
|
|
31
|
+
export function parsePluginHeader(buf) {
|
|
32
|
+
if (buf.length < RECORD_HEADER_LENGTH) {
|
|
33
|
+
throw new PluginHeaderError(`plugin is ${buf.length} bytes; a TES4 record header is ${RECORD_HEADER_LENGTH}`, "too-short");
|
|
34
|
+
}
|
|
35
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
36
|
+
const signature = ascii4(view, 0);
|
|
37
|
+
if (signature !== "TES4") {
|
|
38
|
+
throw new PluginHeaderError(`first record is "${signature}", not TES4; this is not a Bethesda plugin`, "bad-signature");
|
|
39
|
+
}
|
|
40
|
+
const dataSize = view.getUint32(4, true);
|
|
41
|
+
const flags = view.getUint32(8, true);
|
|
42
|
+
const formId = view.getUint32(12, true);
|
|
43
|
+
const formVersion = view.getUint16(20, true);
|
|
44
|
+
const header = {
|
|
45
|
+
signature,
|
|
46
|
+
dataSize,
|
|
47
|
+
flags,
|
|
48
|
+
formId,
|
|
49
|
+
formVersion,
|
|
50
|
+
isMaster: (flags & TES4_FLAG_MASTER) !== 0,
|
|
51
|
+
isLight: (flags & TES4_FLAG_LIGHT) !== 0,
|
|
52
|
+
isLocalized: (flags & TES4_FLAG_LOCALIZED) !== 0,
|
|
53
|
+
isIgnored: (flags & TES4_FLAG_IGNORED) !== 0,
|
|
54
|
+
isCompressed: (flags & RECORD_FLAG_COMPRESSED) !== 0,
|
|
55
|
+
masters: [],
|
|
56
|
+
hasOnam: false,
|
|
57
|
+
subrecords: [],
|
|
58
|
+
};
|
|
59
|
+
if (header.isCompressed) {
|
|
60
|
+
throw new PluginHeaderError("the TES4 record is flagged compressed (0x00040000); this reader does not decompress", "compressed");
|
|
61
|
+
}
|
|
62
|
+
const end = RECORD_HEADER_LENGTH + dataSize;
|
|
63
|
+
if (buf.length < end) {
|
|
64
|
+
throw new PluginHeaderError(`TES4 dataSize is ${dataSize} but only ${buf.length - RECORD_HEADER_LENGTH} bytes follow the header`, "truncated");
|
|
65
|
+
}
|
|
66
|
+
let offset = RECORD_HEADER_LENGTH;
|
|
67
|
+
let sizeOverride;
|
|
68
|
+
while (offset + SUBRECORD_HEADER_LENGTH <= end) {
|
|
69
|
+
const type = ascii4(view, offset);
|
|
70
|
+
let size = view.getUint16(offset + 4, true);
|
|
71
|
+
const dataOffset = offset + SUBRECORD_HEADER_LENGTH;
|
|
72
|
+
if (sizeOverride !== undefined) {
|
|
73
|
+
size = sizeOverride;
|
|
74
|
+
sizeOverride = undefined;
|
|
75
|
+
}
|
|
76
|
+
if (dataOffset + size > end) {
|
|
77
|
+
throw new PluginHeaderError(`subrecord ${type} at ${offset} claims ${size} bytes past the TES4 record's end`, "malformed");
|
|
78
|
+
}
|
|
79
|
+
const data = buf.subarray(dataOffset, dataOffset + size);
|
|
80
|
+
header.subrecords.push({ type, size, offset });
|
|
81
|
+
switch (type) {
|
|
82
|
+
case "XXXX":
|
|
83
|
+
if (size === 4)
|
|
84
|
+
sizeOverride = view.getUint32(dataOffset, true);
|
|
85
|
+
break;
|
|
86
|
+
case "HEDR":
|
|
87
|
+
if (size >= 12) {
|
|
88
|
+
header.headerVersion = Math.round(view.getFloat32(dataOffset, true) * 100) / 100;
|
|
89
|
+
header.recordCount = view.getUint32(dataOffset + 4, true);
|
|
90
|
+
header.nextObjectId = view.getUint32(dataOffset + 8, true);
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case "CNAM":
|
|
94
|
+
header.author = decodeZString(data);
|
|
95
|
+
break;
|
|
96
|
+
case "SNAM":
|
|
97
|
+
header.description = decodeZString(data);
|
|
98
|
+
break;
|
|
99
|
+
case "MAST":
|
|
100
|
+
header.masters.push(decodeZString(data));
|
|
101
|
+
break;
|
|
102
|
+
case "DATA":
|
|
103
|
+
break;
|
|
104
|
+
case "ONAM":
|
|
105
|
+
header.hasOnam = true;
|
|
106
|
+
break;
|
|
107
|
+
default:
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
offset = dataOffset + size;
|
|
111
|
+
}
|
|
112
|
+
return header;
|
|
113
|
+
}
|
|
114
|
+
export async function readPluginHeader(filePath) {
|
|
115
|
+
const handle = await fs.open(filePath, "r");
|
|
116
|
+
try {
|
|
117
|
+
const head = new Uint8Array(RECORD_HEADER_LENGTH);
|
|
118
|
+
const first = await handle.read(head, 0, RECORD_HEADER_LENGTH, 0);
|
|
119
|
+
if (first.bytesRead < RECORD_HEADER_LENGTH) {
|
|
120
|
+
throw new PluginHeaderError(`${path.basename(filePath)} is ${first.bytesRead} bytes; a TES4 record header is ${RECORD_HEADER_LENGTH}`, "too-short");
|
|
121
|
+
}
|
|
122
|
+
const view = new DataView(head.buffer, head.byteOffset, head.byteLength);
|
|
123
|
+
if (ascii4(view, 0) !== "TES4")
|
|
124
|
+
return parsePluginHeader(head);
|
|
125
|
+
const dataSize = view.getUint32(4, true);
|
|
126
|
+
const size = (await handle.stat()).size;
|
|
127
|
+
if (RECORD_HEADER_LENGTH + dataSize > size) {
|
|
128
|
+
throw new PluginHeaderError(`${path.basename(filePath)} declares a TES4 dataSize of ${dataSize} but the file is ${size} bytes`, "truncated");
|
|
129
|
+
}
|
|
130
|
+
const whole = new Uint8Array(RECORD_HEADER_LENGTH + dataSize);
|
|
131
|
+
whole.set(head, 0);
|
|
132
|
+
const rest = await handle.read(whole, RECORD_HEADER_LENGTH, dataSize, RECORD_HEADER_LENGTH);
|
|
133
|
+
return parsePluginHeader(whole.subarray(0, RECORD_HEADER_LENGTH + rest.bytesRead));
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
await handle.close();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export function pluginExtension(fileName) {
|
|
140
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
141
|
+
return ext === ".esp" || ext === ".esm" || ext === ".esl" ? ext : undefined;
|
|
142
|
+
}
|
|
143
|
+
export function pluginType(fileName, header) {
|
|
144
|
+
const ext = pluginExtension(fileName);
|
|
145
|
+
return {
|
|
146
|
+
master: ext === ".esm" || header.isMaster,
|
|
147
|
+
light: ext === ".esl" || header.isLight,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { PluginHeaderError, RECORD_HEADER_LENGTH, parsePluginHeader } from "./plugins.js";
|
|
4
|
+
export const MAX_WALK_BYTES = 256 * 1024 * 1024;
|
|
5
|
+
export const MAX_WALK_RECORDS = 2_000_000;
|
|
6
|
+
export const MAX_WALK_DEPTH = 64;
|
|
7
|
+
export const MAX_WALK_PROBLEMS = 64;
|
|
8
|
+
export class RecordWalkError extends Error {
|
|
9
|
+
reason;
|
|
10
|
+
constructor(reason, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.reason = reason;
|
|
13
|
+
this.name = "RecordWalkError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function ascii4(view, offset) {
|
|
17
|
+
return String.fromCharCode(view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2), view.getUint8(offset + 3));
|
|
18
|
+
}
|
|
19
|
+
export function walkPluginRecords(buf) {
|
|
20
|
+
const header = parsePluginHeader(buf);
|
|
21
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
22
|
+
const walk = { header, records: [], groupCount: 0, truncated: false, problems: [] };
|
|
23
|
+
const problem = (text) => {
|
|
24
|
+
if (walk.problems.length < MAX_WALK_PROBLEMS)
|
|
25
|
+
walk.problems.push(text);
|
|
26
|
+
};
|
|
27
|
+
const visit = (start, end, depth) => {
|
|
28
|
+
if (depth > MAX_WALK_DEPTH) {
|
|
29
|
+
problem(`GRUP nesting at ${start} is deeper than ${MAX_WALK_DEPTH}; that branch was not read`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let at = start;
|
|
33
|
+
while (at + RECORD_HEADER_LENGTH <= end) {
|
|
34
|
+
if (walk.records.length >= MAX_WALK_RECORDS) {
|
|
35
|
+
walk.truncated = true;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const signature = ascii4(view, at);
|
|
39
|
+
const size = view.getUint32(at + 4, true);
|
|
40
|
+
if (signature === "GRUP") {
|
|
41
|
+
if (size < RECORD_HEADER_LENGTH) {
|
|
42
|
+
problem(`GRUP at ${at} declares size ${size}, less than its own header`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const groupEnd = at + size;
|
|
46
|
+
if (groupEnd > end) {
|
|
47
|
+
problem(`GRUP at ${at} declares size ${size}, running ${groupEnd - end} bytes past its parent`);
|
|
48
|
+
walk.groupCount++;
|
|
49
|
+
visit(at + RECORD_HEADER_LENGTH, end, depth + 1);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
walk.groupCount++;
|
|
53
|
+
visit(at + RECORD_HEADER_LENGTH, groupEnd, depth + 1);
|
|
54
|
+
at = groupEnd;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const recordEnd = at + RECORD_HEADER_LENGTH + size;
|
|
58
|
+
walk.records.push({ signature, formId: view.getUint32(at + 12, true), flags: view.getUint32(at + 8, true), offset: at });
|
|
59
|
+
if (recordEnd > end) {
|
|
60
|
+
problem(`${signature} record at ${at} declares dataSize ${size}, running ${recordEnd - end} bytes past its parent`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
at = recordEnd;
|
|
64
|
+
}
|
|
65
|
+
if (at !== end && depth > 0)
|
|
66
|
+
problem(`${end - at} trailing bytes in a GRUP ending at ${end}`);
|
|
67
|
+
};
|
|
68
|
+
visit(RECORD_HEADER_LENGTH + header.dataSize, buf.length, 0);
|
|
69
|
+
return walk;
|
|
70
|
+
}
|
|
71
|
+
export async function readPluginRecords(filePath) {
|
|
72
|
+
const { size } = await fs.stat(filePath);
|
|
73
|
+
if (size > MAX_WALK_BYTES) {
|
|
74
|
+
throw new RecordWalkError("too-large", `${path.basename(filePath)} is ${size} bytes; the record walk holds at most ${MAX_WALK_BYTES}`);
|
|
75
|
+
}
|
|
76
|
+
const buf = new Uint8Array(await fs.readFile(filePath));
|
|
77
|
+
if (buf.length < RECORD_HEADER_LENGTH)
|
|
78
|
+
throw new PluginHeaderError(`${path.basename(filePath)} is ${buf.length} bytes; a TES4 record header is ${RECORD_HEADER_LENGTH}`, "too-short");
|
|
79
|
+
return walkPluginRecords(buf);
|
|
80
|
+
}
|
|
81
|
+
export function modIndexOf(formId) {
|
|
82
|
+
return formId >>> 24;
|
|
83
|
+
}
|
|
84
|
+
export function objectIndexOf(formId) {
|
|
85
|
+
return formId & 0x00ff_ffff;
|
|
86
|
+
}
|
|
87
|
+
export function classifyRecord(formId, masterCount) {
|
|
88
|
+
const index = modIndexOf(formId);
|
|
89
|
+
if (index === masterCount)
|
|
90
|
+
return "new";
|
|
91
|
+
if (index < masterCount)
|
|
92
|
+
return "override";
|
|
93
|
+
return "dangling";
|
|
94
|
+
}
|