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,318 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { PE_MACHINE_AMD64, PE_MACHINE_I386, readPeImageInfo } from "../../../core/compat/pe.js";
|
|
4
|
+
import { walk } from "../../../core/fsutil.js";
|
|
5
|
+
const GAME = "cyberpunk2077";
|
|
6
|
+
const GROUPS = ["plugins"];
|
|
7
|
+
const MOD_TYPES = ["red4ext-plugin"];
|
|
8
|
+
const RED4EXT_ROOT = "red4ext";
|
|
9
|
+
export const RED4EXT_DEPLOY_DEPTH_ID = "cp2077.plugins.red4ext-deploy-depth";
|
|
10
|
+
const DEPLOY_DEPTH_EVIDENCE = [
|
|
11
|
+
"cp2077.red4ext.api.plugin-directory-depth-one",
|
|
12
|
+
"cp2077.red4ext.api.deploy-into-names-the-file",
|
|
13
|
+
];
|
|
14
|
+
function red4extTargets(project) {
|
|
15
|
+
return (project.project.deploy?.targets ?? []).filter((t) => t.rootId === RED4EXT_ROOT);
|
|
16
|
+
}
|
|
17
|
+
function segmentsOf(p) {
|
|
18
|
+
return p
|
|
19
|
+
.replace(/\\/g, "/")
|
|
20
|
+
.split("/")
|
|
21
|
+
.filter((s) => s !== "" && s !== ".");
|
|
22
|
+
}
|
|
23
|
+
async function locateFrom(target, project) {
|
|
24
|
+
const source = project.sources.find((s) => s.id === target.from);
|
|
25
|
+
const outDir = project.build?.absOutDir;
|
|
26
|
+
const candidates = source
|
|
27
|
+
? [source.absPath]
|
|
28
|
+
: path.isAbsolute(target.from)
|
|
29
|
+
? [target.from]
|
|
30
|
+
: [...(outDir ? [path.join(outDir, target.from)] : []), path.join(project.root, target.from)];
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
try {
|
|
33
|
+
return { abs: candidate, dir: (await fs.stat(candidate)).isDirectory() };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
function installedBase(target, sourceName) {
|
|
41
|
+
return target.into ? target.into : sourceName;
|
|
42
|
+
}
|
|
43
|
+
async function resolveFrom(target, project) {
|
|
44
|
+
const found = await locateFrom(target, project);
|
|
45
|
+
if (found)
|
|
46
|
+
return found.dir ? { kind: "dir", abs: found.abs } : { kind: "file", name: path.basename(found.abs) };
|
|
47
|
+
const name = segmentsOf(target.from).pop() ?? "";
|
|
48
|
+
return name.toLowerCase().endsWith(".dll") ? { kind: "file", name } : { kind: "unknown" };
|
|
49
|
+
}
|
|
50
|
+
function judge(p) {
|
|
51
|
+
const name = p.dest[p.dest.length - 1] ?? "";
|
|
52
|
+
const depth = p.dest.length - 1;
|
|
53
|
+
const stem = (p.sourceName ?? name).replace(/\.dll$/i, "");
|
|
54
|
+
if (!name.toLowerCase().endsWith(".dll")) {
|
|
55
|
+
return {
|
|
56
|
+
severity: "block",
|
|
57
|
+
problem: `the installed file has no ".dll" extension, so the loader never loads it (an "into" that names a folder puts a single-file target at that path: the folder name becomes the file)`,
|
|
58
|
+
fixInto: [...p.dest, `${stem}.dll`].join("/"),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (!name.endsWith(".dll")) {
|
|
62
|
+
return {
|
|
63
|
+
severity: "block",
|
|
64
|
+
problem: `its extension is "${name.slice(-4)}", and the loader compares the extension to ".dll" exactly, so the file is never loaded`,
|
|
65
|
+
fixInto: [...p.dest.slice(0, -1), `${name.slice(0, -4)}.dll`].join("/"),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (depth >= 2) {
|
|
69
|
+
return {
|
|
70
|
+
severity: "block",
|
|
71
|
+
problem: `it sits ${depth} folders deep under red4ext/plugins, and the loader stops descending past depth one, so it is never loaded`,
|
|
72
|
+
fixInto: `${p.dest[0]}/${name}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (depth === 0) {
|
|
76
|
+
return {
|
|
77
|
+
severity: "warn",
|
|
78
|
+
problem: `it sits directly in red4ext/plugins; the loader loads it there, but without the altered DLL search path a depth-one plugin gets, so any library shipped beside it will not resolve`,
|
|
79
|
+
fixInto: `${stem}/${name}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
async function lineOfFrom(ctx, from) {
|
|
85
|
+
try {
|
|
86
|
+
const text = await ctx.readText(ctx.project.file);
|
|
87
|
+
const needle = JSON.stringify(from);
|
|
88
|
+
const lines = text.split(/\r?\n/);
|
|
89
|
+
const i = lines.findIndex((l) => /"from"\s*:/.test(l) && l.includes(needle));
|
|
90
|
+
return i === -1 ? undefined : i + 1;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export const red4extDeployDepthValidator = {
|
|
97
|
+
id: RED4EXT_DEPLOY_DEPTH_ID,
|
|
98
|
+
game: GAME,
|
|
99
|
+
groups: GROUPS,
|
|
100
|
+
modTypes: MOD_TYPES,
|
|
101
|
+
summary: "A red4ext deploy target must land every plugin DLL at red4ext/plugins/<Folder>/<Name>.dll: the loader never loads a file without the exact \".dll\" extension or one deeper than one folder, and a DLL directly in plugins/ loads without its own folder on the DLL search path.",
|
|
102
|
+
appliesTo: (target, project) => target.kind === "project" && red4extTargets(project).length > 0,
|
|
103
|
+
appliesToDescription: "the whole project, when deploy.targets has a target on the \"red4ext\" root",
|
|
104
|
+
needs: { project: true },
|
|
105
|
+
cost: "edit",
|
|
106
|
+
severity: "block",
|
|
107
|
+
evidence: DEPLOY_DEPTH_EVIDENCE,
|
|
108
|
+
async run(ctx) {
|
|
109
|
+
const findings = [];
|
|
110
|
+
for (const target of red4extTargets(ctx.project)) {
|
|
111
|
+
const from = await resolveFrom(target, ctx.project);
|
|
112
|
+
if (from.kind === "unknown")
|
|
113
|
+
continue;
|
|
114
|
+
const placements = [];
|
|
115
|
+
if (from.kind === "file") {
|
|
116
|
+
if (!from.name.toLowerCase().endsWith(".dll"))
|
|
117
|
+
continue;
|
|
118
|
+
const dest = segmentsOf(installedBase(target, from.name));
|
|
119
|
+
if (dest.length === 0)
|
|
120
|
+
continue;
|
|
121
|
+
placements.push({ dest, sourceName: from.name });
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const base = segmentsOf(installedBase(target, path.basename(from.abs)));
|
|
125
|
+
const listed = await walk(from.abs, { limit: 5000 });
|
|
126
|
+
for (const rel of listed.files) {
|
|
127
|
+
if (!rel.toLowerCase().endsWith(".dll"))
|
|
128
|
+
continue;
|
|
129
|
+
placements.push({ dest: [...base, ...segmentsOf(rel)] });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const placement of placements) {
|
|
133
|
+
const verdict = judge(placement);
|
|
134
|
+
if (!verdict)
|
|
135
|
+
continue;
|
|
136
|
+
const installed = `red4ext/plugins/${placement.dest.join("/")}`;
|
|
137
|
+
const line = await lineOfFrom(ctx, target.from);
|
|
138
|
+
findings.push({
|
|
139
|
+
validatorId: RED4EXT_DEPLOY_DEPTH_ID,
|
|
140
|
+
severity: verdict.severity,
|
|
141
|
+
file: ctx.project.file,
|
|
142
|
+
...(line !== undefined ? { line } : {}),
|
|
143
|
+
message: `deploy target "${target.from}" (root "red4ext"${target.into ? `, into "${target.into}"` : ""}) installs ${installed}: ${verdict.problem}.`,
|
|
144
|
+
fix: from.kind === "file" && verdict.fixInto
|
|
145
|
+
? `Set "into": "${verdict.fixInto}" so the plugin lands at red4ext/plugins/${verdict.fixInto}.`
|
|
146
|
+
: `Arrange the folder so each plugin DLL sits one folder below red4ext/plugins (plugins/<Folder>/<Name>.dll), or point "into" at that folder.`,
|
|
147
|
+
evidence: DEPLOY_DEPTH_EVIDENCE,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return findings;
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
export const RED4EXT_ARCH_X64_ID = "cp2077.plugins.red4ext-arch-x64";
|
|
155
|
+
export const RED4EXT_EXPORTS_PRESENT_ID = "cp2077.plugins.red4ext-exports-present";
|
|
156
|
+
const ARCH_EVIDENCE = ["cp2077.red4ext.api.any-toolchain-x64-fastcall"];
|
|
157
|
+
const EXPORTS_EVIDENCE = [
|
|
158
|
+
"cp2077.red4ext.api.plugin-exports-supports-query-required",
|
|
159
|
+
"cp2077.red4ext.api.plugin-exports-matched-by-exact-name",
|
|
160
|
+
];
|
|
161
|
+
async function builtDlls(project) {
|
|
162
|
+
const dlls = [];
|
|
163
|
+
const unbuilt = [];
|
|
164
|
+
for (const target of red4extTargets(project)) {
|
|
165
|
+
const found = await locateFrom(target, project);
|
|
166
|
+
if (!found) {
|
|
167
|
+
if (target.from.toLowerCase().endsWith(".dll"))
|
|
168
|
+
unbuilt.push(target);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (!found.dir) {
|
|
172
|
+
if (found.abs.toLowerCase().endsWith(".dll"))
|
|
173
|
+
dlls.push({ target, abs: found.abs, single: true });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const listed = await walk(found.abs, { limit: 5000 });
|
|
177
|
+
for (const rel of listed.files) {
|
|
178
|
+
if (rel.toLowerCase().endsWith(".dll"))
|
|
179
|
+
dlls.push({ target, abs: path.join(found.abs, rel), single: false });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { dlls, unbuilt };
|
|
183
|
+
}
|
|
184
|
+
function unbuiltFinding(id, ctx, target, evidence) {
|
|
185
|
+
return {
|
|
186
|
+
validatorId: id,
|
|
187
|
+
severity: "info",
|
|
188
|
+
file: ctx.project.file,
|
|
189
|
+
message: `deploy target "${target.from}" (root "red4ext") is not built yet, so its DLL was not checked.`,
|
|
190
|
+
fix: "Run build, then validate again.",
|
|
191
|
+
evidence,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function machineName(machine) {
|
|
195
|
+
if (machine === PE_MACHINE_AMD64)
|
|
196
|
+
return "x64 (AMD64)";
|
|
197
|
+
if (machine === PE_MACHINE_I386)
|
|
198
|
+
return "x86 (I386)";
|
|
199
|
+
return `machine 0x${machine.toString(16)}`;
|
|
200
|
+
}
|
|
201
|
+
export const red4extArchX64Validator = {
|
|
202
|
+
id: RED4EXT_ARCH_X64_ID,
|
|
203
|
+
game: GAME,
|
|
204
|
+
groups: GROUPS,
|
|
205
|
+
modTypes: MOD_TYPES,
|
|
206
|
+
summary: "Every DLL a red4ext target deploys must be an x64 image: the game is a 64-bit process, and LoadLibrary refuses anything else, so the loader never loads it.",
|
|
207
|
+
appliesTo: (target, project) => target.kind === "project" && red4extTargets(project).length > 0,
|
|
208
|
+
appliesToDescription: "the whole project, when deploy.targets has a target on the \"red4ext\" root (its built DLLs)",
|
|
209
|
+
needs: { project: true },
|
|
210
|
+
cost: "build",
|
|
211
|
+
severity: "block",
|
|
212
|
+
evidence: ARCH_EVIDENCE,
|
|
213
|
+
async run(ctx) {
|
|
214
|
+
const { dlls, unbuilt } = await builtDlls(ctx.project);
|
|
215
|
+
const findings = unbuilt.map((t) => unbuiltFinding(RED4EXT_ARCH_X64_ID, ctx, t, ARCH_EVIDENCE));
|
|
216
|
+
for (const dll of dlls) {
|
|
217
|
+
const info = await readPeImageInfo(dll.abs);
|
|
218
|
+
if (!info) {
|
|
219
|
+
findings.push({
|
|
220
|
+
validatorId: RED4EXT_ARCH_X64_ID,
|
|
221
|
+
severity: "warn",
|
|
222
|
+
file: dll.abs,
|
|
223
|
+
message: `${path.basename(dll.abs)} could not be read as a PE image, so its machine type was not checked (a non-PE file named .dll, or a layout ModWright's reader does not handle).`,
|
|
224
|
+
evidence: ARCH_EVIDENCE,
|
|
225
|
+
});
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (info.machine === PE_MACHINE_AMD64 && info.pe32Plus)
|
|
229
|
+
continue;
|
|
230
|
+
findings.push({
|
|
231
|
+
validatorId: RED4EXT_ARCH_X64_ID,
|
|
232
|
+
severity: "block",
|
|
233
|
+
file: dll.abs,
|
|
234
|
+
message: `${path.basename(dll.abs)} is built for ${machineName(info.machine)}${info.pe32Plus ? "" : " (a PE32 image)"}; Cyberpunk 2077 is a 64-bit process, so LoadLibrary refuses it and RED4ext logs "Could not load plugin".`,
|
|
235
|
+
fix: "Build the plugin for x64 (cmake -A x64, or the x64 Native Tools prompt for cl.exe).",
|
|
236
|
+
evidence: ARCH_EVIDENCE,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return findings;
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
const REQUIRED_EXPORTS = ["Supports", "Query"];
|
|
243
|
+
function decoratedAs(name, exports) {
|
|
244
|
+
return exports.find((e) => e !== name && (e.startsWith(`?${name}@`) || e.startsWith(`_Z${name.length}${name}`)));
|
|
245
|
+
}
|
|
246
|
+
function describeMissing(names, info) {
|
|
247
|
+
return names
|
|
248
|
+
.map((n) => {
|
|
249
|
+
const decorated = decoratedAs(n, info.exports);
|
|
250
|
+
return decorated ? `"${n}" (exported only as the C++-decorated "${decorated}")` : `"${n}"`;
|
|
251
|
+
})
|
|
252
|
+
.join(", ");
|
|
253
|
+
}
|
|
254
|
+
export const red4extExportsPresentValidator = {
|
|
255
|
+
id: RED4EXT_EXPORTS_PRESENT_ID,
|
|
256
|
+
game: GAME,
|
|
257
|
+
groups: GROUPS,
|
|
258
|
+
modTypes: MOD_TYPES,
|
|
259
|
+
summary: "A RED4ext plugin DLL must export \"Supports\" and \"Query\" under those exact names (the loader looks them up with GetProcAddress and drops the plugin without them) and should export \"Main\", without which it loads but never initialises.",
|
|
260
|
+
appliesTo: (target, project) => target.kind === "project" && red4extTargets(project).length > 0,
|
|
261
|
+
appliesToDescription: "the whole project, when deploy.targets has a target on the \"red4ext\" root (its built DLLs)",
|
|
262
|
+
needs: { project: true },
|
|
263
|
+
cost: "build",
|
|
264
|
+
severity: "block",
|
|
265
|
+
evidence: EXPORTS_EVIDENCE,
|
|
266
|
+
async run(ctx) {
|
|
267
|
+
const { dlls, unbuilt } = await builtDlls(ctx.project);
|
|
268
|
+
const findings = unbuilt.map((t) => unbuiltFinding(RED4EXT_EXPORTS_PRESENT_ID, ctx, t, EXPORTS_EVIDENCE));
|
|
269
|
+
for (const dll of dlls) {
|
|
270
|
+
const info = await readPeImageInfo(dll.abs);
|
|
271
|
+
if (!info)
|
|
272
|
+
continue;
|
|
273
|
+
const name = path.basename(dll.abs);
|
|
274
|
+
const missing = REQUIRED_EXPORTS.filter((n) => !info.exports.includes(n));
|
|
275
|
+
const hasMain = info.exports.includes("Main");
|
|
276
|
+
const anyPluginExport = !(missing.length === REQUIRED_EXPORTS.length) || hasMain;
|
|
277
|
+
const decorationFix = 'Declare the plugin functions with RED4EXT_C_EXPORT (extern "C" __declspec(dllexport)) so they are exported undecorated.';
|
|
278
|
+
if (missing.length > 0) {
|
|
279
|
+
const decoratedAny = [...REQUIRED_EXPORTS, "Main"].some((n) => decoratedAs(n, info.exports));
|
|
280
|
+
if (!dll.single && !anyPluginExport && !decoratedAny) {
|
|
281
|
+
findings.push({
|
|
282
|
+
validatorId: RED4EXT_EXPORTS_PRESENT_ID,
|
|
283
|
+
severity: "info",
|
|
284
|
+
file: dll.abs,
|
|
285
|
+
message: `${name} exports none of Supports, Query or Main, so it is taken for a library shipped beside a plugin. If it sits one folder below red4ext/plugins the loader still tries it as a plugin and logs "Could not retrieve 'Supports' function" for it; that line is expected.`,
|
|
286
|
+
evidence: EXPORTS_EVIDENCE,
|
|
287
|
+
});
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
findings.push({
|
|
291
|
+
validatorId: RED4EXT_EXPORTS_PRESENT_ID,
|
|
292
|
+
severity: "block",
|
|
293
|
+
file: dll.abs,
|
|
294
|
+
message: `${name} does not export ${describeMissing(missing, info)}; RED4ext looks each up with GetProcAddress by that exact name, logs "Could not retrieve '${missing[0]}' function" and does not load the plugin.`,
|
|
295
|
+
fix: decorationFix,
|
|
296
|
+
evidence: EXPORTS_EVIDENCE,
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (!hasMain) {
|
|
301
|
+
findings.push({
|
|
302
|
+
validatorId: RED4EXT_EXPORTS_PRESENT_ID,
|
|
303
|
+
severity: "warn",
|
|
304
|
+
file: dll.abs,
|
|
305
|
+
message: `${name} does not export ${describeMissing(["Main"], info)}; RED4ext loads it but skips the call ("does not export a 'Main' function, skipping the call" at trace level), so nothing it registers in Main exists.`,
|
|
306
|
+
fix: decorationFix,
|
|
307
|
+
evidence: EXPORTS_EVIDENCE,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return findings;
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
export const RED4EXT_VALIDATORS = [
|
|
315
|
+
red4extDeployDepthValidator,
|
|
316
|
+
red4extArchX64Validator,
|
|
317
|
+
red4extExportsPresentValidator,
|
|
318
|
+
];
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { walk } from "../../../core/fsutil.js";
|
|
3
|
+
const ID = "cp2077.history.redscript-duplicate-global-func";
|
|
4
|
+
const EVIDENCE = ["cp2077.redscript.modules.duplicate-global-func-crashes-the-script-load"];
|
|
5
|
+
function stripCommentsAndStrings(text) {
|
|
6
|
+
let out = "";
|
|
7
|
+
let i = 0;
|
|
8
|
+
while (i < text.length) {
|
|
9
|
+
const c = text[i];
|
|
10
|
+
const n = text[i + 1];
|
|
11
|
+
if (c === "/" && n === "/") {
|
|
12
|
+
while (i < text.length && text[i] !== "\n") {
|
|
13
|
+
out += " ";
|
|
14
|
+
i++;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else if (c === "/" && n === "*") {
|
|
18
|
+
out += " ";
|
|
19
|
+
i += 2;
|
|
20
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
21
|
+
out += text[i] === "\n" ? "\n" : " ";
|
|
22
|
+
i++;
|
|
23
|
+
}
|
|
24
|
+
if (i < text.length) {
|
|
25
|
+
out += " ";
|
|
26
|
+
i += 2;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (c === '"') {
|
|
30
|
+
out += " ";
|
|
31
|
+
i++;
|
|
32
|
+
while (i < text.length && text[i] !== '"' && text[i] !== "\n") {
|
|
33
|
+
if (text[i] === "\\") {
|
|
34
|
+
out += " ";
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
out += " ";
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
if (i < text.length && text[i] === '"') {
|
|
41
|
+
out += " ";
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
out += c;
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function splitParams(list) {
|
|
53
|
+
const parts = [];
|
|
54
|
+
let depth = 0;
|
|
55
|
+
let cur = "";
|
|
56
|
+
for (const ch of list) {
|
|
57
|
+
if (ch === "<")
|
|
58
|
+
depth++;
|
|
59
|
+
else if (ch === ">")
|
|
60
|
+
depth--;
|
|
61
|
+
if (ch === "," && depth === 0) {
|
|
62
|
+
parts.push(cur);
|
|
63
|
+
cur = "";
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
cur += ch;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (cur.trim().length > 0)
|
|
70
|
+
parts.push(cur);
|
|
71
|
+
return parts;
|
|
72
|
+
}
|
|
73
|
+
function paramType(param) {
|
|
74
|
+
const p = param.trim();
|
|
75
|
+
const colon = p.indexOf(":");
|
|
76
|
+
const opt = /^opt\s/.test(p) ? "opt " : "";
|
|
77
|
+
const type = colon >= 0 ? p.slice(colon + 1) : p;
|
|
78
|
+
return opt + type.replace(/\s+/g, "");
|
|
79
|
+
}
|
|
80
|
+
const FUNC_RE = /^\s*(?:(?:public|private|protected|static|final|native|exec|cb|quest)\s+)*func\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)/;
|
|
81
|
+
const MODULE_RE = /^\s*module\s+[A-Za-z_]/m;
|
|
82
|
+
export function moduleLessGlobals(text) {
|
|
83
|
+
const clean = stripCommentsAndStrings(text.replace(/\r\n/g, "\n"));
|
|
84
|
+
if (MODULE_RE.test(clean))
|
|
85
|
+
return undefined;
|
|
86
|
+
const rawLines = text.replace(/\r\n/g, "\n").split("\n");
|
|
87
|
+
const lines = clean.split("\n");
|
|
88
|
+
const out = [];
|
|
89
|
+
let depth = 0;
|
|
90
|
+
let annotated = false;
|
|
91
|
+
for (let i = 0; i < lines.length; i++) {
|
|
92
|
+
const line = lines[i];
|
|
93
|
+
const trimmed = line.trim();
|
|
94
|
+
if (depth === 0) {
|
|
95
|
+
if (trimmed.startsWith("@")) {
|
|
96
|
+
annotated = true;
|
|
97
|
+
}
|
|
98
|
+
else if (trimmed.length > 0) {
|
|
99
|
+
const m = FUNC_RE.exec(line);
|
|
100
|
+
if (m && !annotated) {
|
|
101
|
+
out.push({
|
|
102
|
+
name: m[1],
|
|
103
|
+
paramTypes: splitParams(m[2]).map(paramType),
|
|
104
|
+
decl: (rawLines[i] ?? "").trim(),
|
|
105
|
+
line: i + 1,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
annotated = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const ch of line) {
|
|
112
|
+
if (ch === "{")
|
|
113
|
+
depth++;
|
|
114
|
+
else if (ch === "}")
|
|
115
|
+
depth = Math.max(0, depth - 1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
async function collectScriptFiles(project) {
|
|
121
|
+
const files = [];
|
|
122
|
+
for (const source of project.sources) {
|
|
123
|
+
if (source.kind !== "scripts" || !source.exists)
|
|
124
|
+
continue;
|
|
125
|
+
if (source.absPath.toLowerCase().endsWith(".reds")) {
|
|
126
|
+
files.push(source.absPath);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const walked = await walk(source.absPath, { limit: 20_000 });
|
|
130
|
+
for (const rel of walked.files) {
|
|
131
|
+
if (rel.toLowerCase().endsWith(".reds"))
|
|
132
|
+
files.push(path.join(source.absPath, rel));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
files.sort();
|
|
136
|
+
return files;
|
|
137
|
+
}
|
|
138
|
+
const redscriptDuplicateGlobalFunc = {
|
|
139
|
+
id: ID,
|
|
140
|
+
game: "cyberpunk2077",
|
|
141
|
+
groups: ["history"],
|
|
142
|
+
modTypes: ["gameplay-tweak"],
|
|
143
|
+
summary: "No two module-less global redscript functions share a name and a parameter-type list: that compiles, then crashes the game at script load.",
|
|
144
|
+
appliesTo(target) {
|
|
145
|
+
return target.kind === "project";
|
|
146
|
+
},
|
|
147
|
+
appliesToDescription: 'every .reds file under a project source of kind "scripts", reasoned about together once per validation',
|
|
148
|
+
needs: { project: true },
|
|
149
|
+
cost: "edit",
|
|
150
|
+
severity: "block",
|
|
151
|
+
evidence: EVIDENCE,
|
|
152
|
+
async run(ctx) {
|
|
153
|
+
if (ctx.target.kind !== "project")
|
|
154
|
+
return [];
|
|
155
|
+
const files = await collectScriptFiles(ctx.project);
|
|
156
|
+
const seen = new Map();
|
|
157
|
+
const findings = [];
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
let text;
|
|
160
|
+
try {
|
|
161
|
+
text = await ctx.readText(file);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const globals = moduleLessGlobals(text);
|
|
167
|
+
if (!globals)
|
|
168
|
+
continue;
|
|
169
|
+
for (const fn of globals) {
|
|
170
|
+
const key = `${fn.name}(${fn.paramTypes.join(",")})`;
|
|
171
|
+
const first = seen.get(key);
|
|
172
|
+
if (!first) {
|
|
173
|
+
seen.set(key, { file, fn });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const where = first.file === file ? `line ${first.fn.line} of this file` : `${path.basename(first.file)}:${first.fn.line}`;
|
|
177
|
+
findings.push({
|
|
178
|
+
validatorId: ID,
|
|
179
|
+
severity: "block",
|
|
180
|
+
file,
|
|
181
|
+
line: fn.line,
|
|
182
|
+
message: `"${fn.decl}" is a second module-less global ${fn.name} with the same parameter types as "${first.fn.decl}" (${where}). ` +
|
|
183
|
+
"redscript compiles both, and the game then crashes at launch as it loads the script blob (the engine copies one definition onto the other and matches their parameters by name).",
|
|
184
|
+
fix: `Rename one of them (for example the newer one to a name no other file uses), and update its callers, including any CET bridge code that calls Game.${fn.name}.`,
|
|
185
|
+
evidence: EVIDENCE,
|
|
186
|
+
related: [first.file],
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return findings;
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
export const REDSCRIPT_VALIDATORS = [redscriptDuplicateGlobalFunc];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { vanillaExtractNeverShipValidator } from "../../../core/text/validate.js";
|
|
2
|
+
export const vanillaExtractNeverShipsValidator = vanillaExtractNeverShipValidator({
|
|
3
|
+
id: "cp2077.history.vanilla-extract-never-ships",
|
|
4
|
+
game: "cyberpunk2077",
|
|
5
|
+
evidence: ["ModWright research"],
|
|
6
|
+
});
|
|
7
|
+
export const TEMPLATE_VALIDATORS = [vanillaExtractNeverShipsValidator];
|