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,229 @@
1
+ import { promises as fs } from "node:fs";
2
+ export const BSA_HEADER_LENGTH = 36;
3
+ export const BSA_VERSION_OBLIVION = 103;
4
+ export const BSA_VERSION_SKYRIM_LE = 104;
5
+ export const BSA_VERSION_SKYRIM_SE = 105;
6
+ export const BSA_FLAG_INCLUDE_DIRECTORY_NAMES = 0x1;
7
+ export const BSA_FLAG_INCLUDE_FILE_NAMES = 0x2;
8
+ export const BSA_FLAG_COMPRESSED = 0x4;
9
+ export const BSA_FLAG_XBOX360 = 0x40;
10
+ export const BSA_FLAG_EMBED_FILE_NAMES = 0x100;
11
+ export const BSA_FLAG_XMEM = 0x200;
12
+ export const BSA_FILE_SIZE_COMPRESSION_TOGGLE = 0x40000000;
13
+ export const BSA_FILE_SIZE_MASK = 0x3fffffff;
14
+ export const BSA_FILE_RECORD_LENGTH = 16;
15
+ export const BSA_FILE_FLAGS = [
16
+ { bit: 0x1, name: "Meshes" },
17
+ { bit: 0x2, name: "Textures" },
18
+ { bit: 0x4, name: "Menus" },
19
+ { bit: 0x8, name: "Sounds" },
20
+ { bit: 0x10, name: "Voices" },
21
+ { bit: 0x20, name: "Shaders" },
22
+ { bit: 0x40, name: "Trees" },
23
+ { bit: 0x80, name: "Fonts" },
24
+ { bit: 0x100, name: "Miscellaneous" },
25
+ ];
26
+ export class BsaHeaderError extends Error {
27
+ reason;
28
+ constructor(message, reason) {
29
+ super(message);
30
+ this.reason = reason;
31
+ this.name = "BsaHeaderError";
32
+ }
33
+ }
34
+ export function parseBsaHeader(buf) {
35
+ if (buf.length < BSA_HEADER_LENGTH) {
36
+ throw new BsaHeaderError(`archive is ${buf.length} bytes; a BSA header is ${BSA_HEADER_LENGTH}`, "too-short");
37
+ }
38
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
39
+ const sig = String.fromCharCode(buf[0], buf[1], buf[2]) + (buf[3] === 0 ? "\\0" : String.fromCharCode(buf[3]));
40
+ if (!(buf[0] === 0x42 && buf[1] === 0x53 && buf[2] === 0x41 && buf[3] === 0)) {
41
+ throw new BsaHeaderError(`file id is "${sig}", not "BSA\\0"; not a Bethesda archive (a .ba2 is Fallout 4's format)`, "bad-signature");
42
+ }
43
+ const archiveFlags = view.getUint32(12, true);
44
+ const fileFlags = view.getUint16(32, true);
45
+ return {
46
+ version: view.getUint32(4, true),
47
+ folderRecordOffset: view.getUint32(8, true),
48
+ archiveFlags,
49
+ folderCount: view.getUint32(16, true),
50
+ fileCount: view.getUint32(20, true),
51
+ totalFolderNameLength: view.getUint32(24, true),
52
+ totalFileNameLength: view.getUint32(28, true),
53
+ fileFlags,
54
+ includesDirectoryNames: (archiveFlags & BSA_FLAG_INCLUDE_DIRECTORY_NAMES) !== 0,
55
+ includesFileNames: (archiveFlags & BSA_FLAG_INCLUDE_FILE_NAMES) !== 0,
56
+ compressedByDefault: (archiveFlags & BSA_FLAG_COMPRESSED) !== 0,
57
+ xbox360: (archiveFlags & BSA_FLAG_XBOX360) !== 0,
58
+ embedsFileNames: (archiveFlags & BSA_FLAG_EMBED_FILE_NAMES) !== 0,
59
+ contentTypes: BSA_FILE_FLAGS.filter((f) => (fileFlags & f.bit) !== 0).map((f) => f.name),
60
+ };
61
+ }
62
+ export async function readBsaHeader(filePath) {
63
+ const handle = await fs.open(filePath, "r");
64
+ try {
65
+ const buf = new Uint8Array(BSA_HEADER_LENGTH);
66
+ const { bytesRead } = await handle.read(buf, 0, BSA_HEADER_LENGTH, 0);
67
+ return parseBsaHeader(buf.subarray(0, bytesRead));
68
+ }
69
+ finally {
70
+ await handle.close();
71
+ }
72
+ }
73
+ export function bsaFolderRecordLength(version) {
74
+ return version >= BSA_VERSION_SKYRIM_SE ? 24 : 16;
75
+ }
76
+ export function bsaTableLength(h) {
77
+ const folderRecords = h.folderCount * bsaFolderRecordLength(h.version);
78
+ const folderNames = h.includesDirectoryNames ? h.totalFolderNameLength + h.folderCount : 0;
79
+ const fileRecords = h.fileCount * BSA_FILE_RECORD_LENGTH;
80
+ const fileNames = h.includesFileNames ? h.totalFileNameLength : 0;
81
+ return BSA_HEADER_LENGTH + folderRecords + folderNames + fileRecords + fileNames;
82
+ }
83
+ const latin1 = new TextDecoder("latin1");
84
+ export function parseBsaTable(buf) {
85
+ const header = parseBsaHeader(buf);
86
+ const tableLength = bsaTableLength(header);
87
+ if (buf.length < tableLength) {
88
+ throw new BsaHeaderError(`the folder and file tables need ${tableLength} bytes (header ${BSA_HEADER_LENGTH}, ${header.folderCount} folder record(s), ${header.fileCount} file record(s), the name blocks) but only ${buf.length} are there`, "truncated-table");
89
+ }
90
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
91
+ const le = !header.xbox360;
92
+ const problems = [];
93
+ if (header.folderRecordOffset !== BSA_HEADER_LENGTH)
94
+ problems.push(`the header's folder-record offset is ${header.folderRecordOffset}; every BSA header is ${BSA_HEADER_LENGTH} bytes, so it is always ${BSA_HEADER_LENGTH}.`);
95
+ const recLen = bsaFolderRecordLength(header.version);
96
+ const folders = [];
97
+ let pos = BSA_HEADER_LENGTH;
98
+ for (let i = 0; i < header.folderCount; i++) {
99
+ const hash = view.getBigUint64(pos, le);
100
+ const count = view.getUint32(pos + 8, le);
101
+ const offsetField = view.getUint32(pos + (recLen === 24 ? 16 : 12), le);
102
+ folders.push({ index: i, hash, count, offsetField, blockOffset: offsetField - header.totalFileNameLength, actualBlockOffset: -1 });
103
+ pos += recLen;
104
+ }
105
+ const files = [];
106
+ let folderNameBytes = 0;
107
+ let overran = false;
108
+ const room = (n, what) => {
109
+ if (pos + n <= buf.length)
110
+ return true;
111
+ problems.push(`the file record blocks run past byte ${buf.length}, the end of the table the header describes, while reading ${what}; a folder record's file count or name length claims more than the header's counts and name lengths account for.`);
112
+ overran = true;
113
+ return false;
114
+ };
115
+ for (const folder of folders) {
116
+ folder.actualBlockOffset = pos;
117
+ if (folder.blockOffset !== pos)
118
+ problems.push(`folder record ${folder.index}'s offset points at byte ${folder.blockOffset} (after subtracting totalFileNameLength) but its file record block is at byte ${pos}.`);
119
+ if (header.includesDirectoryNames) {
120
+ if (!room(1, `folder record ${folder.index}'s name`))
121
+ break;
122
+ const len = buf[pos];
123
+ if (!room(1 + len, `folder record ${folder.index}'s name`))
124
+ break;
125
+ const raw = buf.subarray(pos + 1, pos + 1 + len);
126
+ const end = raw.indexOf(0);
127
+ folder.name = latin1.decode(end >= 0 ? raw.subarray(0, end) : raw);
128
+ if (len === 0 || end !== len - 1)
129
+ problems.push(`folder record ${folder.index}'s name is not a bzstring ending in \\0 (length byte ${len}).`);
130
+ folderNameBytes += len;
131
+ pos += 1 + len;
132
+ }
133
+ for (let j = 0; j < folder.count; j++) {
134
+ if (!room(BSA_FILE_RECORD_LENGTH, `folder record ${folder.index}'s file record ${j}`))
135
+ break;
136
+ const hash = view.getBigUint64(pos, le);
137
+ const sizeField = view.getUint32(pos + 8, le);
138
+ const offset = view.getUint32(pos + 12, le);
139
+ const toggled = (sizeField & BSA_FILE_SIZE_COMPRESSION_TOGGLE) !== 0;
140
+ files.push({ index: files.length, folder: folder.index, hash, sizeField, size: sizeField & BSA_FILE_SIZE_MASK, compressed: header.compressedByDefault !== toggled, offset });
141
+ pos += BSA_FILE_RECORD_LENGTH;
142
+ }
143
+ if (overran)
144
+ break;
145
+ }
146
+ if (overran)
147
+ return { header, folders, files, tableLength, problems };
148
+ if (files.length !== header.fileCount)
149
+ problems.push(`the folder records' counts add up to ${files.length} file(s) but the header says ${header.fileCount}.`);
150
+ if (header.includesDirectoryNames && folderNameBytes !== header.totalFolderNameLength)
151
+ problems.push(`the folder names take ${folderNameBytes} bytes but the header's totalFolderNameLength is ${header.totalFolderNameLength}.`);
152
+ if (header.includesFileNames) {
153
+ const block = buf.subarray(pos, pos + header.totalFileNameLength);
154
+ let start = 0;
155
+ let n = 0;
156
+ for (let k = 0; k < block.length && n < files.length; k++) {
157
+ if (block[k] === 0) {
158
+ files[n].name = latin1.decode(block.subarray(start, k));
159
+ n++;
160
+ start = k + 1;
161
+ }
162
+ }
163
+ if (n !== files.length)
164
+ problems.push(`the file name block holds ${n} name(s) for ${files.length} file record(s).`);
165
+ else if (start !== block.length)
166
+ problems.push(`the file name block has ${block.length - start} byte(s) after the last name; totalFileNameLength does not match the names.`);
167
+ }
168
+ return { header, folders, files, tableLength, problems };
169
+ }
170
+ export async function readBsaTable(filePath, maxTableBytes = 64 * 1024 * 1024) {
171
+ const handle = await fs.open(filePath, "r");
172
+ try {
173
+ const stat = await handle.stat();
174
+ const head = new Uint8Array(BSA_HEADER_LENGTH);
175
+ const first = await handle.read(head, 0, BSA_HEADER_LENGTH, 0);
176
+ const header = parseBsaHeader(head.subarray(0, first.bytesRead));
177
+ const want = Math.min(bsaTableLength(header), maxTableBytes, stat.size);
178
+ const buf = new Uint8Array(want);
179
+ const { bytesRead } = await handle.read(buf, 0, want, 0);
180
+ return { table: parseBsaTable(buf.subarray(0, bytesRead)), fileSize: stat.size };
181
+ }
182
+ finally {
183
+ await handle.close();
184
+ }
185
+ }
186
+ const HASH_MASK = 0xffffffffn;
187
+ function fold(bytes) {
188
+ let h = 0n;
189
+ for (let i = 0; i < bytes.length; i++)
190
+ h = (h * 0x1003fn + BigInt(bytes.charCodeAt(i) & 0xff)) & HASH_MASK;
191
+ return h;
192
+ }
193
+ export function tesHash(name, kind) {
194
+ const lower = name.toLowerCase().replace(/\//g, "\\");
195
+ let root = lower;
196
+ let ext = "";
197
+ if (kind === "file") {
198
+ const dot = lower.lastIndexOf(".");
199
+ if (dot > 0) {
200
+ root = lower.slice(0, dot);
201
+ ext = lower.slice(dot);
202
+ }
203
+ }
204
+ if (root.length === 0)
205
+ return 0n;
206
+ const code = (i) => BigInt(root.charCodeAt(i) & 0xff);
207
+ let hash1 = code(root.length - 1) | (root.length > 2 ? code(root.length - 2) << 8n : 0n) | (BigInt(root.length) << 16n) | (code(0) << 24n);
208
+ if (ext === ".kf")
209
+ hash1 |= 0x80n;
210
+ else if (ext === ".nif")
211
+ hash1 |= 0x8000n;
212
+ else if (ext === ".dds")
213
+ hash1 |= 0x8080n;
214
+ else if (ext === ".wav")
215
+ hash1 |= 0x80000000n;
216
+ hash1 &= HASH_MASK;
217
+ const hash2 = (fold(root.slice(1, -2)) + fold(ext)) & HASH_MASK;
218
+ return (hash2 << 32n) + hash1;
219
+ }
220
+ export function bsaEntries(table) {
221
+ const out = [];
222
+ for (const f of table.files) {
223
+ const folder = table.folders[f.folder];
224
+ if (f.name === undefined || folder?.name === undefined)
225
+ continue;
226
+ out.push({ path: folder.name.length > 0 ? `${folder.name}\\${f.name}` : f.name, file: f });
227
+ }
228
+ return out;
229
+ }
@@ -0,0 +1,163 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { readPeFileVersion } from "../../core/compat/pe.js";
4
+ import { compareVersions, parseVersion } from "../../core/compat/version.js";
5
+ import { pathExists, readTextIfExists, resolveCandidate } from "../../core/fsutil.js";
6
+ import { findMo2Profile, userDataDirs } from "./loadorder.js";
7
+ export const SKSE_BUILDS = [
8
+ { skse: "2.3.1", runtime: { exact: "1.7.104" }, label: "Anniversary Edition (Steam)" },
9
+ { skse: "2.2.6", runtime: { min: "1.6.1170", max: "1.6.1179" }, label: "Anniversary Edition (GOG)" },
10
+ { skse: "2.0.20", runtime: { exact: "1.5.97" }, label: "Special Edition (pre-AE)" },
11
+ ];
12
+ export const RUNTIME_EXPANDED_LIGHT_RANGE = "1.6.1130";
13
+ const LOADER_BANNER_RE = /skse64 loader (\d+\.\d+\.\d+)/i;
14
+ const VERSIONLIB_RE = /^(?:versionlib|version)-(\d+)-(\d+)-(\d+)-(\d+)\.bin$/i;
15
+ function sameVersion(a, b) {
16
+ if (a === undefined || parseVersion(a) === undefined || parseVersion(b) === undefined)
17
+ return false;
18
+ return compareVersions(a, b) === 0;
19
+ }
20
+ async function detectComponents(install) {
21
+ const at = (...segments) => path.join(install.root, ...segments);
22
+ const components = [];
23
+ const dirs = await userDataDirs(install);
24
+ const gameExe = at("SkyrimSE.exe");
25
+ const gameInstalled = await pathExists(gameExe);
26
+ const gameVersion = install.version ?? (await readPeFileVersion(gameExe));
27
+ components.push({
28
+ id: "game",
29
+ label: "Skyrim Special Edition",
30
+ kind: "game",
31
+ installed: gameInstalled,
32
+ version: gameVersion,
33
+ source: gameVersion ? (install.version ? "install record" : "SkyrimSE.exe (PE FileVersion)") : undefined,
34
+ path: gameExe,
35
+ });
36
+ components.push({ id: `storefront-${dirs.storefront}`, label: `Storefront: ${dirs.storefront}`, kind: "game", installed: true, source: "game root probe (Galaxy64.dll / EOSSDK-Win64-Shipping.dll / install source)" });
37
+ const loader = at("skse64_loader.exe");
38
+ const skseInstalled = await pathExists(loader);
39
+ let skseVersion = await readPeFileVersion(loader);
40
+ let skseSource = skseVersion ? "skse64_loader.exe (PE FileVersion)" : undefined;
41
+ if (!skseVersion) {
42
+ const log = await readTextIfExists(path.join(dirs.myGames, "SKSE", "skse64_loader.log"));
43
+ const m = log?.match(LOADER_BANNER_RE);
44
+ if (m?.[1]) {
45
+ skseVersion = m[1];
46
+ skseSource = "skse64_loader.log (banner; wording unverified)";
47
+ }
48
+ }
49
+ components.push({ id: "skse", label: "SKSE64", kind: "loader", installed: skseInstalled || skseVersion !== undefined, version: skseVersion, source: skseSource, path: loader });
50
+ if (skseVersion) {
51
+ const known = SKSE_BUILDS.find((b) => sameVersion(skseVersion, b.skse));
52
+ components.push({
53
+ id: known ? `skse-build-${known.skse}` : "skse-build-unknown",
54
+ label: known ? `SKSE64 ${known.skse} (${known.label})` : `SKSE64 ${skseVersion} (not in the pairing table)`,
55
+ kind: "loader",
56
+ installed: true,
57
+ version: skseVersion,
58
+ source: "skse.silverlock.org Downloads, read 2026-09-11",
59
+ });
60
+ }
61
+ const pluginsDir = at("Data", "SKSE", "Plugins");
62
+ let dlls = [];
63
+ let versionlibs = [];
64
+ try {
65
+ const entries = await fs.readdir(pluginsDir);
66
+ dlls = entries.filter((e) => e.toLowerCase().endsWith(".dll"));
67
+ versionlibs = entries.filter((e) => VERSIONLIB_RE.test(e));
68
+ }
69
+ catch {
70
+ }
71
+ components.push({ id: "skse-plugins", label: "SKSE plugins", kind: "mod", installed: dlls.length > 0, source: dlls.length ? `${dlls.length} .dll under Data/SKSE/Plugins` : undefined, path: pluginsDir });
72
+ const runtimeParts = gameVersion ? parseVersion(gameVersion)?.numeric : undefined;
73
+ const matching = runtimeParts && runtimeParts.length >= 3
74
+ ? versionlibs.find((v) => {
75
+ const m = VERSIONLIB_RE.exec(v);
76
+ return m !== null && Number(m[1]) === runtimeParts[0] && Number(m[2]) === runtimeParts[1] && Number(m[3]) === runtimeParts[2];
77
+ })
78
+ : undefined;
79
+ components.push({
80
+ id: "addresslib",
81
+ label: "Address Library for SKSE Plugins",
82
+ kind: "framework",
83
+ installed: versionlibs.length > 0,
84
+ version: matching ? gameVersion : undefined,
85
+ source: versionlibs.length ? `${versionlibs.length} versionlib-*.bin under Data/SKSE/Plugins${matching ? `; ${matching} matches the runtime` : gameVersion ? "; none names the detected runtime" : ""}` : undefined,
86
+ path: pluginsDir,
87
+ });
88
+ components.push({
89
+ id: "addresslib-missing-for-runtime",
90
+ label: "Address Library missing for this runtime",
91
+ kind: "framework",
92
+ installed: dlls.length > 0 && matching === undefined,
93
+ source: "derived: SKSE plugins present, no versionlib names this install's runtime",
94
+ });
95
+ const crashLogger = path.join(pluginsDir, "CrashLogger.dll");
96
+ components.push({ id: "crashlogger", label: "Crash Logger SSE", kind: "mod", installed: await pathExists(crashLogger), path: crashLogger });
97
+ const ck = at("CreationKit.exe");
98
+ const ckInstalled = await pathExists(ck);
99
+ components.push({ id: "creationkit", label: "Creation Kit", kind: "tool", installed: ckInstalled, version: ckInstalled ? await readPeFileVersion(ck) : undefined, source: ckInstalled ? "CreationKit.exe (PE FileVersion)" : undefined, path: ck });
100
+ const mo2 = await findMo2Profile(install);
101
+ components.push({ id: "mo2", label: "Mod Organizer 2 (instance managing this install)", kind: "tool", installed: mo2 !== undefined, source: mo2 ? `${mo2.iniPath}, profile ${mo2.profileName}` : undefined, path: mo2?.instanceDir });
102
+ const vortexManifest = await resolveCandidate([at("Data", "vortex.deployment.json")]);
103
+ components.push({ id: "vortex", label: "Vortex (deployment manifest in Data)", kind: "tool", installed: vortexManifest !== undefined, path: vortexManifest?.path ?? at("Data", "vortex.deployment.json") });
104
+ return components;
105
+ }
106
+ const RULES = [
107
+ ...SKSE_BUILDS.map((b) => ({
108
+ id: `sse-skse-${b.skse.replace(/\./g, "-")}-runtime`,
109
+ game: "skyrimse",
110
+ component: `skse-build-${b.skse}`,
111
+ requires: [{ component: "game", ...b.runtime }],
112
+ note: `SKSE64 ${b.skse} (${b.label}) is built for game runtime ${b.runtime.exact ?? `${b.runtime.min} to ${b.runtime.max}`} and refuses any other; a game update needs a matching SKSE release.`,
113
+ source: "https://skse.silverlock.org/ (Downloads, read 2026-09-11); skse64_common/skse_version.h CURRENT_RELEASE_RUNTIME",
114
+ severity: "block",
115
+ undeterminedSeverity: "warn",
116
+ })),
117
+ {
118
+ id: "sse-skse-unknown-build",
119
+ game: "skyrimse",
120
+ component: "skse-build-unknown",
121
+ requires: [],
122
+ advisory: true,
123
+ severity: "info",
124
+ note: "This SKSE64 build is not in ModWright's pairing table (2.3.1, 2.2.6, 2.0.20 as of 2026-09-11); check skse.silverlock.org for the runtime it targets.",
125
+ source: "https://skse.silverlock.org/ (Downloads)",
126
+ },
127
+ {
128
+ id: "sse-skse-no-ms-store",
129
+ game: "skyrimse",
130
+ component: "skse",
131
+ requires: [{ component: "storefront-ms" }],
132
+ advisory: true,
133
+ severity: "block",
134
+ note: "SKSE does not support the Windows Store / Game Pass release of Skyrim; this install's user-data folder is the MS one.",
135
+ source: "https://skse.silverlock.org/ (Compatibility)",
136
+ },
137
+ {
138
+ id: "sse-skse-no-epic",
139
+ game: "skyrimse",
140
+ component: "skse",
141
+ requires: [{ component: "storefront-epic" }],
142
+ advisory: true,
143
+ severity: "block",
144
+ note: "SKSE does not support the Epic Games Store release of Skyrim, although SKSE's own code carries an EPIC save-folder branch.",
145
+ source: "https://skse.silverlock.org/ (Compatibility); skse64_common/skse_version.h RUNTIME_TYPE_EPIC",
146
+ },
147
+ {
148
+ id: "sse-addresslib-for-runtime",
149
+ game: "skyrimse",
150
+ component: "skse-plugins",
151
+ requires: [{ component: "addresslib-missing-for-runtime" }],
152
+ advisory: true,
153
+ severity: "warn",
154
+ versionGate: "game",
155
+ undeterminedSeverity: "info",
156
+ note: "SKSE plugins are installed but no Address Library versionlib-*.bin matches this install's game runtime (reported at info when the runtime itself could not be read); a version-independent plugin will be disabled with 'address library needs to be updated'.",
157
+ source: "skse64/PluginManager.cpp CheckAddressLibrary; the 'disabled, address library needs to be updated' rejection",
158
+ },
159
+ ];
160
+ export const skyrimseCompat = {
161
+ detectComponents,
162
+ rules: RULES,
163
+ };
@@ -0,0 +1,12 @@
1
+ export { skyrimse } from "./surface.js";
2
+ export { SIGNATURES } from "./logs.js";
3
+ export { PLUGIN_VALIDATORS } from "./validators/plugins.js";
4
+ export { ARCHIVE_VALIDATORS } from "./validators/archives.js";
5
+ export { RECORD_VALIDATORS } from "./validators/records.js";
6
+ export { walkPluginRecords, readPluginRecords, classifyRecord, modIndexOf, objectIndexOf, RecordWalkError } from "./records.js";
7
+ export { skyrimseCompat, SKSE_BUILDS } from "./compat.js";
8
+ export { parseBsaHeader, readBsaHeader, BsaHeaderError, parseBsaTable, readBsaTable, bsaTableLength, bsaFolderRecordLength, bsaEntries, tesHash } from "./archives.js";
9
+ export { ARCHIVE_RECORD_VALIDATORS } from "./validators/archive-records.js";
10
+ export { SKYRIM_EPIC_APP_NAMES } from "./surface.js";
11
+ export { parsePluginHeader, readPluginHeader, pluginType, PluginHeaderError } from "./plugins.js";
12
+ export { parsePluginsTxt, readLoadOrder, resolvePluginsTxt, findMo2Profile, storefrontOf, userDataDirs, HARDCODED_PLUGINS } from "./loadorder.js";
@@ -0,0 +1,174 @@
1
+ import { existsSync, promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { documents, localAppData, pathExists } from "../../core/fsutil.js";
4
+ export const STOREFRONT_FOLDER = {
5
+ steam: "Skyrim Special Edition",
6
+ gog: "Skyrim Special Edition GOG",
7
+ epic: "Skyrim Special Edition EPIC",
8
+ ms: "Skyrim Special Edition MS",
9
+ };
10
+ export const HARDCODED_PLUGINS = ["Skyrim.esm", "Update.esm", "Dawnguard.esm", "HearthFires.esm", "Dragonborn.esm"];
11
+ export const CCC_FILE = "Skyrim.ccc";
12
+ const WINDOWS_1252 = new TextDecoder("windows-1252");
13
+ export async function storefrontOf(install) {
14
+ if (install.source === "xbox")
15
+ return "ms";
16
+ if (await pathExists(path.join(install.root, "Galaxy64.dll")))
17
+ return "gog";
18
+ if (await pathExists(path.join(install.root, "EOSSDK-Win64-Shipping.dll")))
19
+ return "epic";
20
+ return "steam";
21
+ }
22
+ export function storefrontOfSync(install) {
23
+ if (install.source === "xbox")
24
+ return "ms";
25
+ if (existsSync(path.join(install.root, "Galaxy64.dll")))
26
+ return "gog";
27
+ if (existsSync(path.join(install.root, "EOSSDK-Win64-Shipping.dll")))
28
+ return "epic";
29
+ return "steam";
30
+ }
31
+ export async function userDataDirs(install) {
32
+ const storefront = await storefrontOf(install);
33
+ const folder = STOREFRONT_FOLDER[storefront];
34
+ return { storefront, localAppData: localAppData(folder), myGames: documents("My Games", folder) };
35
+ }
36
+ export function parsePluginsTxt(bytes) {
37
+ const text = WINDOWS_1252.decode(bytes);
38
+ const byKey = new Map();
39
+ const order = [];
40
+ const lines = text.split(/\r?\n/);
41
+ for (let i = 0; i < lines.length; i++) {
42
+ const raw = lines[i];
43
+ const line = raw.replace(/\s+$/, "");
44
+ if (line.length === 0 || line.startsWith("#"))
45
+ continue;
46
+ const enabled = line.startsWith("*");
47
+ let name = enabled ? line.slice(1) : line;
48
+ if (name.toLowerCase().endsWith(".ghost"))
49
+ name = name.slice(0, -".ghost".length);
50
+ if (name.length === 0)
51
+ continue;
52
+ const key = name.toLowerCase();
53
+ if (byKey.has(key)) {
54
+ order.splice(order.indexOf(key), 1);
55
+ }
56
+ byKey.set(key, { name, enabled, line: i + 1 });
57
+ order.push(key);
58
+ }
59
+ return order.map((k) => byKey.get(k));
60
+ }
61
+ export async function readCcc(root) {
62
+ const p = path.join(root, CCC_FILE);
63
+ if (!(await pathExists(p)))
64
+ return [];
65
+ const bytes = await fs.readFile(p);
66
+ return WINDOWS_1252.decode(bytes)
67
+ .split(/\r?\n/)
68
+ .map((l) => l.trim())
69
+ .filter((l) => l.length > 0);
70
+ }
71
+ export function parseIni(text) {
72
+ const out = new Map();
73
+ let section = "";
74
+ for (const raw of text.split(/\r?\n/)) {
75
+ const line = raw.trim();
76
+ if (!line || line.startsWith(";") || line.startsWith("#"))
77
+ continue;
78
+ const sec = /^\[(.+)\]$/.exec(line);
79
+ if (sec) {
80
+ section = sec[1].trim().toLowerCase();
81
+ continue;
82
+ }
83
+ const eq = line.indexOf("=");
84
+ if (eq < 0)
85
+ continue;
86
+ const key = line.slice(0, eq).trim().toLowerCase();
87
+ let value = line.slice(eq + 1).trim();
88
+ const wrapped = /^@ByteArray\((.*)\)$/.exec(value);
89
+ if (wrapped)
90
+ value = wrapped[1];
91
+ if (!out.has(section))
92
+ out.set(section, new Map());
93
+ out.get(section).set(key, value);
94
+ }
95
+ return out;
96
+ }
97
+ function samePath(a, b) {
98
+ const norm = (p) => path.resolve(p.replace(/\\\\/g, "\\")).replace(/[\\/]+$/, "").toLowerCase();
99
+ return norm(a) === norm(b);
100
+ }
101
+ export async function findMo2Profile(install, instancesRoot = localAppData("ModOrganizer")) {
102
+ if (!(await pathExists(instancesRoot)))
103
+ return undefined;
104
+ let entries;
105
+ try {
106
+ entries = await fs.readdir(instancesRoot);
107
+ }
108
+ catch {
109
+ return undefined;
110
+ }
111
+ for (const name of entries) {
112
+ const instanceDir = path.join(instancesRoot, name);
113
+ const iniPath = path.join(instanceDir, "ModOrganizer.ini");
114
+ if (!(await pathExists(iniPath)))
115
+ continue;
116
+ let ini;
117
+ try {
118
+ ini = parseIni(await fs.readFile(iniPath, "utf8"));
119
+ }
120
+ catch {
121
+ continue;
122
+ }
123
+ const general = ini.get("general");
124
+ const settings = ini.get("settings");
125
+ const gamePath = general?.get("gamepath");
126
+ if (!gamePath || !samePath(gamePath, install.root))
127
+ continue;
128
+ const profileName = general?.get("selected_profile") ?? "Default";
129
+ const base = settings?.get("base_directory") ?? instanceDir;
130
+ const profilesDir = (settings?.get("profiles_directory") ?? "%BASE_DIR%/profiles").replace(/%BASE_DIR%/gi, base);
131
+ const profileDir = path.join(profilesDir, profileName);
132
+ const pluginsTxt = path.join(profileDir, "plugins.txt");
133
+ if (!(await pathExists(pluginsTxt)))
134
+ continue;
135
+ return { instanceDir, iniPath, profileName, profileDir, pluginsTxt };
136
+ }
137
+ return undefined;
138
+ }
139
+ export async function resolvePluginsTxt(install, options = {}) {
140
+ if (!options.ignoreMo2) {
141
+ const mo2 = await findMo2Profile(install, options.mo2InstancesRoot);
142
+ if (mo2)
143
+ return { path: mo2.pluginsTxt, from: "mo2-profile", mo2 };
144
+ }
145
+ const dirs = { ...(await userDataDirs(install)), ...options.dirs };
146
+ return { path: path.join(dirs.localAppData, "Plugins.txt"), from: "game" };
147
+ }
148
+ export async function readLoadOrder(install, options = {}) {
149
+ const source = await resolvePluginsTxt(install, options);
150
+ const implicit = [...HARDCODED_PLUGINS, ...(await readCcc(install.root))];
151
+ const seen = new Set(implicit.map((n) => n.toLowerCase()));
152
+ const listed = [];
153
+ let fileFound = false;
154
+ if (await pathExists(source.path)) {
155
+ fileFound = true;
156
+ for (const entry of parsePluginsTxt(await fs.readFile(source.path))) {
157
+ if (seen.has(entry.name.toLowerCase()))
158
+ continue;
159
+ listed.push(entry);
160
+ }
161
+ }
162
+ const dataDir = path.join(install.root, "Data");
163
+ const entries = [];
164
+ let index = 0;
165
+ const push = async (name, enabled) => {
166
+ const onDisk = await pathExists(path.join(dataDir, name));
167
+ entries.push({ index: index++, name, enabled, ...(onDisk ? { modId: `plugins:${name}` } : {}) });
168
+ };
169
+ for (const name of implicit)
170
+ await push(name, true);
171
+ for (const entry of listed)
172
+ await push(entry.name, entry.enabled);
173
+ return { entries, source, implicit, fileFound };
174
+ }