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,221 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ENTRY_DLL_RE, MANIFEST_FIELDS, parseManifest, PROJECT_VERSION_TOKEN, SEMANTIC_VERSION_RE, SEMANTIC_VERSION_RUNTIME_RE, SLUG_RE, SLUG_RUNTIME_RE, UPDATE_KEY_RE } from "../mods.js";
|
|
4
|
+
const EVIDENCE = ["stardewvalley.smapi.manifest.validation-order", "stardewvalley.smapi.manifest.published-json-schema"];
|
|
5
|
+
const APPLIES = "every manifest.json under a project source";
|
|
6
|
+
function isWithin(parent, child) {
|
|
7
|
+
const rel = path.relative(parent, child);
|
|
8
|
+
return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
|
|
9
|
+
}
|
|
10
|
+
function isManifest(target, project) {
|
|
11
|
+
if (target.kind !== "file" || path.basename(target.path).toLowerCase() !== "manifest.json")
|
|
12
|
+
return false;
|
|
13
|
+
return project.sources.some((s) => s.exists && isWithin(s.absPath, target.path));
|
|
14
|
+
}
|
|
15
|
+
async function load(ctx, id, file) {
|
|
16
|
+
let text;
|
|
17
|
+
try {
|
|
18
|
+
text = await ctx.readText(file);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return { validatorId: id, severity: "block", file, message: `manifest.json could not be read: ${err.message}`, evidence: EVIDENCE };
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return parseManifest(text);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return { validatorId: id, severity: "block", file, message: `manifest.json is not valid JSON: ${err.message}; SMAPI reports the mod as "parsing its manifest failed".`, fix: "Check it at smapi.io/json with the Manifest format.", evidence: EVIDENCE };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const isFinding = (v) => v.validatorId !== undefined;
|
|
31
|
+
const str = (v) => (typeof v === "string" ? v : undefined);
|
|
32
|
+
const blank = (v) => typeof v !== "string" || v.trim().length === 0;
|
|
33
|
+
function rule(id, summary, severity, evidence, run) {
|
|
34
|
+
const ownsParseFailure = id === "parses";
|
|
35
|
+
return {
|
|
36
|
+
id: `stardewvalley.manifest.${id}`,
|
|
37
|
+
game: "stardewvalley",
|
|
38
|
+
groups: ["packaging"],
|
|
39
|
+
modTypes: ["packaging"],
|
|
40
|
+
summary,
|
|
41
|
+
appliesTo: isManifest,
|
|
42
|
+
appliesToDescription: APPLIES,
|
|
43
|
+
needs: { project: true },
|
|
44
|
+
cost: "edit",
|
|
45
|
+
severity,
|
|
46
|
+
evidence,
|
|
47
|
+
async run(ctx) {
|
|
48
|
+
if (ctx.target.kind !== "file")
|
|
49
|
+
return [];
|
|
50
|
+
const m = await load(ctx, this.id, ctx.target.path);
|
|
51
|
+
if (isFinding(m))
|
|
52
|
+
return ownsParseFailure ? [m] : [];
|
|
53
|
+
return run(ctx, m, ctx.target.path, this.id);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const parses = rule("parses", "manifest.json parses as a JSON object (SMAPI's reader accepts comments and trailing commas; ModWright does too). The one rule that reports an unreadable or unparseable manifest; the rest stay quiet, since this one blocks.", "block", EVIDENCE, () => []);
|
|
58
|
+
const requiredFields = rule("required-fields", "Name, UniqueID and Version are present (SMAPI: 'manifest is missing required fields'), with Version = \"0.0.0\" counting as missing; Author and Description, which only the schema requires, warn.", "block", [...EVIDENCE, "stardewvalley.smapi.manifest.version-zero-counts-as-missing"], (_ctx, m, file, rid) => {
|
|
59
|
+
const missing = [];
|
|
60
|
+
if (blank(m.Name))
|
|
61
|
+
missing.push("Name");
|
|
62
|
+
const version = str(m.Version);
|
|
63
|
+
if (m.Version === undefined || m.Version === null || version === "0.0.0")
|
|
64
|
+
missing.push("Version");
|
|
65
|
+
if (blank(m.UniqueID))
|
|
66
|
+
missing.push("UniqueID");
|
|
67
|
+
const findings = [];
|
|
68
|
+
if (missing.length > 0) {
|
|
69
|
+
findings.push({
|
|
70
|
+
validatorId: rid,
|
|
71
|
+
severity: "block",
|
|
72
|
+
file,
|
|
73
|
+
message: `manifest is missing required fields (${missing.join(", ")})${version === "0.0.0" ? "; SMAPI treats Version \"0.0.0\" as absent" : ""}.`,
|
|
74
|
+
fix: "Set the field; the mod is not loaded without it.",
|
|
75
|
+
evidence: EVIDENCE,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
for (const f of ["Author", "Description"]) {
|
|
79
|
+
if (blank(m[f]))
|
|
80
|
+
findings.push({ validatorId: rid, severity: "warn", file, message: `manifest has no ${f}; SMAPI loads the mod, but the published schema (and smapi.io/json) requires it, and the log's mod list shows it.`, evidence: EVIDENCE });
|
|
81
|
+
}
|
|
82
|
+
return findings;
|
|
83
|
+
});
|
|
84
|
+
const entryOrContentPack = rule("entry-or-content-pack", "Exactly one of EntryDll (a C# mod) and ContentPackFor (a content pack) is set; ContentPackFor carries its UniqueID; EntryDll is a plain .dll file name.", "block", [...EVIDENCE, "stardewvalley.smapi.manifest.entrydll-xor-contentpackfor", "stardewvalley.smapi.manifest.contentpackfor-shape"], (_ctx, m, file, rid) => {
|
|
85
|
+
const hasDll = !blank(m.EntryDll);
|
|
86
|
+
const cpf = m.ContentPackFor;
|
|
87
|
+
const hasCpf = cpf !== undefined && cpf !== null;
|
|
88
|
+
const findings = [];
|
|
89
|
+
if (hasDll && hasCpf)
|
|
90
|
+
findings.push({ validatorId: rid, severity: "block", file, message: "manifest sets both EntryDll and ContentPackFor, which are mutually exclusive.", fix: "A C# mod keeps EntryDll; a content pack keeps ContentPackFor.", evidence: EVIDENCE });
|
|
91
|
+
else if (!hasDll && !hasCpf)
|
|
92
|
+
findings.push({ validatorId: rid, severity: "block", file, message: "manifest has no EntryDll or ContentPackFor field; must specify one.", fix: 'A content pack for Content Patcher uses "ContentPackFor": { "UniqueID": "Pathoschild.ContentPatcher" }.', evidence: EVIDENCE });
|
|
93
|
+
if (hasCpf) {
|
|
94
|
+
const uid = cpf && typeof cpf === "object" ? cpf.UniqueID : undefined;
|
|
95
|
+
if (blank(uid))
|
|
96
|
+
findings.push({ validatorId: rid, severity: "block", file, message: "manifest declares ContentPackFor without its required UniqueID field.", evidence: EVIDENCE });
|
|
97
|
+
else if (!SLUG_RE.test(uid))
|
|
98
|
+
findings.push({ validatorId: rid, severity: "block", file, message: `ContentPackFor.UniqueID "${uid}" is not a valid mod id (letters, numbers, underscores, periods, or hyphens).`, evidence: EVIDENCE });
|
|
99
|
+
}
|
|
100
|
+
if (hasDll) {
|
|
101
|
+
const dll = str(m.EntryDll);
|
|
102
|
+
if (/[<>:"/\\|?*\u0000-\u001f]/.test(dll))
|
|
103
|
+
findings.push({ validatorId: rid, severity: "block", file, message: `manifest has invalid filename '${dll}' for the EntryDll field.`, evidence: EVIDENCE });
|
|
104
|
+
else if (!ENTRY_DLL_RE.test(dll))
|
|
105
|
+
findings.push({ validatorId: rid, severity: "warn", file, message: `EntryDll "${dll}" does not match the schema's pattern (a bare file name ending in .dll); SMAPI resolves it relative to the mod folder.`, evidence: EVIDENCE });
|
|
106
|
+
}
|
|
107
|
+
return findings;
|
|
108
|
+
});
|
|
109
|
+
const entryDllExists = rule("entrydll-exists", "The file EntryDll names exists in the mod folder; SMAPI skips the mod with \"its DLL '...' doesn't exist\" otherwise.", "block", [...EVIDENCE, "stardewvalley.smapi.manifest.entrydll-must-exist"], async (ctx, m, file, rid) => {
|
|
110
|
+
const dll = str(m.EntryDll);
|
|
111
|
+
if (!dll || dll.trim().length === 0 || /[<>:"/\\|?*]/.test(dll))
|
|
112
|
+
return [];
|
|
113
|
+
const target = path.join(path.dirname(file), dll);
|
|
114
|
+
if (await ctx.exists(target))
|
|
115
|
+
return [];
|
|
116
|
+
let csproj = false;
|
|
117
|
+
try {
|
|
118
|
+
csproj = (await fs.readdir(path.dirname(file))).some((n) => n.toLowerCase().endsWith(".csproj"));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
csproj = false;
|
|
122
|
+
}
|
|
123
|
+
if (csproj) {
|
|
124
|
+
return [{ validatorId: rid, severity: "info", file, message: `EntryDll names "${dll}", which is not beside this manifest; a .csproj is, so the DLL is the build's output and SMAPI reads the deployed copy (the build package assembles that folder, manifest.json beside the DLL, when it deploys or zips), not this folder.`, evidence: EVIDENCE }];
|
|
125
|
+
}
|
|
126
|
+
return [
|
|
127
|
+
{
|
|
128
|
+
validatorId: rid,
|
|
129
|
+
severity: "block",
|
|
130
|
+
file,
|
|
131
|
+
message: `EntryDll names "${dll}", and ${target} does not exist; SMAPI skips the mod because its DLL '${dll}' doesn't exist.`,
|
|
132
|
+
fix: "Build the mod (the DLL is the build's output), or correct EntryDll to the built file's name.",
|
|
133
|
+
evidence: EVIDENCE,
|
|
134
|
+
related: [target],
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
});
|
|
138
|
+
const uniqueId = rule("uniqueid", "UniqueID is a slug: letters, numbers, underscores, periods or hyphens (PathUtilities.IsSlug); reverse-domain form is the convention.", "block", [...EVIDENCE, "stardewvalley.smapi.manifest.uniqueid-format"], (_ctx, m, file, rid) => {
|
|
139
|
+
const id = str(m.UniqueID);
|
|
140
|
+
if (id === undefined || id.trim().length === 0 || SLUG_RE.test(id))
|
|
141
|
+
return [];
|
|
142
|
+
if (SLUG_RUNTIME_RE.test(id))
|
|
143
|
+
return [{ validatorId: rid, severity: "warn", file, message: `UniqueID "${id}" uses letters outside ASCII; SMAPI's IsSlug accepts any Unicode letter, so the mod loads, but the published schema (and smapi.io/json) rejects it and other mods' tooling may too.`, fix: "Prefer ASCII: YourName.YourProjectName.", evidence: EVIDENCE }];
|
|
144
|
+
return [{ validatorId: rid, severity: "block", file, message: `manifest specifies an invalid ID "${id}" (IDs must only contain letters, numbers, underscores, periods, or hyphens).`, fix: "Use YourName.YourProjectName.", evidence: EVIDENCE }];
|
|
145
|
+
});
|
|
146
|
+
const version = rule("version", `Version is a semantic version as SMAPI parses it (major.minor[.patch][-prerelease]) or the literal ${PROJECT_VERSION_TOKEN} the build package substitutes.`, "block", [...EVIDENCE, "stardewvalley.smapi.manifest.semantic-version-pattern", "stardewvalley.smapi.manifest.project-version-token"], (_ctx, m, file, rid) => {
|
|
147
|
+
const v = m.Version;
|
|
148
|
+
if (v === undefined || v === null)
|
|
149
|
+
return [];
|
|
150
|
+
if (typeof v !== "string")
|
|
151
|
+
return [{ validatorId: rid, severity: "block", file, message: `Version is ${JSON.stringify(v)}, not a string.`, evidence: EVIDENCE }];
|
|
152
|
+
if (v === PROJECT_VERSION_TOKEN || v === "0.0.0" || SEMANTIC_VERSION_RE.test(v))
|
|
153
|
+
return [];
|
|
154
|
+
if (SEMANTIC_VERSION_RUNTIME_RE.test(v))
|
|
155
|
+
return [{ validatorId: rid, severity: "warn", file, message: `Version "${v}" carries build metadata (+...); SMAPI's SemanticVersionReader parses it and the mod loads, but the published schema (and smapi.io/json) rejects it.`, fix: 'Drop the +metadata; use e.g. "1.0.0" or "1.2.0-beta.1".', evidence: EVIDENCE }];
|
|
156
|
+
return [{ validatorId: rid, severity: "block", file, message: `Version "${v}" is not a semantic version SMAPI accepts (major.minor[.patch][-prerelease], no build metadata).`, fix: 'Use e.g. "1.0.0" or "1.2.0-beta.1".', evidence: EVIDENCE }];
|
|
157
|
+
});
|
|
158
|
+
const dependencies = rule("dependencies", "Every Dependencies entry is an object with a slug UniqueID, an optional MinimumVersion that parses, and an optional boolean IsRequired.", "block", [...EVIDENCE, "stardewvalley.smapi.manifest.dependencies-shape"], (_ctx, m, file, rid) => {
|
|
159
|
+
if (m.Dependencies === undefined || m.Dependencies === null)
|
|
160
|
+
return [];
|
|
161
|
+
if (!Array.isArray(m.Dependencies))
|
|
162
|
+
return [{ validatorId: rid, severity: "block", file, message: "Dependencies is not a list.", evidence: EVIDENCE }];
|
|
163
|
+
const findings = [];
|
|
164
|
+
m.Dependencies.forEach((d, i) => {
|
|
165
|
+
if (!d || typeof d !== "object") {
|
|
166
|
+
findings.push({ validatorId: rid, severity: "block", file, message: `manifest has a null or non-object dependency at index ${i}.`, evidence: EVIDENCE });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const r = d;
|
|
170
|
+
if (blank(r.UniqueID))
|
|
171
|
+
findings.push({ validatorId: rid, severity: "block", file, message: `manifest dependency ${i + 1} has no UniqueID.`, evidence: EVIDENCE });
|
|
172
|
+
else if (!SLUG_RE.test(r.UniqueID))
|
|
173
|
+
findings.push({ validatorId: rid, severity: "block", file, message: `manifest dependency "${r.UniqueID}" is not a valid mod id (letters, numbers, underscores, periods, or hyphens).`, evidence: EVIDENCE });
|
|
174
|
+
if (r.MinimumVersion !== undefined && (typeof r.MinimumVersion !== "string" || !SEMANTIC_VERSION_RE.test(r.MinimumVersion)))
|
|
175
|
+
findings.push({ validatorId: rid, severity: "block", file, message: `dependency "${r.UniqueID}" has MinimumVersion ${JSON.stringify(r.MinimumVersion)}, not a semantic version.`, evidence: EVIDENCE });
|
|
176
|
+
if (r.IsRequired !== undefined && typeof r.IsRequired !== "boolean")
|
|
177
|
+
findings.push({ validatorId: rid, severity: "warn", file, message: `dependency "${r.UniqueID}" has IsRequired ${JSON.stringify(r.IsRequired)}; it is a boolean (optional dependencies are loaded first when installed, ignored otherwise).`, evidence: EVIDENCE });
|
|
178
|
+
});
|
|
179
|
+
return findings;
|
|
180
|
+
});
|
|
181
|
+
const updateKeys = rule("update-keys", "Each UpdateKeys entry is Site:ID (Nexus, ModDrop, CurseForge, Chucklefish) or GitHub:owner/repo, with an optional @subkey; none at all is noted, since SMAPI's own 'No update keys' warning group lists such mods.", "warn", [...EVIDENCE, "stardewvalley.smapi.manifest.update-keys", "stardewvalley.smapi.manifest.update-subkeys"], (_ctx, m, file, rid) => {
|
|
182
|
+
const keys = m.UpdateKeys;
|
|
183
|
+
if (keys === undefined || keys === null || (Array.isArray(keys) && keys.length === 0)) {
|
|
184
|
+
return [{ validatorId: rid, severity: "info", file, message: "manifest has no UpdateKeys, so SMAPI cannot tell players about updates (its 'No update keys' warning group will list the mod).", fix: 'Add e.g. "UpdateKeys": ["Nexus:1234"] once the mod has a page.', evidence: EVIDENCE }];
|
|
185
|
+
}
|
|
186
|
+
if (!Array.isArray(keys))
|
|
187
|
+
return [{ validatorId: rid, severity: "warn", file, message: "UpdateKeys is not a list.", evidence: EVIDENCE }];
|
|
188
|
+
const findings = [];
|
|
189
|
+
for (const k of keys) {
|
|
190
|
+
if (typeof k !== "string" || !UPDATE_KEY_RE.test(k))
|
|
191
|
+
findings.push({ validatorId: rid, severity: "warn", file, message: `UpdateKeys entry ${JSON.stringify(k)} is not Chucklefish:ID, Nexus:ID, GitHub:owner/repo, ModDrop:ID or CurseForge:ID (optionally @subkey); SMAPI's update check ignores it.`, evidence: EVIDENCE });
|
|
192
|
+
}
|
|
193
|
+
return findings;
|
|
194
|
+
});
|
|
195
|
+
const extraFields = rule("extra-fields", "Fields outside the manifest's declared set are noted, including a mis-cased known field: SMAPI keeps the unknown ones in ExtraFields (legal), while the published schema's additionalProperties: false rejects them at smapi.io/json; the runtime is authoritative, so all of it is info.", "info", [...EVIDENCE, "stardewvalley.smapi.manifest.fields"], (_ctx, m, file, rid) => {
|
|
196
|
+
const extras = Object.keys(m.extraFields);
|
|
197
|
+
if (extras.length === 0)
|
|
198
|
+
return [];
|
|
199
|
+
const knownLower = new Map(MANIFEST_FIELDS.map((f) => [f.toLowerCase(), f]));
|
|
200
|
+
const miscased = extras.filter((e) => knownLower.has(e.toLowerCase()));
|
|
201
|
+
const findings = [];
|
|
202
|
+
for (const e of miscased) {
|
|
203
|
+
findings.push({ validatorId: rid, severity: "info", file, message: `manifest field "${e}" is not "${knownLower.get(e.toLowerCase())}"; SMAPI's JSON reader still binds it to the known field, but the published schema is case-sensitive, so spell it as documented.`, evidence: EVIDENCE });
|
|
204
|
+
}
|
|
205
|
+
const other = extras.filter((e) => !knownLower.has(e.toLowerCase()));
|
|
206
|
+
if (other.length > 0)
|
|
207
|
+
findings.push({ validatorId: rid, severity: "info", file, message: `manifest has fields SMAPI does not define (${other.join(", ")}); they land in ExtraFields and are ignored, but smapi.io/json's schema rejects them.`, evidence: EVIDENCE });
|
|
208
|
+
return findings;
|
|
209
|
+
});
|
|
210
|
+
const minimumVersions = rule("minimum-versions", "MinimumApiVersion and MinimumGameVersion, when set, are semantic versions; SMAPI refuses the mod with 'it needs SMAPI x or later' / 'it needs Stardew Valley x or later' when they exceed the running versions.", "block", [...EVIDENCE, "stardewvalley.smapi.manifest.minimum-versions"], (_ctx, m, file, rid) => {
|
|
211
|
+
const findings = [];
|
|
212
|
+
for (const f of ["MinimumApiVersion", "MinimumGameVersion"]) {
|
|
213
|
+
const v = m[f];
|
|
214
|
+
if (v === undefined || v === null)
|
|
215
|
+
continue;
|
|
216
|
+
if (typeof v !== "string" || !SEMANTIC_VERSION_RE.test(v))
|
|
217
|
+
findings.push({ validatorId: rid, severity: "block", file, message: `${f} ${JSON.stringify(v)} is not a semantic version.`, evidence: EVIDENCE });
|
|
218
|
+
}
|
|
219
|
+
return findings;
|
|
220
|
+
});
|
|
221
|
+
export const MANIFEST_VALIDATORS = [parses, requiredFields, entryOrContentPack, entryDllExists, uniqueId, version, minimumVersions, dependencies, updateKeys, extraFields];
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { readPeFileVersion } from "../../core/compat/pe.js";
|
|
3
|
+
import { localAppData, pathExists, readTextIfExists } from "../../core/fsutil.js";
|
|
4
|
+
import { SIGNATURES } from "./logs.js";
|
|
5
|
+
const WIN64 = ["Subnautica2", "Binaries", "Win64"];
|
|
6
|
+
const SHIPPING_EXE = "Subnautica2-Win64-Shipping.exe";
|
|
7
|
+
export function versionsFromLog(text) {
|
|
8
|
+
const engine = SIGNATURES.find((s) => s.id === "subnautica2.unreal.engine-version");
|
|
9
|
+
const project = SIGNATURES.find((s) => s.id === "subnautica2.unreal.project-version");
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const line of text.split(/\r?\n/, 4000)) {
|
|
12
|
+
if (out.engine === undefined) {
|
|
13
|
+
const m = engine.pattern.exec(line);
|
|
14
|
+
if (m?.groups) {
|
|
15
|
+
out.engine = m.groups.engine;
|
|
16
|
+
out.changelist = m.groups.changelist;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (out.projectVersion === undefined) {
|
|
20
|
+
const m = project.pattern.exec(line);
|
|
21
|
+
if (m?.groups)
|
|
22
|
+
out.projectVersion = m.groups.version;
|
|
23
|
+
}
|
|
24
|
+
if (out.engine !== undefined && out.projectVersion !== undefined)
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
export function defaultLogPath() {
|
|
30
|
+
return localAppData("Subnautica2", "Saved", "Logs", "Subnautica2.log");
|
|
31
|
+
}
|
|
32
|
+
export async function detectComponentsWith(install, options = {}) {
|
|
33
|
+
const win64 = path.join(install.root, ...WIN64);
|
|
34
|
+
const exe = path.join(win64, SHIPPING_EXE);
|
|
35
|
+
const exeExists = await pathExists(exe);
|
|
36
|
+
const logPath = options.logPath === undefined ? defaultLogPath() : options.logPath;
|
|
37
|
+
const logText = logPath ? await readTextIfExists(logPath) : undefined;
|
|
38
|
+
const fromLog = logText ? versionsFromLog(logText) : {};
|
|
39
|
+
let peVersion;
|
|
40
|
+
if (exeExists && !install.version && !fromLog.projectVersion) {
|
|
41
|
+
try {
|
|
42
|
+
peVersion = await readPeFileVersion(exe);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
peVersion = undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const gameVersion = install.version ?? fromLog.projectVersion ?? peVersion;
|
|
49
|
+
const gameSource = install.version ? "install record" : fromLog.projectVersion ? `${logPath}: LogNetVersion: Set ProjectVersion (with changelist ${fromLog.changelist ?? "unknown"})` : peVersion ? `${SHIPPING_EXE} PE file version (the ProjectVersion is preferred when a log exists)` : "no log and no readable executable";
|
|
50
|
+
const components = [
|
|
51
|
+
{ id: "game", label: "Subnautica 2", kind: "game", installed: exeExists, ...(gameVersion !== undefined ? { version: gameVersion } : {}), source: gameSource, path: exe },
|
|
52
|
+
{
|
|
53
|
+
id: "engine",
|
|
54
|
+
label: "Unreal Engine",
|
|
55
|
+
kind: "framework",
|
|
56
|
+
installed: exeExists,
|
|
57
|
+
...(fromLog.engine !== undefined ? { version: fromLog.engine } : {}),
|
|
58
|
+
source: fromLog.engine ? `${logPath}: LogInit: Engine Version` : "the game log's LogInit: Engine Version line, not found",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
const proxy = path.join(win64, "dwmapi.dll");
|
|
62
|
+
const experimentalDll = path.join(win64, "ue4ss", "UE4SS.dll");
|
|
63
|
+
const stableDll = path.join(win64, "UE4SS.dll");
|
|
64
|
+
const hasProxy = await pathExists(proxy);
|
|
65
|
+
const hasExperimental = await pathExists(experimentalDll);
|
|
66
|
+
const hasStable = await pathExists(stableDll);
|
|
67
|
+
components.push({
|
|
68
|
+
id: "ue4ss",
|
|
69
|
+
label: "UE4SS",
|
|
70
|
+
kind: "loader",
|
|
71
|
+
installed: hasProxy || hasExperimental || hasStable,
|
|
72
|
+
source: hasExperimental ? "ue4ss/UE4SS.dll beside the executable (the experimental layout); its version is not read, since the release is a rolling tag" : hasStable ? "UE4SS.dll directly beside the executable (the stable 3.0.x layout)" : hasProxy ? "dwmapi.dll proxy beside the executable, with no UE4SS.dll found" : "no dwmapi.dll proxy or UE4SS.dll beside the executable",
|
|
73
|
+
...(hasExperimental ? { path: experimentalDll } : hasStable ? { path: stableDll } : hasProxy ? { path: proxy } : {}),
|
|
74
|
+
});
|
|
75
|
+
components.push({ id: "ue4ss-stable-layout", label: "UE4SS in the stable 3.0.x layout", kind: "loader", installed: hasStable && !hasExperimental, source: "UE4SS.dll directly under Binaries/Win64 with no ue4ss/ folder" });
|
|
76
|
+
components.push({ id: "ue4ss-proxy-only", label: "dwmapi.dll proxy with no UE4SS.dll", kind: "loader", installed: hasProxy && !hasExperimental && !hasStable, source: "dwmapi.dll present, neither UE4SS.dll location exists" });
|
|
77
|
+
return components;
|
|
78
|
+
}
|
|
79
|
+
export const RULES = [
|
|
80
|
+
{
|
|
81
|
+
id: "sn2-ue4ss-needs-experimental",
|
|
82
|
+
game: "subnautica2",
|
|
83
|
+
component: "ue4ss",
|
|
84
|
+
requires: [],
|
|
85
|
+
advisory: true,
|
|
86
|
+
severity: "warn",
|
|
87
|
+
note: "Unreal 5.6 support exists only in UE4SS's experimental-latest prerelease, a rolling build under a fixed tag; the latest tagged stable (v3.0.1, 2025-02-14) tops out at UE 5.3, so a UE4SS taken from the stable release will not work here and no version can be pinned.",
|
|
88
|
+
source: "https://github.com/UE4SS-RE/RE-UE4SS/releases/tag/experimental-latest",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "sn2-ue4ss-stable-layout",
|
|
92
|
+
game: "subnautica2",
|
|
93
|
+
component: "ue4ss-stable-layout",
|
|
94
|
+
requires: [],
|
|
95
|
+
advisory: true,
|
|
96
|
+
severity: "block",
|
|
97
|
+
note: "UE4SS.dll sits directly beside the executable, which is the stable 3.0.x layout; the experimental builds that support Unreal 5.6 keep everything but the proxy under a ue4ss/ subfolder, so this is almost certainly a stable build that cannot run this game.",
|
|
98
|
+
source: "experimental-latest release notes: 'All UE4SS files ... now reside in a ue4ss subfolder next to your game executable'",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "sn2-ue4ss-proxy-without-dll",
|
|
102
|
+
game: "subnautica2",
|
|
103
|
+
component: "ue4ss-proxy-only",
|
|
104
|
+
requires: [],
|
|
105
|
+
advisory: true,
|
|
106
|
+
severity: "block",
|
|
107
|
+
note: "A dwmapi.dll proxy is beside the executable but no UE4SS.dll is in either layout; the proxy loads nothing and every UE4SS mod is inert.",
|
|
108
|
+
source: "https://docs.ue4ss.com/dev/installation-guide.html",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "sn2-mods-break-every-patch",
|
|
112
|
+
game: "subnautica2",
|
|
113
|
+
component: "ue4ss",
|
|
114
|
+
requires: [{ component: "game" }],
|
|
115
|
+
advisory: true,
|
|
116
|
+
severity: "warn",
|
|
117
|
+
versionGate: "game",
|
|
118
|
+
undeterminedSeverity: "warn",
|
|
119
|
+
note: "Every Subnautica 2 patch can break mods, and the developer's own hotfix notes tell players to disable mods before launching; with a roughly six-week cadence this is the steady state, so re-verify mods after each update.",
|
|
120
|
+
source: "https://unknownworlds.com/en/news/subnautica-2-buddy-system-hotfix-1",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "sn2-engine-version-drives-tooling",
|
|
124
|
+
game: "subnautica2",
|
|
125
|
+
component: "engine",
|
|
126
|
+
requires: [],
|
|
127
|
+
advisory: true,
|
|
128
|
+
severity: "info",
|
|
129
|
+
versionGate: "engine",
|
|
130
|
+
undeterminedSeverity: "info",
|
|
131
|
+
note: "The engine version (5.6.1 on the launch build) is the input to every tool choice: which UE4SS build, retoc's --version target (UE5_6), FModel's UE Version setting, and which mappings file matches.",
|
|
132
|
+
source: "local Subnautica2.log 'LogInit: Engine Version: 5.6.1-113109+++Project+SN2-Release-Hotfix-Live'",
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
export const subnautica2Compat = {
|
|
136
|
+
detectComponents: (install) => detectComponentsWith(install),
|
|
137
|
+
rules: RULES,
|
|
138
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { subnautica2, PROJECT_DIR, SHIPPING_EXE } from "./surface.js";
|
|
2
|
+
export { SIGNATURES } from "./logs.js";
|
|
3
|
+
export { subnautica2Compat, detectComponentsWith, versionsFromLog } from "./compat.js";
|
|
4
|
+
export { readPakFooter, readPakInfo, parsePakFooter, parseFString, readUtocHeader, parseUtocHeader, tripletsIn, hasPrioritySuffix, GAME_MOUNT_POINT, PAK_MAGIC, UTOC_MAGIC } from "./paks.js";
|
|
5
|
+
export { PACKAGING_VALIDATORS } from "./validators/packaging.js";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const FIXTURE = "test/fixtures/logs/subnautica2/real/Subnautica2.head.log";
|
|
2
|
+
const PRE = String.raw `^(?:\[\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}\.\d{2}:\d{3}\]\[\s*\d+\])?`;
|
|
3
|
+
function re(body) {
|
|
4
|
+
return new RegExp(PRE + body);
|
|
5
|
+
}
|
|
6
|
+
export const SIGNATURES = [
|
|
7
|
+
{
|
|
8
|
+
id: "subnautica2.unreal.engine-version",
|
|
9
|
+
game: "subnautica2",
|
|
10
|
+
sources: ["game"],
|
|
11
|
+
severity: "info",
|
|
12
|
+
category: "version",
|
|
13
|
+
pattern: re(String.raw `LogInit: Engine Version: (?<engine>\d+\.\d+\.\d+)-(?<changelist>\d+)\+\+\+(?<branch>\S+)`),
|
|
14
|
+
explain: "The Unreal Engine point release and changelist the build runs on; UE4SS support is decided by this line.",
|
|
15
|
+
seenIn: [FIXTURE],
|
|
16
|
+
extract: ["engine", "changelist", "branch"],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "subnautica2.uwe.changelist",
|
|
20
|
+
game: "subnautica2",
|
|
21
|
+
sources: ["game"],
|
|
22
|
+
severity: "info",
|
|
23
|
+
category: "version",
|
|
24
|
+
pattern: re(String.raw `LogUWE: Changelist: (?<changelist>\d+)`),
|
|
25
|
+
explain: "The game's own changelist; mods break across these, and the developer says so.",
|
|
26
|
+
seenIn: [FIXTURE],
|
|
27
|
+
extract: ["changelist"],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "subnautica2.unreal.project-version",
|
|
31
|
+
game: "subnautica2",
|
|
32
|
+
sources: ["game"],
|
|
33
|
+
severity: "info",
|
|
34
|
+
category: "version",
|
|
35
|
+
pattern: re(String.raw `LogNetVersion: Set ProjectVersion to (?<version>\d+(?:\.\d+)*)`),
|
|
36
|
+
explain: "The project version the network layer checks; two builds with different versions will not play together.",
|
|
37
|
+
seenIn: [FIXTURE],
|
|
38
|
+
extract: ["version"],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "subnautica2.unreal.toc-loaded",
|
|
42
|
+
game: "subnautica2",
|
|
43
|
+
sources: ["game"],
|
|
44
|
+
severity: "info",
|
|
45
|
+
category: "load",
|
|
46
|
+
pattern: re(String.raw `LogIoDispatcher: Display: Toc loaded : (?<toc>\S+), Id=(?<id>[0-9a-f]+), Order=(?<order>\d+), EntryCount=(?<entries>\d+)`),
|
|
47
|
+
explain: "An IoStore table of contents was read: which .utoc, at what order, with how many entries. A mod's .utoc appears here or it did not mount.",
|
|
48
|
+
seenIn: [FIXTURE],
|
|
49
|
+
extract: ["toc", "order", "entries"],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "subnautica2.unreal.container-mounted",
|
|
53
|
+
game: "subnautica2",
|
|
54
|
+
sources: ["game"],
|
|
55
|
+
severity: "info",
|
|
56
|
+
category: "success",
|
|
57
|
+
pattern: re(String.raw `LogPakFile: Display: Mounted IoStore container "(?<container>[^"]+)"`),
|
|
58
|
+
explain: "An IoStore container mounted; the game's own and any mod's.",
|
|
59
|
+
seenIn: [FIXTURE],
|
|
60
|
+
extract: ["container"],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "subnautica2.unreal.pak-mounted",
|
|
64
|
+
game: "subnautica2",
|
|
65
|
+
sources: ["game"],
|
|
66
|
+
severity: "info",
|
|
67
|
+
category: "load",
|
|
68
|
+
pattern: re(String.raw `LogPakFile: Display: Mounted Pak file '(?<pak>[^']+)', mount point: '(?<mount>[^']+)'`),
|
|
69
|
+
explain: "A .pak mounted, with its mount point; a mod pak with the wrong mount point mounts and delivers nothing.",
|
|
70
|
+
seenIn: [FIXTURE],
|
|
71
|
+
extract: ["pak", "mount"],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "subnautica2.unreal.plugin-mounted",
|
|
75
|
+
game: "subnautica2",
|
|
76
|
+
sources: ["game"],
|
|
77
|
+
severity: "info",
|
|
78
|
+
category: "load",
|
|
79
|
+
pattern: re(String.raw `LogPluginManager: Mounting (?<kind>Project|Engine) plugin (?<plugin>\S+)`),
|
|
80
|
+
explain: "An Unreal plugin mounted; a DLC-style content plugin dropped under Mods/ shows up here without any loader.",
|
|
81
|
+
seenIn: [FIXTURE],
|
|
82
|
+
extract: ["kind", "plugin"],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "subnautica2.unreal.crash-guid",
|
|
86
|
+
game: "subnautica2",
|
|
87
|
+
sources: ["game"],
|
|
88
|
+
severity: "info",
|
|
89
|
+
category: "crash",
|
|
90
|
+
pattern: re(String.raw `\s*(?:LogInit: )?Session CrashGUID >\s+(?<guid>UECC-\S+)`),
|
|
91
|
+
explain: "The session's crash GUID, printed at startup; it names the crash report folder if this session crashes.",
|
|
92
|
+
seenIn: [FIXTURE],
|
|
93
|
+
extract: ["guid"],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "subnautica2.eos.anticheat-not-available",
|
|
97
|
+
game: "subnautica2",
|
|
98
|
+
sources: ["game"],
|
|
99
|
+
severity: "info",
|
|
100
|
+
category: "load",
|
|
101
|
+
pattern: re(String.raw `LogEOSSDK: LogEOSAntiCheat: \[AntiCheatClient\] Anti-cheat client not available`),
|
|
102
|
+
explain: "The EOS anti-cheat client was not started for this launch: the bootstrapper was not used. Present in the build, inert on this launch; whether co-op engages it is unresolved.",
|
|
103
|
+
seenIn: [FIXTURE],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "subnautica2.uwe.ai-error",
|
|
107
|
+
game: "subnautica2",
|
|
108
|
+
sources: ["game"],
|
|
109
|
+
severity: "warn",
|
|
110
|
+
category: "script",
|
|
111
|
+
pattern: re(String.raw `LogUWEAI: Error: (?<message>.+)$`),
|
|
112
|
+
explain: "The game's own AI system logging an error; a clean vanilla launch prints these too, so it is noise unless a mod touches AI.",
|
|
113
|
+
seenIn: [FIXTURE],
|
|
114
|
+
extract: ["message"],
|
|
115
|
+
systemImpact: false,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "subnautica2.unreal.rhi-pipeline-error",
|
|
119
|
+
game: "subnautica2",
|
|
120
|
+
sources: ["game"],
|
|
121
|
+
severity: "warn",
|
|
122
|
+
category: "resource",
|
|
123
|
+
pattern: re(String.raw `LogRHI: Error: Failed to create (?<kind>\w+) pipeline with hash (?<hash>[0-9A-F]+)`),
|
|
124
|
+
explain: "A graphics pipeline state failed to build; driver-side noise seen on a clean vanilla run, not a mod system failing.",
|
|
125
|
+
seenIn: [FIXTURE],
|
|
126
|
+
extract: ["kind", "hash"],
|
|
127
|
+
systemImpact: false,
|
|
128
|
+
},
|
|
129
|
+
];
|