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,195 @@
1
+ import { promises as fs } from "node:fs";
2
+ const DEFAULT_MAX_LINES = 2000;
3
+ const DEFAULT_MAX_TAIL_BYTES = 8 * 1024 * 1024;
4
+ const LAUNCH_GRACE_MS = 60_000;
5
+ const BRACKET_TIMESTAMP = /^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\]/;
6
+ const BARE_TIMESTAMP = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/;
7
+ function parseLineTimestamp(line) {
8
+ const raw = BRACKET_TIMESTAMP.exec(line)?.[1] ?? BARE_TIMESTAMP.exec(line)?.[1];
9
+ if (!raw)
10
+ return undefined;
11
+ const date = new Date(raw.replace(" ", "T"));
12
+ return Number.isNaN(date.getTime()) ? undefined : date;
13
+ }
14
+ function splitLines(text) {
15
+ if (text.length === 0)
16
+ return [];
17
+ const lines = text.split(/\r\n|\r|\n/);
18
+ if (lines.length > 0 && lines[lines.length - 1] === "")
19
+ lines.pop();
20
+ return lines;
21
+ }
22
+ function buildScanKey(path, birthtimeMs, mtimeMs, size) {
23
+ return `${path}|${birthtimeMs}|${mtimeMs}|${size}`;
24
+ }
25
+ function parseScanKey(key) {
26
+ const parts = key.split("|");
27
+ if (parts.length < 4)
28
+ return undefined;
29
+ const size = Number(parts[parts.length - 1]);
30
+ const mtimeMs = Number(parts[parts.length - 2]);
31
+ const birthtimeMs = Number(parts[parts.length - 3]);
32
+ const path = parts.slice(0, parts.length - 3).join("|");
33
+ if (!path || !Number.isFinite(size) || !Number.isFinite(mtimeMs) || !Number.isFinite(birthtimeMs)) {
34
+ return undefined;
35
+ }
36
+ return { path, birthtimeMs, mtimeMs, size };
37
+ }
38
+ export async function scanSource(source, signatures, scope, ctx) {
39
+ if (!source.exists) {
40
+ return { status: { source, linesScanned: 0, findingCount: 0 }, findings: [] };
41
+ }
42
+ let stat;
43
+ try {
44
+ stat = await fs.stat(source.path);
45
+ }
46
+ catch {
47
+ return { status: { source, linesScanned: 0, findingCount: 0 }, findings: [] };
48
+ }
49
+ const sizeBytes = stat.size;
50
+ const modifiedAt = stat.mtime;
51
+ const zeroByte = sizeBytes === 0;
52
+ const stale = ctx.lastLaunch ? modifiedAt.getTime() < ctx.lastLaunch.getTime() - LAUNCH_GRACE_MS : undefined;
53
+ const maxLines = scope.maxLines ?? DEFAULT_MAX_LINES;
54
+ if (source.binary) {
55
+ const status = {
56
+ source,
57
+ sizeBytes,
58
+ modifiedAt: modifiedAt.toISOString(),
59
+ zeroByte,
60
+ linesScanned: 0,
61
+ endOffset: sizeBytes,
62
+ findingCount: 0,
63
+ };
64
+ if (stale !== undefined)
65
+ status.stale = stale;
66
+ return { status, findings: [] };
67
+ }
68
+ const birthtimeMs = Math.round(stat.birthtimeMs);
69
+ const mtimeMs = Math.round(stat.mtimeMs);
70
+ const key = buildScanKey(source.path, birthtimeMs, mtimeMs, sizeBytes);
71
+ const requestedOffset = scope.offsets?.[source.id];
72
+ const hadOffset = requestedOffset !== undefined;
73
+ let startOffset = requestedOffset ?? 0;
74
+ let rotated = false;
75
+ let rewritten = false;
76
+ if (startOffset > sizeBytes) {
77
+ rotated = true;
78
+ startOffset = 0;
79
+ }
80
+ const previousKey = scope.offsetKeys?.[source.id];
81
+ if (hadOffset && previousKey !== undefined && !rotated) {
82
+ const parsed = parseScanKey(previousKey);
83
+ if (!parsed) {
84
+ rotated = true;
85
+ startOffset = 0;
86
+ }
87
+ else if (parsed.path !== source.path || parsed.birthtimeMs !== birthtimeMs) {
88
+ rotated = true;
89
+ startOffset = 0;
90
+ }
91
+ else if (requestedOffset === sizeBytes && mtimeMs > parsed.mtimeMs) {
92
+ rotated = true;
93
+ rewritten = true;
94
+ startOffset = 0;
95
+ }
96
+ }
97
+ let clipped = false;
98
+ if ((!hadOffset || rotated) && sizeBytes - startOffset > DEFAULT_MAX_TAIL_BYTES) {
99
+ startOffset = sizeBytes - DEFAULT_MAX_TAIL_BYTES;
100
+ clipped = true;
101
+ }
102
+ let text = "";
103
+ const length = sizeBytes - startOffset;
104
+ if (length > 0) {
105
+ const handle = await fs.open(source.path, "r");
106
+ try {
107
+ const buffer = Buffer.alloc(length);
108
+ await handle.read(buffer, 0, length, startOffset);
109
+ text = buffer.toString("utf8");
110
+ }
111
+ finally {
112
+ await handle.close();
113
+ }
114
+ }
115
+ if (startOffset === 0 && text.charCodeAt(0) === 0xfeff)
116
+ text = text.slice(1);
117
+ let lines = splitLines(text);
118
+ if (clipped && lines.length > 0)
119
+ lines.shift();
120
+ if ((!hadOffset || rotated) && lines.length > maxLines) {
121
+ lines = lines.slice(lines.length - maxLines);
122
+ }
123
+ const sinceDate = scope.since ? new Date(scope.since) : undefined;
124
+ let anyTimestampSeen = false;
125
+ let lastTimestamp;
126
+ const findingsBySignature = new Map();
127
+ const findings = [];
128
+ for (let i = 0; i < lines.length; i++) {
129
+ const line = lines[i];
130
+ if (sinceDate) {
131
+ const ts = parseLineTimestamp(line);
132
+ if (ts) {
133
+ anyTimestampSeen = true;
134
+ lastTimestamp = ts;
135
+ }
136
+ const effective = ts ?? lastTimestamp;
137
+ if (effective && effective.getTime() < sinceDate.getTime())
138
+ continue;
139
+ }
140
+ for (const sig of signatures) {
141
+ const match = sig.pattern.exec(line);
142
+ if (!match)
143
+ continue;
144
+ const existing = findingsBySignature.get(sig.id);
145
+ if (existing) {
146
+ existing.repeats = (existing.repeats ?? 0) + 1;
147
+ continue;
148
+ }
149
+ const finding = {
150
+ sourceId: source.id,
151
+ signatureId: sig.id,
152
+ severity: sig.severity,
153
+ category: sig.category,
154
+ lineNumber: i + 1,
155
+ line,
156
+ explain: sig.explain,
157
+ };
158
+ if (sig.hint)
159
+ finding.hint = sig.hint;
160
+ if (sig.systemImpact === false)
161
+ finding.systemImpact = false;
162
+ if (sig.extract?.length && match.groups) {
163
+ const extracted = {};
164
+ for (const name of sig.extract) {
165
+ const value = match.groups[name];
166
+ if (value !== undefined)
167
+ extracted[name] = value;
168
+ }
169
+ if (Object.keys(extracted).length > 0)
170
+ finding.extracted = extracted;
171
+ }
172
+ findingsBySignature.set(sig.id, finding);
173
+ findings.push(finding);
174
+ }
175
+ }
176
+ const status = {
177
+ source,
178
+ sizeBytes,
179
+ modifiedAt: modifiedAt.toISOString(),
180
+ zeroByte,
181
+ linesScanned: lines.length,
182
+ endOffset: sizeBytes,
183
+ key,
184
+ findingCount: findings.length,
185
+ };
186
+ if (stale !== undefined)
187
+ status.stale = stale;
188
+ if (rotated)
189
+ status.rotated = true;
190
+ if (rewritten)
191
+ status.rewritten = true;
192
+ if (sinceDate && !anyTimestampSeen)
193
+ status.timestamps = false;
194
+ return { status, findings };
195
+ }
@@ -0,0 +1,160 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { findRunningProcesses } from "../process.js";
3
+ import { resolveLogs } from "../surfaceutil.js";
4
+ import { scanSource } from "./scan.js";
5
+ const FINDINGS_CAP = 200;
6
+ export async function triageLogs(surface, install, registry, scope = {}) {
7
+ const sources = await resolveLogs(surface, install);
8
+ const processResult = await findRunningProcesses(surface.processNames ?? []);
9
+ const gameRunning = processResult.checked ? processResult.running.length > 0 : undefined;
10
+ let lastLaunch;
11
+ let newestModified;
12
+ for (const source of sources) {
13
+ if (!source.exists)
14
+ continue;
15
+ try {
16
+ const stat = await fs.stat(source.path);
17
+ if (!newestModified || stat.mtime > newestModified)
18
+ newestModified = stat.mtime;
19
+ if (stat.birthtimeMs > 0 && (!lastLaunch || stat.birthtime > lastLaunch))
20
+ lastLaunch = stat.birthtime;
21
+ }
22
+ catch {
23
+ }
24
+ }
25
+ if (!lastLaunch)
26
+ lastLaunch = newestModified;
27
+ let missingReasons = {};
28
+ if (surface.explainMissing && sources.some((s) => !s.exists)) {
29
+ try {
30
+ missingReasons = await surface.explainMissing(install);
31
+ }
32
+ catch {
33
+ }
34
+ }
35
+ const statuses = [];
36
+ const allFindings = [];
37
+ const offsets = {};
38
+ const offsetKeys = {};
39
+ for (const source of sources) {
40
+ const signatures = registry.forSource(surface.id, source.id);
41
+ const { status, findings } = await scanSource(source, signatures, scope, { lastLaunch });
42
+ if (!source.exists && missingReasons[source.id])
43
+ status.missingReason = missingReasons[source.id];
44
+ statuses.push(status);
45
+ allFindings.push(...findings);
46
+ if (status.endOffset !== undefined)
47
+ offsets[source.id] = status.endOffset;
48
+ if (status.key !== undefined)
49
+ offsetKeys[source.id] = status.key;
50
+ }
51
+ const truncated = allFindings.length > FINDINGS_CAP;
52
+ const findings = truncated ? allFindings.slice(0, FINDINGS_CAP) : allFindings;
53
+ const summary = { errors: 0, warnings: 0, successes: 0 };
54
+ for (const finding of allFindings) {
55
+ if (finding.category === "success")
56
+ summary.successes++;
57
+ else if (finding.severity === "error")
58
+ summary.errors++;
59
+ else if (finding.severity === "warn")
60
+ summary.warnings++;
61
+ }
62
+ const rootIds = new Set(sources.map((s) => s.rootId).filter((id) => id !== undefined));
63
+ const systems = {};
64
+ const systemNotes = {};
65
+ for (const rootId of rootIds) {
66
+ const rootSources = sources.filter((s) => s.rootId === rootId);
67
+ const rootFindings = allFindings.filter((f) => rootSources.some((s) => s.id === f.sourceId));
68
+ const impactfulErrors = rootFindings.filter((f) => f.severity === "error" && f.systemImpact !== false);
69
+ const contentFindings = rootFindings.filter((f) => f.systemImpact === false);
70
+ if (impactfulErrors.length > 0) {
71
+ systems[rootId] = "failed";
72
+ continue;
73
+ }
74
+ const hasSuccess = rootFindings.some((f) => f.category === "success");
75
+ const hasUsableSource = rootSources.some((s) => {
76
+ const status = statuses.find((st) => st.source.id === s.id);
77
+ return s.exists && status !== undefined && !status.zeroByte && !status.stale;
78
+ });
79
+ systems[rootId] = hasSuccess || hasUsableSource ? "loaded" : "unknown";
80
+ if (systems[rootId] === "loaded" && contentFindings.length > 0) {
81
+ const first = contentFindings[0];
82
+ const count = contentFindings.length;
83
+ systemNotes[rootId] =
84
+ `${count} content ${count === 1 ? "issue" : "issues"} found that do not affect loading ` +
85
+ `(first: ${first.signatureId} at line ${first.lineNumber} — ${first.explain})`;
86
+ }
87
+ }
88
+ const verdict = {
89
+ game: surface.id,
90
+ install: install.root,
91
+ scope,
92
+ lastLaunch: lastLaunch?.toISOString(),
93
+ sources: statuses,
94
+ findings,
95
+ summary,
96
+ systems,
97
+ headline: buildHeadline(sources, statuses, allFindings, systems),
98
+ offsets,
99
+ offsetKeys,
100
+ };
101
+ if (gameRunning !== undefined)
102
+ verdict.gameRunning = gameRunning;
103
+ if (truncated)
104
+ verdict.truncated = true;
105
+ if (Object.keys(systemNotes).length > 0)
106
+ verdict.systemNotes = systemNotes;
107
+ return verdict;
108
+ }
109
+ function buildHeadline(sources, statuses, findings, systems) {
110
+ if (sources.length === 0) {
111
+ return "No log sources are declared for this game; triage has nothing to read.";
112
+ }
113
+ const note = [describeFullyMissingSystems(sources, statuses), describeRewrittenSources(statuses)]
114
+ .filter((n) => n !== undefined)
115
+ .join(" ") || undefined;
116
+ const firstError = findings.find((f) => f.severity === "error");
117
+ if (firstError) {
118
+ const source = sources.find((s) => s.id === firstError.sourceId);
119
+ const label = source?.label ?? firstError.sourceId;
120
+ return appendNote(`${label}: ${firstError.explain} (line ${firstError.lineNumber}).`, note);
121
+ }
122
+ const usableCount = statuses.filter((st) => st.source.exists && !st.zeroByte && !st.stale).length;
123
+ if (usableCount === 0) {
124
+ return appendNote(`All ${sources.length} log source(s) were stale, empty, or missing; nothing to report this run.`, note);
125
+ }
126
+ const loadedCount = Object.values(systems).filter((v) => v === "loaded").length;
127
+ return appendNote(`No errors in ${sources.length} sources; ${loadedCount} system(s) confirmed loaded.`, note);
128
+ }
129
+ function appendNote(headline, note) {
130
+ return note ? `${headline} ${note}` : headline;
131
+ }
132
+ function describeRewrittenSources(statuses) {
133
+ const rewritten = statuses.filter((st) => st.rewritten);
134
+ if (rewritten.length === 0)
135
+ return undefined;
136
+ const labels = rewritten.map((st) => st.source.label).join(", ");
137
+ const plural = rewritten.length > 1;
138
+ return (`${labels} ${plural ? "were" : "was"} truncated and rewritten in place since the last check ` +
139
+ `(same name, unchanged creation time); ${plural ? "their" : "its"} full content was rescanned ` +
140
+ `rather than treated as unchanged.`);
141
+ }
142
+ function describeFullyMissingSystems(sources, statuses) {
143
+ const notes = [];
144
+ const seenGroups = new Set();
145
+ for (const status of statuses) {
146
+ if (status.source.exists || !status.missingReason)
147
+ continue;
148
+ const groupKey = status.source.rootId ?? `source:${status.source.id}`;
149
+ if (seenGroups.has(groupKey))
150
+ continue;
151
+ const group = status.source.rootId
152
+ ? sources.filter((s) => s.rootId === status.source.rootId)
153
+ : [status.source];
154
+ if (!group.every((s) => !s.exists))
155
+ continue;
156
+ seenGroups.add(groupKey);
157
+ notes.push(`${status.source.label} is missing: ${status.missingReason}`);
158
+ }
159
+ return notes.length > 0 ? notes.join(" ") : undefined;
160
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ export function parseMetaLsxModuleInfo(xml) {
2
+ const withoutComments = xml.replace(/<!--[\s\S]*?-->/g, "");
3
+ const node = withoutComments.match(/<node\s+id="ModuleInfo">([\s\S]*?)(?:<children>|<\/node>)/);
4
+ if (!node)
5
+ return undefined;
6
+ const body = node[1] ?? "";
7
+ const attr = (id) => body.match(new RegExp(`<attribute\\s+id="${id}"[^>]*?value="([^"]*)"`))?.[1];
8
+ const found = { folder: attr("Folder"), name: attr("Name"), uuid: attr("UUID"), version64: attr("Version64") };
9
+ return found.folder || found.name || found.uuid ? found : undefined;
10
+ }
@@ -0,0 +1,66 @@
1
+ function readLengthExtra(src, pos) {
2
+ let extra = 0;
3
+ for (;;) {
4
+ if (pos.ip >= src.length) {
5
+ throw new Error("decodeLz4Block: truncated length-extension byte");
6
+ }
7
+ const b = src[pos.ip++];
8
+ extra += b;
9
+ if (b !== 255)
10
+ break;
11
+ }
12
+ return extra;
13
+ }
14
+ export function decodeLz4Block(src, expectedSize) {
15
+ const out = new Uint8Array(expectedSize);
16
+ const pos = { ip: 0 };
17
+ let op = 0;
18
+ const srcLen = src.length;
19
+ while (op < expectedSize) {
20
+ if (pos.ip >= srcLen) {
21
+ throw new Error(`decodeLz4Block: unexpected end of input after ${op} of ${expectedSize} output bytes`);
22
+ }
23
+ const token = src[pos.ip++];
24
+ let literalLength = token >> 4;
25
+ if (literalLength === 15)
26
+ literalLength += readLengthExtra(src, pos);
27
+ if (literalLength > 0) {
28
+ if (pos.ip + literalLength > srcLen) {
29
+ throw new Error("decodeLz4Block: literal run reads past end of input");
30
+ }
31
+ if (op + literalLength > expectedSize) {
32
+ throw new Error("decodeLz4Block: literal run writes past expected output size");
33
+ }
34
+ out.set(src.subarray(pos.ip, pos.ip + literalLength), op);
35
+ pos.ip += literalLength;
36
+ op += literalLength;
37
+ }
38
+ if (op >= expectedSize)
39
+ break;
40
+ if (pos.ip + 2 > srcLen) {
41
+ throw new Error("decodeLz4Block: truncated match offset");
42
+ }
43
+ const offset = src[pos.ip] | (src[pos.ip + 1] << 8);
44
+ pos.ip += 2;
45
+ if (offset === 0) {
46
+ throw new Error("decodeLz4Block: match offset of zero");
47
+ }
48
+ if (offset > op) {
49
+ throw new Error("decodeLz4Block: match offset points before the start of the output");
50
+ }
51
+ let matchLength = (token & 0x0f) + 4;
52
+ if ((token & 0x0f) === 15)
53
+ matchLength += readLengthExtra(src, pos);
54
+ if (op + matchLength > expectedSize) {
55
+ throw new Error("decodeLz4Block: match run writes past expected output size");
56
+ }
57
+ let from = op - offset;
58
+ for (let i = 0; i < matchLength; i++) {
59
+ out[op++] = out[from++];
60
+ }
61
+ }
62
+ if (op !== expectedSize) {
63
+ throw new Error(`decodeLz4Block: decompressed ${op} bytes, expected exactly ${expectedSize}`);
64
+ }
65
+ return out;
66
+ }
@@ -0,0 +1,229 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { statFile, walk } from "./fsutil.js";
4
+ import { resolveRoots } from "./surfaceutil.js";
5
+ export async function listMods(surface, install, options = {}) {
6
+ const allRoots = await resolveRoots(surface, install);
7
+ const roots = allRoots.filter((root) => root.exists && (!options.rootIds || options.rootIds.includes(root.id)));
8
+ const mods = [];
9
+ for (const root of roots) {
10
+ mods.push(...(await listModsInRoot(surface, root, options.fileLimit ?? 2000)));
11
+ }
12
+ return { mods, roots: allRoots };
13
+ }
14
+ async function listModsInRoot(surface, root, fileLimit) {
15
+ const mods = [];
16
+ if (root.layout === "flat") {
17
+ const result = await walk(root.path, { limit: fileLimit });
18
+ if (result.files.length === 0)
19
+ return mods;
20
+ mods.push({
21
+ id: `${root.id}:${root.label}`,
22
+ name: root.label,
23
+ rootId: root.id,
24
+ path: root.path,
25
+ sizeBytes: result.sizeBytes,
26
+ modifiedAt: result.modifiedAt.toISOString(),
27
+ files: result.files,
28
+ warnings: result.truncated ? [`File listing truncated at ${fileLimit} entries.`] : undefined,
29
+ });
30
+ return mods;
31
+ }
32
+ let entries;
33
+ try {
34
+ entries = await fs.readdir(root.path, { withFileTypes: true });
35
+ }
36
+ catch {
37
+ return mods;
38
+ }
39
+ for (const entry of entries) {
40
+ const abs = path.join(root.path, entry.name);
41
+ const isDir = entry.isDirectory();
42
+ if (root.layout === "directory" && !isDir)
43
+ continue;
44
+ if (root.layout === "file") {
45
+ if (isDir)
46
+ continue;
47
+ const ext = path.extname(entry.name).toLowerCase();
48
+ if (root.extensions && !root.extensions.includes(ext))
49
+ continue;
50
+ }
51
+ const result = isDir
52
+ ? await walk(abs, { limit: fileLimit })
53
+ : await statFile(abs).catch(() => undefined);
54
+ if (!result)
55
+ continue;
56
+ const warnings = [];
57
+ if (result.truncated)
58
+ warnings.push(`File listing truncated at ${fileLimit} entries.`);
59
+ let manifest;
60
+ if (surface.readManifest) {
61
+ try {
62
+ manifest = await surface.readManifest(abs, root);
63
+ }
64
+ catch (error) {
65
+ warnings.push(`Manifest unreadable: ${error.message}`);
66
+ }
67
+ }
68
+ mods.push({
69
+ id: `${root.id}:${entry.name}`,
70
+ name: entry.name,
71
+ rootId: root.id,
72
+ path: abs,
73
+ sizeBytes: result.sizeBytes,
74
+ modifiedAt: result.modifiedAt.toISOString(),
75
+ files: result.files,
76
+ manifest,
77
+ warnings: warnings.length ? warnings : undefined,
78
+ });
79
+ }
80
+ return mods;
81
+ }
82
+ const CONFLICT_NOISE = new Set([
83
+ "__folder_managed_by_vortex",
84
+ "db.sqlite3",
85
+ "meta.ini",
86
+ ".gitkeep",
87
+ ".keep",
88
+ "thumbs.db",
89
+ "desktop.ini",
90
+ ".ds_store",
91
+ "readme.txt",
92
+ "readme.md",
93
+ "changelog.txt",
94
+ "license.txt",
95
+ ]);
96
+ function isNoise(relativePath) {
97
+ const base = relativePath.slice(relativePath.lastIndexOf("/") + 1).toLowerCase();
98
+ return CONFLICT_NOISE.has(base);
99
+ }
100
+ export function findConflicts(mods, roots) {
101
+ const byId = new Map(roots.map((root) => [root.id, root]));
102
+ const byRoot = new Map();
103
+ let suppressed = 0;
104
+ const skippedRoots = roots
105
+ .filter((root) => root.exists && root.conflictScope !== "merged")
106
+ .map((root) => ({
107
+ rootId: root.id,
108
+ scope: root.conflictScope,
109
+ reason: root.conflictScope === "isolated"
110
+ ? "Each mod owns a separate subtree here, so file paths cannot collide."
111
+ : "Mods are packed archives; conflicts inside them need game-specific unpacking.",
112
+ }));
113
+ for (const mod of mods) {
114
+ if (byId.get(mod.rootId)?.conflictScope !== "merged")
115
+ continue;
116
+ let claims = byRoot.get(mod.rootId);
117
+ if (!claims) {
118
+ claims = new Map();
119
+ byRoot.set(mod.rootId, claims);
120
+ }
121
+ for (const file of mod.files) {
122
+ if (isNoise(file)) {
123
+ suppressed++;
124
+ continue;
125
+ }
126
+ const key = file.toLowerCase();
127
+ const owners = claims.get(key);
128
+ if (owners)
129
+ owners.push(mod.id);
130
+ else
131
+ claims.set(key, [mod.id]);
132
+ }
133
+ }
134
+ const conflicts = [];
135
+ for (const [rootId, claims] of byRoot) {
136
+ for (const [file, owners] of claims) {
137
+ if (owners.length < 2)
138
+ continue;
139
+ conflicts.push({
140
+ path: file,
141
+ mods: owners,
142
+ rootId,
143
+ kind: byId.get(rootId)?.ordered ? "overwrite" : "ambiguous",
144
+ });
145
+ }
146
+ }
147
+ conflicts.sort((a, b) => b.mods.length - a.mods.length || a.path.localeCompare(b.path));
148
+ return { conflicts, skippedRoots, suppressedNoiseFiles: suppressed };
149
+ }
150
+ export const INERT_COPY_LIMIT = 25;
151
+ export function crossCheckIdentities(order, copies, unreadable, inertLimit = INERT_COPY_LIMIT) {
152
+ const byUuid = new Map();
153
+ for (const copy of copies) {
154
+ if (!copy.uuid)
155
+ continue;
156
+ const key = copy.uuid.toLowerCase();
157
+ const list = byUuid.get(key);
158
+ if (list)
159
+ list.push(copy);
160
+ else
161
+ byUuid.set(key, [copy]);
162
+ }
163
+ const claimed = new Set();
164
+ const entries = order.map((entry) => {
165
+ const key = entry.uuid?.toLowerCase();
166
+ const found = key ? (byUuid.get(key) ?? []) : [];
167
+ if (key)
168
+ claimed.add(key);
169
+ const matches = found.map((copy) => ({
170
+ ...copy,
171
+ folderMatchesEntry: entry.folder === undefined ? undefined : copy.folder === entry.folder,
172
+ }));
173
+ return {
174
+ index: entry.index,
175
+ name: entry.name,
176
+ uuid: entry.uuid,
177
+ entryFolder: entry.folder,
178
+ matches,
179
+ duplicated: matches.length > 1,
180
+ missing: matches.length === 0 && !entry.baseGame,
181
+ ...(entry.baseGame ? { baseGame: true } : {}),
182
+ };
183
+ });
184
+ const inert = copies.filter((copy) => !copy.uuid || !claimed.has(copy.uuid.toLowerCase()));
185
+ return {
186
+ available: true,
187
+ entries,
188
+ inertCopies: {
189
+ total: inert.length,
190
+ ...(inert.length > inertLimit ? { truncated: true } : {}),
191
+ copies: inert.slice(0, inertLimit),
192
+ },
193
+ unreadable,
194
+ };
195
+ }
196
+ export const SHARED_IDENTITY_LIMIT = 25;
197
+ export const SHARED_IDENTITY_COPY_LIMIT = 10;
198
+ export function sharedIdentities(copies, limit = SHARED_IDENTITY_LIMIT, copyLimit = SHARED_IDENTITY_COPY_LIMIT) {
199
+ const byUuid = new Map();
200
+ for (const copy of copies) {
201
+ if (!copy.uuid)
202
+ continue;
203
+ const key = copy.uuid.toLowerCase();
204
+ const list = byUuid.get(key);
205
+ if (list)
206
+ list.push(copy);
207
+ else
208
+ byUuid.set(key, [copy]);
209
+ }
210
+ const groups = [];
211
+ for (const [uuid, group] of byUuid) {
212
+ if (group.length < 2)
213
+ continue;
214
+ groups.push([uuid, group]);
215
+ }
216
+ groups.sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]));
217
+ const identities = groups.slice(0, limit).map(([uuid, group]) => ({
218
+ uuid: group[0]?.uuid ?? uuid,
219
+ name: group.find((c) => c.name)?.name,
220
+ copyCount: group.length,
221
+ ...(group.length > copyLimit ? { truncated: true } : {}),
222
+ copies: group.slice(0, copyLimit),
223
+ }));
224
+ return {
225
+ total: groups.length,
226
+ ...(groups.length > limit ? { truncated: true } : {}),
227
+ identities,
228
+ };
229
+ }