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,229 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { readPeFileVersion } from "../../core/compat/pe.js";
|
|
4
|
+
import { pathExists, readTextIfExists } from "../../core/fsutil.js";
|
|
5
|
+
export const STEAM_APP_ID = "1488490";
|
|
6
|
+
export const GAME_EXE = "Nivalis Nights.exe";
|
|
7
|
+
async function peVersionOf(file) {
|
|
8
|
+
if (!(await pathExists(file)))
|
|
9
|
+
return undefined;
|
|
10
|
+
try {
|
|
11
|
+
return await readPeFileVersion(file);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function mtimeMs(file) {
|
|
18
|
+
try {
|
|
19
|
+
return (await fs.stat(file)).mtimeMs;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function parseAppManifestBuildId(vdf) {
|
|
26
|
+
return /"buildid"\s+"(\d+)"/i.exec(vdf)?.[1];
|
|
27
|
+
}
|
|
28
|
+
export async function readSteamBuildId(root) {
|
|
29
|
+
const manifest = path.resolve(root, "..", "..", `appmanifest_${STEAM_APP_ID}.acf`);
|
|
30
|
+
const text = await readTextIfExists(manifest);
|
|
31
|
+
return text === undefined ? undefined : parseAppManifestBuildId(text);
|
|
32
|
+
}
|
|
33
|
+
export function findBleedingEdgeBuild(bytes) {
|
|
34
|
+
const text = bytes.toString("latin1");
|
|
35
|
+
const anchored = /\x01\x00([\s\S])(\d+\.\d+\.\d+-be\.\d+(?:\+[0-9a-f]{7,40})?)/g;
|
|
36
|
+
for (let m = anchored.exec(text); m; m = anchored.exec(text)) {
|
|
37
|
+
if (m[1].charCodeAt(0) === m[2].length)
|
|
38
|
+
return m[2];
|
|
39
|
+
}
|
|
40
|
+
return /\d\.\d+\.\d+-be\.\d+(?:\+[0-9a-f]{7,40})?/.exec(text)?.[0];
|
|
41
|
+
}
|
|
42
|
+
async function bepinexBuildOf(dll) {
|
|
43
|
+
try {
|
|
44
|
+
return findBleedingEdgeBuild(await fs.readFile(dll));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function detectComponents(install) {
|
|
51
|
+
const root = install.root;
|
|
52
|
+
const components = [];
|
|
53
|
+
const exe = path.join(root, GAME_EXE);
|
|
54
|
+
const buildId = await readSteamBuildId(root);
|
|
55
|
+
const exeVersion = await peVersionOf(exe);
|
|
56
|
+
const gameVersion = install.version ?? buildId ?? exeVersion;
|
|
57
|
+
components.push({
|
|
58
|
+
id: "game",
|
|
59
|
+
label: "Nivalis Nights",
|
|
60
|
+
kind: "game",
|
|
61
|
+
installed: await pathExists(exe),
|
|
62
|
+
...(gameVersion !== undefined ? { version: gameVersion } : {}),
|
|
63
|
+
source: install.version
|
|
64
|
+
? "install record"
|
|
65
|
+
: buildId
|
|
66
|
+
? `steamapps/appmanifest_${STEAM_APP_ID}.acf buildid (the Steam build; changes with every game update)`
|
|
67
|
+
: `${GAME_EXE} PE file version (the Unity player version; does not change between game updates)`,
|
|
68
|
+
path: exe,
|
|
69
|
+
});
|
|
70
|
+
const core = path.join(root, "BepInEx", "core");
|
|
71
|
+
const coreDll6 = path.join(core, "BepInEx.Core.dll");
|
|
72
|
+
const coreDll5 = path.join(core, "BepInEx.dll");
|
|
73
|
+
const il2cppDll = path.join(core, "BepInEx.Unity.IL2CPP.dll");
|
|
74
|
+
const has6 = await pathExists(coreDll6);
|
|
75
|
+
const has5 = await pathExists(coreDll5);
|
|
76
|
+
const bepinexInstalled = has6 || has5;
|
|
77
|
+
const bepinexDll = has6 ? coreDll6 : coreDll5;
|
|
78
|
+
const bepinexVersion = bepinexInstalled ? ((has6 ? await bepinexBuildOf(coreDll6) : undefined) ?? (await peVersionOf(bepinexDll))) : undefined;
|
|
79
|
+
components.push({
|
|
80
|
+
id: "bepinex",
|
|
81
|
+
label: "BepInEx",
|
|
82
|
+
kind: "loader",
|
|
83
|
+
installed: bepinexInstalled,
|
|
84
|
+
...(bepinexVersion !== undefined ? { version: bepinexVersion } : {}),
|
|
85
|
+
source: has6 ? "BepInEx/core/BepInEx.Core.dll informational version (else its PE file version)" : "BepInEx/core/BepInEx.dll PE file version (BepInEx 5)",
|
|
86
|
+
path: bepinexDll,
|
|
87
|
+
});
|
|
88
|
+
const il2cpp = await pathExists(il2cppDll);
|
|
89
|
+
const il2cppVersion = il2cpp ? ((await bepinexBuildOf(il2cppDll)) ?? (await peVersionOf(il2cppDll))) : undefined;
|
|
90
|
+
components.push({
|
|
91
|
+
id: "bepinex-il2cpp",
|
|
92
|
+
label: "BepInEx Unity IL2CPP runtime",
|
|
93
|
+
kind: "loader",
|
|
94
|
+
installed: il2cpp,
|
|
95
|
+
...(il2cppVersion !== undefined ? { version: il2cppVersion } : {}),
|
|
96
|
+
source: "BepInEx/core/BepInEx.Unity.IL2CPP.dll informational version (else its PE file version)",
|
|
97
|
+
path: il2cppDll,
|
|
98
|
+
});
|
|
99
|
+
components.push({
|
|
100
|
+
id: "bepinex-wrong-build",
|
|
101
|
+
label: "BepInEx that is not the Unity IL2CPP build",
|
|
102
|
+
kind: "loader",
|
|
103
|
+
installed: bepinexInstalled && !il2cpp,
|
|
104
|
+
source: "BepInEx core present without BepInEx.Unity.IL2CPP.dll (BepInEx 5, or a 6.x Unity.Mono / NET build)",
|
|
105
|
+
});
|
|
106
|
+
const doorstopText = await readTextIfExists(path.join(root, ".doorstop_version"));
|
|
107
|
+
const doorstopVersion = doorstopText?.trim().split(/\s+/)[0];
|
|
108
|
+
const winhttp = path.join(root, "winhttp.dll");
|
|
109
|
+
const doorstopInstalled = (await pathExists(winhttp)) || doorstopText !== undefined;
|
|
110
|
+
components.push({
|
|
111
|
+
id: "doorstop",
|
|
112
|
+
label: "UnityDoorstop (BepInEx's entry point)",
|
|
113
|
+
kind: "loader",
|
|
114
|
+
installed: doorstopInstalled,
|
|
115
|
+
...(doorstopVersion ? { version: doorstopVersion } : {}),
|
|
116
|
+
source: ".doorstop_version beside the executable",
|
|
117
|
+
path: winhttp,
|
|
118
|
+
});
|
|
119
|
+
const interopProbe = path.join(root, "BepInEx", "interop", "Assembly-CSharp.dll");
|
|
120
|
+
const interopAt = await mtimeMs(interopProbe);
|
|
121
|
+
const gameAssemblyAt = await mtimeMs(path.join(root, "GameAssembly.dll"));
|
|
122
|
+
components.push({ id: "bepinex-interop", label: "BepInEx interop assemblies", kind: "framework", installed: interopAt !== undefined, source: "BepInEx/interop/Assembly-CSharp.dll", path: interopProbe });
|
|
123
|
+
components.push({
|
|
124
|
+
id: "bepinex-interop-missing",
|
|
125
|
+
label: "BepInEx IL2CPP installed but never launched (no interop yet)",
|
|
126
|
+
kind: "framework",
|
|
127
|
+
installed: il2cpp && interopAt === undefined,
|
|
128
|
+
source: "BepInEx.Unity.IL2CPP.dll present, BepInEx/interop/Assembly-CSharp.dll absent",
|
|
129
|
+
});
|
|
130
|
+
components.push({
|
|
131
|
+
id: "bepinex-interop-stale",
|
|
132
|
+
label: "Interop older than GameAssembly.dll (the game updated since BepInEx last ran)",
|
|
133
|
+
kind: "framework",
|
|
134
|
+
installed: interopAt !== undefined && gameAssemblyAt !== undefined && interopAt < gameAssemblyAt,
|
|
135
|
+
source: "mtime of BepInEx/interop/Assembly-CSharp.dll against GameAssembly.dll",
|
|
136
|
+
});
|
|
137
|
+
for (const [id, label, file] of [
|
|
138
|
+
["harmony", "HarmonyX", "0Harmony.dll"],
|
|
139
|
+
["il2cppinterop", "Il2CppInterop", "Il2CppInterop.Runtime.dll"],
|
|
140
|
+
]) {
|
|
141
|
+
const dll = path.join(core, file);
|
|
142
|
+
const version = await peVersionOf(dll);
|
|
143
|
+
components.push({ id, label, kind: "framework", installed: await pathExists(dll), ...(version !== undefined ? { version } : {}), source: `BepInEx/core/${file} PE file version`, path: dll });
|
|
144
|
+
}
|
|
145
|
+
const melonDir = path.join(root, "MelonLoader");
|
|
146
|
+
const melonInstalled = (await pathExists(path.join(root, "version.dll"))) && (await pathExists(melonDir));
|
|
147
|
+
let melonVersion;
|
|
148
|
+
let melonDll;
|
|
149
|
+
if (melonInstalled) {
|
|
150
|
+
for (const candidate of [path.join(melonDir, "net6", "MelonLoader.dll"), path.join(melonDir, "MelonLoader.dll")]) {
|
|
151
|
+
if (await pathExists(candidate)) {
|
|
152
|
+
melonDll = candidate;
|
|
153
|
+
melonVersion = await peVersionOf(candidate);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
components.push({
|
|
159
|
+
id: "melonloader",
|
|
160
|
+
label: "MelonLoader",
|
|
161
|
+
kind: "loader",
|
|
162
|
+
installed: melonInstalled,
|
|
163
|
+
...(melonVersion !== undefined ? { version: melonVersion } : {}),
|
|
164
|
+
source: "version.dll and MelonLoader/ beside the executable; version from MelonLoader/net6/MelonLoader.dll (layout from MelonLoader's docs, not yet seen on this machine)",
|
|
165
|
+
...(melonDll ? { path: melonDll } : {}),
|
|
166
|
+
});
|
|
167
|
+
components.push({
|
|
168
|
+
id: "both-loaders",
|
|
169
|
+
label: "BepInEx and MelonLoader both installed",
|
|
170
|
+
kind: "loader",
|
|
171
|
+
installed: (doorstopInstalled || bepinexInstalled) && melonInstalled,
|
|
172
|
+
source: "BepInEx's doorstop or core, and MelonLoader's version.dll and folder",
|
|
173
|
+
});
|
|
174
|
+
return components;
|
|
175
|
+
}
|
|
176
|
+
export const RULES = [
|
|
177
|
+
{
|
|
178
|
+
id: "nn-bepinex-must-be-il2cpp",
|
|
179
|
+
game: "nivalisnights",
|
|
180
|
+
component: "bepinex-wrong-build",
|
|
181
|
+
requires: [],
|
|
182
|
+
advisory: true,
|
|
183
|
+
severity: "block",
|
|
184
|
+
note: "Nivalis Nights is a Unity IL2CPP game. BepInEx 5.x and the 6.x Unity.Mono and NET builds load nothing on it. The working build is BepInEx 6 bleeding-edge, Unity.IL2CPP (be.788 is the one the community and ModWright tested).",
|
|
185
|
+
source: "Nexus mod 2 'Nivalis - FIFO' requirements, read 2026-10-03",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "nn-one-loader",
|
|
189
|
+
game: "nivalisnights",
|
|
190
|
+
component: "both-loaders",
|
|
191
|
+
requires: [],
|
|
192
|
+
advisory: true,
|
|
193
|
+
severity: "warn",
|
|
194
|
+
note: "BepInEx and MelonLoader are both installed. Mods for this game ship separate builds per loader and say to choose only one; what breaks when both are present has not been observed.",
|
|
195
|
+
source: "Nexus mod 3 'Nivalis Nights Trainer' install notes, read 2026-10-03",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "nn-interop-missing",
|
|
199
|
+
game: "nivalisnights",
|
|
200
|
+
component: "bepinex-interop-missing",
|
|
201
|
+
requires: [],
|
|
202
|
+
advisory: true,
|
|
203
|
+
severity: "info",
|
|
204
|
+
note: "BepInEx IL2CPP is installed but has not run yet. The first launch downloads Unity base libraries (network needed) and generates interop, about 25 s at a black screen; close the game at the main menu, then install plugins.",
|
|
205
|
+
source: "observed in game 2026-10-03",
|
|
206
|
+
verifiedOn: "25680465",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: "nn-interop-stale",
|
|
210
|
+
game: "nivalisnights",
|
|
211
|
+
component: "bepinex-interop-stale",
|
|
212
|
+
requires: [],
|
|
213
|
+
advisory: true,
|
|
214
|
+
severity: "info",
|
|
215
|
+
note: "The game updated since BepInEx last generated interop. The next launch regenerates it automatically; plugins compiled against the previous build may then fail to load or patch.",
|
|
216
|
+
source: "Il2CppInteropManager.cs at BepInEx be.788 ('Detected outdated interop assemblies')",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "nn-game-updates-often",
|
|
220
|
+
game: "nivalisnights",
|
|
221
|
+
component: "game",
|
|
222
|
+
requires: [],
|
|
223
|
+
advisory: true,
|
|
224
|
+
severity: "info",
|
|
225
|
+
note: "The game patches often: two updates in its first four days, one of which made a mod obsolete by fixing the bug it addressed. After an update, check each plugin's page for a matching build before blaming load errors on anything else.",
|
|
226
|
+
source: "ModWright research",
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
export const nivalisCompat = { detectComponents, rules: RULES };
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { existsSync, promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { defaultCacheDir, indexFile, IndexStore } from "../../../core/knowledge/store.js";
|
|
5
|
+
import { assertAbsolute, locateTool, runTool, toNativePath, TOOL_SPECS } from "../../../core/toolchain/index.js";
|
|
6
|
+
import { readSteamBuildId } from "../compat.js";
|
|
7
|
+
export const GAME = "nivalisnights";
|
|
8
|
+
export const DEFAULT_FOLDERS = [
|
|
9
|
+
"itemtypes",
|
|
10
|
+
"recipes",
|
|
11
|
+
"vendors",
|
|
12
|
+
"locations",
|
|
13
|
+
"venues",
|
|
14
|
+
"objecttags",
|
|
15
|
+
"apartments",
|
|
16
|
+
"greenhouses",
|
|
17
|
+
"processors",
|
|
18
|
+
"achievementseries",
|
|
19
|
+
"variables",
|
|
20
|
+
"actions",
|
|
21
|
+
"worldobjs",
|
|
22
|
+
"sosingletons",
|
|
23
|
+
"peopledatabase",
|
|
24
|
+
];
|
|
25
|
+
export const FOLLOW_FOLDERS = ["peopledatabase"];
|
|
26
|
+
export const FOLDER_ALIASES = { "peopledatabase.refs": "people", "peopledatabase.refs2": "people-links" };
|
|
27
|
+
export const SKIPPED_FOLDERS = {
|
|
28
|
+
quests: "SerializeReference graphs (onStart/onComplete GameActions, SubQuests): the type-tree generator runs away on them (past 12 GB when tried)",
|
|
29
|
+
skills: "SerializeReference level data: the type-tree generator fails ('Failed to get ref type node')",
|
|
30
|
+
ghosts: "SerializeReference fields: the type-tree generator fails ('Failed to get ref type node')",
|
|
31
|
+
backgroundnpcconversations: "SerializeReference dialogue graphs: decoding exhausted memory when tried",
|
|
32
|
+
newsarticles: "a consistent 44-byte layout mismatch between the generated type tree and the data",
|
|
33
|
+
};
|
|
34
|
+
export function createSchema(store) {
|
|
35
|
+
store.exec(`
|
|
36
|
+
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
37
|
+
CREATE TABLE IF NOT EXISTS definition (
|
|
38
|
+
id INTEGER PRIMARY KEY,
|
|
39
|
+
key TEXT NOT NULL UNIQUE,
|
|
40
|
+
folder TEXT NOT NULL,
|
|
41
|
+
path TEXT NOT NULL,
|
|
42
|
+
script TEXT NOT NULL,
|
|
43
|
+
name TEXT COLLATE NOCASE,
|
|
44
|
+
guid TEXT COLLATE NOCASE,
|
|
45
|
+
data_json TEXT NOT NULL
|
|
46
|
+
);
|
|
47
|
+
CREATE TABLE IF NOT EXISTS link (
|
|
48
|
+
from_key TEXT NOT NULL,
|
|
49
|
+
field TEXT NOT NULL,
|
|
50
|
+
to_key TEXT NOT NULL
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_def_folder_name ON definition(folder, name);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_def_name ON definition(name);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_def_guid ON definition(guid);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_link_to ON link(to_key);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_link_from ON link(from_key);
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
export function isBuilt(store) {
|
|
60
|
+
const row = store.get("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'definition'");
|
|
61
|
+
return row !== undefined && store.get("SELECT COUNT(*) AS n FROM definition")?.n > 0;
|
|
62
|
+
}
|
|
63
|
+
export function guidOf(data) {
|
|
64
|
+
const g = data.guid;
|
|
65
|
+
if (typeof g === "string" && g.length > 0)
|
|
66
|
+
return g;
|
|
67
|
+
if (g && typeof g === "object" && typeof g.guid === "string" && g.guid.length > 0)
|
|
68
|
+
return g.guid;
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
export function resolveRefs(value, targets, field, out) {
|
|
72
|
+
if (Array.isArray(value))
|
|
73
|
+
return value.map((v, i) => resolveRefs(v, targets, `${field}[${i}]`, out));
|
|
74
|
+
if (value && typeof value === "object") {
|
|
75
|
+
const obj = value;
|
|
76
|
+
if (typeof obj.$ref === "string" && Object.keys(obj).length === 1) {
|
|
77
|
+
out.push({ field, key: obj.$ref });
|
|
78
|
+
const t = targets.get(obj.$ref);
|
|
79
|
+
return t ? { $ref: obj.$ref, folder: t.folder, ...(t.name !== undefined ? { name: t.name } : {}), ...(t.guid !== undefined ? { guid: t.guid } : {}) } : obj;
|
|
80
|
+
}
|
|
81
|
+
const result = {};
|
|
82
|
+
for (const [k, v] of Object.entries(obj))
|
|
83
|
+
result[k] = resolveRefs(v, targets, field ? `${field}.${k}` : k, out);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
export async function importExtraction(store, outDir) {
|
|
89
|
+
createSchema(store);
|
|
90
|
+
store.exec("DELETE FROM definition; DELETE FROM link;");
|
|
91
|
+
const summary = JSON.parse(await fs.readFile(path.join(outDir, "summary.json"), "utf8"));
|
|
92
|
+
const records = [];
|
|
93
|
+
const byKey = new Map();
|
|
94
|
+
const otherPaths = new Map();
|
|
95
|
+
let badLines = 0;
|
|
96
|
+
for (const folder of Object.keys(summary.folders)) {
|
|
97
|
+
const file = path.join(outDir, `${folder}.jsonl`);
|
|
98
|
+
if (!existsSync(file))
|
|
99
|
+
continue;
|
|
100
|
+
for (const line of (await fs.readFile(file, "utf8")).split(/\r?\n/)) {
|
|
101
|
+
if (!line.trim())
|
|
102
|
+
continue;
|
|
103
|
+
let r;
|
|
104
|
+
try {
|
|
105
|
+
r = JSON.parse(line);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
badLines++;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (byKey.has(r.key)) {
|
|
112
|
+
otherPaths.set(r.key, [...(otherPaths.get(r.key) ?? []), r.path]);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const rec = { ...r, folder: FOLDER_ALIASES[r.folder] ?? r.folder };
|
|
116
|
+
byKey.set(r.key, rec);
|
|
117
|
+
records.push(rec);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const [key, paths] of otherPaths) {
|
|
121
|
+
const rec = byKey.get(key);
|
|
122
|
+
rec.data = { ...rec.data, $otherResourcePaths: paths };
|
|
123
|
+
}
|
|
124
|
+
const targets = new Map();
|
|
125
|
+
for (const r of records) {
|
|
126
|
+
const name = typeof r.data.m_Name === "string" && r.data.m_Name.length > 0 ? r.data.m_Name : undefined;
|
|
127
|
+
const guid = guidOf(r.data);
|
|
128
|
+
targets.set(r.key, { folder: r.folder, ...(name !== undefined ? { name } : {}), ...(guid !== undefined ? { guid } : {}) });
|
|
129
|
+
}
|
|
130
|
+
const defs = [];
|
|
131
|
+
const links = [];
|
|
132
|
+
const perFolder = {};
|
|
133
|
+
let resolved = 0;
|
|
134
|
+
for (const r of records) {
|
|
135
|
+
const refs = [];
|
|
136
|
+
const data = resolveRefs(r.data, targets, "", refs);
|
|
137
|
+
const t = targets.get(r.key);
|
|
138
|
+
defs.push([r.key, r.folder, r.path, r.script, t.name ?? null, t.guid ?? null, JSON.stringify(data)]);
|
|
139
|
+
for (const ref of refs) {
|
|
140
|
+
links.push([r.key, ref.field, ref.key]);
|
|
141
|
+
if (targets.has(ref.key))
|
|
142
|
+
resolved++;
|
|
143
|
+
}
|
|
144
|
+
perFolder[r.folder] = (perFolder[r.folder] ?? 0) + 1;
|
|
145
|
+
}
|
|
146
|
+
if (defs.length > 0)
|
|
147
|
+
store.insertMany("definition", ["key", "folder", "path", "script", "name", "guid", "data_json"], defs);
|
|
148
|
+
if (links.length > 0)
|
|
149
|
+
store.insertMany("link", ["from_key", "field", "to_key"], links);
|
|
150
|
+
store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('extraction', ?)", [JSON.stringify(summary)]);
|
|
151
|
+
return { definitions: defs.length, links: links.length, resolvedLinks: resolved, folders: perFolder, badLines, duplicatePaths: [...otherPaths.values()].reduce((n, p) => n + p.length, 0) };
|
|
152
|
+
}
|
|
153
|
+
function findPackageRoot(startDir) {
|
|
154
|
+
let dir = startDir;
|
|
155
|
+
for (;;) {
|
|
156
|
+
if (existsSync(path.join(dir, "package.json")))
|
|
157
|
+
return dir;
|
|
158
|
+
const parent = path.dirname(dir);
|
|
159
|
+
if (parent === dir)
|
|
160
|
+
throw new Error(`nivalisnights index: could not find package.json above ${startDir}`);
|
|
161
|
+
dir = parent;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export function extractorScript() {
|
|
165
|
+
return path.join(findPackageRoot(path.dirname(fileURLToPath(import.meta.url))), "assets", "nivalisnights", "extract_resources.py");
|
|
166
|
+
}
|
|
167
|
+
async function locate(toolId, options) {
|
|
168
|
+
if (options.locate)
|
|
169
|
+
return options.locate(toolId);
|
|
170
|
+
const spec = TOOL_SPECS.find((s) => s.id === toolId);
|
|
171
|
+
if (!spec)
|
|
172
|
+
throw new Error(`No toolchain spec registered for '${toolId}'.`);
|
|
173
|
+
return locateTool(spec, { projectOverrides: options.toolOverrides });
|
|
174
|
+
}
|
|
175
|
+
function summarize(store, sources) {
|
|
176
|
+
const builtAt = store.get("SELECT value FROM meta WHERE key = 'builtAt'")?.value;
|
|
177
|
+
const gameVersion = store.get("SELECT value FROM meta WHERE key = 'gameVersion'")?.value;
|
|
178
|
+
return {
|
|
179
|
+
game: GAME,
|
|
180
|
+
file: indexFile(defaultCacheDir(), GAME),
|
|
181
|
+
exists: true,
|
|
182
|
+
...(builtAt !== undefined ? { builtAt: String(builtAt) } : {}),
|
|
183
|
+
...(gameVersion !== undefined ? { gameVersion: String(gameVersion) } : {}),
|
|
184
|
+
tables: store.tableCounts(),
|
|
185
|
+
sources,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
export async function buildIndex(install, store, options = {}) {
|
|
189
|
+
const sources = [];
|
|
190
|
+
createSchema(store);
|
|
191
|
+
if (isBuilt(store) && !options.force) {
|
|
192
|
+
sources.push("(Nivalis Nights index already built — pass { force: true } to rebuild)");
|
|
193
|
+
return summarize(store, sources);
|
|
194
|
+
}
|
|
195
|
+
const root = path.resolve(install.root);
|
|
196
|
+
const data = path.join(root, "Nivalis Nights_Data");
|
|
197
|
+
if (!existsSync(path.join(data, "globalgamemanagers"))) {
|
|
198
|
+
throw new Error(`Cannot build the Nivalis Nights index: ${data} has no globalgamemanagers, so ${root} is not a Nivalis Nights install.`);
|
|
199
|
+
}
|
|
200
|
+
const buildId = (await readSteamBuildId(root)) ?? "unknown";
|
|
201
|
+
const work = path.resolve(options.workDir ?? path.join(defaultCacheDir(), GAME, `work-${buildId}`));
|
|
202
|
+
const dlls = path.join(work, "dummydll");
|
|
203
|
+
const out = path.join(work, "resources");
|
|
204
|
+
assertAbsolute([root, work, dlls, out]);
|
|
205
|
+
await fs.mkdir(work, { recursive: true });
|
|
206
|
+
const requested = options.only && options.only.length > 0 ? options.only.map((f) => f.toLowerCase()) : [...DEFAULT_FOLDERS];
|
|
207
|
+
const skippedAsked = requested.filter((f) => f in SKIPPED_FOLDERS);
|
|
208
|
+
const folders = requested.filter((f) => !(f in SKIPPED_FOLDERS));
|
|
209
|
+
for (const f of skippedAsked)
|
|
210
|
+
sources.push(`${f}: not decoded — ${SKIPPED_FOLDERS[f]}.`);
|
|
211
|
+
if (folders.length === 0)
|
|
212
|
+
throw new Error("Cannot build the Nivalis Nights index: every folder asked for is one the decoder skips (see the reasons above).");
|
|
213
|
+
if (!existsSync(path.join(dlls, "Assembly-CSharp.dll"))) {
|
|
214
|
+
const cpp2il = await locate("cpp2il", options);
|
|
215
|
+
if (!cpp2il.found || !cpp2il.path) {
|
|
216
|
+
throw new Error(`Cannot build the Nivalis Nights index: ${cpp2il.hint ?? "Cpp2IL was not found"}. Install Cpp2IL (pre-release.21 is tested), then point ModWright at it once: check_toolchain action=remember tool=cpp2il toolPath=<path to Cpp2IL.exe> mode=apply.`);
|
|
217
|
+
}
|
|
218
|
+
options.progress?.({ step: "cpp2il", note: `dummy assemblies for build ${buildId}` });
|
|
219
|
+
const result = await runTool(cpp2il, {
|
|
220
|
+
args: ["--game-path", toNativePath(root), "--exe-name", "Nivalis Nights", "--use-processor", "attributeanalyzer,attributeinjector", "--output-as", "dummydll", "--output-to", toNativePath(dlls)],
|
|
221
|
+
cwd: work,
|
|
222
|
+
timeoutMs: 15 * 60_000,
|
|
223
|
+
expectOutputs: [path.join(dlls, "Assembly-CSharp.dll")],
|
|
224
|
+
});
|
|
225
|
+
if (!result.ok)
|
|
226
|
+
throw new Error(`Cpp2IL failed: ${result.error?.message ?? `exit ${result.exitCode}`}\n${result.stderr.slice(-2000)}`);
|
|
227
|
+
sources.push(`Cpp2IL dummy assemblies for build ${buildId} (${toNativePath(dlls)})`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
sources.push(`reused Cpp2IL dummy assemblies for build ${buildId} (${toNativePath(dlls)})`);
|
|
231
|
+
}
|
|
232
|
+
const python = await locate("unitypy-python", options);
|
|
233
|
+
if (!python.found || !python.path) {
|
|
234
|
+
throw new Error(`Cannot build the Nivalis Nights index: ${python.hint ?? "no Python was found"}. Install Python 3 with \`pip install UnityPy TypeTreeGeneratorAPI\` (a venv is cleanest), then: check_toolchain action=remember tool=unitypy-python toolPath=<that python.exe> mode=apply.`);
|
|
235
|
+
}
|
|
236
|
+
const script = extractorScript();
|
|
237
|
+
const follow = FOLLOW_FOLDERS.filter((f) => folders.includes(f));
|
|
238
|
+
options.progress?.({ step: "decode", note: folders.join(", ") });
|
|
239
|
+
const decoded = await runTool(python, {
|
|
240
|
+
args: [toNativePath(script), "--data", toNativePath(data), "--dlls", toNativePath(dlls), "--out", toNativePath(out), "--folders", folders.join(","), ...(follow.length > 0 ? ["--follow", follow.join(",")] : [])],
|
|
241
|
+
cwd: work,
|
|
242
|
+
timeoutMs: 30 * 60_000,
|
|
243
|
+
expectOutputs: [path.join(out, "summary.json")],
|
|
244
|
+
requireFreshOutputs: true,
|
|
245
|
+
maxCaptureBytes: 4_000_000,
|
|
246
|
+
});
|
|
247
|
+
if (!decoded.ok) {
|
|
248
|
+
const missingModule = /No module named '?(UnityPy|TypeTreeGeneratorAPI)/.exec(decoded.stderr);
|
|
249
|
+
const hint = missingModule ? ` The Python at ${python.path} lacks ${missingModule[1]}: \`pip install UnityPy TypeTreeGeneratorAPI\` there, or remember one that has them (check_toolchain action=remember tool=unitypy-python).` : "";
|
|
250
|
+
throw new Error(`The resource decoder failed: ${decoded.error?.message ?? `exit ${decoded.exitCode}`}.${hint}\n${decoded.stderr.slice(-2000)}`);
|
|
251
|
+
}
|
|
252
|
+
options.progress?.({ step: "import" });
|
|
253
|
+
const imported = await importExtraction(store, out);
|
|
254
|
+
const extraction = JSON.parse(await fs.readFile(path.join(out, "summary.json"), "utf8"));
|
|
255
|
+
for (const [folder, s] of Object.entries(extraction.folders)) {
|
|
256
|
+
const errors = Object.entries(s.errors ?? {});
|
|
257
|
+
const shown = FOLDER_ALIASES[folder] ?? folder;
|
|
258
|
+
sources.push(`${shown}: ${s.decoded}/${s.entries} decoded${errors.length > 0 ? `; ${errors.reduce((n, [, c]) => n + c, 0)} failed (${errors.slice(0, 2).map(([m]) => m).join("; ")})` : ""}`);
|
|
259
|
+
}
|
|
260
|
+
for (const f of Object.keys(SKIPPED_FOLDERS))
|
|
261
|
+
if (!skippedAsked.includes(f))
|
|
262
|
+
sources.push(`${f}: not decoded — ${SKIPPED_FOLDERS[f]}.`);
|
|
263
|
+
if (imported.duplicatePaths > 0)
|
|
264
|
+
sources.push(`${imported.duplicatePaths} Resources path(s) named an object already indexed under another path; kept as $otherResourcePaths on that definition.`);
|
|
265
|
+
if (imported.badLines > 0)
|
|
266
|
+
sources.push(`${imported.badLines} decoder output line(s) were not valid JSON and were skipped.`);
|
|
267
|
+
sources.push(`${imported.definitions} definitions, ${imported.links} references (${imported.resolvedLinks} to indexed definitions).`);
|
|
268
|
+
store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('builtAt', ?)", [new Date().toISOString()]);
|
|
269
|
+
store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('gameVersion', ?)", [buildId]);
|
|
270
|
+
store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('unity', ?)", [extraction.unity ?? "unknown"]);
|
|
271
|
+
store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('skipped', ?)", [JSON.stringify(SKIPPED_FOLDERS)]);
|
|
272
|
+
return summarize(store, sources);
|
|
273
|
+
}
|
|
274
|
+
export async function info(install) {
|
|
275
|
+
const file = indexFile(defaultCacheDir(), GAME);
|
|
276
|
+
const result = { game: GAME, file, exists: false };
|
|
277
|
+
let stat;
|
|
278
|
+
try {
|
|
279
|
+
stat = await fs.stat(file);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
result.exists = true;
|
|
285
|
+
result.sizeBytes = stat.size;
|
|
286
|
+
let store;
|
|
287
|
+
try {
|
|
288
|
+
store = await IndexStore.open(file);
|
|
289
|
+
const builtAt = store.get("SELECT value FROM meta WHERE key = 'builtAt'")?.value;
|
|
290
|
+
const gameVersion = store.get("SELECT value FROM meta WHERE key = 'gameVersion'")?.value;
|
|
291
|
+
if (builtAt !== undefined)
|
|
292
|
+
result.builtAt = String(builtAt);
|
|
293
|
+
if (gameVersion !== undefined)
|
|
294
|
+
result.gameVersion = String(gameVersion);
|
|
295
|
+
result.tables = store.tableCounts();
|
|
296
|
+
const rows = store.all("SELECT folder, COUNT(*) AS n FROM definition GROUP BY folder ORDER BY folder");
|
|
297
|
+
if (rows.length > 0)
|
|
298
|
+
result.sources = rows.map((r) => `${r.folder}: ${r.n} definitions`);
|
|
299
|
+
const current = await readSteamBuildId(path.resolve(install.root));
|
|
300
|
+
if (current && gameVersion !== undefined && String(gameVersion) !== current) {
|
|
301
|
+
result.stale = `built from Steam build ${String(gameVersion)}; the install is now on build ${current}. Rebuild with force to pick up the update.`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
store?.close();
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { buildIndex, info } from "./build.js";
|
|
2
|
+
import { query, queryKinds } from "./queries.js";
|
|
3
|
+
export const nivalisIndex = { info, build: buildIndex, queryKinds, query };
|
|
4
|
+
export { buildIndex, info, importExtraction, resolveRefs, guidOf, createSchema, isBuilt, extractorScript, DEFAULT_FOLDERS, SKIPPED_FOLDERS, FOLLOW_FOLDERS } from "./build.js";
|
|
5
|
+
export { query, queryKinds } from "./queries.js";
|