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,132 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { parse as parseToml } from "smol-toml";
4
+ import { isDirectory, localAppData, pathExists } from "../../core/fsutil.js";
5
+ export const MODENGINE2_CONFIG = "config_eldenring.toml";
6
+ export function me3ProfilesDir() {
7
+ return localAppData("garyttierney", "me3", "config", "profiles");
8
+ }
9
+ function asRecord(v) {
10
+ return v && typeof v === "object" && !Array.isArray(v) ? v : {};
11
+ }
12
+ export function parseModEngine2Config(text) {
13
+ const doc = asRecord(parseToml(text));
14
+ const modengine = asRecord(doc.modengine);
15
+ const extension = asRecord(doc.extension);
16
+ const loader = asRecord(extension.mod_loader);
17
+ const modsRaw = Array.isArray(loader.mods) ? loader.mods : [];
18
+ const mods = [];
19
+ for (const raw of modsRaw) {
20
+ const m = asRecord(raw);
21
+ if (typeof m.path !== "string")
22
+ continue;
23
+ mods.push({ enabled: m.enabled !== false, name: typeof m.name === "string" ? m.name : path.basename(m.path), path: m.path });
24
+ }
25
+ const dlls = Array.isArray(modengine.external_dlls) ? modengine.external_dlls.filter((d) => typeof d === "string") : [];
26
+ return { debug: modengine.debug === true, externalDlls: dlls, looseParams: loader.loose_params === true, mods };
27
+ }
28
+ export const ME3_GAME_IDS = ["darksouls3", "ds3", "sekiro", "sdt", "eldenring", "er", "elden-ring", "armoredcore6", "ac6", "nightreign", "nr", "nightrein"];
29
+ export const ME3_ELDENRING_IDS = ["eldenring", "er", "elden-ring"];
30
+ export const ME3_PROFILE_VERSION = "v1";
31
+ export const ME3_KNOWN_KEYS = {
32
+ profile: ["profileVersion", "savefile", "start_online", "supports", "packages", "natives", "disable_arxan", "mem_patch", "mem_patch_heap_size", "debug_properties"],
33
+ supports: ["game", "since"],
34
+ packages: ["id", "enabled", "path", "load_after", "load_before"],
35
+ natives: ["path", "optional", "enabled", "load_before", "load_after", "initializer", "finalizer", "load_early"],
36
+ dependent: ["id", "optional"],
37
+ };
38
+ export function parseMe3ProfileDetailed(text) {
39
+ const doc = asRecord(parseToml(text));
40
+ const unknownKeys = [];
41
+ const supportsWithoutGame = [];
42
+ const entriesWithoutPath = [];
43
+ const noteUnknown = (where, record, known) => {
44
+ for (const key of Object.keys(record))
45
+ if (!known.includes(key))
46
+ unknownKeys.push({ where, key });
47
+ };
48
+ noteUnknown("profile", doc, ME3_KNOWN_KEYS.profile);
49
+ const dependents = (raw, where) => {
50
+ if (!Array.isArray(raw))
51
+ return undefined;
52
+ const out = [];
53
+ raw.forEach((d, i) => {
54
+ const r = asRecord(d);
55
+ noteUnknown(`${where}[${i}]`, r, ME3_KNOWN_KEYS.dependent);
56
+ if (typeof r.id === "string")
57
+ out.push({ id: r.id, ...(typeof r.optional === "boolean" ? { optional: r.optional } : {}) });
58
+ });
59
+ return out;
60
+ };
61
+ const list = (block) => {
62
+ const raw = Array.isArray(doc[block]) ? doc[block] : [];
63
+ const out = [];
64
+ raw.forEach((e, index) => {
65
+ const r = asRecord(e);
66
+ noteUnknown(`${block}[${index}]`, r, ME3_KNOWN_KEYS[block]);
67
+ if (typeof r.path !== "string") {
68
+ entriesWithoutPath.push({ block, index });
69
+ return;
70
+ }
71
+ const entry = { path: r.path };
72
+ if (typeof r.id === "string")
73
+ entry.id = r.id;
74
+ if (typeof r.enabled === "boolean")
75
+ entry.enabled = r.enabled;
76
+ if (typeof r.optional === "boolean")
77
+ entry.optional = r.optional;
78
+ const after = dependents(r.load_after, `${block}[${index}].load_after`);
79
+ if (after)
80
+ entry.loadAfter = after;
81
+ const before = dependents(r.load_before, `${block}[${index}].load_before`);
82
+ if (before)
83
+ entry.loadBefore = before;
84
+ out.push(entry);
85
+ });
86
+ return out;
87
+ };
88
+ const supports = [];
89
+ (Array.isArray(doc.supports) ? doc.supports : []).forEach((s, i) => {
90
+ const r = asRecord(s);
91
+ noteUnknown(`supports[${i}]`, r, ME3_KNOWN_KEYS.supports);
92
+ if (typeof r.game === "string")
93
+ supports.push(r.game);
94
+ else
95
+ supportsWithoutGame.push(i);
96
+ });
97
+ const profile = {
98
+ ...(typeof doc.profileVersion === "string" ? { profileVersion: doc.profileVersion } : {}),
99
+ ...(typeof doc.savefile === "string" ? { savefile: doc.savefile } : {}),
100
+ ...(typeof doc.start_online === "boolean" ? { startOnline: doc.start_online } : {}),
101
+ supports,
102
+ packages: list("packages"),
103
+ natives: list("natives"),
104
+ };
105
+ return { profile, unknownKeys, supportsWithoutGame, entriesWithoutPath };
106
+ }
107
+ export function parseMe3Profile(text) {
108
+ return parseMe3ProfileDetailed(text).profile;
109
+ }
110
+ export function gameDir(install) {
111
+ return path.join(install.root, "Game");
112
+ }
113
+ export function modengine2ModDir(install) {
114
+ return path.join(gameDir(install), "mod");
115
+ }
116
+ export async function readModEngine2LoadOrder(install) {
117
+ const configPath = path.join(gameDir(install), MODENGINE2_CONFIG);
118
+ if (!(await pathExists(configPath)))
119
+ return { configPath, entries: [] };
120
+ const config = parseModEngine2Config(await fs.readFile(configPath, "utf8"));
121
+ const modDir = path.resolve(modengine2ModDir(install)).toLowerCase();
122
+ const entries = [];
123
+ let index = 0;
124
+ for (const mod of config.mods) {
125
+ const modPath = mod.path.replace(/\\/g, path.sep);
126
+ const abs = path.isAbsolute(modPath) ? modPath : path.join(gameDir(install), modPath);
127
+ const inModDir = path.dirname(path.resolve(abs)).toLowerCase() === modDir;
128
+ const resolves = inModDir && (await isDirectory(abs));
129
+ entries.push({ index: index++, name: mod.name, enabled: mod.enabled, ...(resolves ? { modId: `modengine2:${path.basename(abs)}` } : {}) });
130
+ }
131
+ return { configPath, config, entries };
132
+ }
@@ -0,0 +1,67 @@
1
+ const FIXTURE = "test/fixtures/logs/eldenring/real/modengine_2026-03-07.log";
2
+ const PRE = String.raw `^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\] \[modengine\] `;
3
+ function re(...parts) {
4
+ return new RegExp(parts.join(""));
5
+ }
6
+ export const SIGNATURES = [
7
+ {
8
+ id: "eldenring.modengine2.initializing",
9
+ game: "eldenring",
10
+ sources: ["modengine2"],
11
+ severity: "info",
12
+ category: "version",
13
+ pattern: re(PRE, String.raw `\[info\] ModEngine version (?<version>\S+) initializing for Elden Ring`),
14
+ explain: "Mod Engine 2 started and named its version; the anchor for a run and for compatibility checks.",
15
+ seenIn: [FIXTURE],
16
+ extract: ["version"],
17
+ },
18
+ {
19
+ id: "eldenring.modengine2.settings-loaded",
20
+ game: "eldenring",
21
+ sources: ["modengine2"],
22
+ severity: "info",
23
+ category: "load",
24
+ pattern: re(PRE, String.raw `\[info\] Local settings loaded: (?<local>true|false), Global settings loaded: (?<global>true|false)`),
25
+ explain: "Which configuration files Mod Engine 2 found: the per-game config_eldenring.toml (local) and modengine2/config.toml (global). false/false means no configuration at all was read.",
26
+ hint: "If both are false, config_eldenring.toml is not beside the launcher; nothing in the mods list will load.",
27
+ seenIn: [FIXTURE],
28
+ extract: ["local", "global"],
29
+ },
30
+ {
31
+ id: "eldenring.modengine2.dll-load-failed",
32
+ game: "eldenring",
33
+ sources: ["modengine2"],
34
+ severity: "error",
35
+ category: "load",
36
+ pattern: re(PRE, String.raw `\[error\] Failed to load DLL (?<path>.+?)\s*$`),
37
+ explain: "A native (DLL) mod named in external_dlls did not load; the game reports this nowhere else. That mod is absent from the run.",
38
+ hint: "Check the DLL's own dependencies and that its path is right relative to the launcher; a DLL built for another game version will also fail here.",
39
+ seenIn: [FIXTURE],
40
+ extract: ["path"],
41
+ },
42
+ {
43
+ id: "eldenring.modengine2.mod-installed",
44
+ game: "eldenring",
45
+ sources: ["modengine2"],
46
+ severity: "info",
47
+ category: "order",
48
+ pattern: re(PRE, String.raw `\[info\] Installing mod location (?<path>.+?)\s*$`),
49
+ explain: "One line per enabled mod folder the loader installed, so the set of lines is the set of " +
50
+ "mods actually in the run. Priority is the order of the mods list in config_eldenring.toml " +
51
+ "(the first enabled entry wins a conflicting file, regulation.bin included); whether these " +
52
+ "lines are printed in that same order is not confirmed — read the config for the order.",
53
+ seenIn: [FIXTURE],
54
+ extract: ["path"],
55
+ },
56
+ {
57
+ id: "eldenring.modengine2.hooks-applied",
58
+ game: "eldenring",
59
+ sources: ["modengine2"],
60
+ severity: "info",
61
+ category: "success",
62
+ pattern: re(PRE, String.raw `\[info\] Applied (?<count>\d+) hooks`),
63
+ explain: "The injection took: the loader hooked the game's asset system.",
64
+ seenIn: [FIXTURE],
65
+ extract: ["count"],
66
+ },
67
+ ];
@@ -0,0 +1,256 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { isDirectory, pathExists } from "../../core/fsutil.js";
4
+ import { parseXml } from "../../core/text/xml.js";
5
+ export const PARAMDEF_TYPES = ["s8", "u8", "s16", "u16", "s32", "u32", "b32", "f32", "angle32", "f64", "fixstr", "fixstrW", "dummy8"];
6
+ export class ParamdefError extends Error {
7
+ file;
8
+ constructor(message, file) {
9
+ super(file ? `${file}: ${message}` : message);
10
+ this.file = file;
11
+ this.name = "ParamdefError";
12
+ }
13
+ }
14
+ const DEF_RE = /^(s8|u8|s16|u16|s32|u32|b32|f32|angle32|f64|fixstr|fixstrW|dummy8)\s+([A-Za-z_][A-Za-z0-9_]*)(?::(\d+))?(?:\[(\d+)\])?(?:\s*=\s*(-?\d+(?:\.\d+)?))?\s*$/;
15
+ export function parseParamdefFieldDef(def) {
16
+ const m = DEF_RE.exec(def.trim());
17
+ if (!m)
18
+ throw new ParamdefError(`field Def "${def}" is not "<type> <name>[:bits][[n]][ = default]" with a known type`);
19
+ const out = { type: m[1], name: m[2], def: def.trim() };
20
+ if (m[3] !== undefined)
21
+ out.bits = Number(m[3]);
22
+ if (m[4] !== undefined)
23
+ out.arrayLength = Number(m[4]);
24
+ if (m[5] !== undefined)
25
+ out.defaultValue = Number(m[5]);
26
+ return out;
27
+ }
28
+ function child(el, tag) {
29
+ return el.children.find((c) => c.tag === tag);
30
+ }
31
+ function childText(el, tag) {
32
+ const c = child(el, tag);
33
+ return c ? c.text.trim() : undefined;
34
+ }
35
+ function optionalNumber(el, tag) {
36
+ const t = childText(el, tag);
37
+ if (t === undefined || t === "")
38
+ return undefined;
39
+ const n = Number(t);
40
+ return Number.isFinite(n) ? n : undefined;
41
+ }
42
+ export function parseParamdef(xml, file) {
43
+ const root = parseXml(xml);
44
+ if (root.tag !== "PARAMDEF")
45
+ throw new ParamdefError(`root element is <${root.tag}>, not <PARAMDEF>`, file);
46
+ const paramType = childText(root, "ParamType");
47
+ if (!paramType)
48
+ throw new ParamdefError("no <ParamType>", file);
49
+ const fieldsEl = child(root, "Fields");
50
+ if (!fieldsEl)
51
+ throw new ParamdefError("no <Fields>", file);
52
+ const fields = [];
53
+ for (const f of fieldsEl.children) {
54
+ if (f.tag !== "Field")
55
+ continue;
56
+ const def = f.attrs.Def;
57
+ if (def === undefined)
58
+ throw new ParamdefError("a <Field> has no Def attribute", file);
59
+ const field = { ...parseParamdefFieldDef(def) };
60
+ const displayName = childText(f, "DisplayName");
61
+ if (displayName)
62
+ field.displayName = displayName;
63
+ const description = childText(f, "Description");
64
+ if (description)
65
+ field.description = description;
66
+ const enumName = childText(f, "Enum");
67
+ if (enumName)
68
+ field.enum = enumName;
69
+ const minimum = optionalNumber(f, "Minimum");
70
+ if (minimum !== undefined)
71
+ field.minimum = minimum;
72
+ const maximum = optionalNumber(f, "Maximum");
73
+ if (maximum !== undefined)
74
+ field.maximum = maximum;
75
+ const increment = optionalNumber(f, "Increment");
76
+ if (increment !== undefined)
77
+ field.increment = increment;
78
+ const sortId = optionalNumber(f, "SortID");
79
+ if (sortId !== undefined)
80
+ field.sortId = sortId;
81
+ fields.push(field);
82
+ }
83
+ const bool = (tag) => (childText(root, tag) ?? "").toLowerCase() === "true";
84
+ const out = {
85
+ paramType,
86
+ dataVersion: optionalNumber(root, "DataVersion") ?? 0,
87
+ formatVersion: optionalNumber(root, "FormatVersion") ?? 0,
88
+ bigEndian: bool("BigEndian"),
89
+ unicode: bool("Unicode"),
90
+ fields,
91
+ };
92
+ if (file)
93
+ out.file = file;
94
+ return out;
95
+ }
96
+ export function parseTdf(text, file) {
97
+ const lines = text
98
+ .replace(/^/, "")
99
+ .split(/\r?\n/)
100
+ .map((l) => l.trim())
101
+ .filter((l) => l.length > 0);
102
+ const unquote = (s) => s.replace(/^"(.*)"$/, "$1");
103
+ if (lines.length < 2)
104
+ throw new ParamdefError("a tdf needs a name line and a type line", file);
105
+ const entries = [];
106
+ for (const line of lines.slice(2)) {
107
+ const m = /^"([^"]*)"\s*,\s*"?(-?\d+)"?$/.exec(line);
108
+ if (!m)
109
+ throw new ParamdefError(`tdf line "${line}" is not "label","value"`, file);
110
+ entries.push({ label: m[1], value: Number(m[2]) });
111
+ }
112
+ return { name: unquote(lines[0]), type: unquote(lines[1]), entries };
113
+ }
114
+ export async function findParamdexGameDir(dir) {
115
+ for (const candidate of [dir, path.join(dir, "ER")]) {
116
+ if (await isDirectory(path.join(candidate, "Defs")))
117
+ return candidate;
118
+ }
119
+ return undefined;
120
+ }
121
+ export async function loadParamdex(dir) {
122
+ const gameDir = await findParamdexGameDir(dir);
123
+ if (!gameDir)
124
+ throw new ParamdefError(`no Defs/ directory under ${dir} or ${path.join(dir, "ER")}`);
125
+ const out = { dir: gameDir, defs: new Map(), byName: new Map(), tdfs: new Map(), problems: [] };
126
+ const defsDir = path.join(gameDir, "Defs");
127
+ for (const name of (await fs.readdir(defsDir)).sort()) {
128
+ if (path.extname(name).toLowerCase() !== ".xml")
129
+ continue;
130
+ const file = path.join(defsDir, name);
131
+ try {
132
+ const def = parseParamdef(await fs.readFile(file, "utf8"), file);
133
+ out.defs.set(def.paramType, def);
134
+ out.byName.set(path.basename(name, path.extname(name)).toLowerCase(), def);
135
+ }
136
+ catch (err) {
137
+ out.problems.push(err.message);
138
+ }
139
+ }
140
+ const tdfsDir = path.join(gameDir, "Tdfs");
141
+ if (await pathExists(tdfsDir)) {
142
+ for (const name of (await fs.readdir(tdfsDir)).sort()) {
143
+ if (path.extname(name).toLowerCase() !== ".tdf")
144
+ continue;
145
+ const file = path.join(tdfsDir, name);
146
+ try {
147
+ const tdf = parseTdf(await fs.readFile(file, "utf8"), file);
148
+ out.tdfs.set(tdf.name, tdf);
149
+ }
150
+ catch (err) {
151
+ out.problems.push(err.message);
152
+ }
153
+ }
154
+ }
155
+ return out;
156
+ }
157
+ export function findParamdef(paramdex, key) {
158
+ return paramdex.defs.get(key) ?? paramdex.byName.get(key.toLowerCase().replace(/\.xml$/, ""));
159
+ }
160
+ const INT_RANGES = {
161
+ s8: [-128, 127],
162
+ u8: [0, 255],
163
+ s16: [-32768, 32767],
164
+ u16: [0, 65535],
165
+ s32: [-2147483648, 2147483647],
166
+ u32: [0, 4294967295],
167
+ dummy8: [0, 255],
168
+ };
169
+ const INT_RE = /^-?\d+$/;
170
+ function checkInteger(field, raw) {
171
+ if (!INT_RE.test(raw))
172
+ return { kind: "type", message: `"${raw}" is not an integer for ${field.type} ${field.name}` };
173
+ const n = Number(raw);
174
+ if (field.bits !== undefined) {
175
+ const max = 2 ** field.bits - 1;
176
+ if (n < 0 || n > max)
177
+ return { kind: "range", message: `${field.name} is a ${field.bits}-bit field (0..${max}); ${raw} does not fit` };
178
+ return undefined;
179
+ }
180
+ const [lo, hi] = INT_RANGES[field.type];
181
+ if (n < lo || n > hi)
182
+ return { kind: "range", message: `${raw} is outside ${field.type}'s range ${lo}..${hi} for ${field.name}` };
183
+ return undefined;
184
+ }
185
+ export function checkFieldValue(field, rawIn, tdfs) {
186
+ let raw = rawIn.trim();
187
+ if (field.type === "dummy8" && field.bits !== undefined && field.arrayLength === undefined) {
188
+ const single = /^\[\s*(-?\d+)\s*\]$/.exec(raw);
189
+ if (single)
190
+ raw = single[1];
191
+ }
192
+ const isArray = field.arrayLength !== undefined && field.type === "dummy8";
193
+ if (isArray) {
194
+ const m = /^\[(.*)\]$/.exec(raw);
195
+ const parts = (m ? m[1] : raw).split(/[|,\s]+/).filter((p) => p.length > 0);
196
+ if (parts.length !== field.arrayLength) {
197
+ return { kind: "array", message: `${field.name} is dummy8[${field.arrayLength}] but the cell "${raw}" has ${parts.length} entr${parts.length === 1 ? "y" : "ies"}` };
198
+ }
199
+ for (const p of parts) {
200
+ if (!INT_RE.test(p) || Number(p) < 0 || Number(p) > 255)
201
+ return { kind: "array", message: `${field.name}: "${p}" is not a byte (0..255)` };
202
+ }
203
+ return undefined;
204
+ }
205
+ switch (field.type) {
206
+ case "s8":
207
+ case "u8":
208
+ case "s16":
209
+ case "u16":
210
+ case "s32":
211
+ case "u32":
212
+ case "dummy8": {
213
+ const problem = checkInteger(field, raw);
214
+ if (problem)
215
+ return problem;
216
+ break;
217
+ }
218
+ case "b32": {
219
+ if (!/^(0|1|true|false)$/i.test(raw))
220
+ return { kind: "type", message: `"${raw}" is not a b32 value (0, 1, True or False) for ${field.name}` };
221
+ break;
222
+ }
223
+ case "f32":
224
+ case "angle32":
225
+ case "f64": {
226
+ if (raw === "" || !/^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(raw))
227
+ return { kind: "type", message: `"${raw}" is not a number for ${field.type} ${field.name}` };
228
+ const n = Number(raw);
229
+ if (!Number.isFinite(n))
230
+ return { kind: "range", message: `"${raw}" is not finite for ${field.type} ${field.name}` };
231
+ if (field.type !== "f64" && Math.abs(n) > 3.4028235e38)
232
+ return { kind: "range", message: `${raw} is outside f32's range for ${field.name}` };
233
+ break;
234
+ }
235
+ case "fixstr":
236
+ case "fixstrW": {
237
+ const limit = field.arrayLength;
238
+ const length = field.type === "fixstr" ? Buffer.byteLength(raw, "utf8") : [...raw].length;
239
+ if (limit !== undefined && length > limit) {
240
+ return { kind: "range", message: `${field.name} is ${field.type}[${limit}] and "${raw}" is ${length} ${field.type === "fixstr" ? "bytes" : "characters"}` };
241
+ }
242
+ return undefined;
243
+ }
244
+ }
245
+ if (field.enum && tdfs) {
246
+ const tdf = tdfs.get(field.enum);
247
+ if (tdf) {
248
+ const n = Number(raw);
249
+ if (!tdf.entries.some((e) => e.value === n)) {
250
+ const known = tdf.entries.map((e) => `${e.value}=${e.label}`).join(", ");
251
+ return { kind: "enum", message: `${field.name} is ${field.enum}; ${raw} is not one of its values (${known})` };
252
+ }
253
+ }
254
+ }
255
+ return undefined;
256
+ }
@@ -0,0 +1,129 @@
1
+ import { parseXml } from "../../core/text/xml.js";
2
+ const HEADER_RE = /^ID([^A-Za-z0-9_])Name(?:\1|$)/;
3
+ export function detectCsvDelimiter(firstLine) {
4
+ const h = HEADER_RE.exec(firstLine);
5
+ if (h)
6
+ return h[1];
7
+ let best = ",";
8
+ let bestCount = -1;
9
+ for (const d of [",", ";", "\t"]) {
10
+ const count = firstLine.split(d).length - 1;
11
+ if (count > bestCount) {
12
+ best = d;
13
+ bestCount = count;
14
+ }
15
+ }
16
+ return best;
17
+ }
18
+ export function parseDsmsCsv(text) {
19
+ const lines = text.replace(/^/, "").split("\n");
20
+ const first = (lines.find((l) => l.trim().length > 0) ?? "").replace(/\r$/, "");
21
+ const delimiter = detectCsvDelimiter(first);
22
+ const out = { shape: "dsms-csv", delimiter, rows: [] };
23
+ let sawHeader = false;
24
+ lines.forEach((rawLine, index) => {
25
+ const line = rawLine.replace(/\r$/, "");
26
+ if (line.trim().length === 0)
27
+ return;
28
+ const cols = line.split(delimiter);
29
+ if (!sawHeader && out.rows.length === 0 && HEADER_RE.test(line)) {
30
+ sawHeader = true;
31
+ out.fields = cols.slice(2).map((c) => c.trim()).filter((c, i, all) => !(c === "" && i === all.length - 1));
32
+ return;
33
+ }
34
+ const idText = (cols[0] ?? "").trim();
35
+ const row = { line: index + 1, idText };
36
+ if (/^-?\d+$/.test(idText))
37
+ row.id = Number(idText);
38
+ if (cols.length > 1)
39
+ row.name = cols[1];
40
+ const values = cols.slice(2);
41
+ if (out.fields) {
42
+ row.cells = new Map();
43
+ out.fields.forEach((f, i) => {
44
+ if (i < values.length)
45
+ row.cells.set(f, values[i]);
46
+ });
47
+ if (values.length !== out.fields.length)
48
+ row.positional = values;
49
+ }
50
+ else {
51
+ row.positional = values;
52
+ }
53
+ out.rows.push(row);
54
+ });
55
+ return out;
56
+ }
57
+ function child(el, tag) {
58
+ return el.children.find((c) => c.tag === tag);
59
+ }
60
+ function num(s) {
61
+ if (s === undefined || s.trim() === "")
62
+ return undefined;
63
+ const n = Number(s);
64
+ return Number.isFinite(n) ? n : undefined;
65
+ }
66
+ export function isWitchyParamXml(root) {
67
+ return root.tag === "param" && child(root, "rows") !== undefined;
68
+ }
69
+ export function parseWitchyParamXml(text) {
70
+ const root = parseXml(text);
71
+ if (!isWitchyParamXml(root))
72
+ return undefined;
73
+ const out = { shape: "witchy-xml", fields: [], rows: [], unreadRows: 0 };
74
+ const type = child(root, "type")?.text.trim();
75
+ if (type)
76
+ out.paramType = type;
77
+ const paramdef = child(root, "paramdef");
78
+ if (paramdef) {
79
+ const t = child(paramdef, "type")?.text.trim();
80
+ if (t)
81
+ out.paramdefType = t;
82
+ const dv = num(child(paramdef, "dataVersion")?.text);
83
+ if (dv !== undefined)
84
+ out.dataVersion = dv;
85
+ const fv = num(child(paramdef, "formatVersion")?.text);
86
+ if (fv !== undefined)
87
+ out.formatVersion = fv;
88
+ }
89
+ const fieldsEl = child(root, "fields");
90
+ if (fieldsEl) {
91
+ for (const f of fieldsEl.children) {
92
+ if (f.tag !== "field" || !f.attrs.name)
93
+ continue;
94
+ const entry = { name: f.attrs.name };
95
+ if (f.attrs.type)
96
+ entry.type = f.attrs.type;
97
+ const al = num(f.attrs.arraylength);
98
+ if (al !== undefined)
99
+ entry.arrayLength = al;
100
+ const bs = num(f.attrs.bitsize);
101
+ if (bs !== undefined)
102
+ entry.bitSize = bs;
103
+ out.fields.push(entry);
104
+ }
105
+ }
106
+ const rowsEl = child(root, "rows");
107
+ for (const r of rowsEl.children) {
108
+ if (r.tag !== "row")
109
+ continue;
110
+ const idText = (r.attrs.id ?? "").trim();
111
+ const row = { idText };
112
+ if (/^-?\d+$/.test(idText))
113
+ row.id = Number(idText);
114
+ if (r.attrs.name !== undefined)
115
+ row.name = r.attrs.name;
116
+ const cellElements = r.children.filter((c) => c.tag === "field" && c.attrs.name !== undefined);
117
+ if (cellElements.length > 0) {
118
+ row.cells = new Map(cellElements.map((c) => [c.attrs.name, c.text]));
119
+ }
120
+ else if (r.text.trim().length > 0) {
121
+ out.unreadRows++;
122
+ }
123
+ else {
124
+ row.cells = new Map(Object.entries(r.attrs).filter(([k]) => k !== "id" && k !== "name" && k !== "paramdexName"));
125
+ }
126
+ out.rows.push(row);
127
+ }
128
+ return out;
129
+ }