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,182 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export const SLUG_RE = /^[a-zA-Z0-9_.-]+$/;
|
|
4
|
+
export const SLUG_RUNTIME_RE = /^[\p{L}\d_.-]+$/u;
|
|
5
|
+
export const SEMANTIC_VERSION_RE = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))?(?:-(?:[a-zA-Z0-9]+[-.]?)+)?$/;
|
|
6
|
+
export const SEMANTIC_VERSION_RUNTIME_RE = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))?(?:-(?:[a-zA-Z0-9]+[-.]?)+)?(?:\+[a-zA-Z0-9.-]+)?$/;
|
|
7
|
+
export const ENTRY_DLL_RE = /^[a-zA-Z0-9_.-]+\.dll$/;
|
|
8
|
+
export const UPDATE_KEY_RE = /^(Chucklefish:\d+|Nexus:\d+|GitHub:[A-Za-z0-9_\-.]+\/[A-Za-z0-9_\-]+|ModDrop:\d+|CurseForge:\d+)(?: *@ *[a-zA-Z0-9_]+ *)?$/i;
|
|
9
|
+
export const PROJECT_VERSION_TOKEN = "%ProjectVersion%";
|
|
10
|
+
export const MANIFEST_FIELDS = ["Name", "Author", "Version", "Description", "UniqueID", "MinimumApiVersion", "MinimumGameVersion", "EntryDll", "ContentPackFor", "Dependencies", "UpdateKeys"];
|
|
11
|
+
export function relaxJson(text) {
|
|
12
|
+
let out = "";
|
|
13
|
+
let i = 0;
|
|
14
|
+
const t = text.replace(/^\uFEFF/, "");
|
|
15
|
+
while (i < t.length) {
|
|
16
|
+
const c = t[i];
|
|
17
|
+
if (c === '"') {
|
|
18
|
+
let j = i + 1;
|
|
19
|
+
while (j < t.length && t[j] !== '"') {
|
|
20
|
+
if (t[j] === "\\")
|
|
21
|
+
j++;
|
|
22
|
+
j++;
|
|
23
|
+
}
|
|
24
|
+
out += t.slice(i, j + 1);
|
|
25
|
+
i = j + 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (c === "/" && t[i + 1] === "/") {
|
|
29
|
+
while (i < t.length && t[i] !== "\n")
|
|
30
|
+
i++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (c === "/" && t[i + 1] === "*") {
|
|
34
|
+
const end = t.indexOf("*/", i + 2);
|
|
35
|
+
i = end < 0 ? t.length : end + 2;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
out += c;
|
|
39
|
+
i++;
|
|
40
|
+
}
|
|
41
|
+
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
42
|
+
}
|
|
43
|
+
export function parseManifest(text) {
|
|
44
|
+
const parsed = JSON.parse(relaxJson(text));
|
|
45
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
46
|
+
throw new TypeError("manifest.json is not a JSON object");
|
|
47
|
+
const raw = parsed;
|
|
48
|
+
const known = new Set(MANIFEST_FIELDS);
|
|
49
|
+
const out = { extraFields: {}, raw };
|
|
50
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
51
|
+
if (known.has(k))
|
|
52
|
+
out[k] = v;
|
|
53
|
+
else
|
|
54
|
+
out.extraFields[k] = v;
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
export function manifestType(m) {
|
|
59
|
+
const isSmapi = typeof m.EntryDll === "string" && m.EntryDll.trim().length > 0;
|
|
60
|
+
const cpf = m.ContentPackFor;
|
|
61
|
+
const isContentPack = !!cpf && typeof cpf === "object" && typeof cpf.UniqueID === "string" && cpf.UniqueID.trim().length > 0;
|
|
62
|
+
if (isSmapi === isContentPack)
|
|
63
|
+
return "invalid";
|
|
64
|
+
return isSmapi ? "smapi" : "content-pack";
|
|
65
|
+
}
|
|
66
|
+
export const IGNORED_NAMES = [/^__folder_managed_by_vortex$/i, /(?:^\._|^\.DS_Store$|^__MACOSX$|^mcs$)/i, /^(?:desktop\.ini|Thumbs\.db)$/i];
|
|
67
|
+
export const IGNORED_EXTENSIONS = new Set([".doc", ".docx", ".md", ".rtf", ".txt", ".bmp", ".gif", ".ico", ".jpeg", ".jpg", ".png", ".psd", ".tif", ".xcf", ".rar", ".zip", ".7z", ".tar", ".tar.gz", ".backup", ".bak", ".old", ".url", ".lnk"]);
|
|
68
|
+
export const VORTEX_MARKER = "__folder_managed_by_vortex";
|
|
69
|
+
const CONFIG_FILE_NAME = "config.json";
|
|
70
|
+
const INSTALLER_NAMES = ["install on Linux.sh", "install on macOS.command", "install on Windows.bat"];
|
|
71
|
+
export const XNB_EXTENSIONS = new Set([".xgs", ".xnb", ".xsb", ".xwb"]);
|
|
72
|
+
const XNB_SIDE_EXTENSIONS = new Set([".json", ".yaml"]);
|
|
73
|
+
function isIgnoredName(name) {
|
|
74
|
+
return IGNORED_NAMES.some((re) => re.test(name));
|
|
75
|
+
}
|
|
76
|
+
function isRelevantFile(name) {
|
|
77
|
+
return !IGNORED_EXTENSIONS.has(path.extname(name).toLowerCase()) && !name.startsWith(".") && !isIgnoredName(name);
|
|
78
|
+
}
|
|
79
|
+
function isRelevantDir(name) {
|
|
80
|
+
return !isIgnoredName(name);
|
|
81
|
+
}
|
|
82
|
+
export async function scanMods(modsDir) {
|
|
83
|
+
const walk = async (dir, rel) => {
|
|
84
|
+
const isRoot = rel === "";
|
|
85
|
+
if (!isRoot) {
|
|
86
|
+
const name = path.basename(dir);
|
|
87
|
+
if (name.startsWith("."))
|
|
88
|
+
return [{ path: dir, relative: rel, kind: "ignored", reason: "ignored folder because its name starts with a dot.", error: "ignored-folder" }];
|
|
89
|
+
if (!isRelevantDir(name))
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const entries = await readDir(dir);
|
|
93
|
+
const dirs = entries.filter((e) => e.isDirectory() && isRelevantDir(e.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
94
|
+
const files = entries.filter((e) => e.isFile() && isRelevantFile(e.name));
|
|
95
|
+
if (isRoot || (dirs.length > 0 && files.length === 0)) {
|
|
96
|
+
const found = [];
|
|
97
|
+
for (const d of dirs)
|
|
98
|
+
found.push(...(await walk(path.join(dir, d.name), isRoot ? d.name : `${rel}/${d.name}`)));
|
|
99
|
+
return isRoot ? found : consolidate(dir, rel, found);
|
|
100
|
+
}
|
|
101
|
+
return [await readFolder(dir, rel, entries)];
|
|
102
|
+
};
|
|
103
|
+
const consolidate = (dir, rel, subfolders) => {
|
|
104
|
+
if (subfolders.length > 1) {
|
|
105
|
+
if (subfolders.every((s) => s.error === "empty-folder"))
|
|
106
|
+
return [{ path: dir, relative: rel, kind: "not-a-mod", reason: subfolders[0].reason, error: "empty-folder" }];
|
|
107
|
+
if (subfolders.every((s) => s.kind === "xnb" || s.error === "empty-folder"))
|
|
108
|
+
return [{ path: dir, relative: rel, kind: "xnb", reason: subfolders[0].reason, error: "xnb-mod" }];
|
|
109
|
+
}
|
|
110
|
+
return subfolders;
|
|
111
|
+
};
|
|
112
|
+
const readFolder = async (dir, rel, entries) => {
|
|
113
|
+
const manifest = entries.find((e) => e.isFile() && e.name.toLowerCase() === "manifest.json");
|
|
114
|
+
if (manifest) {
|
|
115
|
+
const file = path.join(dir, manifest.name);
|
|
116
|
+
try {
|
|
117
|
+
const m = parseManifest(await fs.readFile(file, "utf8"));
|
|
118
|
+
const type = manifestType(m);
|
|
119
|
+
return { path: dir, relative: rel, kind: type, manifest: m, ...(type === "invalid" ? { reason: "its manifest is invalid.", error: "manifest-invalid" } : {}) };
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
return { path: dir, relative: rel, kind: "invalid", reason: `parsing its manifest failed: ${err.message}`, error: "manifest-invalid" };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const allFiles = await filesBelow(dir);
|
|
126
|
+
const relevant = allFiles.filter((f) => isRelevantFile(path.basename(f)));
|
|
127
|
+
if (isEmptyVortexFolder(allFiles))
|
|
128
|
+
return { path: dir, relative: rel, kind: "not-a-mod", reason: "it's an empty Vortex folder (is the mod disabled in Vortex?).", error: "empty-vortex-folder" };
|
|
129
|
+
if (relevant.length === 0)
|
|
130
|
+
return { path: dir, relative: rel, kind: "not-a-mod", reason: "it's an empty folder.", error: "empty-folder" };
|
|
131
|
+
if (isXnbMod(relevant))
|
|
132
|
+
return { path: dir, relative: rel, kind: "xnb", reason: "it's not a SMAPI mod (see https://smapi.io/xnb for info).", error: "xnb-mod" };
|
|
133
|
+
if (relevant.some((f) => INSTALLER_NAMES.some((n) => n.toLowerCase() === path.basename(f).toLowerCase()))) {
|
|
134
|
+
return { path: dir, relative: rel, kind: "not-a-mod", reason: "the SMAPI installer isn't a mod (you can delete this folder after running the installer file).", error: "manifest-missing" };
|
|
135
|
+
}
|
|
136
|
+
return { path: dir, relative: rel, kind: "not-a-mod", reason: "it contains files, but none of them are manifest.json.", error: "manifest-missing" };
|
|
137
|
+
};
|
|
138
|
+
const isXnbMod = (files) => {
|
|
139
|
+
const exts = files.map((f) => path.extname(f).toLowerCase());
|
|
140
|
+
return exts.some((x) => XNB_EXTENSIONS.has(x)) && exts.every((x) => XNB_EXTENSIONS.has(x) || XNB_SIDE_EXTENSIONS.has(x));
|
|
141
|
+
};
|
|
142
|
+
const isEmptyVortexFolder = (files) => {
|
|
143
|
+
let hasMarker = false;
|
|
144
|
+
for (const f of files) {
|
|
145
|
+
const name = path.basename(f);
|
|
146
|
+
if (name === VORTEX_MARKER) {
|
|
147
|
+
hasMarker = true;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (isRelevantFile(name) && name !== CONFIG_FILE_NAME)
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return hasMarker;
|
|
154
|
+
};
|
|
155
|
+
const filesBelow = async (dir) => {
|
|
156
|
+
const found = [];
|
|
157
|
+
const walkDir = async (d) => {
|
|
158
|
+
for (const e of await readDir(d)) {
|
|
159
|
+
if (e.isDirectory()) {
|
|
160
|
+
if (isRelevantDir(e.name))
|
|
161
|
+
await walkDir(path.join(d, e.name));
|
|
162
|
+
}
|
|
163
|
+
else if (e.isFile())
|
|
164
|
+
found.push(path.join(d, e.name));
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
await walkDir(dir);
|
|
168
|
+
return found;
|
|
169
|
+
};
|
|
170
|
+
const readDir = async (dir) => {
|
|
171
|
+
try {
|
|
172
|
+
return await fs.readdir(dir, { withFileTypes: true });
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
return walk(modsDir, "");
|
|
179
|
+
}
|
|
180
|
+
export function displayName(m) {
|
|
181
|
+
return typeof m.Name === "string" ? m.Name.trim().replace(/[\r\n]/g, " ").replace(/\[/g, "(").replace(/\]/g, ")") : undefined;
|
|
182
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { readPeFileVersion } from "../../core/compat/pe.js";
|
|
3
|
+
import { compareVersions, parseVersion } from "../../core/compat/version.js";
|
|
4
|
+
import { appData, homePath, pathExists, readTextIfExists } from "../../core/fsutil.js";
|
|
5
|
+
import { readRegistryValue } from "../../core/registry.js";
|
|
6
|
+
import { findInSteam } from "../../core/steam.js";
|
|
7
|
+
import { dedupeByRoot, logs, roots } from "../../core/surfaceutil.js";
|
|
8
|
+
import { SIGNATURES } from "./logs.js";
|
|
9
|
+
import { WIKI_FETCH } from "./index/wiki.js";
|
|
10
|
+
import { parseManifest, scanMods } from "./mods.js";
|
|
11
|
+
import { stardewvalleyCompat } from "./compat.js";
|
|
12
|
+
import { readSmapiConfig, stringList } from "./config.js";
|
|
13
|
+
import { stardewvalleyIndex } from "./index/index.js";
|
|
14
|
+
import { CONTENT_VALIDATORS } from "./validators/content.js";
|
|
15
|
+
import { FIELDS_VALIDATORS } from "./validators/fields.js";
|
|
16
|
+
import { LOADORDER_VALIDATORS } from "./validators/loadorder.js";
|
|
17
|
+
import { MANIFEST_VALIDATORS } from "./validators/manifest.js";
|
|
18
|
+
export const STEAM_APP_ID = "413150";
|
|
19
|
+
export function dataDir() {
|
|
20
|
+
return appData("StardewValley");
|
|
21
|
+
}
|
|
22
|
+
export const REGISTRY_INSTALL_KEYS = [
|
|
23
|
+
{ keys: [String.raw `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150`], value: "InstallLocation", source: "steam" },
|
|
24
|
+
{ keys: [String.raw `HKLM\SOFTWARE\WOW6432Node\GOG.com\Games\1453375253`], value: "PATH", source: "gog" },
|
|
25
|
+
];
|
|
26
|
+
function conventionalPaths() {
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const programFiles of ["C:\\Program Files", "C:\\Program Files (x86)"]) {
|
|
29
|
+
out.push({ root: path.join(programFiles, "GalaxyClient", "Games", "Stardew Valley"), source: "gog" });
|
|
30
|
+
out.push({ root: path.join(programFiles, "GOG Galaxy", "Games", "Stardew Valley"), source: "gog" });
|
|
31
|
+
out.push({ root: path.join(programFiles, "GOG Games", "Stardew Valley"), source: "gog" });
|
|
32
|
+
out.push({ root: path.join(programFiles, "Steam", "steamapps", "common", "Stardew Valley"), source: "steam" });
|
|
33
|
+
}
|
|
34
|
+
for (const drive of ["C", "D", "E", "F", "G", "H"])
|
|
35
|
+
out.push({ root: `${drive}:\\Program Files\\ModifiableWindowsApps\\Stardew Valley`, source: "xbox" });
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
export async function gameFolderType(root, readVersion = readPeFileVersion) {
|
|
39
|
+
if (await pathExists(path.join(root, "Stardew Valley.dll")))
|
|
40
|
+
return "valid";
|
|
41
|
+
let found;
|
|
42
|
+
for (const e of [path.join(root, "Stardew Valley.exe"), path.join(root, "StardewValley.exe")]) {
|
|
43
|
+
if (await pathExists(e)) {
|
|
44
|
+
found = e;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!found)
|
|
49
|
+
return "no-game";
|
|
50
|
+
let version;
|
|
51
|
+
try {
|
|
52
|
+
version = await readVersion(found);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
version = undefined;
|
|
56
|
+
}
|
|
57
|
+
if (!version || !parseVersion(version))
|
|
58
|
+
return "invalid-unknown";
|
|
59
|
+
if (compareVersions(version, "1.6") < 0)
|
|
60
|
+
return "legacy-version";
|
|
61
|
+
if (!(await pathExists(path.join(root, "MonoGame.Framework.dll"))))
|
|
62
|
+
return "legacy-compatibility-branch";
|
|
63
|
+
return "invalid-unknown";
|
|
64
|
+
}
|
|
65
|
+
export async function targetsFileGamePath(file = homePath("stardewvalley.targets")) {
|
|
66
|
+
const text = await readTextIfExists(file);
|
|
67
|
+
if (!text)
|
|
68
|
+
return undefined;
|
|
69
|
+
const m = /<GamePath>\s*([^<]+?)\s*<\/GamePath>/i.exec(text);
|
|
70
|
+
return m ? m[1] : undefined;
|
|
71
|
+
}
|
|
72
|
+
export const SMAPI_EXE = "StardewModdingAPI.exe";
|
|
73
|
+
export const MODS_DIR = "Mods";
|
|
74
|
+
export const INTERNAL_DIR = "smapi-internal";
|
|
75
|
+
export function configCandidates(install) {
|
|
76
|
+
return [path.join(install.root, MODS_DIR, "SMAPI-config.json"), path.join(install.root, INTERNAL_DIR, "config.user.json"), path.join(install.root, INTERNAL_DIR, "config.json")];
|
|
77
|
+
}
|
|
78
|
+
export async function readLoadOrderOverrides(install) {
|
|
79
|
+
const config = await readSmapiConfig(install);
|
|
80
|
+
const setters = [config.sources.ModsToLoadEarly, config.sources.ModsToLoadLate].filter((f) => f !== undefined);
|
|
81
|
+
const file = setters.length > 0 ? setters.sort((a, b) => config.files.indexOf(a) - config.files.indexOf(b)).pop() : config.files[config.files.length - 1];
|
|
82
|
+
return { ...(file !== undefined ? { file } : {}), early: stringList(config.settings, "ModsToLoadEarly"), late: stringList(config.settings, "ModsToLoadLate") };
|
|
83
|
+
}
|
|
84
|
+
export const stardewvalley = {
|
|
85
|
+
id: "stardewvalley",
|
|
86
|
+
name: "Stardew Valley",
|
|
87
|
+
aliases: ["stardew", "sdv", "stardewvalley"],
|
|
88
|
+
summary: "ConcernedApe's farming game, modded through SMAPI: C# mods and Content Patcher content packs, " +
|
|
89
|
+
"one folder per mod under Mods/, launched through StardewModdingAPI.exe.",
|
|
90
|
+
async detect() {
|
|
91
|
+
const installs = [];
|
|
92
|
+
const consider = async (root, source) => {
|
|
93
|
+
if (!root)
|
|
94
|
+
return;
|
|
95
|
+
if ((await gameFolderType(root)) === "valid")
|
|
96
|
+
installs.push({ gameId: "stardewvalley", root, source });
|
|
97
|
+
};
|
|
98
|
+
await consider(process.env.MODWRIGHT_STARDEWVALLEY_ROOT, "env");
|
|
99
|
+
await consider(await targetsFileGamePath(), "config");
|
|
100
|
+
for (const root of await findInSteam(["Stardew Valley"]))
|
|
101
|
+
await consider(root, "steam");
|
|
102
|
+
for (const { keys, value, source } of REGISTRY_INSTALL_KEYS) {
|
|
103
|
+
const hit = await readRegistryValue(keys, value);
|
|
104
|
+
await consider(hit?.value, source);
|
|
105
|
+
}
|
|
106
|
+
for (const { root, source } of conventionalPaths())
|
|
107
|
+
await consider(root, source);
|
|
108
|
+
return dedupeByRoot(installs);
|
|
109
|
+
},
|
|
110
|
+
modRoots(install) {
|
|
111
|
+
return roots([
|
|
112
|
+
{
|
|
113
|
+
id: "mods",
|
|
114
|
+
label: "SMAPI mods",
|
|
115
|
+
path: path.join(install.root, MODS_DIR),
|
|
116
|
+
layout: "directory",
|
|
117
|
+
conflictScope: "isolated",
|
|
118
|
+
notes: "Each subfolder holding a manifest.json is one mod, at any depth (grouping folders " +
|
|
119
|
+
"are normal); a folder whose name starts with a dot is disabled. Load order is a " +
|
|
120
|
+
"topological sort of each manifest's Dependencies plus smapi-internal/config.json's " +
|
|
121
|
+
"ModsToLoadEarly/ModsToLoadLate, never folder position. Real collisions are on " +
|
|
122
|
+
"UniqueID, not on file path: two folders with the same id load neither.",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "smapi-internal",
|
|
126
|
+
label: "SMAPI internals",
|
|
127
|
+
path: path.join(install.root, INTERNAL_DIR),
|
|
128
|
+
layout: "flat",
|
|
129
|
+
conflictScope: "merged",
|
|
130
|
+
notes: "SMAPI's own assemblies, config.json (reset on every SMAPI update; config.user.json survives) and metadata.json. Never a deploy target; where 'is SMAPI installed' is answered.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: "content",
|
|
134
|
+
label: "Game content (XNB)",
|
|
135
|
+
path: path.join(install.root, "Content"),
|
|
136
|
+
layout: "flat",
|
|
137
|
+
conflictScope: "merged",
|
|
138
|
+
notes: "The game's compiled XNA assets. Never a deploy target: a folder of .xnb files under Mods/ is the legacy XNB-mod shape SMAPI refuses ('it's not a SMAPI mod').",
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
},
|
|
142
|
+
async readManifest(modPath, root) {
|
|
143
|
+
if (root.id !== "mods")
|
|
144
|
+
return undefined;
|
|
145
|
+
const text = await readTextIfExists(path.join(modPath, "manifest.json"));
|
|
146
|
+
if (text === undefined)
|
|
147
|
+
return undefined;
|
|
148
|
+
try {
|
|
149
|
+
const m = parseManifest(text);
|
|
150
|
+
const deps = Array.isArray(m.Dependencies) ? m.Dependencies.map((d) => (d && typeof d === "object" ? d.UniqueID : undefined)).filter((d) => typeof d === "string") : [];
|
|
151
|
+
const cpf = m.ContentPackFor && typeof m.ContentPackFor === "object" ? m.ContentPackFor.UniqueID : undefined;
|
|
152
|
+
return {
|
|
153
|
+
format: "smapi-manifest.json",
|
|
154
|
+
...(typeof m.Name === "string" ? { name: m.Name } : {}),
|
|
155
|
+
...(typeof m.Author === "string" ? { author: m.Author } : {}),
|
|
156
|
+
...(typeof m.Version === "string" ? { version: m.Version } : {}),
|
|
157
|
+
...(typeof m.Description === "string" ? { description: m.Description } : {}),
|
|
158
|
+
dependencies: [...(typeof cpf === "string" ? [cpf] : []), ...deps],
|
|
159
|
+
raw: m.raw,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
async loadOrder(install) {
|
|
167
|
+
const overrides = await readLoadOrderOverrides(install);
|
|
168
|
+
if (overrides.early.length === 0 && overrides.late.length === 0)
|
|
169
|
+
return [];
|
|
170
|
+
const mods = await scanMods(path.join(install.root, MODS_DIR));
|
|
171
|
+
const byId = new Map();
|
|
172
|
+
for (const m of mods) {
|
|
173
|
+
const id = typeof m.manifest?.UniqueID === "string" ? m.manifest.UniqueID.toLowerCase() : undefined;
|
|
174
|
+
if (id && !m.relative.includes("/") && (m.kind === "smapi" || m.kind === "content-pack"))
|
|
175
|
+
byId.set(id, m.relative);
|
|
176
|
+
}
|
|
177
|
+
const entries = [];
|
|
178
|
+
let index = 0;
|
|
179
|
+
for (const id of [...overrides.early, ...overrides.late]) {
|
|
180
|
+
const folder = byId.get(id.toLowerCase());
|
|
181
|
+
entries.push({ index: index++, name: id, enabled: true, ...(folder ? { modId: `mods:${folder}` } : {}) });
|
|
182
|
+
}
|
|
183
|
+
return entries;
|
|
184
|
+
},
|
|
185
|
+
loadOrderFile(install) {
|
|
186
|
+
return path.join(install.root, INTERNAL_DIR, "config.json");
|
|
187
|
+
},
|
|
188
|
+
logs() {
|
|
189
|
+
const dir = path.join(dataDir(), "ErrorLogs");
|
|
190
|
+
return logs([
|
|
191
|
+
{
|
|
192
|
+
id: "smapi",
|
|
193
|
+
label: "SMAPI log (SMAPI-latest.txt)",
|
|
194
|
+
candidates: [path.join(dir, "SMAPI-latest.txt"), ...[2, 3, 4, 5].map((n) => path.join(dir, `SMAPI-latest.player-${n}.txt`))],
|
|
195
|
+
notes: "One fresh file per run, flushed on every line, so it is complete up to the moment it " +
|
|
196
|
+
"is read. Lines are '[HH:mm:ss LEVEL source] message' with no date; the one absolute " +
|
|
197
|
+
"timestamp is the 'Log started at ... UTC' line. A line without that prefix continues " +
|
|
198
|
+
"the previous message (stack traces). The source is the mod's display name, not its " +
|
|
199
|
+
"UniqueID. A second running instance writes SMAPI-latest.player-2.txt.",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: "smapi-crash",
|
|
203
|
+
label: "SMAPI crash log",
|
|
204
|
+
candidates: [path.join(dir, "SMAPI-crash.txt")],
|
|
205
|
+
notes: "Written only when the game crashed; kept until the next crash.",
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
},
|
|
209
|
+
async explainMissing(install) {
|
|
210
|
+
const reasons = {};
|
|
211
|
+
if (!(await pathExists(dataDir()))) {
|
|
212
|
+
reasons.smapi = `${dataDir()} does not exist: the game has never been run on this account, so no SMAPI log can exist yet (SMAPI's install guide has the game run once without SMAPI first).`;
|
|
213
|
+
}
|
|
214
|
+
else if (await pathExists(path.join(install.root, SMAPI_EXE))) {
|
|
215
|
+
reasons.smapi = `SMAPI is installed (${SMAPI_EXE} is beside the game) but has not written a log: it was not launched. On Windows Steam runs it only through a launch-options string that is not readable from disk; run ${SMAPI_EXE} directly to check.`;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
reasons.smapi = `${SMAPI_EXE} is not in the game folder: SMAPI is not installed, so no SMAPI log exists.`;
|
|
219
|
+
}
|
|
220
|
+
reasons["smapi-crash"] = "The crash log exists only after the game has crashed under SMAPI.";
|
|
221
|
+
return reasons;
|
|
222
|
+
},
|
|
223
|
+
logSignatures: SIGNATURES,
|
|
224
|
+
validators: [...MANIFEST_VALIDATORS, ...CONTENT_VALIDATORS, ...FIELDS_VALIDATORS, ...LOADORDER_VALIDATORS],
|
|
225
|
+
index: stardewvalleyIndex,
|
|
226
|
+
compat: stardewvalleyCompat,
|
|
227
|
+
toolNeeds: [
|
|
228
|
+
{ feature: "index", data: [{ id: "stardewvalley.wiki", envVar: "MODWRIGHT_STARDEWVALLEY_WIKI_DATA", what: "the wiki's Modding: data pages, saved locally (check_toolchain action=install data=stardewvalley.wiki fetches them)", fetch: WIKI_FETCH }], note: "build_index reads saved wiki pages; no tool is needed." },
|
|
229
|
+
{ feature: "build", tools: ["dotnet"], note: "dotnet build compiles SMAPI C# mods (Content Patcher packs need no build)." },
|
|
230
|
+
],
|
|
231
|
+
processNames: ["StardewModdingAPI.exe", "Stardew Valley.exe", "StardewModdingAPI", "StardewValley"],
|
|
232
|
+
guidance: "Mods load only through SMAPI (StardewModdingAPI.exe); a missing SMAPI log usually means " +
|
|
233
|
+
"SMAPI was not launched, not that anything is broken, and on Windows whether Steam runs it " +
|
|
234
|
+
"is decided by a launch-options string that is not on disk. The log lists mods by display " +
|
|
235
|
+
"name, not UniqueID, and its 'Loaded N mods' list is alphabetical, not the load order; the " +
|
|
236
|
+
"order is a dependency sort with only smapi-internal/config.json's ModsToLoadEarly and " +
|
|
237
|
+
"ModsToLoadLate as an editable file. A mod is a folder with manifest.json at any depth under " +
|
|
238
|
+
"Mods/; a dot-prefixed folder is disabled; a folder of .xnb files is refused. A C# mod cannot " +
|
|
239
|
+
"compile without the game installed (the build package needs Stardew Valley.dll), so a " +
|
|
240
|
+
"content pack is the first project to try here. Nothing here has run against an installed game.",
|
|
241
|
+
};
|
|
242
|
+
export async function scanInstallMods(install) {
|
|
243
|
+
return scanMods(path.join(install.root, MODS_DIR));
|
|
244
|
+
}
|