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,334 @@
1
+ import * as path from "node:path";
2
+ import { locateTool, TOOL_SPECS } from "../../../core/toolchain/index.js";
3
+ import { checkFieldValue, findParamdef, findParamdexGameDir, loadParamdex } from "../paramdef.js";
4
+ import { parseDsmsCsv, parseWitchyParamXml } from "../params.js";
5
+ import { witchyBndParamdexDir } from "../tools.js";
6
+ export const PARAMDEX_ENV = "MODWRIGHT_ELDENRING_PARAMDEX";
7
+ export const PARAMDEX_SOURCE_KIND = "paramdex";
8
+ export const PARAMS_SOURCE_KIND = "params";
9
+ const EVIDENCE = ["eldenring.regulation.paramdefs-come-from-paramdex"];
10
+ const SCHEMA_EVIDENCE = [...EVIDENCE, "eldenring.paramdef.def-files-are-named-after-the-param", "eldenring.params.dsmapstudio-csv-shape", "eldenring.params.witchybnd-xml-shape"];
11
+ const VALUES_EVIDENCE = [...EVIDENCE, "eldenring.paramdef.def-grammar-is-a-c-declaration", "eldenring.paramdef.tdfs-cover-few-of-the-enums", "eldenring.paramdef.minimum-and-maximum-are-editor-hints"];
12
+ const APPLIES = "every .csv and .xml (other than a _witchy-* sidecar) under a project source of kind \"params\"";
13
+ const MAX_FINDINGS_PER_FILE = 40;
14
+ function isWithin(parent, child) {
15
+ const rel = path.relative(parent, child);
16
+ return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
17
+ }
18
+ function isParamExport(target, project) {
19
+ if (target.kind !== "file")
20
+ return false;
21
+ const ext = path.extname(target.path).toLowerCase();
22
+ if (ext !== ".csv" && ext !== ".xml")
23
+ return false;
24
+ if (path.basename(target.path).toLowerCase().startsWith("_witchy-"))
25
+ return false;
26
+ return project.sources.some((s) => s.exists && s.kind === PARAMS_SOURCE_KIND && isWithin(s.absPath, target.path));
27
+ }
28
+ export async function resolveParamdexDir(project, options = {}) {
29
+ const env = options.env ?? process.env;
30
+ const tried = [];
31
+ for (const s of project.sources) {
32
+ if (s.kind !== PARAMDEX_SOURCE_KIND)
33
+ continue;
34
+ tried.push(`source "${s.id}" (${s.absPath})`);
35
+ const dir = await findParamdexGameDir(s.absPath);
36
+ if (dir)
37
+ return { dir, via: "source", tried };
38
+ }
39
+ const fromEnv = env[PARAMDEX_ENV];
40
+ if (fromEnv) {
41
+ tried.push(`${PARAMDEX_ENV}=${fromEnv}`);
42
+ const dir = await findParamdexGameDir(fromEnv);
43
+ if (dir)
44
+ return { dir, via: "env", tried };
45
+ }
46
+ let exe = options.witchyBndPath;
47
+ if (exe === undefined) {
48
+ const spec = TOOL_SPECS.find((t) => t.id === "witchybnd");
49
+ const overrides = project.project.toolchain;
50
+ const located = spec ? await locateTool(spec, { projectOverrides: overrides, probe: "none" }) : undefined;
51
+ exe = located?.found && located.path ? located.path : null;
52
+ }
53
+ if (exe) {
54
+ const candidate = witchyBndParamdexDir(exe);
55
+ tried.push(`WitchyBND's ${candidate}`);
56
+ const dir = await findParamdexGameDir(candidate);
57
+ if (dir)
58
+ return { dir, via: "witchybnd", tried };
59
+ }
60
+ else {
61
+ tried.push("WitchyBND (not located, so no bundled Paramdex)");
62
+ }
63
+ return { tried };
64
+ }
65
+ const CACHE_TTL_MS = 60_000;
66
+ const cache = new Map();
67
+ export function loadParamdexCached(dir) {
68
+ const hit = cache.get(dir);
69
+ if (hit && Date.now() - hit.at < CACHE_TTL_MS)
70
+ return hit.loaded;
71
+ const loaded = loadParamdex(dir);
72
+ cache.set(dir, { at: Date.now(), loaded });
73
+ loaded.catch(() => cache.delete(dir));
74
+ return loaded;
75
+ }
76
+ const lookupCache = new Map();
77
+ function resolveParamdexDirCached(project) {
78
+ const key = project.file;
79
+ const hit = lookupCache.get(key);
80
+ if (hit && Date.now() - hit.at < CACHE_TTL_MS)
81
+ return hit.lookup;
82
+ const lookup = resolveParamdexDir(project);
83
+ lookupCache.set(key, { at: Date.now(), lookup });
84
+ lookup.catch(() => lookupCache.delete(key));
85
+ return lookup;
86
+ }
87
+ export function clearParamdexCache() {
88
+ cache.clear();
89
+ lookupCache.clear();
90
+ }
91
+ async function readExport(ctx, file) {
92
+ const ext = path.extname(file).toLowerCase();
93
+ let text;
94
+ try {
95
+ text = await ctx.readText(file);
96
+ }
97
+ catch (err) {
98
+ return { error: `could not be read: ${err.message}` };
99
+ }
100
+ if (ext === ".csv") {
101
+ const data = parseDsmsCsv(text);
102
+ if (data.rows.length === 0 && !data.fields)
103
+ return { error: "is empty: no header and no rows" };
104
+ const key = path.basename(file, path.extname(file)).replace(/\.param$/i, "");
105
+ return { data, key };
106
+ }
107
+ let data;
108
+ try {
109
+ data = parseWitchyParamXml(text);
110
+ }
111
+ catch (err) {
112
+ return { error: `is not well-formed XML: ${err.message}` };
113
+ }
114
+ if (!data)
115
+ return { skip: true };
116
+ const key = data.paramType ?? data.paramdefType;
117
+ if (!key)
118
+ return { error: "is a WitchyBND param XML with no <type> (the ParamType), so no paramdef can be chosen for it" };
119
+ return { data, key };
120
+ }
121
+ function paramdexInfo(id, evidence, file, lookup) {
122
+ return {
123
+ validatorId: id,
124
+ severity: "info",
125
+ file,
126
+ message: `${path.basename(file)} was not checked against a paramdef: no Paramdex was found (tried ${lookup.tried.join("; ")}).`,
127
+ fix: `Add a project source of kind "${PARAMDEX_SOURCE_KIND}" pointing at a soulsmods/Paramdex checkout, set ${PARAMDEX_ENV}, or install WitchyBND (it bundles Paramdex under Assets/Paramdex/ER).`,
128
+ evidence,
129
+ };
130
+ }
131
+ async function load(id, evidence, ctx, file) {
132
+ const exported = await readExport(ctx, file);
133
+ if ("skip" in exported)
134
+ return [];
135
+ const name = path.basename(file);
136
+ if ("error" in exported) {
137
+ return [{ validatorId: id, severity: "block", file, message: `${name} ${exported.error}; expected a DSMapStudio CSV export or a WitchyBND param XML.`, evidence }];
138
+ }
139
+ const lookup = await resolveParamdexDirCached(ctx.project);
140
+ if (!lookup.dir)
141
+ return [paramdexInfo(id, evidence, file, lookup)];
142
+ let paramdex;
143
+ try {
144
+ paramdex = await loadParamdexCached(lookup.dir);
145
+ }
146
+ catch (err) {
147
+ return [{ validatorId: id, severity: "block", file, message: `Paramdex at ${lookup.dir} could not be loaded: ${err.message}`, evidence }];
148
+ }
149
+ const def = findParamdef(paramdex, exported.key);
150
+ if (!def) {
151
+ return [
152
+ {
153
+ validatorId: id,
154
+ severity: "block",
155
+ file,
156
+ message: `${name} names param "${exported.key}", and ${lookup.dir} has no paramdef for it (by ParamType or by def file name).`,
157
+ fix: "Name a CSV after the param as DSMapStudio does (EquipParamWeapon.csv); a WitchyBND XML's <type> must be the def's ParamType (EQUIP_PARAM_WEAPON_ST). If the param is new to the game, update the Paramdex checkout.",
158
+ evidence,
159
+ },
160
+ ];
161
+ }
162
+ return { paramdex, def, data: exported.data };
163
+ }
164
+ const isFindings = (v) => Array.isArray(v);
165
+ function capped(findings, id, evidence, file) {
166
+ if (findings.length <= MAX_FINDINGS_PER_FILE)
167
+ return findings;
168
+ const rest = findings.length - MAX_FINDINGS_PER_FILE;
169
+ return [
170
+ ...findings.slice(0, MAX_FINDINGS_PER_FILE),
171
+ { validatorId: id, severity: "info", file, message: `${rest} more finding${rest === 1 ? "" : "s"} of the same kind in ${path.basename(file)} not listed.`, evidence },
172
+ ];
173
+ }
174
+ function withoutTrailingBlank(values, expected) {
175
+ return values.length === expected + 1 && values[expected].trim() === "" ? values.slice(0, expected) : values;
176
+ }
177
+ const schema = {
178
+ id: "eldenring.params.schema",
179
+ game: "eldenring",
180
+ groups: ["params"],
181
+ modTypes: ["gameplay-tweak", "weapon", "armor", "item"],
182
+ summary: "A param export names a param Paramdex has a def for, and every field it names is in that def; a positional CSV row has one value per def field.",
183
+ appliesTo: isParamExport,
184
+ appliesToDescription: APPLIES,
185
+ needs: { project: true },
186
+ cost: "edit",
187
+ severity: "block",
188
+ evidence: SCHEMA_EVIDENCE,
189
+ async run(ctx) {
190
+ if (ctx.target.kind !== "file")
191
+ return [];
192
+ const file = ctx.target.path;
193
+ const loaded = await load(this.id, SCHEMA_EVIDENCE, ctx, file);
194
+ if (isFindings(loaded))
195
+ return loaded;
196
+ const { def, data } = loaded;
197
+ const known = new Set(def.fields.map((f) => f.name));
198
+ const name = path.basename(file);
199
+ const findings = [];
200
+ const unknownField = (field, line) => ({
201
+ validatorId: this.id,
202
+ severity: "block",
203
+ file,
204
+ ...(line !== undefined ? { line } : {}),
205
+ message: `${name} names field "${field}", which ${def.paramType} (${def.fields.length} fields) does not have; DSMapStudio's import stops on an unknown column.`,
206
+ fix: "Check the spelling against the def's field names (Paramdex Defs, or the tool's own field list); an editor-renamed field keeps its internal name in the def.",
207
+ evidence: SCHEMA_EVIDENCE,
208
+ });
209
+ if (data.shape === "dsms-csv") {
210
+ if (data.fields) {
211
+ for (const f of data.fields)
212
+ if (!known.has(f))
213
+ findings.push(unknownField(f, 1));
214
+ const width = data.fields.length;
215
+ for (const row of data.rows) {
216
+ const values = row.positional ? withoutTrailingBlank(row.positional, width) : undefined;
217
+ if (values && values.length !== width) {
218
+ findings.push({
219
+ validatorId: this.id,
220
+ severity: "block",
221
+ file,
222
+ ...(row.line !== undefined ? { line: row.line } : {}),
223
+ message: `Row ${row.idText} in ${name} has ${values.length} value${values.length === 1 ? "" : "s"} for ${width} header field${width === 1 ? "" : "s"}.`,
224
+ evidence: SCHEMA_EVIDENCE,
225
+ });
226
+ }
227
+ }
228
+ }
229
+ else {
230
+ for (const row of data.rows) {
231
+ const count = withoutTrailingBlank(row.positional ?? [], def.fields.length).length;
232
+ if (count !== def.fields.length) {
233
+ findings.push({
234
+ validatorId: this.id,
235
+ severity: "block",
236
+ file,
237
+ ...(row.line !== undefined ? { line: row.line } : {}),
238
+ message: `Row ${row.idText} in ${name} has ${count} value${count === 1 ? "" : "s"}; with no header the columns are positional and ${def.paramType} has ${def.fields.length} fields.`,
239
+ fix: "Export with the header row (ID, Name, then every field), or supply every field in def order.",
240
+ evidence: SCHEMA_EVIDENCE,
241
+ });
242
+ }
243
+ }
244
+ }
245
+ }
246
+ else {
247
+ for (const f of data.fields)
248
+ if (!known.has(f.name))
249
+ findings.push(unknownField(f.name));
250
+ if (data.dataVersion !== undefined && data.dataVersion !== def.dataVersion) {
251
+ findings.push({
252
+ validatorId: this.id,
253
+ severity: "warn",
254
+ file,
255
+ message: `${name} was unpacked against paramdef data version ${data.dataVersion}; the Paramdex on hand has ${def.dataVersion} for ${def.paramType}, so its field list may not be the one the values were written for.`,
256
+ fix: "Unpack again with a WitchyBND whose bundled Paramdex matches the checkout ModWright is reading, or point ModWright at that WitchyBND's copy.",
257
+ evidence: SCHEMA_EVIDENCE,
258
+ });
259
+ }
260
+ if (data.unreadRows > 0) {
261
+ findings.push({
262
+ validatorId: this.id,
263
+ severity: "info",
264
+ file,
265
+ message: `${data.unreadRows} row${data.unreadRows === 1 ? "" : "s"} in ${name} store cells as one delimited string (WitchyBND's CSV cell style), which ModWright does not read; their values were not checked.`,
266
+ fix: "Unpack with WitchyBND's cell style set to Element or Attribute to have values checked.",
267
+ evidence: SCHEMA_EVIDENCE,
268
+ });
269
+ }
270
+ }
271
+ return capped(findings, this.id, SCHEMA_EVIDENCE, file);
272
+ },
273
+ };
274
+ function cellsOf(row, def, data) {
275
+ if (row.cells)
276
+ return [...row.cells.entries()];
277
+ if (data.shape === "dsms-csv" && !data.fields && row.positional) {
278
+ const values = withoutTrailingBlank(row.positional, def.fields.length);
279
+ if (values.length === def.fields.length)
280
+ return values.map((v, i) => [def.fields[i].name, v]);
281
+ }
282
+ return [];
283
+ }
284
+ const values = {
285
+ id: "eldenring.params.values",
286
+ game: "eldenring",
287
+ groups: ["params"],
288
+ modTypes: ["gameplay-tweak", "weapon", "armor", "item"],
289
+ summary: "Every cell in a param export fits its def field: integer ranges and bit widths, finite floats, byte arrays of the declared length, fixed strings within their length, and (where Paramdex has the tdf) an enumerated field's known values.",
290
+ appliesTo: isParamExport,
291
+ appliesToDescription: APPLIES,
292
+ needs: { project: true },
293
+ cost: "edit",
294
+ severity: "block",
295
+ evidence: VALUES_EVIDENCE,
296
+ async run(ctx) {
297
+ if (ctx.target.kind !== "file")
298
+ return [];
299
+ const file = ctx.target.path;
300
+ const loaded = await load(this.id, VALUES_EVIDENCE, ctx, file);
301
+ if (isFindings(loaded))
302
+ return loaded;
303
+ const { paramdex, def, data } = loaded;
304
+ const byName = new Map(def.fields.map((f) => [f.name, f]));
305
+ const name = path.basename(file);
306
+ const findings = [];
307
+ for (const row of data.rows) {
308
+ const at = row.line !== undefined ? { line: row.line } : {};
309
+ if (row.id === undefined) {
310
+ findings.push({ validatorId: this.id, severity: "block", file, ...at, message: `Row id "${row.idText}" in ${name} is not an integer.`, evidence: VALUES_EVIDENCE });
311
+ continue;
312
+ }
313
+ for (const [fieldName, raw] of cellsOf(row, def, data)) {
314
+ const field = byName.get(fieldName);
315
+ if (!field)
316
+ continue;
317
+ const problem = checkFieldValue(field, raw, paramdex.tdfs);
318
+ if (!problem)
319
+ continue;
320
+ findings.push({
321
+ validatorId: this.id,
322
+ severity: problem.kind === "enum" ? "warn" : "block",
323
+ file,
324
+ ...at,
325
+ message: `Row ${row.id}${row.name && row.name !== "null" ? ` (${row.name})` : ""} in ${name}: ${problem.message}.`,
326
+ ...(problem.kind === "enum" ? { fix: "A value outside the tdf is not always wrong (Paramdex's tdfs lag the game), but it is worth a look." } : {}),
327
+ evidence: VALUES_EVIDENCE,
328
+ });
329
+ }
330
+ }
331
+ return capped(findings, this.id, VALUES_EVIDENCE, file);
332
+ },
333
+ };
334
+ export const PARAM_VALIDATORS = [schema, values];
@@ -0,0 +1,287 @@
1
+ import * as path from "node:path";
2
+ import { isDirectory } from "../../../core/fsutil.js";
3
+ import { ME3_ELDENRING_IDS, ME3_GAME_IDS, ME3_PROFILE_VERSION, parseMe3ProfileDetailed } from "../loaders.js";
4
+ const EVIDENCE = [
5
+ "eldenring.me3.profile-is-a-toml-file",
6
+ "eldenring.me3.package-paths-are-relative-to-the-profile",
7
+ "eldenring.me3.profile-schema-fixes-version-games-and-keys",
8
+ ];
9
+ const APPLIES = "every .me3 file under a project source";
10
+ function isWithin(parent, child) {
11
+ const rel = path.relative(parent, child);
12
+ return rel === "" || (!(rel === ".." || rel.startsWith(`..${path.sep}`)) && !path.isAbsolute(rel));
13
+ }
14
+ function isProfile(target, project) {
15
+ if (target.kind !== "file" || path.extname(target.path).toLowerCase() !== ".me3")
16
+ return false;
17
+ return project.sources.some((s) => s.exists && isWithin(s.absPath, target.path));
18
+ }
19
+ async function parse(ctx, file) {
20
+ let text;
21
+ try {
22
+ text = await ctx.readText(file);
23
+ }
24
+ catch (err) {
25
+ return { error: `could not be read: ${err.message}` };
26
+ }
27
+ try {
28
+ return { detail: parseMe3ProfileDetailed(text) };
29
+ }
30
+ catch (err) {
31
+ const line = err.line;
32
+ return { error: `is not valid TOML: ${err.message.split("\n")[0]}`, ...(typeof line === "number" ? { line } : {}) };
33
+ }
34
+ }
35
+ const version = {
36
+ id: "eldenring.me3.profile-version",
37
+ game: "eldenring",
38
+ groups: ["packaging"],
39
+ modTypes: ["packaging"],
40
+ summary: `A .me3 profile parses as TOML and carries profileVersion = "${ME3_PROFILE_VERSION}", the one version me3's schema accepts.`,
41
+ appliesTo: isProfile,
42
+ appliesToDescription: APPLIES,
43
+ needs: { project: true },
44
+ cost: "edit",
45
+ severity: "block",
46
+ evidence: EVIDENCE,
47
+ async run(ctx) {
48
+ if (ctx.target.kind !== "file")
49
+ return [];
50
+ const file = ctx.target.path;
51
+ const name = path.basename(file);
52
+ const parsed = await parse(ctx, file);
53
+ if ("error" in parsed) {
54
+ return [{ validatorId: this.id, severity: "block", file, ...(parsed.line !== undefined ? { line: parsed.line } : {}), message: `${name} ${parsed.error}; me3 cannot load it.`, evidence: EVIDENCE }];
55
+ }
56
+ const v = parsed.detail.profile.profileVersion;
57
+ if (v === ME3_PROFILE_VERSION)
58
+ return [];
59
+ return [
60
+ {
61
+ validatorId: this.id,
62
+ severity: "block",
63
+ file,
64
+ message: v === undefined ? `${name} has no profileVersion; me3's schema requires profileVersion = "${ME3_PROFILE_VERSION}".` : `${name} has profileVersion = "${v}"; me3's schema accepts only "${ME3_PROFILE_VERSION}".`,
65
+ fix: `Add profileVersion = "${ME3_PROFILE_VERSION}" as the first line.`,
66
+ evidence: EVIDENCE,
67
+ },
68
+ ];
69
+ },
70
+ };
71
+ const supportedGame = {
72
+ id: "eldenring.me3.supported-game",
73
+ game: "eldenring",
74
+ groups: ["packaging"],
75
+ modTypes: ["packaging"],
76
+ summary: "Every [[supports]].game is an id me3 knows, and at least one of them is Elden Ring; a profile with no game needs -g at every launch.",
77
+ appliesTo: isProfile,
78
+ appliesToDescription: APPLIES,
79
+ needs: { project: true },
80
+ cost: "edit",
81
+ severity: "warn",
82
+ evidence: EVIDENCE,
83
+ async run(ctx) {
84
+ if (ctx.target.kind !== "file")
85
+ return [];
86
+ const file = ctx.target.path;
87
+ const name = path.basename(file);
88
+ const parsed = await parse(ctx, file);
89
+ if ("error" in parsed)
90
+ return [];
91
+ const { profile, supportsWithoutGame } = parsed.detail;
92
+ const findings = [];
93
+ for (const i of supportsWithoutGame) {
94
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `[[supports]] block ${i + 1} in ${name} has no game key.`, fix: 'Give it game = "eldenring".', evidence: EVIDENCE });
95
+ }
96
+ for (const g of profile.supports) {
97
+ if (!ME3_GAME_IDS.includes(g)) {
98
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `[[supports]] names game "${g}", which me3 does not know (its ids: ${ME3_GAME_IDS.join(", ")}).`, fix: 'Use game = "eldenring".', evidence: EVIDENCE });
99
+ }
100
+ }
101
+ const known = profile.supports.filter((g) => ME3_GAME_IDS.includes(g));
102
+ if (profile.supports.length === 0 && supportsWithoutGame.length === 0) {
103
+ findings.push({ validatorId: this.id, severity: "info", file, message: `${name} lists no [[supports]] game, so 'me3 launch' needs -g eldenring every time and double-clicking the profile cannot pick the game.`, fix: 'Add a [[supports]] block with game = "eldenring".', evidence: EVIDENCE });
104
+ }
105
+ else if (known.length > 0 && !known.some((g) => ME3_ELDENRING_IDS.includes(g))) {
106
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${name} supports ${known.join(", ")} but not Elden Ring, in an Elden Ring project.`, evidence: EVIDENCE });
107
+ }
108
+ return findings;
109
+ },
110
+ };
111
+ const pathsResolve = {
112
+ id: "eldenring.me3.paths-resolve",
113
+ game: "eldenring",
114
+ groups: ["packaging"],
115
+ modTypes: ["packaging"],
116
+ summary: "Every [[packages]].path is an existing directory and every [[natives]].path an existing .dll, resolved against the profile file (not the game).",
117
+ appliesTo: isProfile,
118
+ appliesToDescription: APPLIES,
119
+ needs: { project: true },
120
+ cost: "edit",
121
+ severity: "block",
122
+ evidence: EVIDENCE,
123
+ async run(ctx) {
124
+ if (ctx.target.kind !== "file")
125
+ return [];
126
+ const file = ctx.target.path;
127
+ const name = path.basename(file);
128
+ const parsed = await parse(ctx, file);
129
+ if ("error" in parsed)
130
+ return [];
131
+ const { profile, entriesWithoutPath } = parsed.detail;
132
+ const base = path.dirname(file);
133
+ const findings = [];
134
+ for (const e of entriesWithoutPath) {
135
+ findings.push({ validatorId: this.id, severity: "block", file, message: `[[${e.block}]] block ${e.index + 1} in ${name} has no path.`, evidence: EVIDENCE });
136
+ }
137
+ for (const block of ["packages", "natives"]) {
138
+ for (const entry of profile[block]) {
139
+ if (entry.enabled === false)
140
+ continue;
141
+ const abs = path.isAbsolute(entry.path) ? entry.path : path.resolve(base, entry.path);
142
+ const exists = await ctx.exists(abs);
143
+ const label = entry.id ? `${block} "${entry.id}"` : `${block} entry "${entry.path}"`;
144
+ if (!exists) {
145
+ findings.push({
146
+ validatorId: this.id,
147
+ severity: "block",
148
+ file,
149
+ message: `${label} in ${name} resolves to ${abs}, which does not exist; me3 resolves paths against the profile file, not the game folder.`,
150
+ fix: `Write the path relative to ${base}, or make it absolute (single-quoted so backslashes need no escaping).`,
151
+ evidence: EVIDENCE,
152
+ related: [abs],
153
+ });
154
+ continue;
155
+ }
156
+ const isDir = await isDirectory(abs);
157
+ if (block === "packages") {
158
+ if (!isDir) {
159
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${label} in ${name} resolves to a file, not a directory; a [[packages]] path is a folder laid out like the game's archives.`, evidence: EVIDENCE, related: [abs] });
160
+ }
161
+ }
162
+ else if (isDir) {
163
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${label} in ${name} is a folder, not a .dll; [[natives]] are DLL mods, and asset folders belong under [[packages]].`, evidence: EVIDENCE, related: [abs] });
164
+ }
165
+ else if (path.extname(entry.path).toLowerCase() !== ".dll") {
166
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${label} in ${name} is not a .dll; [[natives]] are DLL mods, and asset folders belong under [[packages]].`, evidence: EVIDENCE });
167
+ }
168
+ }
169
+ }
170
+ return findings;
171
+ },
172
+ };
173
+ const unknownKeys = {
174
+ id: "eldenring.me3.unknown-keys",
175
+ game: "eldenring",
176
+ groups: ["packaging"],
177
+ modTypes: ["packaging"],
178
+ summary: "Every key in the profile and its blocks is one me3's schema defines; a typo'd keyword (pakages, paths, idd) is silently ignored, which the docs name as a common failure.",
179
+ appliesTo: isProfile,
180
+ appliesToDescription: APPLIES,
181
+ needs: { project: true },
182
+ cost: "edit",
183
+ severity: "warn",
184
+ evidence: EVIDENCE,
185
+ async run(ctx) {
186
+ if (ctx.target.kind !== "file")
187
+ return [];
188
+ const file = ctx.target.path;
189
+ const name = path.basename(file);
190
+ const parsed = await parse(ctx, file);
191
+ if ("error" in parsed)
192
+ return [];
193
+ return parsed.detail.unknownKeys.map((u) => ({
194
+ validatorId: this.id,
195
+ severity: "warn",
196
+ file,
197
+ message: `${name}: "${u.key}" in ${u.where} is not a key me3's profile schema defines, so me3 ignores it.`,
198
+ fix: "Check the spelling against me3's profile schema (profileVersion, savefile, start_online, disable_arxan, mem_patch, mem_patch_heap_size, debug_properties; [[supports]] game/since; [[packages]] id/path/enabled/load_after/load_before; [[natives]] path/optional/enabled/load_after/load_before/initializer/finalizer/load_early; a load_after/load_before entry id/optional).",
199
+ evidence: EVIDENCE,
200
+ }));
201
+ },
202
+ };
203
+ const dependencyIds = {
204
+ id: "eldenring.me3.dependency-ids",
205
+ game: "eldenring",
206
+ groups: ["packaging"],
207
+ modTypes: ["packaging"],
208
+ summary: "Every load_after/load_before id names a package or native in the same profile, and no two entries share an id.",
209
+ appliesTo: isProfile,
210
+ appliesToDescription: APPLIES,
211
+ needs: { project: true },
212
+ cost: "edit",
213
+ severity: "warn",
214
+ evidence: EVIDENCE,
215
+ async run(ctx) {
216
+ if (ctx.target.kind !== "file")
217
+ return [];
218
+ const file = ctx.target.path;
219
+ const name = path.basename(file);
220
+ const parsed = await parse(ctx, file);
221
+ if ("error" in parsed)
222
+ return [];
223
+ const { profile } = parsed.detail;
224
+ const entries = [...profile.packages, ...profile.natives];
225
+ const ids = new Map();
226
+ for (const e of entries)
227
+ if (e.id)
228
+ ids.set(e.id, (ids.get(e.id) ?? 0) + 1);
229
+ const findings = [];
230
+ for (const [id, count] of ids) {
231
+ if (count > 1)
232
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${count} entries in ${name} share id "${id}"; load_after/load_before cannot tell them apart.`, evidence: EVIDENCE });
233
+ }
234
+ for (const e of entries) {
235
+ for (const [key, deps] of [["load_after", e.loadAfter], ["load_before", e.loadBefore]]) {
236
+ for (const d of deps ?? []) {
237
+ if (ids.has(d.id))
238
+ continue;
239
+ findings.push({
240
+ validatorId: this.id,
241
+ severity: d.optional ? "info" : "warn",
242
+ file,
243
+ message: `${e.id ? `"${e.id}"` : `entry "${e.path}"`} in ${name} has ${key} = "${d.id}", which no package or native in this profile is named${d.optional ? " (optional, so me3 proceeds)" : ""}.`,
244
+ fix: "Give the other entry that id, or fix the spelling.",
245
+ evidence: EVIDENCE,
246
+ });
247
+ }
248
+ }
249
+ }
250
+ return findings;
251
+ },
252
+ };
253
+ const saveAndOnline = {
254
+ id: "eldenring.me3.save-and-online",
255
+ game: "eldenring",
256
+ groups: ["packaging"],
257
+ modTypes: ["packaging"],
258
+ summary: "A profile that loads mods does not set start_online = true (me3's own help calls it a ban risk) and names a savefile (a modded save on the vanilla file is the documented corruption path).",
259
+ appliesTo: isProfile,
260
+ appliesToDescription: APPLIES,
261
+ needs: { project: true },
262
+ cost: "edit",
263
+ severity: "warn",
264
+ evidence: [...EVIDENCE, "eldenring.me3.savefile-field-isolates-modded-saves", "eldenring.me3.blocks-matchmaking-by-default"],
265
+ async run(ctx) {
266
+ if (ctx.target.kind !== "file")
267
+ return [];
268
+ const file = ctx.target.path;
269
+ const name = path.basename(file);
270
+ const parsed = await parse(ctx, file);
271
+ if ("error" in parsed)
272
+ return [];
273
+ const { profile } = parsed.detail;
274
+ const loadsMods = profile.packages.some((p) => p.enabled !== false) || profile.natives.some((n) => n.enabled !== false);
275
+ if (!loadsMods)
276
+ return [];
277
+ const findings = [];
278
+ if (profile.startOnline === true) {
279
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${name} sets start_online = true while loading mods; me3 blocks matchmaking by default because a modded online session risks a ban.`, fix: "Drop start_online, or keep it only for a private-server setup that needs it.", evidence: this.evidence });
280
+ }
281
+ if (profile.savefile === undefined) {
282
+ findings.push({ validatorId: this.id, severity: "info", file, message: `${name} loads mods with no savefile, so the modded game writes the vanilla save; me3's docs recommend a separate one to avoid corrupting it and to keep modded progress off the online character.`, fix: 'Add savefile = "Modded.sl2" (me3 copies the existing save the first time).', evidence: this.evidence });
283
+ }
284
+ return findings;
285
+ },
286
+ };
287
+ export const PROFILE_VALIDATORS = [version, supportedGame, pathsResolve, unknownKeys, dependencyIds, saveAndOnline];
@@ -0,0 +1,34 @@
1
+ import { baldursgate3 } from "./baldursgate3/index.js";
2
+ import { cyberpunk2077 } from "./cyberpunk2077/index.js";
3
+ import { skyrimse } from "./skyrimse/index.js";
4
+ import { eldenring } from "./eldenring/index.js";
5
+ import { valheim } from "./valheim/index.js";
6
+ import { subnautica2 } from "./subnautica2/index.js";
7
+ import { stardewvalley } from "./stardewvalley/index.js";
8
+ import { nivalisnights } from "./nivalisnights/index.js";
9
+ export const SURFACES = [cyberpunk2077, baldursgate3, skyrimse, eldenring, valheim, subnautica2, stardewvalley, nivalisnights];
10
+ export function getSurface(nameOrId) {
11
+ const needle = nameOrId.trim().toLowerCase();
12
+ return SURFACES.find((surface) => surface.id === needle ||
13
+ surface.name.toLowerCase() === needle ||
14
+ surface.aliases?.includes(needle));
15
+ }
16
+ export function surfaceIds() {
17
+ return SURFACES.map((surface) => surface.id);
18
+ }
19
+ export async function resolveInstall(surface, explicitPath) {
20
+ if (explicitPath) {
21
+ return { gameId: surface.id, root: explicitPath, source: "manual" };
22
+ }
23
+ const detected = await surface.detect();
24
+ return detected[0];
25
+ }
26
+ export async function installGameVersion(surface, install) {
27
+ if (install.version)
28
+ return install.version;
29
+ if (!surface.compat)
30
+ return undefined;
31
+ const components = await surface.compat.detectComponents(install).catch(() => undefined);
32
+ const game = components?.find((c) => c.kind === "game");
33
+ return game?.marketingVersion ?? game?.version ?? undefined;
34
+ }