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.
Files changed (455) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +212 -0
  3. package/assets/claude/agents/mw-explore.md +46 -0
  4. package/assets/claude/agents/mw-fixture.md +33 -0
  5. package/assets/claude/agents/mw-rule-author.md +51 -0
  6. package/assets/claude/agents/mw-scaffold-author.md +48 -0
  7. package/assets/claude/skills/mw-author.md +59 -0
  8. package/assets/claude/skills/mw-new-item.md +51 -0
  9. package/assets/claude/skills/mw-new-spell.md +50 -0
  10. package/assets/claude/skills/mw-scaffold.md +59 -0
  11. package/assets/claude/skills/mw-ship.md +57 -0
  12. package/assets/claude/skills/mw-validate.md +51 -0
  13. package/assets/claude/skills/mw-verify.md +59 -0
  14. package/assets/nivalisnights/extract_resources.py +177 -0
  15. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
  16. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
  17. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
  18. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
  19. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
  20. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
  21. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
  22. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
  23. package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
  24. package/bridges/bg3-se/README.md +99 -0
  25. package/bridges/bg3-se/bridge.json +68 -0
  26. package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
  27. package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
  28. package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
  29. package/bridges/cp2077-cet/README.md +165 -0
  30. package/bridges/cp2077-cet/bridge.json +53 -0
  31. package/dist/core/bridge/arm.js +175 -0
  32. package/dist/core/bridge/cet-console.js +26 -0
  33. package/dist/core/bridge/cet-log.js +362 -0
  34. package/dist/core/bridge/index.js +11 -0
  35. package/dist/core/bridge/locate.js +35 -0
  36. package/dist/core/bridge/paths.js +34 -0
  37. package/dist/core/bridge/pending.js +89 -0
  38. package/dist/core/bridge/poll.js +155 -0
  39. package/dist/core/bridge/stage.js +100 -0
  40. package/dist/core/bridge/status.js +97 -0
  41. package/dist/core/bridge/types.js +145 -0
  42. package/dist/core/bridge/validate.js +107 -0
  43. package/dist/core/build/index.js +6 -0
  44. package/dist/core/build/manifest.js +47 -0
  45. package/dist/core/build/paths.js +32 -0
  46. package/dist/core/build/run.js +342 -0
  47. package/dist/core/build/stage.js +265 -0
  48. package/dist/core/build/steps/bg3.js +129 -0
  49. package/dist/core/build/steps/core.js +195 -0
  50. package/dist/core/build/steps/cyberpunk.js +95 -0
  51. package/dist/core/build/steps/listing.js +35 -0
  52. package/dist/core/build/types.js +13 -0
  53. package/dist/core/build/zip.js +148 -0
  54. package/dist/core/compat/check.js +113 -0
  55. package/dist/core/compat/pe.js +220 -0
  56. package/dist/core/compat/types.js +1 -0
  57. package/dist/core/compat/version.js +70 -0
  58. package/dist/core/deploy/guards.js +292 -0
  59. package/dist/core/deploy/index.js +5 -0
  60. package/dist/core/deploy/plan.js +696 -0
  61. package/dist/core/deploy/reload-apply.js +361 -0
  62. package/dist/core/deploy/reload.js +358 -0
  63. package/dist/core/deploy/sources.js +169 -0
  64. package/dist/core/deploy/types.js +1 -0
  65. package/dist/core/epic.js +54 -0
  66. package/dist/core/fsutil.js +191 -0
  67. package/dist/core/home.js +70 -0
  68. package/dist/core/knowledge/facts.js +229 -0
  69. package/dist/core/knowledge/index.js +4 -0
  70. package/dist/core/knowledge/search.js +112 -0
  71. package/dist/core/knowledge/store.js +123 -0
  72. package/dist/core/knowledge/types.js +1 -0
  73. package/dist/core/ledger/check.js +42 -0
  74. package/dist/core/ledger/hooks.js +80 -0
  75. package/dist/core/ledger/index.js +7 -0
  76. package/dist/core/ledger/query.js +17 -0
  77. package/dist/core/ledger/record.js +84 -0
  78. package/dist/core/ledger/status.js +53 -0
  79. package/dist/core/ledger/store.js +98 -0
  80. package/dist/core/ledger/types.js +108 -0
  81. package/dist/core/logs/index.js +4 -0
  82. package/dist/core/logs/registry.js +50 -0
  83. package/dist/core/logs/scan.js +195 -0
  84. package/dist/core/logs/triage.js +160 -0
  85. package/dist/core/logs/types.js +1 -0
  86. package/dist/core/lsx.js +10 -0
  87. package/dist/core/lz4.js +66 -0
  88. package/dist/core/mods.js +229 -0
  89. package/dist/core/process.js +49 -0
  90. package/dist/core/project/build-steps.js +83 -0
  91. package/dist/core/project/describe.js +74 -0
  92. package/dist/core/project/index.js +5 -0
  93. package/dist/core/project/load.js +146 -0
  94. package/dist/core/project/schema.js +264 -0
  95. package/dist/core/project/types.js +3 -0
  96. package/dist/core/prompts.js +72 -0
  97. package/dist/core/registry.js +35 -0
  98. package/dist/core/safety/backup.js +206 -0
  99. package/dist/core/safety/execute.js +188 -0
  100. package/dist/core/safety/index.js +5 -0
  101. package/dist/core/safety/plan.js +122 -0
  102. package/dist/core/safety/restore.js +172 -0
  103. package/dist/core/safety/types.js +1 -0
  104. package/dist/core/scaffold/bootstrap.js +7 -0
  105. package/dist/core/scaffold/catalog.js +28 -0
  106. package/dist/core/scaffold/claude-assets.js +271 -0
  107. package/dist/core/scaffold/collisions.js +64 -0
  108. package/dist/core/scaffold/emit.js +356 -0
  109. package/dist/core/scaffold/engine.js +680 -0
  110. package/dist/core/scaffold/ids.js +78 -0
  111. package/dist/core/scaffold/index.js +10 -0
  112. package/dist/core/scaffold/jsonpath.js +121 -0
  113. package/dist/core/scaffold/load.js +81 -0
  114. package/dist/core/scaffold/render.js +85 -0
  115. package/dist/core/scaffold/sequence.js +396 -0
  116. package/dist/core/scaffold/types.js +309 -0
  117. package/dist/core/steam.js +63 -0
  118. package/dist/core/surfaceutil.js +36 -0
  119. package/dist/core/testplan/check.js +67 -0
  120. package/dist/core/testplan/index.js +5 -0
  121. package/dist/core/testplan/load.js +100 -0
  122. package/dist/core/testplan/registry.js +317 -0
  123. package/dist/core/testplan/run.js +389 -0
  124. package/dist/core/testplan/types.js +82 -0
  125. package/dist/core/text/converters/bg3.js +50 -0
  126. package/dist/core/text/converters/cyberpunk.js +36 -0
  127. package/dist/core/text/converters/index.js +261 -0
  128. package/dist/core/text/diff/cr2w.js +180 -0
  129. package/dist/core/text/diff/index.js +16 -0
  130. package/dist/core/text/diff/loca.js +43 -0
  131. package/dist/core/text/diff/lsx.js +126 -0
  132. package/dist/core/text/handles.js +86 -0
  133. package/dist/core/text/index.js +6 -0
  134. package/dist/core/text/json.js +46 -0
  135. package/dist/core/text/templates/cache.js +71 -0
  136. package/dist/core/text/templates/index.js +3 -0
  137. package/dist/core/text/templates/library.js +245 -0
  138. package/dist/core/text/templates/types.js +51 -0
  139. package/dist/core/text/toolchain.js +16 -0
  140. package/dist/core/text/types.js +1 -0
  141. package/dist/core/text/validate.js +149 -0
  142. package/dist/core/text/xml.js +58 -0
  143. package/dist/core/toolchain/datafetch.js +151 -0
  144. package/dist/core/toolchain/env.js +63 -0
  145. package/dist/core/toolchain/features.js +170 -0
  146. package/dist/core/toolchain/index.js +8 -0
  147. package/dist/core/toolchain/install.js +191 -0
  148. package/dist/core/toolchain/locate.js +275 -0
  149. package/dist/core/toolchain/remember.js +84 -0
  150. package/dist/core/toolchain/run.js +318 -0
  151. package/dist/core/toolchain/specs.js +351 -0
  152. package/dist/core/toolchain/types.js +1 -0
  153. package/dist/core/types.js +1 -0
  154. package/dist/core/userconfig.js +66 -0
  155. package/dist/core/validate/context.js +139 -0
  156. package/dist/core/validate/index.js +5 -0
  157. package/dist/core/validate/registry.js +58 -0
  158. package/dist/core/validate/run.js +131 -0
  159. package/dist/core/validate/suppressions.js +91 -0
  160. package/dist/core/validate/types.js +2 -0
  161. package/dist/core/yamledit.js +51 -0
  162. package/dist/index.js +389 -0
  163. package/dist/server.js +1914 -0
  164. package/dist/surfaces/baldursgate3/compat.js +164 -0
  165. package/dist/surfaces/baldursgate3/identity.js +110 -0
  166. package/dist/surfaces/baldursgate3/index/build.js +331 -0
  167. package/dist/surfaces/baldursgate3/index/index.js +17 -0
  168. package/dist/surfaces/baldursgate3/index/info.js +40 -0
  169. package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
  170. package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
  171. package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
  172. package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
  173. package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
  174. package/dist/surfaces/baldursgate3/index/queries.js +327 -0
  175. package/dist/surfaces/baldursgate3/index/schema.js +62 -0
  176. package/dist/surfaces/baldursgate3/index.js +3 -0
  177. package/dist/surfaces/baldursgate3/logs.js +278 -0
  178. package/dist/surfaces/baldursgate3/modsettings.js +115 -0
  179. package/dist/surfaces/baldursgate3/pak.js +153 -0
  180. package/dist/surfaces/baldursgate3/settings.js +49 -0
  181. package/dist/surfaces/baldursgate3/surface.js +232 -0
  182. package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
  183. package/dist/surfaces/baldursgate3/validators/files.js +476 -0
  184. package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
  185. package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
  186. package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
  187. package/dist/surfaces/baldursgate3/validators/story.js +790 -0
  188. package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
  189. package/dist/surfaces/cyberpunk2077/compat.js +223 -0
  190. package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
  191. package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
  192. package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
  193. package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
  194. package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
  195. package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
  196. package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
  197. package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
  198. package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
  199. package/dist/surfaces/cyberpunk2077/index.js +3 -0
  200. package/dist/surfaces/cyberpunk2077/logs.js +742 -0
  201. package/dist/surfaces/cyberpunk2077/paths.js +12 -0
  202. package/dist/surfaces/cyberpunk2077/surface.js +218 -0
  203. package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
  204. package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
  205. package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
  206. package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
  207. package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
  208. package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
  209. package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
  210. package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
  211. package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
  212. package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
  213. package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
  214. package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
  215. package/dist/surfaces/eldenring/binders.js +111 -0
  216. package/dist/surfaces/eldenring/compat.js +166 -0
  217. package/dist/surfaces/eldenring/dcx.js +49 -0
  218. package/dist/surfaces/eldenring/index.js +13 -0
  219. package/dist/surfaces/eldenring/loaders.js +132 -0
  220. package/dist/surfaces/eldenring/logs.js +67 -0
  221. package/dist/surfaces/eldenring/paramdef.js +256 -0
  222. package/dist/surfaces/eldenring/params.js +129 -0
  223. package/dist/surfaces/eldenring/surface.js +121 -0
  224. package/dist/surfaces/eldenring/tools.js +52 -0
  225. package/dist/surfaces/eldenring/validators/binders.js +193 -0
  226. package/dist/surfaces/eldenring/validators/packages.js +159 -0
  227. package/dist/surfaces/eldenring/validators/params.js +334 -0
  228. package/dist/surfaces/eldenring/validators/profiles.js +287 -0
  229. package/dist/surfaces/index.js +34 -0
  230. package/dist/surfaces/nivalisnights/compat.js +229 -0
  231. package/dist/surfaces/nivalisnights/index/build.js +310 -0
  232. package/dist/surfaces/nivalisnights/index/index.js +5 -0
  233. package/dist/surfaces/nivalisnights/index/queries.js +231 -0
  234. package/dist/surfaces/nivalisnights/index.js +4 -0
  235. package/dist/surfaces/nivalisnights/logs.js +473 -0
  236. package/dist/surfaces/nivalisnights/surface.js +195 -0
  237. package/dist/surfaces/skyrimse/archives.js +229 -0
  238. package/dist/surfaces/skyrimse/compat.js +163 -0
  239. package/dist/surfaces/skyrimse/index.js +12 -0
  240. package/dist/surfaces/skyrimse/loadorder.js +174 -0
  241. package/dist/surfaces/skyrimse/logs.js +241 -0
  242. package/dist/surfaces/skyrimse/plugins.js +149 -0
  243. package/dist/surfaces/skyrimse/records.js +94 -0
  244. package/dist/surfaces/skyrimse/surface.js +240 -0
  245. package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
  246. package/dist/surfaces/skyrimse/validators/archives.js +110 -0
  247. package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
  248. package/dist/surfaces/skyrimse/validators/records.js +117 -0
  249. package/dist/surfaces/stardewvalley/compat.js +190 -0
  250. package/dist/surfaces/stardewvalley/config.js +42 -0
  251. package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
  252. package/dist/surfaces/stardewvalley/index/index.js +229 -0
  253. package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
  254. package/dist/surfaces/stardewvalley/index.js +12 -0
  255. package/dist/surfaces/stardewvalley/logs.js +307 -0
  256. package/dist/surfaces/stardewvalley/mods.js +182 -0
  257. package/dist/surfaces/stardewvalley/surface.js +244 -0
  258. package/dist/surfaces/stardewvalley/validators/content.js +321 -0
  259. package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
  260. package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
  261. package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
  262. package/dist/surfaces/subnautica2/compat.js +138 -0
  263. package/dist/surfaces/subnautica2/index.js +5 -0
  264. package/dist/surfaces/subnautica2/logs.js +129 -0
  265. package/dist/surfaces/subnautica2/paks.js +173 -0
  266. package/dist/surfaces/subnautica2/surface.js +151 -0
  267. package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
  268. package/dist/surfaces/valheim/compat.js +171 -0
  269. package/dist/surfaces/valheim/index/index.js +301 -0
  270. package/dist/surfaces/valheim/index/tables.js +103 -0
  271. package/dist/surfaces/valheim/index.js +11 -0
  272. package/dist/surfaces/valheim/logs.js +197 -0
  273. package/dist/surfaces/valheim/packaging.js +131 -0
  274. package/dist/surfaces/valheim/plugins.js +270 -0
  275. package/dist/surfaces/valheim/references.js +121 -0
  276. package/dist/surfaces/valheim/surface.js +223 -0
  277. package/dist/surfaces/valheim/validators/packaging.js +218 -0
  278. package/dist/surfaces/valheim/validators/plugins.js +287 -0
  279. package/dist/surfaces/valheim/validators/references.js +169 -0
  280. package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
  281. package/knowledge/baldursgate3/audio.voice.yaml +182 -0
  282. package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
  283. package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
  284. package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
  285. package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
  286. package/knowledge/baldursgate3/crash.causes.yaml +90 -0
  287. package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
  288. package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
  289. package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
  290. package/knowledge/baldursgate3/dialogue.yaml +122 -0
  291. package/knowledge/baldursgate3/feats.classes.yaml +164 -0
  292. package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
  293. package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
  294. package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
  295. package/knowledge/baldursgate3/journal.quests.yaml +214 -0
  296. package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
  297. package/knowledge/baldursgate3/localization.handles.yaml +92 -0
  298. package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
  299. package/knowledge/baldursgate3/origins.companions.yaml +144 -0
  300. package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
  301. package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
  302. package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
  303. package/knowledge/baldursgate3/process.launch.yaml +34 -0
  304. package/knowledge/baldursgate3/progressions.yaml +92 -0
  305. package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
  306. package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
  307. package/knowledge/baldursgate3/races.progression.yaml +228 -0
  308. package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
  309. package/knowledge/baldursgate3/scriptextender.yaml +252 -0
  310. package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
  311. package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
  312. package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
  313. package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
  314. package/knowledge/baldursgate3/stats.spells.yaml +293 -0
  315. package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
  316. package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
  317. package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
  318. package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
  319. package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
  320. package/knowledge/baldursgate3/ui.icons.yaml +412 -0
  321. package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
  322. package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
  323. package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
  324. package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
  325. package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
  326. package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
  327. package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
  328. package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
  329. package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
  330. package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
  331. package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
  332. package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
  333. package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
  334. package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
  335. package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
  336. package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
  337. package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
  338. package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
  339. package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
  340. package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
  341. package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
  342. package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
  343. package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
  344. package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
  345. package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
  346. package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
  347. package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
  348. package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
  349. package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
  350. package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
  351. package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
  352. package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
  353. package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
  354. package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
  355. package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
  356. package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
  357. package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
  358. package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
  359. package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
  360. package/knowledge/cyberpunk2077/player.control.yaml +918 -0
  361. package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
  362. package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
  363. package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
  364. package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
  365. package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
  366. package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
  367. package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
  368. package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
  369. package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
  370. package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
  371. package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
  372. package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
  373. package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
  374. package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
  375. package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
  376. package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
  377. package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
  378. package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
  379. package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
  380. package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
  381. package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
  382. package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
  383. package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
  384. package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
  385. package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
  386. package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
  387. package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
  388. package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
  389. package/knowledge/eldenring/formats.binders.yaml +237 -0
  390. package/knowledge/eldenring/launcher.me3.yaml +230 -0
  391. package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
  392. package/knowledge/eldenring/params.paramdef.yaml +149 -0
  393. package/knowledge/eldenring/params.regulation.yaml +142 -0
  394. package/knowledge/eldenring/saves.online.yaml +118 -0
  395. package/knowledge/eldenring/toolchain.cli.yaml +256 -0
  396. package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
  397. package/knowledge/skyrimse/archives.bsa.yaml +212 -0
  398. package/knowledge/skyrimse/install.detection.yaml +41 -0
  399. package/knowledge/skyrimse/loadorder.files.yaml +242 -0
  400. package/knowledge/skyrimse/plugins.esl.yaml +189 -0
  401. package/knowledge/skyrimse/plugins.format.yaml +224 -0
  402. package/knowledge/skyrimse/skse.runtime.yaml +283 -0
  403. package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
  404. package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
  405. package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
  406. package/knowledge/stardewvalley/game.versions.yaml +202 -0
  407. package/knowledge/stardewvalley/install.layout.yaml +277 -0
  408. package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
  409. package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
  410. package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
  411. package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
  412. package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
  413. package/knowledge/subnautica2/game.build.yaml +331 -0
  414. package/knowledge/subnautica2/modding.state.yaml +210 -0
  415. package/knowledge/subnautica2/ue5.containers.yaml +72 -0
  416. package/knowledge/subnautica2/ue5.paks.yaml +203 -0
  417. package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
  418. package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
  419. package/knowledge/valheim/bepinex.loader.yaml +247 -0
  420. package/knowledge/valheim/bepinex.plugins.yaml +251 -0
  421. package/knowledge/valheim/game.versions.yaml +321 -0
  422. package/knowledge/valheim/install.layout.yaml +216 -0
  423. package/knowledge/valheim/jotunn.library.yaml +334 -0
  424. package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
  425. package/knowledge/valheim/toolchain.build.yaml +111 -0
  426. package/package.json +29 -0
  427. package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
  428. package/scaffolds/baldursgate3/new-item.yaml +264 -0
  429. package/scaffolds/baldursgate3/new-mod.yaml +174 -0
  430. package/scaffolds/baldursgate3/new-passive.yaml +100 -0
  431. package/scaffolds/baldursgate3/new-project.yaml +118 -0
  432. package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
  433. package/scaffolds/baldursgate3/new-spell.yaml +102 -0
  434. package/scaffolds/baldursgate3/new-status.yaml +119 -0
  435. package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
  436. package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
  437. package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
  438. package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
  439. package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
  440. package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
  441. package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
  442. package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
  443. package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
  444. package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
  445. package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
  446. package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
  447. package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
  448. package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
  449. package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
  450. package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
  451. package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
  452. package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
  453. package/scaffolds/stardewvalley/new-project.yaml +184 -0
  454. package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
  455. package/scripts/mw-tool.mjs +184 -0
@@ -0,0 +1,121 @@
1
+ import * as path from "node:path";
2
+ import { pathExists } from "../../core/fsutil.js";
3
+ import { findInSteam } from "../../core/steam.js";
4
+ import { dedupeByRoot, logs, roots } from "../../core/surfaceutil.js";
5
+ import { MODENGINE2_CONFIG, gameDir, me3ProfilesDir, modengine2ModDir, readModEngine2LoadOrder } from "./loaders.js";
6
+ import { SIGNATURES } from "./logs.js";
7
+ import { eldenringCompat } from "./compat.js";
8
+ import { BINDER_VALIDATORS } from "./validators/binders.js";
9
+ import { PACKAGE_VALIDATORS } from "./validators/packages.js";
10
+ import { PARAM_VALIDATORS } from "./validators/params.js";
11
+ import { PROFILE_VALIDATORS } from "./validators/profiles.js";
12
+ export const eldenring = {
13
+ id: "eldenring",
14
+ name: "Elden Ring",
15
+ aliases: ["elden", "er", "eldenring"],
16
+ summary: "FromSoftware. Packed archives under Game/, regulation.bin loose beside them, mods as a " +
17
+ "loose-file overlay through me3 or Mod Engine 2, which launch the game without EAC.",
18
+ async detect() {
19
+ const installs = [];
20
+ const override = process.env.MODWRIGHT_ELDENRING_ROOT;
21
+ if (override && (await pathExists(override))) {
22
+ installs.push({ gameId: "eldenring", root: override, source: "env" });
23
+ }
24
+ for (const root of await findInSteam(["ELDEN RING"])) {
25
+ if (await pathExists(path.join(root, "Game", "eldenring.exe"))) {
26
+ installs.push({ gameId: "eldenring", root, source: "steam" });
27
+ }
28
+ }
29
+ return dedupeByRoot(installs);
30
+ },
31
+ modRoots(install) {
32
+ const game = gameDir(install);
33
+ return roots([
34
+ {
35
+ id: "me3-profiles",
36
+ label: "me3 profiles",
37
+ path: me3ProfilesDir(),
38
+ layout: "file",
39
+ extensions: [".me3"],
40
+ conflictScope: "isolated",
41
+ notes: "One .me3 TOML profile per setup; its [[packages]] and [[natives]] paths are relative " +
42
+ "to the profile file. Profiles never contend with each other. The packages a profile " +
43
+ "names are an overlay in which identical relative paths genuinely conflict; which " +
44
+ "profile is active is known only at launch (me3 launch -p).",
45
+ },
46
+ {
47
+ id: "modengine2",
48
+ label: "Mod Engine 2 mods",
49
+ path: modengine2ModDir(install),
50
+ layout: "directory",
51
+ ordered: true,
52
+ conflictScope: "merged",
53
+ notes: "Legacy loader (archived 2024-07-19; me3 is the maintained one). Each subdirectory is " +
54
+ "a mod, all overlaying one namespace: Mod Engine 2 has no way to resolve conflicting " +
55
+ "files including regulation.bin, so the first enabled entry in config_eldenring.toml's " +
56
+ "mods list wins a conflict outright.",
57
+ },
58
+ {
59
+ id: "modengine2-dlls",
60
+ label: "Mod Engine 2 native DLLs",
61
+ path: path.join(game, "mods"),
62
+ layout: "file",
63
+ extensions: [".dll"],
64
+ conflictScope: "isolated",
65
+ notes: "DLL mods named in config_eldenring.toml's external_dlls; the Vortex layout keeps them " +
66
+ "here. A DLL that fails to load is reported only in modengine2/logs.",
67
+ },
68
+ {
69
+ id: "uxm-loose",
70
+ label: "Loose game files (UXM unpack)",
71
+ path: game,
72
+ layout: "flat",
73
+ conflictScope: "merged",
74
+ notes: "Only meaningful after UXM has unpacked the archives and patched the executable to " +
75
+ "read loose files; then Game/ itself is the overlay. Patching without unpacking " +
76
+ "crashes the game.",
77
+ },
78
+ ]);
79
+ },
80
+ async loadOrder(install) {
81
+ return (await readModEngine2LoadOrder(install)).entries;
82
+ },
83
+ loadOrderFile(install) {
84
+ return path.join(gameDir(install), MODENGINE2_CONFIG);
85
+ },
86
+ logs(install) {
87
+ return logs([
88
+ {
89
+ id: "modengine2",
90
+ label: "Mod Engine 2 log",
91
+ candidates: [path.join(gameDir(install), "modengine2", "logs", "modengine_*.log")],
92
+ rootId: "modengine2",
93
+ notes: "A daily-rotating spdlog file: the version banner, which configs loaded, one " +
94
+ "'Installing mod location' per enabled mod in priority order, 'Failed to load DLL' " +
95
+ "for a native mod that did not load, and 'Applied N hooks' when the injection took. " +
96
+ "The game writes no log of its own.",
97
+ },
98
+ ]);
99
+ },
100
+ async explainMissing(install) {
101
+ const launcher = path.join(gameDir(install), "modengine2_launcher.exe");
102
+ if (!(await pathExists(launcher))) {
103
+ return { modengine2: `${launcher} was not found: Mod Engine 2 is not installed here, so no modengine log exists. A me3 setup logs instead, to <me3 data dir>/logs/<profile>/; 'me3 info' prints that folder as its "Logs directory" line (the Windows default is not sourced from disk yet).` };
104
+ }
105
+ return { modengine2: "Mod Engine 2 is installed but has not been launched since its logs directory was created, or the log rotated out (five are kept)." };
106
+ },
107
+ logSignatures: SIGNATURES,
108
+ validators: [...PARAM_VALIDATORS, ...PROFILE_VALIDATORS, ...PACKAGE_VALIDATORS, ...BINDER_VALIDATORS],
109
+ compat: eldenringCompat,
110
+ processNames: ["eldenring.exe", "start_protected_game.exe"],
111
+ guidance: "Mods run only through a loader (me3, or the legacy Mod Engine 2) that launches " +
112
+ "Game/eldenring.exe directly so Easy Anti-Cheat never loads; modded play is offline, and " +
113
+ "'Inappropriate Activity Detected' after a modded launch means the injection failed. Two " +
114
+ "mods both shipping regulation.bin is the normal case and resolves by priority with no " +
115
+ "merge, so a file-level conflict report here is true but shallow: the real question is " +
116
+ "which params differ, which needs Smithbox or DSMSPortable. Param edits are checked as text " +
117
+ "(DSMapStudio CSV or WitchyBND XML under a 'params' source) against a Paramdex checkout: a " +
118
+ "'paramdex' source, MODWRIGHT_ELDENRING_PARAMDEX, or WitchyBND's bundled copy. There is no in-game bridge and " +
119
+ "none is planned. Only a Steam install with Game/eldenring.exe present, or " +
120
+ "MODWRIGHT_ELDENRING_ROOT, is detected. Nothing here has run against an installed game.",
121
+ };
@@ -0,0 +1,52 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ export const DSMSPORTABLE_EXIT_CODES = {
4
+ 0: "success",
5
+ 1: "Unhandled Exception",
6
+ 2: "Unhandled Exception processing arguments",
7
+ 3: "No gamepath specified",
8
+ 4: "Could not resolve output path",
9
+ 5: "Unrecognized argument switch",
10
+ 6: "Failed to read TAE file",
11
+ 7: "Timed out loading param files",
12
+ 8: "Failed to save/overwrite file",
13
+ 9: "Failed to save param file",
14
+ 10: "Failed to generate temporary files",
15
+ 11: "Invalid FMG file specified",
16
+ 12: "XML parsing error",
17
+ 13: "Invalid in-line FMG entry",
18
+ 14: "Invalid sblytbnd file specified",
19
+ 15: "Invalid TPF file specified",
20
+ 16: "Invalid BND file specified",
21
+ 17: "Invalid HKS/Lua file specified",
22
+ 18: "Invalid msgbnd file specified",
23
+ 19: "Invalid anibnd file specified",
24
+ 20: "Massedit parse error",
25
+ };
26
+ export function explainDsmsPortableExit(code) {
27
+ if (code === null || code === undefined)
28
+ return "DSMSPortable ended without an exit code (killed or timed out).";
29
+ if (code < 0)
30
+ return `DSMSPortable exit ${code}: .NET not installed (it needs the .NET 6 Desktop Runtime).`;
31
+ const meaning = DSMSPORTABLE_EXIT_CODES[code];
32
+ return meaning ? `DSMSPortable exit ${code}: ${meaning}.` : `DSMSPortable exit ${code}: not in its documented table (0..20).`;
33
+ }
34
+ export const WITCHYBND_PARAMDEX_DIR = path.join("Assets", "Paramdex", "ER");
35
+ export const WITCHYBND_PARAMDEX_VERSION_FILE = path.join(WITCHYBND_PARAMDEX_DIR, "Upgrader", "version.txt");
36
+ export function witchyBndParamdexDir(exePath) {
37
+ return path.join(path.dirname(exePath), WITCHYBND_PARAMDEX_DIR);
38
+ }
39
+ export async function readWitchyBndParamdexVersion(exePath) {
40
+ try {
41
+ const text = await fs.readFile(path.join(path.dirname(exePath), WITCHYBND_PARAMDEX_VERSION_FILE), "utf8");
42
+ const line = text
43
+ .replace(/^/, "")
44
+ .split(/\r?\n/)
45
+ .map((l) => l.trim())
46
+ .find((l) => l.length > 0);
47
+ return line;
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
@@ -0,0 +1,193 @@
1
+ import * as path from "node:path";
2
+ import { walk } from "../../../core/fsutil.js";
3
+ import { Bnd4HeaderError, isBareBinderName, looksLikeBinderUnpackDir, parseWitchySidecar, readBnd4Header, SIDECAR_RE, sidecarEntryPath } from "../binders.js";
4
+ import { PACKAGE_SOURCE_KIND } from "./packages.js";
5
+ const SIDECAR_EVIDENCE = ["eldenring.toolchain.witchybnd-xml-is-the-text-converter", "eldenring.toolchain.witchybnd-sidecar-shape"];
6
+ const HEADER_EVIDENCE = ["eldenring.formats.bnd4-header-is-fully-published", "eldenring.formats.bnd4-header-byte-offsets"];
7
+ const NAMED_EXTRAS = 5;
8
+ const PLAIN_BINDER_TAGS = new Set(["bnd4", "bnd3", "bxf4", "bxf3"]);
9
+ function isWithin(parent, child) {
10
+ const rel = path.relative(parent, child);
11
+ return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
12
+ }
13
+ const packageSources = (project) => project.sources.filter((s) => s.exists && s.kind === PACKAGE_SOURCE_KIND);
14
+ const sidecarPresent = {
15
+ id: "eldenring.binders.unpacked-in-package",
16
+ game: "eldenring",
17
+ groups: ["packaging"],
18
+ modTypes: ["packaging"],
19
+ summary: "A WitchyBND unpack folder inside a package is reported: the game reads the packed binder, not the folder, and a folder that has lost its _witchy-*.xml sidecar cannot be repacked at all.",
20
+ appliesTo: (target, project) => target.kind === "project" && packageSources(project).length > 0,
21
+ appliesToDescription: `the project, when it has a source of kind "${PACKAGE_SOURCE_KIND}"`,
22
+ needs: { project: true },
23
+ cost: "build",
24
+ severity: "warn",
25
+ evidence: SIDECAR_EVIDENCE,
26
+ async run(ctx) {
27
+ const findings = [];
28
+ for (const source of packageSources(ctx.project)) {
29
+ const { files, truncated } = await walk(source.absPath);
30
+ if (truncated)
31
+ findings.push({ validatorId: this.id, severity: "info", file: source.absPath, message: `Package "${source.id}" has more files than this scan's limit; the unpack-folder check may be incomplete.`, evidence: SIDECAR_EVIDENCE });
32
+ const dirsWithSidecar = new Set();
33
+ const dirs = new Set();
34
+ for (const rel of files) {
35
+ const parent = path.posix.dirname(rel);
36
+ for (let dir = parent; dir !== "." && !dirs.has(dir); dir = path.posix.dirname(dir))
37
+ dirs.add(dir);
38
+ if (SIDECAR_RE.test(path.posix.basename(rel)))
39
+ dirsWithSidecar.add(parent);
40
+ }
41
+ for (const rel of [...dirs].sort()) {
42
+ const name = path.posix.basename(rel);
43
+ const full = path.join(source.absPath, ...rel.split("/"));
44
+ if (dirsWithSidecar.has(rel)) {
45
+ findings.push({
46
+ validatorId: this.id,
47
+ severity: "warn",
48
+ file: full,
49
+ message: `${rel}/ in package "${source.id}" is a WitchyBND unpack folder (it holds a _witchy-*.xml sidecar); the game reads the packed binder at that path, so the folder as staged delivers nothing.`,
50
+ fix: "Repack it (WitchyBND -r on the folder) and ship the packed file; keep the unpacked folder out of the package.",
51
+ evidence: SIDECAR_EVIDENCE,
52
+ });
53
+ }
54
+ else if (looksLikeBinderUnpackDir(name)) {
55
+ findings.push({
56
+ validatorId: this.id,
57
+ severity: "warn",
58
+ file: full,
59
+ message: `${rel}/ in package "${source.id}" is named like a WitchyBND unpack folder but holds no _witchy-*.xml sidecar; without the sidecar it cannot be repacked, and as a folder the game does not read it.`,
60
+ fix: "Unpack the original binder again with WitchyBND to regenerate the sidecar, or rename the folder if it is not an unpacked binder.",
61
+ evidence: SIDECAR_EVIDENCE,
62
+ });
63
+ }
64
+ }
65
+ }
66
+ return findings;
67
+ },
68
+ };
69
+ function isSidecarInPackage(target, project) {
70
+ if (target.kind !== "file" || !SIDECAR_RE.test(path.basename(target.path)))
71
+ return false;
72
+ return packageSources(project).some((s) => isWithin(s.absPath, target.path));
73
+ }
74
+ const sidecarEntries = {
75
+ id: "eldenring.binders.sidecar-entries",
76
+ game: "eldenring",
77
+ groups: ["packaging"],
78
+ modTypes: ["packaging"],
79
+ summary: "Every <file><path> a _witchy-*.xml sidecar names exists beside it (with WitchyBND's duplicate suffix applied), a bnd4 sidecar carries its <version>, and for a plain bnd/bxf sidecar the files it does not name are noted, since repack drops them.",
80
+ appliesTo: isSidecarInPackage,
81
+ appliesToDescription: `every _witchy-*.xml under a source of kind "${PACKAGE_SOURCE_KIND}"`,
82
+ needs: { project: true },
83
+ cost: "build",
84
+ severity: "block",
85
+ evidence: SIDECAR_EVIDENCE,
86
+ async run(ctx) {
87
+ if (ctx.target.kind !== "file")
88
+ return [];
89
+ const file = ctx.target.path;
90
+ const dir = path.dirname(file);
91
+ const name = path.basename(file);
92
+ let sidecar;
93
+ try {
94
+ sidecar = parseWitchySidecar(await ctx.readText(file));
95
+ }
96
+ catch (err) {
97
+ return [{ validatorId: this.id, severity: "block", file, message: `${name} could not be read as XML: ${err.message}`, evidence: SIDECAR_EVIDENCE }];
98
+ }
99
+ const findings = [];
100
+ const tag = SIDECAR_RE.exec(name)[1].toLowerCase();
101
+ if (sidecar.tag.toLowerCase() !== tag) {
102
+ findings.push({ validatorId: this.id, severity: "block", file, message: `${name} has root element <${sidecar.tag}>, but WitchyBND expects <${tag}> in a file of that name (it checks the root tag before repacking).`, evidence: SIDECAR_EVIDENCE });
103
+ }
104
+ if (PLAIN_BINDER_TAGS.has(tag) && sidecar.version === undefined) {
105
+ findings.push({ validatorId: this.id, severity: "block", file, message: `${name} has no <version> element; WitchyBND's repack reads it unconditionally and fails without it.`, fix: "Restore the sidecar from the original unpack; the version is the binder's own eight-character tag.", evidence: SIDECAR_EVIDENCE });
106
+ }
107
+ const named = new Set();
108
+ for (const entry of sidecar.files) {
109
+ const expected = sidecarEntryPath(dir, entry);
110
+ named.add(path.relative(dir, expected).replace(/\\/g, "/").toLowerCase());
111
+ if (!(await ctx.exists(expected))) {
112
+ findings.push({
113
+ validatorId: this.id,
114
+ severity: "block",
115
+ file,
116
+ message: `${name} names ${entry.path}${entry.suffix ? ` (suffix "${entry.suffix}")` : ""}, and ${path.relative(dir, expected)} is not in the folder; WitchyBND's repack stops with "File not found" on it.`,
117
+ fix: "Restore the file, or remove its <file> element from the sidecar if the entry was meant to go (the binder then loses that entry).",
118
+ evidence: SIDECAR_EVIDENCE,
119
+ related: [expected],
120
+ });
121
+ }
122
+ }
123
+ if (sidecar.hasFilesElement && PLAIN_BINDER_TAGS.has(tag)) {
124
+ const { files } = await walk(dir);
125
+ const extras = files.filter((rel) => {
126
+ const base = path.posix.basename(rel);
127
+ if (SIDECAR_RE.test(base))
128
+ return false;
129
+ if (/\.bak$/i.test(base))
130
+ return false;
131
+ if (rel.includes("/") && files.some((other) => other !== rel && SIDECAR_RE.test(path.posix.basename(other)) && rel.startsWith(path.posix.dirname(other) + "/")))
132
+ return false;
133
+ return !named.has(rel.toLowerCase());
134
+ });
135
+ if (extras.length > 0) {
136
+ findings.push({
137
+ validatorId: this.id,
138
+ severity: "info",
139
+ file,
140
+ message: `${extras.length} file${extras.length === 1 ? "" : "s"} beside ${name} ${extras.length === 1 ? "is" : "are"} not named in it (${extras.slice(0, NAMED_EXTRAS).join(", ")}${extras.length > NAMED_EXTRAS ? ", ..." : ""}); repack packs only the sidecar's entries, so ${extras.length === 1 ? "it stays" : "they stay"} out of the binder.`,
141
+ fix: "Add a <file> element for each file that belongs in the binder, or delete the strays.",
142
+ evidence: SIDECAR_EVIDENCE,
143
+ });
144
+ }
145
+ }
146
+ return findings;
147
+ },
148
+ };
149
+ function isBareBinderInPackage(target, project) {
150
+ if (target.kind !== "file" || !isBareBinderName(target.path))
151
+ return false;
152
+ return packageSources(project).some((s) => isWithin(s.absPath, target.path));
153
+ }
154
+ const bnd4Header = {
155
+ id: "eldenring.binders.bnd4-header",
156
+ game: "eldenring",
157
+ groups: ["packaging"],
158
+ modTypes: ["packaging"],
159
+ summary: "A bare (un-DCX'd) binder file starts with the BND4 magic and the 0x40-byte header SoulsFormats asserts: header size 0x40, a known extended byte, a non-negative file count.",
160
+ appliesTo: isBareBinderInPackage,
161
+ appliesToDescription: `every .bnd or *bnd file (not .dcx) under a source of kind "${PACKAGE_SOURCE_KIND}"`,
162
+ needs: { project: true },
163
+ cost: "edit",
164
+ severity: "block",
165
+ evidence: HEADER_EVIDENCE,
166
+ async run(ctx) {
167
+ if (ctx.target.kind !== "file")
168
+ return [];
169
+ const file = ctx.target.path;
170
+ const name = path.basename(file);
171
+ try {
172
+ const h = await readBnd4Header(file);
173
+ if (h.fileCount === 0)
174
+ return [{ validatorId: this.id, severity: "warn", file, message: `${name} is a BND4 (version ${JSON.stringify(h.version)}) with no entries.`, evidence: HEADER_EVIDENCE }];
175
+ return [];
176
+ }
177
+ catch (err) {
178
+ const reason = err instanceof Bnd4HeaderError ? err.reason : "unreadable";
179
+ const bnd3 = reason === "bad-signature" && /"BND3"/.test(err.message);
180
+ return [
181
+ {
182
+ validatorId: this.id,
183
+ severity: "block",
184
+ file,
185
+ message: bnd3 ? `${name} is a BND3, the pre-Dark Souls II binder; Elden Ring's binders are BND4.` : `${name} has no readable BND4 header (${reason}): ${err.message}.`,
186
+ fix: "Repack with WitchyBND from the unpacked folder; a file the game shipped as .dcx must keep its DCX wrapper, and this rule does not read through one.",
187
+ evidence: HEADER_EVIDENCE,
188
+ },
189
+ ];
190
+ }
191
+ },
192
+ };
193
+ export const BINDER_VALIDATORS = [sidecarPresent, sidecarEntries, bnd4Header];
@@ -0,0 +1,159 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { DcxHeaderError, readDcxHeader } from "../dcx.js";
4
+ export const PACKAGE_SOURCE_KIND = "package";
5
+ export const PACKAGE_DIRECTORY_VOCABULARY = new Set([
6
+ "action",
7
+ "asset",
8
+ "chr",
9
+ "cutscene",
10
+ "event",
11
+ "expression",
12
+ "facegen",
13
+ "font",
14
+ "map",
15
+ "material",
16
+ "menu",
17
+ "movie",
18
+ "msg",
19
+ "mtd",
20
+ "other",
21
+ "param",
22
+ "parts",
23
+ "script",
24
+ "sfx",
25
+ "shader",
26
+ "sound",
27
+ ]);
28
+ export const PACKAGE_ROOT_FILES = new Set(["regulation.bin"]);
29
+ const LAYOUT_EVIDENCE = ["eldenring.formats.the-unpacked-tree-is-a-fixed-directory-vocabulary", "eldenring.me3.package-mirrors-the-dvdbnd-hierarchy"];
30
+ const DCX_EVIDENCE = [
31
+ "eldenring.formats.dcx-is-the-outer-compression-wrapper",
32
+ "eldenring.formats.dcx-header-byte-layout",
33
+ "eldenring.formats.zstd-dcx-arrived-with-the-dlc",
34
+ ];
35
+ function isWithin(parent, child) {
36
+ const rel = path.relative(parent, child);
37
+ return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
38
+ }
39
+ const layout = {
40
+ id: "eldenring.package.layout",
41
+ game: "eldenring",
42
+ groups: ["packaging"],
43
+ modTypes: ["packaging"],
44
+ summary: "A package's top-level entries are directories from the archives' vocabulary (chr, parts, map, msg, param, ...) or regulation.bin; anything else is served at no path the game asks for.",
45
+ appliesTo: (target, project) => target.kind === "project" && project.sources.some((s) => s.exists && s.kind === PACKAGE_SOURCE_KIND),
46
+ appliesToDescription: `the project, when it has a source of kind "${PACKAGE_SOURCE_KIND}"`,
47
+ needs: { project: true },
48
+ cost: "edit",
49
+ severity: "warn",
50
+ evidence: LAYOUT_EVIDENCE,
51
+ async run(ctx) {
52
+ const findings = [];
53
+ for (const source of ctx.project.sources) {
54
+ if (!source.exists || source.kind !== PACKAGE_SOURCE_KIND)
55
+ continue;
56
+ const entries = await fs.readdir(source.absPath, { withFileTypes: true });
57
+ if (entries.length === 0) {
58
+ findings.push({ validatorId: this.id, severity: "info", file: source.absPath, message: `Package source "${source.id}" is empty.`, evidence: LAYOUT_EVIDENCE });
59
+ continue;
60
+ }
61
+ for (const entry of entries) {
62
+ const lower = entry.name.toLowerCase();
63
+ const full = path.join(source.absPath, entry.name);
64
+ if (entry.isDirectory()) {
65
+ if (PACKAGE_DIRECTORY_VOCABULARY.has(lower))
66
+ continue;
67
+ const inner = await fs.readdir(full, { withFileTypes: true }).catch(() => []);
68
+ const nested = inner.some((i) => (i.isDirectory() && PACKAGE_DIRECTORY_VOCABULARY.has(i.name.toLowerCase())) || (i.isFile() && PACKAGE_ROOT_FILES.has(i.name.toLowerCase())));
69
+ findings.push({
70
+ validatorId: this.id,
71
+ severity: "warn",
72
+ file: full,
73
+ message: nested
74
+ ? `${entry.name}/ in package "${source.id}" is not an archive directory, but it contains one (${inner.filter((i) => PACKAGE_DIRECTORY_VOCABULARY.has(i.name.toLowerCase()) || PACKAGE_ROOT_FILES.has(i.name.toLowerCase())).map((i) => i.name).join(", ")}): the mod is nested one level too deep and the loader serves none of it.`
75
+ : `${entry.name}/ in package "${source.id}" is not a directory the game's archives use (${[...PACKAGE_DIRECTORY_VOCABULARY].join(", ")}); a loader serves a package file only at the path the archive uses, so everything under it is inert.`,
76
+ fix: nested ? `Move the contents of ${entry.name}/ up into the package root.` : "Place files under the archive directory they override (regulation.bin at the root; parts/, chr/, msg/ ... as in the game), or drop the folder if it is documentation.",
77
+ evidence: LAYOUT_EVIDENCE,
78
+ });
79
+ }
80
+ else if (entry.isFile() && !PACKAGE_ROOT_FILES.has(lower)) {
81
+ findings.push({
82
+ validatorId: this.id,
83
+ severity: "warn",
84
+ file: full,
85
+ message: `${entry.name} sits at the root of package "${source.id}"; the archives keep only regulation.bin there, so the loader never serves it.`,
86
+ fix: "Move it under the directory the game reads it from, or out of the package if it is a readme.",
87
+ evidence: LAYOUT_EVIDENCE,
88
+ });
89
+ }
90
+ }
91
+ }
92
+ return findings;
93
+ },
94
+ };
95
+ function isDcxInPackage(target, project) {
96
+ if (target.kind !== "file" || path.extname(target.path).toLowerCase() !== ".dcx")
97
+ return false;
98
+ return project.sources.some((s) => s.exists && s.kind === PACKAGE_SOURCE_KIND && isWithin(s.absPath, target.path));
99
+ }
100
+ const codec = {
101
+ id: "eldenring.dcx.codec",
102
+ game: "eldenring",
103
+ groups: ["packaging"],
104
+ modTypes: ["packaging", "mesh", "texture"],
105
+ summary: "A .dcx carries a DCX header with a codec Elden Ring reads: KRAK (its own) or DFLT; ZSTD only on a DLC-era game; EDGE and the bare DCP shape belong to older games.",
106
+ appliesTo: isDcxInPackage,
107
+ appliesToDescription: `every .dcx under a project source of kind "${PACKAGE_SOURCE_KIND}"`,
108
+ needs: { project: true },
109
+ cost: "edit",
110
+ severity: "block",
111
+ evidence: DCX_EVIDENCE,
112
+ async run(ctx) {
113
+ if (ctx.target.kind !== "file")
114
+ return [];
115
+ const file = ctx.target.path;
116
+ const name = path.basename(file);
117
+ let header;
118
+ try {
119
+ header = await readDcxHeader(file);
120
+ }
121
+ catch (err) {
122
+ const reason = err instanceof DcxHeaderError ? err.reason : "unreadable";
123
+ return [
124
+ {
125
+ validatorId: this.id,
126
+ severity: "block",
127
+ file,
128
+ message: `${name} has no readable DCX header (${reason}): ${err.message}.`,
129
+ fix: "Repack it with WitchyBND (-r), which writes the game's own KRAK wrapper; a file that was never compressed should not carry the .dcx extension.",
130
+ evidence: DCX_EVIDENCE,
131
+ },
132
+ ];
133
+ }
134
+ if (header.magic === "DCP") {
135
+ return [{ validatorId: this.id, severity: "warn", file, message: `${name} uses the bare DCP shape (codec ${JSON.stringify(header.codec)}), which SoulsFormats lists for Demon's Souls test maps and ACE:R textures, not Elden Ring.`, fix: "Repack with WitchyBND for Elden Ring.", evidence: DCX_EVIDENCE }];
136
+ }
137
+ switch (header.codec) {
138
+ case "KRAK":
139
+ case "DFLT":
140
+ return [];
141
+ case "ZSTD":
142
+ return [
143
+ {
144
+ validatorId: this.id,
145
+ severity: "info",
146
+ file,
147
+ message: `${name} is ZSTD-compressed (level ${header.level}); Elden Ring reads ZSTD since the DLC release, so a game older than that cannot open it.`,
148
+ fix: "Fine on a current game. For a pre-DLC build, repack with WitchyBND as KRAK.",
149
+ evidence: DCX_EVIDENCE,
150
+ },
151
+ ];
152
+ case "EDGE":
153
+ return [{ validatorId: this.id, severity: "warn", file, message: `${name} is EDGE-compressed, the chunked-deflate codec SoulsFormats lists for Demon's Souls; Elden Ring's files are KRAK, DFLT or ZSTD.`, fix: "Repack with WitchyBND for Elden Ring.", evidence: DCX_EVIDENCE }];
154
+ default:
155
+ return [{ validatorId: this.id, severity: "block", file, message: `${name} names codec ${JSON.stringify(header.codec)} at offset 0x28; the known tags are DFLT, EDGE, KRAK and ZSTD.`, fix: "The file is damaged or not a DCX; repack it from the source with WitchyBND.", evidence: DCX_EVIDENCE }];
156
+ }
157
+ },
158
+ };
159
+ export const PACKAGE_VALIDATORS = [layout, codec];