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,374 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { readPeFileVersion } from "../../../core/compat/pe.js";
|
|
3
|
+
import { compareVersions, parseVersion } from "../../../core/compat/version.js";
|
|
4
|
+
import { FORM_VERSION_LE, FORM_VERSION_SSE, HEADER_VERSION_EXPANDED_LIGHT_RANGE, MASTER_LIMIT_SSE, PluginHeaderError, VALID_HEADER_VERSIONS_SSE, pluginExtension, readPluginHeader, } from "../plugins.js";
|
|
5
|
+
import { RUNTIME_EXPANDED_LIGHT_RANGE } from "../compat.js";
|
|
6
|
+
const EVIDENCE = ["ModWright research"];
|
|
7
|
+
function isPluginUnderSource(target, project) {
|
|
8
|
+
if (target.kind !== "file" || pluginExtension(target.path) === undefined)
|
|
9
|
+
return false;
|
|
10
|
+
return project.sources.some((s) => s.exists && isWithin(s.absPath, target.path));
|
|
11
|
+
}
|
|
12
|
+
function isWithin(parent, child) {
|
|
13
|
+
const rel = path.relative(parent, child);
|
|
14
|
+
return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
|
|
15
|
+
}
|
|
16
|
+
const APPLIES = "every .esp, .esm or .esl file under a project source";
|
|
17
|
+
async function headerOrFinding(id, file) {
|
|
18
|
+
try {
|
|
19
|
+
return await readPluginHeader(file);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const reason = err instanceof PluginHeaderError ? err.reason : "unreadable";
|
|
23
|
+
return {
|
|
24
|
+
validatorId: id,
|
|
25
|
+
severity: "block",
|
|
26
|
+
file,
|
|
27
|
+
message: `${path.basename(file)} has no readable TES4 header (${reason}): ${err.message}`,
|
|
28
|
+
fix: "A plugin must begin with a TES4 record; re-save it from the Creation Kit or xEdit, or check the file is not truncated.",
|
|
29
|
+
evidence: EVIDENCE,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function isFinding(v) {
|
|
34
|
+
return v.validatorId !== undefined;
|
|
35
|
+
}
|
|
36
|
+
async function loadOrderOf(ctx) {
|
|
37
|
+
if (!ctx.install || !ctx.surface.loadOrder)
|
|
38
|
+
return undefined;
|
|
39
|
+
return ctx.surface.loadOrder(ctx.install);
|
|
40
|
+
}
|
|
41
|
+
function findEntry(order, name) {
|
|
42
|
+
const key = name.toLowerCase();
|
|
43
|
+
return order.find((e) => e.name.toLowerCase() === key);
|
|
44
|
+
}
|
|
45
|
+
const mastersPresent = {
|
|
46
|
+
id: "sse.plugins.masters-present",
|
|
47
|
+
game: "skyrimse",
|
|
48
|
+
groups: ["plugins"],
|
|
49
|
+
modTypes: ["plugin"],
|
|
50
|
+
summary: "Every master a plugin's TES4 MAST entries name is present and active in the live load order.",
|
|
51
|
+
appliesTo: isPluginUnderSource,
|
|
52
|
+
appliesToDescription: APPLIES,
|
|
53
|
+
needs: { project: true, install: true },
|
|
54
|
+
cost: "edit",
|
|
55
|
+
severity: "block",
|
|
56
|
+
evidence: EVIDENCE,
|
|
57
|
+
async run(ctx) {
|
|
58
|
+
if (ctx.target.kind !== "file")
|
|
59
|
+
return [];
|
|
60
|
+
const header = await headerOrFinding(this.id, ctx.target.path);
|
|
61
|
+
if (isFinding(header))
|
|
62
|
+
return [header];
|
|
63
|
+
const order = await loadOrderOf(ctx);
|
|
64
|
+
if (!order)
|
|
65
|
+
return [];
|
|
66
|
+
const findings = [];
|
|
67
|
+
for (const master of header.masters) {
|
|
68
|
+
const entry = findEntry(order, master);
|
|
69
|
+
if (entry?.enabled)
|
|
70
|
+
continue;
|
|
71
|
+
const onDisk = ctx.install ? await ctx.exists(path.join(ctx.install.root, "Data", master)) : false;
|
|
72
|
+
findings.push({
|
|
73
|
+
validatorId: this.id,
|
|
74
|
+
severity: "block",
|
|
75
|
+
file: ctx.target.path,
|
|
76
|
+
message: entry
|
|
77
|
+
? `Master "${master}" is listed in Plugins.txt but not active; ${path.basename(ctx.target.path)} depends on it and the game will refuse to load, or load with its records dropped.`
|
|
78
|
+
: onDisk
|
|
79
|
+
? `Master "${master}" is under Data but not in the load order at all; ${path.basename(ctx.target.path)} depends on it.`
|
|
80
|
+
: `Master "${master}" is neither in the load order nor under Data; ${path.basename(ctx.target.path)} cannot load without it.`,
|
|
81
|
+
fix: entry ? `Activate ${master} (a * line in Plugins.txt).` : `Install ${master} and activate it ahead of ${path.basename(ctx.target.path)}.`,
|
|
82
|
+
evidence: EVIDENCE,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return findings;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const masterOrder = {
|
|
89
|
+
id: "sse.plugins.master-order",
|
|
90
|
+
game: "skyrimse",
|
|
91
|
+
groups: ["plugins"],
|
|
92
|
+
modTypes: ["plugin"],
|
|
93
|
+
summary: "Every master of a plugin loads before the plugin itself in the live load order.",
|
|
94
|
+
appliesTo: isPluginUnderSource,
|
|
95
|
+
appliesToDescription: APPLIES,
|
|
96
|
+
needs: { project: true, install: true },
|
|
97
|
+
cost: "edit",
|
|
98
|
+
severity: "block",
|
|
99
|
+
evidence: [...EVIDENCE],
|
|
100
|
+
async run(ctx) {
|
|
101
|
+
if (ctx.target.kind !== "file")
|
|
102
|
+
return [];
|
|
103
|
+
const header = await headerOrFinding(this.id, ctx.target.path);
|
|
104
|
+
if (isFinding(header))
|
|
105
|
+
return [header];
|
|
106
|
+
const order = await loadOrderOf(ctx);
|
|
107
|
+
if (!order)
|
|
108
|
+
return [];
|
|
109
|
+
const self = findEntry(order, path.basename(ctx.target.path));
|
|
110
|
+
if (!self) {
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
validatorId: this.id,
|
|
114
|
+
severity: "info",
|
|
115
|
+
file: ctx.target.path,
|
|
116
|
+
message: `${path.basename(ctx.target.path)} is not in the load order, so its masters' positions relative to it cannot be checked yet.`,
|
|
117
|
+
evidence: EVIDENCE,
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
const findings = [];
|
|
122
|
+
for (const master of header.masters) {
|
|
123
|
+
const entry = findEntry(order, master);
|
|
124
|
+
if (!entry || entry.index < self.index)
|
|
125
|
+
continue;
|
|
126
|
+
findings.push({
|
|
127
|
+
validatorId: this.id,
|
|
128
|
+
severity: "block",
|
|
129
|
+
file: ctx.target.path,
|
|
130
|
+
message: `Master "${master}" loads at position ${entry.index}, after ${path.basename(ctx.target.path)} at ${self.index}; a plugin must load after every master it depends on.`,
|
|
131
|
+
fix: `Move ${master} above ${path.basename(ctx.target.path)} in Plugins.txt (LOOT does this).`,
|
|
132
|
+
evidence: [...EVIDENCE],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return findings;
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
const eslHeaderVersion = {
|
|
139
|
+
id: "sse.plugins.esl-header-version",
|
|
140
|
+
game: "skyrimse",
|
|
141
|
+
groups: ["plugins"],
|
|
142
|
+
modTypes: ["plugin"],
|
|
143
|
+
summary: "A plugin's HEDR version is one Skyrim SE accepts (0.94, 1.70, 1.71), and a plugin whose masters are 1.71 is itself 1.71.",
|
|
144
|
+
appliesTo: isPluginUnderSource,
|
|
145
|
+
appliesToDescription: APPLIES,
|
|
146
|
+
needs: { project: true },
|
|
147
|
+
cost: "edit",
|
|
148
|
+
severity: "block",
|
|
149
|
+
evidence: EVIDENCE,
|
|
150
|
+
async run(ctx) {
|
|
151
|
+
if (ctx.target.kind !== "file")
|
|
152
|
+
return [];
|
|
153
|
+
const header = await headerOrFinding(this.id, ctx.target.path);
|
|
154
|
+
if (isFinding(header))
|
|
155
|
+
return [header];
|
|
156
|
+
const findings = [];
|
|
157
|
+
const version = header.headerVersion;
|
|
158
|
+
if (version === undefined) {
|
|
159
|
+
findings.push({
|
|
160
|
+
validatorId: this.id,
|
|
161
|
+
severity: "block",
|
|
162
|
+
file: ctx.target.path,
|
|
163
|
+
message: `${path.basename(ctx.target.path)} has no HEDR subrecord; every plugin's TES4 record carries one.`,
|
|
164
|
+
evidence: EVIDENCE,
|
|
165
|
+
});
|
|
166
|
+
return findings;
|
|
167
|
+
}
|
|
168
|
+
if (!VALID_HEADER_VERSIONS_SSE.includes(version)) {
|
|
169
|
+
findings.push({
|
|
170
|
+
validatorId: this.id,
|
|
171
|
+
severity: "block",
|
|
172
|
+
file: ctx.target.path,
|
|
173
|
+
message: `HEDR version ${version.toFixed(2)} is not one Skyrim SE accepts (${VALID_HEADER_VERSIONS_SSE.map((v) => v.toFixed(2)).join(", ")}).`,
|
|
174
|
+
fix: "Re-save the plugin with the Skyrim SE Creation Kit or xEdit in SSE mode.",
|
|
175
|
+
evidence: EVIDENCE,
|
|
176
|
+
});
|
|
177
|
+
return findings;
|
|
178
|
+
}
|
|
179
|
+
if (version >= HEADER_VERSION_EXPANDED_LIGHT_RANGE && ctx.install) {
|
|
180
|
+
const runtime = ctx.install.version ?? (await readPeFileVersion(path.join(ctx.install.root, "SkyrimSE.exe")));
|
|
181
|
+
if (runtime && parseVersion(runtime) && compareVersions(runtime, RUNTIME_EXPANDED_LIGHT_RANGE) < 0) {
|
|
182
|
+
findings.push({
|
|
183
|
+
validatorId: this.id,
|
|
184
|
+
severity: "block",
|
|
185
|
+
file: ctx.target.path,
|
|
186
|
+
message: `${path.basename(ctx.target.path)} has HEDR version ${version.toFixed(2)}, which needs game runtime ${RUNTIME_EXPANDED_LIGHT_RANGE} or later; this install is ${runtime}.`,
|
|
187
|
+
fix: "Update the game, or re-save the plugin with a 1.70 header if it uses no FormID below 0x800.",
|
|
188
|
+
evidence: EVIDENCE,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (version < HEADER_VERSION_EXPANDED_LIGHT_RANGE) {
|
|
193
|
+
for (const master of header.masters) {
|
|
194
|
+
const candidates = [path.join(path.dirname(ctx.target.path), master), ...(ctx.install ? [path.join(ctx.install.root, "Data", master)] : [])];
|
|
195
|
+
for (const candidate of candidates) {
|
|
196
|
+
if (!(await ctx.exists(candidate)))
|
|
197
|
+
continue;
|
|
198
|
+
try {
|
|
199
|
+
const mh = await readPluginHeader(candidate);
|
|
200
|
+
if (mh.headerVersion !== undefined && mh.headerVersion >= HEADER_VERSION_EXPANDED_LIGHT_RANGE) {
|
|
201
|
+
findings.push({
|
|
202
|
+
validatorId: this.id,
|
|
203
|
+
severity: "warn",
|
|
204
|
+
file: ctx.target.path,
|
|
205
|
+
message: `Master "${master}" is a ${mh.headerVersion.toFixed(2)} plugin while ${path.basename(ctx.target.path)} is ${version.toFixed(2)}; referencing or overriding that master's records in the extended FormID range (001-7FF) will error.`,
|
|
206
|
+
fix: "Re-save this plugin with a 1.71 header (xEdit 4.1.5b or later, or the current Creation Kit).",
|
|
207
|
+
evidence: EVIDENCE,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return findings;
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
const flagExtensionMismatch = {
|
|
221
|
+
id: "sse.plugins.flag-extension-mismatch",
|
|
222
|
+
game: "skyrimse",
|
|
223
|
+
groups: ["plugins"],
|
|
224
|
+
modTypes: ["plugin"],
|
|
225
|
+
summary: "An .esm carries the master flag and an .esp does not; the flag, not the extension, decides how the game treats the file.",
|
|
226
|
+
appliesTo: isPluginUnderSource,
|
|
227
|
+
appliesToDescription: APPLIES,
|
|
228
|
+
needs: { project: true },
|
|
229
|
+
cost: "edit",
|
|
230
|
+
severity: "warn",
|
|
231
|
+
evidence: EVIDENCE,
|
|
232
|
+
async run(ctx) {
|
|
233
|
+
if (ctx.target.kind !== "file")
|
|
234
|
+
return [];
|
|
235
|
+
const header = await headerOrFinding(this.id, ctx.target.path);
|
|
236
|
+
if (isFinding(header))
|
|
237
|
+
return [header];
|
|
238
|
+
const ext = pluginExtension(ctx.target.path);
|
|
239
|
+
const name = path.basename(ctx.target.path);
|
|
240
|
+
if (ext === ".esp" && header.isMaster) {
|
|
241
|
+
return [
|
|
242
|
+
{
|
|
243
|
+
validatorId: this.id,
|
|
244
|
+
severity: "warn",
|
|
245
|
+
file: ctx.target.path,
|
|
246
|
+
message: `${name} has the ESM flag set: the game treats it as a master regardless of the .esp extension, so it loads among the masters and every header-reading tool disagrees with its name.`,
|
|
247
|
+
fix: "Clear the ESM flag in xEdit, or rename the file .esm so the extension says what the flag does.",
|
|
248
|
+
evidence: EVIDENCE,
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
if (ext === ".esm" && !header.isMaster) {
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
validatorId: this.id,
|
|
256
|
+
severity: "warn",
|
|
257
|
+
file: ctx.target.path,
|
|
258
|
+
message: `${name} has no ESM flag: the .esm extension makes the game load it as a master anyway, but tools reading the header will not.`,
|
|
259
|
+
fix: "Set the ESM flag in xEdit so the header and the extension agree.",
|
|
260
|
+
evidence: EVIDENCE,
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
}
|
|
264
|
+
return [];
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
const eslExtensionImpliesFlag = {
|
|
268
|
+
id: "sse.plugins.esl-extension-implies-flag",
|
|
269
|
+
game: "skyrimse",
|
|
270
|
+
groups: ["plugins"],
|
|
271
|
+
modTypes: ["plugin"],
|
|
272
|
+
summary: "An .esl file carries the light flag in its header; the extension forces the flag at runtime, so a header without it misleads every tool.",
|
|
273
|
+
appliesTo: isPluginUnderSource,
|
|
274
|
+
appliesToDescription: APPLIES,
|
|
275
|
+
needs: { project: true },
|
|
276
|
+
cost: "edit",
|
|
277
|
+
severity: "warn",
|
|
278
|
+
evidence: EVIDENCE,
|
|
279
|
+
async run(ctx) {
|
|
280
|
+
if (ctx.target.kind !== "file")
|
|
281
|
+
return [];
|
|
282
|
+
const header = await headerOrFinding(this.id, ctx.target.path);
|
|
283
|
+
if (isFinding(header))
|
|
284
|
+
return [header];
|
|
285
|
+
if (pluginExtension(ctx.target.path) !== ".esl" || header.isLight)
|
|
286
|
+
return [];
|
|
287
|
+
return [
|
|
288
|
+
{
|
|
289
|
+
validatorId: this.id,
|
|
290
|
+
severity: "warn",
|
|
291
|
+
file: ctx.target.path,
|
|
292
|
+
message: `${path.basename(ctx.target.path)} is an .esl without the light flag (0x200) in its header; the game treats it as light because of the extension, header-reading tools do not.`,
|
|
293
|
+
fix: "Set the ESL flag in xEdit (it checks the plugin's new records fit the light range first).",
|
|
294
|
+
evidence: EVIDENCE,
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
const formVersion = {
|
|
300
|
+
id: "sse.plugins.form-version",
|
|
301
|
+
game: "skyrimse",
|
|
302
|
+
groups: ["plugins"],
|
|
303
|
+
modTypes: ["plugin"],
|
|
304
|
+
summary: "The TES4 record's form version is 44, Skyrim SE's; 43 is a Skyrim LE plugin.",
|
|
305
|
+
appliesTo: isPluginUnderSource,
|
|
306
|
+
appliesToDescription: APPLIES,
|
|
307
|
+
needs: { project: true },
|
|
308
|
+
cost: "edit",
|
|
309
|
+
severity: "block",
|
|
310
|
+
evidence: EVIDENCE,
|
|
311
|
+
async run(ctx) {
|
|
312
|
+
if (ctx.target.kind !== "file")
|
|
313
|
+
return [];
|
|
314
|
+
const header = await headerOrFinding(this.id, ctx.target.path);
|
|
315
|
+
if (isFinding(header))
|
|
316
|
+
return [header];
|
|
317
|
+
if (header.formVersion === FORM_VERSION_SSE)
|
|
318
|
+
return [];
|
|
319
|
+
const le = header.formVersion === FORM_VERSION_LE;
|
|
320
|
+
return [
|
|
321
|
+
{
|
|
322
|
+
validatorId: this.id,
|
|
323
|
+
severity: le ? "block" : "warn",
|
|
324
|
+
file: ctx.target.path,
|
|
325
|
+
message: le
|
|
326
|
+
? `${path.basename(ctx.target.path)} has form version 43: a Skyrim Legendary Edition plugin, not converted for Special Edition.`
|
|
327
|
+
: `${path.basename(ctx.target.path)} has form version ${header.formVersion}; Skyrim SE plugins carry 44.`,
|
|
328
|
+
fix: le ? "Open and re-save the plugin in the Skyrim SE Creation Kit to convert it (and re-pack any LE .bsa)." : "Re-save the plugin with the Skyrim SE Creation Kit or xEdit.",
|
|
329
|
+
evidence: EVIDENCE,
|
|
330
|
+
},
|
|
331
|
+
];
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
const masterCount = {
|
|
335
|
+
id: "sse.plugins.master-count",
|
|
336
|
+
game: "skyrimse",
|
|
337
|
+
groups: ["plugins"],
|
|
338
|
+
modTypes: ["plugin"],
|
|
339
|
+
summary: "A plugin names at most 253 masters; the FE slot is reserved for light plugins, in master lists too.",
|
|
340
|
+
appliesTo: isPluginUnderSource,
|
|
341
|
+
appliesToDescription: APPLIES,
|
|
342
|
+
needs: { project: true },
|
|
343
|
+
cost: "edit",
|
|
344
|
+
severity: "block",
|
|
345
|
+
evidence: EVIDENCE,
|
|
346
|
+
async run(ctx) {
|
|
347
|
+
if (ctx.target.kind !== "file")
|
|
348
|
+
return [];
|
|
349
|
+
const header = await headerOrFinding(this.id, ctx.target.path);
|
|
350
|
+
if (isFinding(header))
|
|
351
|
+
return [header];
|
|
352
|
+
if (header.masters.length <= MASTER_LIMIT_SSE)
|
|
353
|
+
return [];
|
|
354
|
+
return [
|
|
355
|
+
{
|
|
356
|
+
validatorId: this.id,
|
|
357
|
+
severity: "block",
|
|
358
|
+
file: ctx.target.path,
|
|
359
|
+
message: `${path.basename(ctx.target.path)} names ${header.masters.length} masters; Skyrim SE allows ${MASTER_LIMIT_SSE}.`,
|
|
360
|
+
fix: "Clean unused masters in xEdit, or split the plugin.",
|
|
361
|
+
evidence: EVIDENCE,
|
|
362
|
+
},
|
|
363
|
+
];
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
export const PLUGIN_VALIDATORS = [
|
|
367
|
+
mastersPresent,
|
|
368
|
+
masterOrder,
|
|
369
|
+
eslHeaderVersion,
|
|
370
|
+
flagExtensionMismatch,
|
|
371
|
+
eslExtensionImpliesFlag,
|
|
372
|
+
formVersion,
|
|
373
|
+
masterCount,
|
|
374
|
+
];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { HEADER_VERSION_EXPANDED_LIGHT_RANGE, PluginHeaderError, pluginExtension } from "../plugins.js";
|
|
3
|
+
import { RecordWalkError, classifyRecord, objectIndexOf, readPluginRecords } from "../records.js";
|
|
4
|
+
const EVIDENCE = [
|
|
5
|
+
"sse.plugins.esl.validity-counts-new-records-only",
|
|
6
|
+
"sse.plugins.esl.hedr-1-71-unlocks-the-expanded-range",
|
|
7
|
+
"sse.plugins.esl.expanded-floor-is-disputed",
|
|
8
|
+
"sse.plugins.format.modindex-equal-to-master-count-is-the-plugins-own",
|
|
9
|
+
];
|
|
10
|
+
export const LIGHT_OBJECT_INDEX_MAX = 0xfff;
|
|
11
|
+
export const LIGHT_OBJECT_INDEX_MIN_LEGACY = 0x800;
|
|
12
|
+
export const LIGHT_OBJECT_INDEX_MIN_EXPANDED = 0x001;
|
|
13
|
+
const NAMED_RECORDS = 5;
|
|
14
|
+
function isWithin(parent, child) {
|
|
15
|
+
const rel = path.relative(parent, child);
|
|
16
|
+
return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
|
|
17
|
+
}
|
|
18
|
+
function isPluginUnderSource(target, project) {
|
|
19
|
+
if (target.kind !== "file" || pluginExtension(target.path) === undefined)
|
|
20
|
+
return false;
|
|
21
|
+
return project.sources.some((s) => s.exists && isWithin(s.absPath, target.path));
|
|
22
|
+
}
|
|
23
|
+
const hex = (formId) => formId.toString(16).toUpperCase().padStart(8, "0");
|
|
24
|
+
const eslRange = {
|
|
25
|
+
id: "sse.plugins.esl-range",
|
|
26
|
+
game: "skyrimse",
|
|
27
|
+
groups: ["plugins"],
|
|
28
|
+
modTypes: ["plugin"],
|
|
29
|
+
summary: "A light plugin's new records (every record that does not override a master) have object indices within the range its HEDR version allows: 0x800-0xFFF below 1.71, up to 0xFFF from 1.71 (with 0x000 reported as disputed); overrides are exempt.",
|
|
30
|
+
appliesTo: isPluginUnderSource,
|
|
31
|
+
appliesToDescription: "every .esp, .esm or .esl file under a project source",
|
|
32
|
+
needs: { project: true },
|
|
33
|
+
cost: "build",
|
|
34
|
+
severity: "block",
|
|
35
|
+
evidence: EVIDENCE,
|
|
36
|
+
async run(ctx) {
|
|
37
|
+
if (ctx.target.kind !== "file")
|
|
38
|
+
return [];
|
|
39
|
+
const file = ctx.target.path;
|
|
40
|
+
const name = path.basename(file);
|
|
41
|
+
let walk;
|
|
42
|
+
try {
|
|
43
|
+
walk = await readPluginRecords(file);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err instanceof RecordWalkError) {
|
|
47
|
+
return [{ validatorId: this.id, severity: "info", file, message: `${name} was not walked: ${err.message}; the light-plugin range was not checked.`, evidence: EVIDENCE }];
|
|
48
|
+
}
|
|
49
|
+
const reason = err instanceof PluginHeaderError ? err.reason : "unreadable";
|
|
50
|
+
return [{ validatorId: this.id, severity: "block", file, message: `${name} has no readable TES4 header (${reason}): ${err.message}`, fix: "A plugin must begin with a TES4 record; re-save it from the Creation Kit or xEdit, or check the file is not truncated.", evidence: EVIDENCE }];
|
|
51
|
+
}
|
|
52
|
+
const { header } = walk;
|
|
53
|
+
const light = header.isLight || pluginExtension(file) === ".esl";
|
|
54
|
+
const findings = [];
|
|
55
|
+
for (const p of walk.problems.slice(0, 3)) {
|
|
56
|
+
findings.push({ validatorId: this.id, severity: "warn", file, message: `${name}'s record structure is damaged: ${p}; records after that point were not read.`, fix: "Re-save the plugin from xEdit or the Creation Kit.", evidence: EVIDENCE });
|
|
57
|
+
}
|
|
58
|
+
if (walk.truncated) {
|
|
59
|
+
findings.push({ validatorId: this.id, severity: "info", file, message: `${name} has more records than this walk reads; only the first ${walk.records.length} were checked.`, evidence: EVIDENCE });
|
|
60
|
+
}
|
|
61
|
+
const masters = header.masters.length;
|
|
62
|
+
const dangling = walk.records.filter((r) => classifyRecord(r.formId, masters) === "dangling");
|
|
63
|
+
if (dangling.length > 0) {
|
|
64
|
+
const named = dangling.slice(0, NAMED_RECORDS).map((r) => `${r.signature} ${hex(r.formId)}`).join(", ");
|
|
65
|
+
findings.push({
|
|
66
|
+
validatorId: this.id,
|
|
67
|
+
severity: "warn",
|
|
68
|
+
file,
|
|
69
|
+
message: `${name} has ${dangling.length} record${dangling.length === 1 ? "" : "s"} whose mod index is above its ${masters} master${masters === 1 ? "" : "s"} (${named}${dangling.length > NAMED_RECORDS ? ", ..." : ""}); the index names a master the header does not list, so nothing can tell whether the record overrides a master's form or is the plugin's own. xEdit's error check calls this a HITME.`,
|
|
70
|
+
fix: "Usually a master was force-removed from the header. Re-saving in xEdit or the Creation Kit sometimes repairs it; often the plugin has to be rebuilt against the master it lost.",
|
|
71
|
+
evidence: EVIDENCE,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (!light)
|
|
75
|
+
return findings;
|
|
76
|
+
const version = header.headerVersion;
|
|
77
|
+
const expanded = version !== undefined && version >= HEADER_VERSION_EXPANDED_LIGHT_RANGE;
|
|
78
|
+
const floor = expanded ? LIGHT_OBJECT_INDEX_MIN_EXPANDED : LIGHT_OBJECT_INDEX_MIN_LEGACY;
|
|
79
|
+
const own = walk.records.filter((r) => classifyRecord(r.formId, masters) !== "override");
|
|
80
|
+
const above = own.filter((r) => objectIndexOf(r.formId) > LIGHT_OBJECT_INDEX_MAX);
|
|
81
|
+
const below = own.filter((r) => objectIndexOf(r.formId) < floor && !(expanded && objectIndexOf(r.formId) === 0));
|
|
82
|
+
const zero = expanded ? own.filter((r) => objectIndexOf(r.formId) === 0) : [];
|
|
83
|
+
const describe = (records) => records.slice(0, NAMED_RECORDS).map((r) => `${r.signature} ${hex(r.formId)}`).join(", ") + (records.length > NAMED_RECORDS ? ", ..." : "");
|
|
84
|
+
if (above.length > 0) {
|
|
85
|
+
findings.push({
|
|
86
|
+
validatorId: this.id,
|
|
87
|
+
severity: "block",
|
|
88
|
+
file,
|
|
89
|
+
message: `${name} is a light plugin with ${above.length} new record${above.length === 1 ? "" : "s"} whose object index is above 0xFFF (${describe(above)}); a light plugin has 12 bits of object id, so the game cannot address them. xEdit refuses to save the ESL flag on such a file and Wrye Bash reports "This plugin contains records with FormIDs greater than 0xFFF".`,
|
|
90
|
+
fix: "Run xEdit's Compact FormIDs for ESL to renumber the plugin's own records into the light range, then rebuild anything that references them by FormID.",
|
|
91
|
+
evidence: EVIDENCE,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (below.length > 0) {
|
|
95
|
+
findings.push({
|
|
96
|
+
validatorId: this.id,
|
|
97
|
+
severity: "block",
|
|
98
|
+
file,
|
|
99
|
+
message: `${name} is a light plugin with HEDR ${version === undefined ? "missing" : version.toFixed(2)} and ${below.length} new record${below.length === 1 ? "" : "s"} whose object index is below 0x${floor.toString(16).toUpperCase().padStart(3, "0")} (${describe(below)}); below header version 1.71 a light plugin's new records must start at 0x800.`,
|
|
100
|
+
fix: "Either raise the header to 1.71 (which then needs game runtime 1.6.1130 or later) or compact the FormIDs into 0x800-0xFFF with xEdit.",
|
|
101
|
+
evidence: EVIDENCE,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (zero.length > 0) {
|
|
105
|
+
findings.push({
|
|
106
|
+
validatorId: this.id,
|
|
107
|
+
severity: "warn",
|
|
108
|
+
file,
|
|
109
|
+
message: `${name} is a 1.71 light plugin with ${zero.length} new record${zero.length === 1 ? "" : "s"} at object index 0x000 (${describe(zero)}); esplugin accepts 0x000, xEdit's notes say the expanded range starts at 001, and no real 1.71 plugin has been read here to settle it.`,
|
|
110
|
+
fix: "Renumber that record to 0x001 or above to be safe with every tool.",
|
|
111
|
+
evidence: EVIDENCE,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return findings;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
export const RECORD_VALIDATORS = [eslRange];
|