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,504 @@
|
|
|
1
|
+
import { overlayFlats } from "./model.js";
|
|
2
|
+
const DEFAULT_LIMIT = 50;
|
|
3
|
+
const MAX_CHAIN_DEPTH = 25;
|
|
4
|
+
const MAX_CHILDREN_DEPTH = 25;
|
|
5
|
+
function str(v) {
|
|
6
|
+
if (v === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
const s = String(v).trim();
|
|
9
|
+
return s.length > 0 ? s : undefined;
|
|
10
|
+
}
|
|
11
|
+
function limitOf(params) {
|
|
12
|
+
const n = Number(params.limit);
|
|
13
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_LIMIT;
|
|
14
|
+
}
|
|
15
|
+
function truthy(v) {
|
|
16
|
+
const s = str(v);
|
|
17
|
+
return s === "1" || s?.toLowerCase() === "true";
|
|
18
|
+
}
|
|
19
|
+
function page(rows, limit) {
|
|
20
|
+
if (rows.length > limit)
|
|
21
|
+
return { rows: rows.slice(0, limit), truncated: true };
|
|
22
|
+
return { rows, truncated: false };
|
|
23
|
+
}
|
|
24
|
+
function result(kind, params, rows, truncated, note) {
|
|
25
|
+
return { kind, params, count: rows.length, truncated, rows, note };
|
|
26
|
+
}
|
|
27
|
+
function parseValueJson(raw) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(String(raw));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return raw;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function rawOccurrencesFor(store, name) {
|
|
36
|
+
const rows = store.all(`SELECT id, base, type, source_id, file, line FROM record WHERE name = ? ORDER BY id`, [name]);
|
|
37
|
+
return rows.map((r) => {
|
|
38
|
+
const flatRows = store.all(`SELECT name, kind, value_json, op FROM flat WHERE record_id = ? ORDER BY id`, [r.id ?? null]);
|
|
39
|
+
return {
|
|
40
|
+
id: Number(r.id),
|
|
41
|
+
file: String(r.file),
|
|
42
|
+
line: Number(r.line),
|
|
43
|
+
base: r.base === null || r.base === undefined ? null : String(r.base),
|
|
44
|
+
type: r.type === null || r.type === undefined ? null : String(r.type),
|
|
45
|
+
sourceId: r.source_id,
|
|
46
|
+
flats: flatRows.map((f) => ({
|
|
47
|
+
name: String(f.name),
|
|
48
|
+
kind: String(f.kind),
|
|
49
|
+
value: parseValueJson(f.value_json),
|
|
50
|
+
op: f.op,
|
|
51
|
+
})),
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function latestPointer(store, name, column) {
|
|
56
|
+
const row = store.get(`SELECT ${column} AS v FROM record WHERE name = ? AND ${column} IS NOT NULL ORDER BY id DESC LIMIT 1`, [
|
|
57
|
+
name,
|
|
58
|
+
]);
|
|
59
|
+
return row?.v === undefined || row.v === null ? undefined : String(row.v);
|
|
60
|
+
}
|
|
61
|
+
function effectiveOwnFlats(name, occs) {
|
|
62
|
+
return overlayFlats(occs.map((o) => ({ recordLabel: name, flats: o.flats })));
|
|
63
|
+
}
|
|
64
|
+
function queryRecord(store, params) {
|
|
65
|
+
const name = str(params.name);
|
|
66
|
+
const prefix = str(params.prefix);
|
|
67
|
+
if (!name && !prefix) {
|
|
68
|
+
return result("record", params, [], false, "No 'name' or 'prefix' given; nothing searched.");
|
|
69
|
+
}
|
|
70
|
+
const limit = limitOf(params);
|
|
71
|
+
let names;
|
|
72
|
+
if (name) {
|
|
73
|
+
names = store.get(`SELECT 1 FROM record WHERE name = ? LIMIT 1`, [name]) ? [name] : [];
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const rows = store.all(`SELECT DISTINCT name FROM record WHERE name LIKE ? ORDER BY name LIMIT ?`, [
|
|
77
|
+
`${prefix}%`,
|
|
78
|
+
limit + 1,
|
|
79
|
+
]);
|
|
80
|
+
names = rows.map((r) => String(r.name));
|
|
81
|
+
}
|
|
82
|
+
const { rows: pageNames, truncated } = page(names, limit);
|
|
83
|
+
const mapped = pageNames.map((n) => {
|
|
84
|
+
const occs = rawOccurrencesFor(store, n);
|
|
85
|
+
const effective = effectiveOwnFlats(n, occs);
|
|
86
|
+
return {
|
|
87
|
+
name: n,
|
|
88
|
+
base: latestPointer(store, n, "base") ?? null,
|
|
89
|
+
type: latestPointer(store, n, "type") ?? null,
|
|
90
|
+
occurrences: occs.map((o) => ({ file: o.file, line: o.line })),
|
|
91
|
+
flats: [...effective.values()].map((f) => ({ name: f.name, value: f.value, kind: f.kind })),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
const how = name ? `exact name '${name}'` : `name prefix '${prefix}'`;
|
|
95
|
+
const note = `Searched TweakDB records by ${how}; each record's flats are merged across every occurrence that touched it.`;
|
|
96
|
+
return result("record", params, mapped, truncated, note);
|
|
97
|
+
}
|
|
98
|
+
function queryChain(store, params) {
|
|
99
|
+
const name = str(params.name);
|
|
100
|
+
if (!name)
|
|
101
|
+
return result("chain", params, [], false, "No 'name' given; nothing searched.");
|
|
102
|
+
if (!store.get(`SELECT 1 FROM record WHERE name = ? LIMIT 1`, [name])) {
|
|
103
|
+
return result("chain", params, [], false, `No record named '${name}' found.`);
|
|
104
|
+
}
|
|
105
|
+
const ancestors = [];
|
|
106
|
+
const seen = new Set([name]);
|
|
107
|
+
let current = latestPointer(store, name, "base");
|
|
108
|
+
let hitCap = false;
|
|
109
|
+
while (current && !seen.has(current)) {
|
|
110
|
+
ancestors.push(current);
|
|
111
|
+
seen.add(current);
|
|
112
|
+
if (ancestors.length >= MAX_CHAIN_DEPTH) {
|
|
113
|
+
hitCap = latestPointer(store, current, "base") !== undefined;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
current = latestPointer(store, current, "base");
|
|
117
|
+
}
|
|
118
|
+
const cyclic = current !== undefined && seen.has(current) && !hitCap && ancestors[ancestors.length - 1] !== current;
|
|
119
|
+
const rootToLeaf = [...ancestors].reverse();
|
|
120
|
+
rootToLeaf.push(name);
|
|
121
|
+
const rawOccs = rootToLeaf.flatMap((n) => rawOccurrencesFor(store, n).map((o) => ({ recordLabel: n, flats: o.flats })));
|
|
122
|
+
const effective = overlayFlats(rawOccs);
|
|
123
|
+
const type = [name, ...ancestors].map((n) => latestPointer(store, n, "type")).find((t) => t !== undefined);
|
|
124
|
+
const flats = [...effective.values()]
|
|
125
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
126
|
+
.map((f) => ({ name: f.name, value: f.value, kind: f.kind, from: f.from, contributors: f.contributors }));
|
|
127
|
+
const row = {
|
|
128
|
+
name,
|
|
129
|
+
base: latestPointer(store, name, "base") ?? null,
|
|
130
|
+
type: type ?? null,
|
|
131
|
+
chain: ancestors.map((n) => ({ name: n, type: latestPointer(store, n, "type") ?? null })),
|
|
132
|
+
flats,
|
|
133
|
+
};
|
|
134
|
+
const notes = [`Resolved the $base chain for '${name}' (${ancestors.length} ancestor(s)).`];
|
|
135
|
+
if (hitCap)
|
|
136
|
+
notes.push(`Chain walk stopped at ${MAX_CHAIN_DEPTH} ancestors; the true root may be further up.`);
|
|
137
|
+
if (cyclic)
|
|
138
|
+
notes.push(`A cycle was detected in the $base chain and the walk stopped early.`);
|
|
139
|
+
return result("chain", params, [row], false, notes.join(" "));
|
|
140
|
+
}
|
|
141
|
+
function queryFlat(store, params) {
|
|
142
|
+
const name = str(params.name);
|
|
143
|
+
if (!name)
|
|
144
|
+
return result("flat", params, [], false, "No flat 'name' given; nothing searched.");
|
|
145
|
+
const valueFilterRaw = str(params.value);
|
|
146
|
+
const limit = limitOf(params);
|
|
147
|
+
const candidateRows = store.all(`SELECT DISTINCT r.name AS name FROM flat f JOIN record r ON r.id = f.record_id WHERE f.name = ? ORDER BY r.name`, [
|
|
148
|
+
name,
|
|
149
|
+
]);
|
|
150
|
+
const totalCandidates = candidateRows.length;
|
|
151
|
+
let valueFilter;
|
|
152
|
+
let hasValueFilter = false;
|
|
153
|
+
if (valueFilterRaw !== undefined) {
|
|
154
|
+
hasValueFilter = true;
|
|
155
|
+
try {
|
|
156
|
+
valueFilter = JSON.parse(valueFilterRaw);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
valueFilter = valueFilterRaw;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const matched = [];
|
|
163
|
+
for (const row of candidateRows) {
|
|
164
|
+
if (matched.length > limit)
|
|
165
|
+
break;
|
|
166
|
+
const recName = String(row.name);
|
|
167
|
+
const occs = rawOccurrencesFor(store, recName);
|
|
168
|
+
const effective = effectiveOwnFlats(recName, occs);
|
|
169
|
+
const flat = effective.get(name);
|
|
170
|
+
if (!flat)
|
|
171
|
+
continue;
|
|
172
|
+
if (hasValueFilter && JSON.stringify(flat.value) !== JSON.stringify(valueFilter))
|
|
173
|
+
continue;
|
|
174
|
+
matched.push({ record: recName, value: flat.value, kind: flat.kind, from: flat.from, contributors: flat.contributors });
|
|
175
|
+
}
|
|
176
|
+
const { rows: pageRows, truncated } = page(matched, limit);
|
|
177
|
+
const note = hasValueFilter
|
|
178
|
+
? `Records whose flat '${name}' equals the given value (${totalCandidates} record(s) declare '${name}' at all).`
|
|
179
|
+
: `Records that declare flat '${name}' (${totalCandidates} total).`;
|
|
180
|
+
return result("flat", params, pageRows, truncated, note);
|
|
181
|
+
}
|
|
182
|
+
function queryReverse(store, params) {
|
|
183
|
+
const token = str(params.token);
|
|
184
|
+
if (!token)
|
|
185
|
+
return result("reverse", params, [], false, "No 'token' given; nothing searched.");
|
|
186
|
+
const flatName = str(params.flat);
|
|
187
|
+
const limit = limitOf(params);
|
|
188
|
+
let sql = `SELECT r.name AS record, f.name AS flat, f.value_json AS value_json, f.kind AS kind, f.op AS op, r.file AS file, r.line AS line
|
|
189
|
+
FROM flat f JOIN record r ON r.id = f.record_id
|
|
190
|
+
WHERE f.value_json LIKE ?`;
|
|
191
|
+
const args = [`%${token}%`];
|
|
192
|
+
if (flatName) {
|
|
193
|
+
sql += ` AND f.name = ?`;
|
|
194
|
+
args.push(flatName);
|
|
195
|
+
}
|
|
196
|
+
sql += ` ORDER BY r.name, f.name LIMIT ?`;
|
|
197
|
+
args.push(limit + 1);
|
|
198
|
+
const rows = store.all(sql, args);
|
|
199
|
+
const { rows: pageRows, truncated } = page(rows, limit);
|
|
200
|
+
const mapped = pageRows.map((row) => ({
|
|
201
|
+
record: row.record,
|
|
202
|
+
flat: row.flat,
|
|
203
|
+
value: parseValueJson(row.value_json),
|
|
204
|
+
kind: row.kind,
|
|
205
|
+
op: row.op,
|
|
206
|
+
file: row.file,
|
|
207
|
+
line: row.line,
|
|
208
|
+
}));
|
|
209
|
+
const note = `Records whose flat value contains '${token}'${flatName ? ` (restricted to flat '${flatName}')` : ""}, via substring LIKE on the indexed value_json column.`;
|
|
210
|
+
return result("reverse", params, mapped, truncated, note);
|
|
211
|
+
}
|
|
212
|
+
function queryChildren(store, params) {
|
|
213
|
+
const base = str(params.base);
|
|
214
|
+
if (!base)
|
|
215
|
+
return result("children", params, [], false, "No 'base' given; nothing searched.");
|
|
216
|
+
const directOnly = truthy(params.direct);
|
|
217
|
+
const limit = limitOf(params);
|
|
218
|
+
const out = [];
|
|
219
|
+
const visited = new Set([base]);
|
|
220
|
+
let frontier = [base];
|
|
221
|
+
let depth = 0;
|
|
222
|
+
let hitDepthCap = false;
|
|
223
|
+
while (frontier.length > 0 && out.length <= limit && depth < MAX_CHILDREN_DEPTH) {
|
|
224
|
+
depth++;
|
|
225
|
+
const nextFrontier = [];
|
|
226
|
+
for (const parent of frontier) {
|
|
227
|
+
const rows = store.all(`SELECT DISTINCT name FROM record WHERE base = ? ORDER BY name`, [parent]);
|
|
228
|
+
for (const row of rows) {
|
|
229
|
+
const childName = String(row.name);
|
|
230
|
+
if (visited.has(childName))
|
|
231
|
+
continue;
|
|
232
|
+
visited.add(childName);
|
|
233
|
+
out.push({ name: childName, base: parent, depth });
|
|
234
|
+
nextFrontier.push(childName);
|
|
235
|
+
if (out.length > limit)
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
if (out.length > limit)
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
if (!directOnly)
|
|
242
|
+
frontier = nextFrontier;
|
|
243
|
+
else
|
|
244
|
+
break;
|
|
245
|
+
if (frontier.length > 0 && depth >= MAX_CHILDREN_DEPTH)
|
|
246
|
+
hitDepthCap = true;
|
|
247
|
+
}
|
|
248
|
+
const { rows: pageRows, truncated } = page(out, limit);
|
|
249
|
+
const notes = [
|
|
250
|
+
`${directOnly ? "Direct" : "Direct and transitive"} derivations of '${base}' (records whose $base chain passes through it).`,
|
|
251
|
+
];
|
|
252
|
+
if (hitDepthCap)
|
|
253
|
+
notes.push(`Depth capped at ${MAX_CHILDREN_DEPTH} levels.`);
|
|
254
|
+
return result("children", params, pageRows, truncated, notes.join(" "));
|
|
255
|
+
}
|
|
256
|
+
function queryType(store, params) {
|
|
257
|
+
const type = str(params.type);
|
|
258
|
+
if (!type)
|
|
259
|
+
return result("type", params, [], false, "No 'type' given; nothing searched.");
|
|
260
|
+
const limit = limitOf(params);
|
|
261
|
+
const bare = type.replace(/^gamedata/, "").replace(/_Record$/, "");
|
|
262
|
+
const forms = [type, bare, `gamedata${bare}_Record`];
|
|
263
|
+
const rows = store.all(`SELECT COALESCE(package, '(none)') AS package, COUNT(*) AS n
|
|
264
|
+
FROM record WHERE type IN (?, ?, ?)
|
|
265
|
+
GROUP BY package ORDER BY n DESC, package LIMIT ?`, [...forms, limit + 1]);
|
|
266
|
+
const { rows: pageRows, truncated } = page(rows, limit);
|
|
267
|
+
const mapped = pageRows.map((row) => ({ package: row.package, count: Number(row.n) }));
|
|
268
|
+
const total = store.get(`SELECT COUNT(*) AS n FROM record WHERE type IN (?, ?, ?)`, forms)?.n ?? 0;
|
|
269
|
+
const note = `Records of $type '${type}' (also matched as '${bare}' / 'gamedata${bare}_Record'): ${Number(total)} total, broken out by package.`;
|
|
270
|
+
return result("type", params, mapped, truncated, note);
|
|
271
|
+
}
|
|
272
|
+
function queryResource(store, params) {
|
|
273
|
+
const exactPath = str(params.path);
|
|
274
|
+
const prefix = str(params.prefix);
|
|
275
|
+
const ext = str(params.ext);
|
|
276
|
+
if (!exactPath && !prefix) {
|
|
277
|
+
return result("resource", params, [], false, "No 'path' or 'prefix' given; nothing searched.");
|
|
278
|
+
}
|
|
279
|
+
const limit = limitOf(params);
|
|
280
|
+
const extSuffix = ext ? (ext.startsWith(".") ? ext : `.${ext}`) : undefined;
|
|
281
|
+
if (exactPath) {
|
|
282
|
+
let sql = `SELECT path, archive, sizeBytes FROM resource WHERE path = ?`;
|
|
283
|
+
const args = [exactPath];
|
|
284
|
+
if (extSuffix) {
|
|
285
|
+
sql += ` AND path LIKE ?`;
|
|
286
|
+
args.push(`%${extSuffix}`);
|
|
287
|
+
}
|
|
288
|
+
sql += ` ORDER BY archive LIMIT ?`;
|
|
289
|
+
args.push(limit + 1);
|
|
290
|
+
const rows = store.all(sql, args);
|
|
291
|
+
const { rows: pageRows, truncated } = page(rows, limit);
|
|
292
|
+
const mapped = pageRows.map((row) => ({ path: row.path, archive: row.archive, sizeBytes: row.sizeBytes }));
|
|
293
|
+
const note = `Searched the archive resource listing by exact depot path '${exactPath}'${ext ? ` filtered to extension '${ext}'` : ""} ` +
|
|
294
|
+
`(COLLATE NOCASE): one row per archive that lists it; count > 0 means the path exists in at least one archive. ` +
|
|
295
|
+
`'sizeBytes' is always null — 'archive <path> -l' prints no per-entry size (resource.ts's own note) — not "size unknown for this row".`;
|
|
296
|
+
return result("resource", params, mapped, truncated, note);
|
|
297
|
+
}
|
|
298
|
+
let sql = `SELECT path, COUNT(*) AS archiveCount FROM resource WHERE path LIKE ?`;
|
|
299
|
+
const args = [`${prefix}%`];
|
|
300
|
+
if (extSuffix) {
|
|
301
|
+
sql += ` AND path LIKE ?`;
|
|
302
|
+
args.push(`%${extSuffix}`);
|
|
303
|
+
}
|
|
304
|
+
sql += ` GROUP BY path ORDER BY path LIMIT ?`;
|
|
305
|
+
args.push(limit + 1);
|
|
306
|
+
const rows = store.all(sql, args);
|
|
307
|
+
const { rows: pageRows, truncated } = page(rows, limit);
|
|
308
|
+
const mapped = pageRows.map((row) => ({ path: row.path, archiveCount: Number(row.archiveCount) }));
|
|
309
|
+
const note = `Searched the archive resource listing by depot path prefix '${prefix}'${ext ? ` filtered to extension '${ext}'` : ""} (COLLATE NOCASE), deduped by path with the count of archives carrying each.`;
|
|
310
|
+
return result("resource", params, mapped, truncated, note);
|
|
311
|
+
}
|
|
312
|
+
const BLUEPRINT_MAX_DEPTH = 8;
|
|
313
|
+
const INLINE_RECORD_NAME_RE = /_inline\d+$/;
|
|
314
|
+
function renderBlueprintElement(element, pad) {
|
|
315
|
+
const lines = [];
|
|
316
|
+
if (element.slot !== undefined)
|
|
317
|
+
lines.push(`${pad}slot: ${element.slot}`);
|
|
318
|
+
if (element.childElements.length > 0) {
|
|
319
|
+
lines.push(`${pad}childElements:`);
|
|
320
|
+
for (const child of element.childElements) {
|
|
321
|
+
if (typeof child === "string") {
|
|
322
|
+
lines.push(`${pad} - ${child}`);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const sub = renderBlueprintElement(child, `${pad} `);
|
|
326
|
+
if (sub.length === 0) {
|
|
327
|
+
lines.push(`${pad} - {}`);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
lines.push(`${pad} - ${sub[0].trimStart()}`);
|
|
331
|
+
lines.push(...sub.slice(1));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return lines;
|
|
335
|
+
}
|
|
336
|
+
function queryBlueprintRoot(store, params) {
|
|
337
|
+
const name = str(params.name);
|
|
338
|
+
if (!name)
|
|
339
|
+
return result("blueprint-root", params, [], false, "No 'name' given; nothing searched.");
|
|
340
|
+
if (!store.get(`SELECT 1 FROM record WHERE name = ? LIMIT 1`, [name])) {
|
|
341
|
+
return result("blueprint-root", params, [], false, `No record named '${name}' found.`);
|
|
342
|
+
}
|
|
343
|
+
const indentRaw = Number(params.indent ?? 0);
|
|
344
|
+
const indent = Number.isFinite(indentRaw) && indentRaw > 0 ? Math.floor(indentRaw) : 0;
|
|
345
|
+
const prepend = (str(params.prepend) ?? "")
|
|
346
|
+
.split(",")
|
|
347
|
+
.map((s) => s.trim())
|
|
348
|
+
.filter((s) => s.length > 0);
|
|
349
|
+
const chain = queryChain(store, { name });
|
|
350
|
+
const flats = (chain.rows[0]?.flats ?? []);
|
|
351
|
+
const flatValue = (flatName) => flats.find((f) => f.name === flatName)?.value;
|
|
352
|
+
const notes = [];
|
|
353
|
+
const expanding = new Set();
|
|
354
|
+
let hitDepthCap = false;
|
|
355
|
+
function elementOf(value, depth, forceExpand) {
|
|
356
|
+
if (depth > BLUEPRINT_MAX_DEPTH) {
|
|
357
|
+
hitDepthCap = true;
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
if (typeof value === "string") {
|
|
361
|
+
const occs = rawOccurrencesFor(store, value);
|
|
362
|
+
if (occs.length === 0)
|
|
363
|
+
return value;
|
|
364
|
+
const hasIdentity = latestPointer(store, value, "type") !== undefined || latestPointer(store, value, "base") !== undefined;
|
|
365
|
+
const inlineNamed = INLINE_RECORD_NAME_RE.test(value);
|
|
366
|
+
if (hasIdentity && !inlineNamed && !forceExpand)
|
|
367
|
+
return value;
|
|
368
|
+
if (expanding.has(value))
|
|
369
|
+
return value;
|
|
370
|
+
expanding.add(value);
|
|
371
|
+
const own = [...effectiveOwnFlats(value, occs).values()];
|
|
372
|
+
const ownValue = (flatName) => own.find((f) => f.name === flatName)?.value;
|
|
373
|
+
const element = elementFromFields(ownValue("slot"), ownValue("childElements"), depth);
|
|
374
|
+
expanding.delete(value);
|
|
375
|
+
return element;
|
|
376
|
+
}
|
|
377
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
378
|
+
const o = value;
|
|
379
|
+
return elementFromFields(o.slot, o.childElements, depth);
|
|
380
|
+
}
|
|
381
|
+
return undefined;
|
|
382
|
+
}
|
|
383
|
+
function elementFromFields(slot, children, depth) {
|
|
384
|
+
const element = { childElements: [] };
|
|
385
|
+
if (typeof slot === "string")
|
|
386
|
+
element.slot = slot;
|
|
387
|
+
if (Array.isArray(children)) {
|
|
388
|
+
for (const child of children) {
|
|
389
|
+
const resolved = elementOf(child, depth + 1, false);
|
|
390
|
+
if (resolved !== undefined)
|
|
391
|
+
element.childElements.push(resolved);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return element;
|
|
395
|
+
}
|
|
396
|
+
const rootRaw = flatValue("rootElement");
|
|
397
|
+
let root;
|
|
398
|
+
let rootSource;
|
|
399
|
+
if (rootRaw !== undefined) {
|
|
400
|
+
const resolved = elementOf(rootRaw, 0, true);
|
|
401
|
+
if (resolved !== undefined && typeof resolved !== "string") {
|
|
402
|
+
root = resolved;
|
|
403
|
+
rootSource = "rootElement";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (!root) {
|
|
407
|
+
const children = flatValue("childElements");
|
|
408
|
+
const slot = flatValue("slot");
|
|
409
|
+
if (Array.isArray(children) || typeof slot === "string") {
|
|
410
|
+
root = elementFromFields(slot, children, 0);
|
|
411
|
+
rootSource = "record-flats";
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!root || !rootSource) {
|
|
415
|
+
const why = rootRaw === undefined
|
|
416
|
+
? "has no rootElement and no slot/childElements flats along its $base chain"
|
|
417
|
+
: typeof rootRaw === "string"
|
|
418
|
+
? `names its rootElement '${rootRaw}', which this index does not hold, and has no slot/childElements flats along its $base chain to fall back on`
|
|
419
|
+
: "has a rootElement that is neither a record name nor a slot/childElements mapping, and no slot/childElements flats along its $base chain to fall back on";
|
|
420
|
+
return result("blueprint-root", params, [], false, `'${name}' ${why}; nothing to restate.`);
|
|
421
|
+
}
|
|
422
|
+
const present = new Set(root.childElements.filter((c) => typeof c === "string"));
|
|
423
|
+
const prepended = prepend.filter((p) => !present.has(p));
|
|
424
|
+
root.childElements = [...prepended, ...root.childElements];
|
|
425
|
+
const pad = " ".repeat(indent);
|
|
426
|
+
const row = {
|
|
427
|
+
name,
|
|
428
|
+
rootSource,
|
|
429
|
+
slot: root.slot ?? null,
|
|
430
|
+
root,
|
|
431
|
+
prepended,
|
|
432
|
+
rootYaml: renderBlueprintElement(root, pad).join("\n"),
|
|
433
|
+
};
|
|
434
|
+
if (hitDepthCap)
|
|
435
|
+
notes.push(`Stopped expanding inline elements at depth ${BLUEPRINT_MAX_DEPTH}; a deeper branch was dropped.`);
|
|
436
|
+
notes.unshift(`Restated the root element of '${name}' from its ${rootSource === "rootElement" ? "rootElement" : "own slot/childElements flats"} (${root.childElements.length} child element(s), ${prepended.length} prepended).`);
|
|
437
|
+
return result("blueprint-root", params, [row], false, notes.join(" "));
|
|
438
|
+
}
|
|
439
|
+
export function queryKinds() {
|
|
440
|
+
return [
|
|
441
|
+
{
|
|
442
|
+
kind: "record",
|
|
443
|
+
description: "A TweakDB record by exact or prefix name: its $base, $type, and own flats merged across every occurrence that touched it.",
|
|
444
|
+
params: ["name", "prefix", "limit"],
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
kind: "chain",
|
|
448
|
+
description: "The $base inheritance chain for a record up to the root, with effective flats overlaid parent-to-child (append ops concatenate arrays), each flat marked with the record that contributed it.",
|
|
449
|
+
params: ["name"],
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
kind: "flat",
|
|
453
|
+
description: "Records that declare a given flat name, with its effective value; optionally filtered to a specific value.",
|
|
454
|
+
params: ["name", "value", "limit"],
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
kind: "reverse",
|
|
458
|
+
description: "Records whose any flat value contains a token — e.g. who references 'Items.Preset_Ashura_Default' — via substring search, optionally restricted to one flat name.",
|
|
459
|
+
params: ["token", "flat", "limit"],
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
kind: "children",
|
|
463
|
+
description: "Records that derive (directly, or transitively unless 'direct' is set) from a base record via $base.",
|
|
464
|
+
params: ["base", "direct", "limit"],
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
kind: "type",
|
|
468
|
+
description: "Records of a given $type, counted by package.",
|
|
469
|
+
params: ["type", "limit"],
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
kind: "blueprint-root",
|
|
473
|
+
description: "An item blueprint's root element restated for a derived blueprint: the root's slot and every child, named elements by name and inline elements as nested slot/childElements mappings, rendered as YAML lines ('rootYaml', padded by 'indent') with any 'prepend' names put first.",
|
|
474
|
+
params: ["name", "indent", "prepend"],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
kind: "resource",
|
|
478
|
+
description: "Depot paths listed out of the install's .archive files: an exact 'path' answers existence with one row per archive holding it (a path can be listed by more than one archive), a 'prefix' lists matches deduped by path with an archiveCount, optionally filtered by file extension.",
|
|
479
|
+
params: ["path", "prefix", "ext", "limit"],
|
|
480
|
+
},
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
export async function query(store, kind, params) {
|
|
484
|
+
switch (kind) {
|
|
485
|
+
case "record":
|
|
486
|
+
return queryRecord(store, params);
|
|
487
|
+
case "chain":
|
|
488
|
+
return queryChain(store, params);
|
|
489
|
+
case "flat":
|
|
490
|
+
return queryFlat(store, params);
|
|
491
|
+
case "reverse":
|
|
492
|
+
return queryReverse(store, params);
|
|
493
|
+
case "children":
|
|
494
|
+
return queryChildren(store, params);
|
|
495
|
+
case "type":
|
|
496
|
+
return queryType(store, params);
|
|
497
|
+
case "resource":
|
|
498
|
+
return queryResource(store, params);
|
|
499
|
+
case "blueprint-root":
|
|
500
|
+
return queryBlueprintRoot(store, params);
|
|
501
|
+
default:
|
|
502
|
+
throw new Error(`Unknown CP2077 index query kind: '${kind}'. See queryKinds() for the supported set.`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { pathExists } from "../../../core/fsutil.js";
|
|
4
|
+
import { locateTool, runTool, toNativePath, TOOL_SPECS } from "../../../core/toolchain/index.js";
|
|
5
|
+
import { ensureResourceSchema } from "./schema.js";
|
|
6
|
+
export const LISTING_CAPTURE_BYTES = 512 * 1024 * 1024;
|
|
7
|
+
export function isDepotPathLine(line) {
|
|
8
|
+
if (line.length === 0)
|
|
9
|
+
return false;
|
|
10
|
+
if (line.startsWith("[") || line.startsWith("..."))
|
|
11
|
+
return false;
|
|
12
|
+
if (!/[\\/]/.test(line))
|
|
13
|
+
return false;
|
|
14
|
+
return /\.[A-Za-z0-9_]+$/.test(line);
|
|
15
|
+
}
|
|
16
|
+
const ARCHIVE_SUBDIRS = [path.join("archive", "pc", "content"), path.join("archive", "pc", "ep1")];
|
|
17
|
+
async function listArchiveFiles(dir) {
|
|
18
|
+
if (!(await pathExists(dir)))
|
|
19
|
+
return [];
|
|
20
|
+
let entries;
|
|
21
|
+
try {
|
|
22
|
+
entries = await fs.readdir(dir);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
return entries
|
|
28
|
+
.filter((e) => e.toLowerCase().endsWith(".archive"))
|
|
29
|
+
.map((e) => path.resolve(dir, e))
|
|
30
|
+
.sort();
|
|
31
|
+
}
|
|
32
|
+
async function defaultLocate(toolId, toolOverrides) {
|
|
33
|
+
const spec = TOOL_SPECS.find((s) => s.id === toolId);
|
|
34
|
+
if (!spec)
|
|
35
|
+
throw new Error(`No toolchain spec registered for '${toolId}'.`);
|
|
36
|
+
return locateTool(spec, { projectOverrides: toolOverrides });
|
|
37
|
+
}
|
|
38
|
+
export async function buildResourceListing(install, store, options = {}) {
|
|
39
|
+
ensureResourceSchema(store);
|
|
40
|
+
const progress = options.progress;
|
|
41
|
+
const notes = [];
|
|
42
|
+
const archivePaths = [];
|
|
43
|
+
const foldersScanned = [];
|
|
44
|
+
for (const sub of ARCHIVE_SUBDIRS) {
|
|
45
|
+
const dir = path.join(install.root, sub);
|
|
46
|
+
const found = await listArchiveFiles(dir);
|
|
47
|
+
foldersScanned.push({ dir, exists: await pathExists(dir), archives: found.length });
|
|
48
|
+
archivePaths.push(...found);
|
|
49
|
+
}
|
|
50
|
+
store.run("INSERT OR REPLACE INTO meta (key, value) VALUES ('resourceFoldersScanned', ?)", [JSON.stringify(foldersScanned)]);
|
|
51
|
+
const resourceCountNow = () => Number(store.get(`SELECT COUNT(*) AS n FROM resource`)?.n ?? 0);
|
|
52
|
+
if (archivePaths.length === 0) {
|
|
53
|
+
notes.push(`${ARCHIVE_SUBDIRS.map((s) => path.join(install.root, s)).join(", ")}: no .archive files found — ` +
|
|
54
|
+
"resource paths cannot be listed without at least one.");
|
|
55
|
+
return { archivesFound: 0, archivesListed: 0, archivesUnchanged: 0, resourceCount: resourceCountNow(), notes };
|
|
56
|
+
}
|
|
57
|
+
const locate = options.resourceToolLocator ?? ((id) => defaultLocate(id, options.toolOverrides));
|
|
58
|
+
let toolLocation;
|
|
59
|
+
let listed = 0;
|
|
60
|
+
let unchanged = 0;
|
|
61
|
+
const seenArchives = new Set();
|
|
62
|
+
for (const archivePath of archivePaths) {
|
|
63
|
+
seenArchives.add(archivePath);
|
|
64
|
+
let stat;
|
|
65
|
+
try {
|
|
66
|
+
stat = await fs.stat(archivePath);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const mtime = stat.mtime.toISOString();
|
|
72
|
+
const sizeBytes = stat.size;
|
|
73
|
+
const prior = store.get(`SELECT mtime, sizeBytes FROM resource_archive WHERE path = ?`, [archivePath]);
|
|
74
|
+
if (!options.force && prior && String(prior.mtime) === mtime && Number(prior.sizeBytes) === sizeBytes) {
|
|
75
|
+
unchanged++;
|
|
76
|
+
progress?.({ step: "resource-unchanged", note: archivePath });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!toolLocation) {
|
|
80
|
+
toolLocation = await locate("wolvenkit-cli");
|
|
81
|
+
if (!toolLocation.found || !toolLocation.path) {
|
|
82
|
+
notes.push(`wolvenkit-cli not found — resource listing skipped for ${archivePaths.length} archive(s): ` +
|
|
83
|
+
`${toolLocation.hint ?? "WolvenKit CLI was not located."}`);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
progress?.({ step: "resource-list", note: archivePath });
|
|
88
|
+
const result = await runTool(toolLocation, {
|
|
89
|
+
args: ["archive", toNativePath(archivePath), "-l"],
|
|
90
|
+
maxCaptureBytes: LISTING_CAPTURE_BYTES,
|
|
91
|
+
});
|
|
92
|
+
if (!result.ok) {
|
|
93
|
+
notes.push(`archive -l failed for ${archivePath}: ${result.error?.message ?? "unknown error"}`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (result.stdoutTruncated) {
|
|
97
|
+
notes.push(`archive -l output for ${archivePath} exceeded the ${LISTING_CAPTURE_BYTES} byte capture cap and was not indexed; ` +
|
|
98
|
+
`raise LISTING_CAPTURE_BYTES in cyberpunk2077/index/resource.ts.`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const depotPaths = result.stdout
|
|
102
|
+
.split(/\r?\n/)
|
|
103
|
+
.map((line) => line.trim())
|
|
104
|
+
.filter(isDepotPathLine);
|
|
105
|
+
const rows = depotPaths.map((depotPath) => [depotPath, archivePath, null]);
|
|
106
|
+
store.transaction(() => {
|
|
107
|
+
store.run(`DELETE FROM resource WHERE archive = ?`, [archivePath]);
|
|
108
|
+
if (rows.length > 0)
|
|
109
|
+
store.insertMany("resource", ["path", "archive", "sizeBytes"], rows);
|
|
110
|
+
store.run(`INSERT OR REPLACE INTO resource_archive (path, mtime, sizeBytes, listedAt) VALUES (?, ?, ?, ?)`, [
|
|
111
|
+
archivePath,
|
|
112
|
+
mtime,
|
|
113
|
+
sizeBytes,
|
|
114
|
+
new Date().toISOString(),
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
listed++;
|
|
118
|
+
}
|
|
119
|
+
const staleArchives = store
|
|
120
|
+
.all(`SELECT path FROM resource_archive`)
|
|
121
|
+
.map((r) => String(r.path))
|
|
122
|
+
.filter((p) => !seenArchives.has(p));
|
|
123
|
+
if (staleArchives.length > 0) {
|
|
124
|
+
store.transaction(() => {
|
|
125
|
+
for (const stale of staleArchives) {
|
|
126
|
+
store.run(`DELETE FROM resource WHERE archive = ?`, [stale]);
|
|
127
|
+
store.run(`DELETE FROM resource_archive WHERE path = ?`, [stale]);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
notes.push(`${staleArchives.length} previously-listed archive(s) no longer found on disk; their resource rows were removed.`);
|
|
131
|
+
}
|
|
132
|
+
const totalNow = resourceCountNow();
|
|
133
|
+
notes.push(`Cyberpunk resource index: ${archivePaths.length} archive(s) found, ${listed} (re)listed, ${unchanged} unchanged and skipped, ${totalNow} resource path(s) total.`);
|
|
134
|
+
return { archivesFound: archivePaths.length, archivesListed: listed, archivesUnchanged: unchanged, resourceCount: totalNow, notes };
|
|
135
|
+
}
|