modwright 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +212 -0
  3. package/assets/claude/agents/mw-explore.md +46 -0
  4. package/assets/claude/agents/mw-fixture.md +33 -0
  5. package/assets/claude/agents/mw-rule-author.md +51 -0
  6. package/assets/claude/agents/mw-scaffold-author.md +48 -0
  7. package/assets/claude/skills/mw-author.md +59 -0
  8. package/assets/claude/skills/mw-new-item.md +51 -0
  9. package/assets/claude/skills/mw-new-spell.md +50 -0
  10. package/assets/claude/skills/mw-scaffold.md +59 -0
  11. package/assets/claude/skills/mw-ship.md +57 -0
  12. package/assets/claude/skills/mw-validate.md +51 -0
  13. package/assets/claude/skills/mw-verify.md +59 -0
  14. package/assets/nivalisnights/extract_resources.py +177 -0
  15. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Config.json +5 -0
  16. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapClient.lua +55 -0
  17. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/BootstrapServer.lua +81 -0
  18. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Log.lua +96 -0
  19. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Probes.lua +1038 -0
  20. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/ProbesClient.lua +74 -0
  21. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Protocol.lua +985 -0
  22. package/bridges/bg3-se/Mods/ModWrightBridge/ScriptExtender/Lua/ModWrightBridge/Watch.lua +175 -0
  23. package/bridges/bg3-se/Mods/ModWrightBridge/meta.lsx +45 -0
  24. package/bridges/bg3-se/README.md +99 -0
  25. package/bridges/bg3-se/bridge.json +68 -0
  26. package/bridges/cp2077-cet/ModWrightBridge/init.lua +375 -0
  27. package/bridges/cp2077-cet/ModWrightBridge/probes.lua +1104 -0
  28. package/bridges/cp2077-cet/ModWrightBridge/protocol.lua +1306 -0
  29. package/bridges/cp2077-cet/README.md +165 -0
  30. package/bridges/cp2077-cet/bridge.json +53 -0
  31. package/dist/core/bridge/arm.js +175 -0
  32. package/dist/core/bridge/cet-console.js +26 -0
  33. package/dist/core/bridge/cet-log.js +362 -0
  34. package/dist/core/bridge/index.js +11 -0
  35. package/dist/core/bridge/locate.js +35 -0
  36. package/dist/core/bridge/paths.js +34 -0
  37. package/dist/core/bridge/pending.js +89 -0
  38. package/dist/core/bridge/poll.js +155 -0
  39. package/dist/core/bridge/stage.js +100 -0
  40. package/dist/core/bridge/status.js +97 -0
  41. package/dist/core/bridge/types.js +145 -0
  42. package/dist/core/bridge/validate.js +107 -0
  43. package/dist/core/build/index.js +6 -0
  44. package/dist/core/build/manifest.js +47 -0
  45. package/dist/core/build/paths.js +32 -0
  46. package/dist/core/build/run.js +342 -0
  47. package/dist/core/build/stage.js +265 -0
  48. package/dist/core/build/steps/bg3.js +129 -0
  49. package/dist/core/build/steps/core.js +195 -0
  50. package/dist/core/build/steps/cyberpunk.js +95 -0
  51. package/dist/core/build/steps/listing.js +35 -0
  52. package/dist/core/build/types.js +13 -0
  53. package/dist/core/build/zip.js +148 -0
  54. package/dist/core/compat/check.js +113 -0
  55. package/dist/core/compat/pe.js +220 -0
  56. package/dist/core/compat/types.js +1 -0
  57. package/dist/core/compat/version.js +70 -0
  58. package/dist/core/deploy/guards.js +292 -0
  59. package/dist/core/deploy/index.js +5 -0
  60. package/dist/core/deploy/plan.js +696 -0
  61. package/dist/core/deploy/reload-apply.js +361 -0
  62. package/dist/core/deploy/reload.js +358 -0
  63. package/dist/core/deploy/sources.js +169 -0
  64. package/dist/core/deploy/types.js +1 -0
  65. package/dist/core/epic.js +54 -0
  66. package/dist/core/fsutil.js +191 -0
  67. package/dist/core/home.js +70 -0
  68. package/dist/core/knowledge/facts.js +229 -0
  69. package/dist/core/knowledge/index.js +4 -0
  70. package/dist/core/knowledge/search.js +112 -0
  71. package/dist/core/knowledge/store.js +123 -0
  72. package/dist/core/knowledge/types.js +1 -0
  73. package/dist/core/ledger/check.js +42 -0
  74. package/dist/core/ledger/hooks.js +80 -0
  75. package/dist/core/ledger/index.js +7 -0
  76. package/dist/core/ledger/query.js +17 -0
  77. package/dist/core/ledger/record.js +84 -0
  78. package/dist/core/ledger/status.js +53 -0
  79. package/dist/core/ledger/store.js +98 -0
  80. package/dist/core/ledger/types.js +108 -0
  81. package/dist/core/logs/index.js +4 -0
  82. package/dist/core/logs/registry.js +50 -0
  83. package/dist/core/logs/scan.js +195 -0
  84. package/dist/core/logs/triage.js +160 -0
  85. package/dist/core/logs/types.js +1 -0
  86. package/dist/core/lsx.js +10 -0
  87. package/dist/core/lz4.js +66 -0
  88. package/dist/core/mods.js +229 -0
  89. package/dist/core/process.js +49 -0
  90. package/dist/core/project/build-steps.js +83 -0
  91. package/dist/core/project/describe.js +74 -0
  92. package/dist/core/project/index.js +5 -0
  93. package/dist/core/project/load.js +146 -0
  94. package/dist/core/project/schema.js +264 -0
  95. package/dist/core/project/types.js +3 -0
  96. package/dist/core/prompts.js +72 -0
  97. package/dist/core/registry.js +35 -0
  98. package/dist/core/safety/backup.js +206 -0
  99. package/dist/core/safety/execute.js +188 -0
  100. package/dist/core/safety/index.js +5 -0
  101. package/dist/core/safety/plan.js +122 -0
  102. package/dist/core/safety/restore.js +172 -0
  103. package/dist/core/safety/types.js +1 -0
  104. package/dist/core/scaffold/bootstrap.js +7 -0
  105. package/dist/core/scaffold/catalog.js +28 -0
  106. package/dist/core/scaffold/claude-assets.js +271 -0
  107. package/dist/core/scaffold/collisions.js +64 -0
  108. package/dist/core/scaffold/emit.js +356 -0
  109. package/dist/core/scaffold/engine.js +680 -0
  110. package/dist/core/scaffold/ids.js +78 -0
  111. package/dist/core/scaffold/index.js +10 -0
  112. package/dist/core/scaffold/jsonpath.js +121 -0
  113. package/dist/core/scaffold/load.js +81 -0
  114. package/dist/core/scaffold/render.js +85 -0
  115. package/dist/core/scaffold/sequence.js +396 -0
  116. package/dist/core/scaffold/types.js +309 -0
  117. package/dist/core/steam.js +63 -0
  118. package/dist/core/surfaceutil.js +36 -0
  119. package/dist/core/testplan/check.js +67 -0
  120. package/dist/core/testplan/index.js +5 -0
  121. package/dist/core/testplan/load.js +100 -0
  122. package/dist/core/testplan/registry.js +317 -0
  123. package/dist/core/testplan/run.js +389 -0
  124. package/dist/core/testplan/types.js +82 -0
  125. package/dist/core/text/converters/bg3.js +50 -0
  126. package/dist/core/text/converters/cyberpunk.js +36 -0
  127. package/dist/core/text/converters/index.js +261 -0
  128. package/dist/core/text/diff/cr2w.js +180 -0
  129. package/dist/core/text/diff/index.js +16 -0
  130. package/dist/core/text/diff/loca.js +43 -0
  131. package/dist/core/text/diff/lsx.js +126 -0
  132. package/dist/core/text/handles.js +86 -0
  133. package/dist/core/text/index.js +6 -0
  134. package/dist/core/text/json.js +46 -0
  135. package/dist/core/text/templates/cache.js +71 -0
  136. package/dist/core/text/templates/index.js +3 -0
  137. package/dist/core/text/templates/library.js +245 -0
  138. package/dist/core/text/templates/types.js +51 -0
  139. package/dist/core/text/toolchain.js +16 -0
  140. package/dist/core/text/types.js +1 -0
  141. package/dist/core/text/validate.js +149 -0
  142. package/dist/core/text/xml.js +58 -0
  143. package/dist/core/toolchain/datafetch.js +151 -0
  144. package/dist/core/toolchain/env.js +63 -0
  145. package/dist/core/toolchain/features.js +170 -0
  146. package/dist/core/toolchain/index.js +8 -0
  147. package/dist/core/toolchain/install.js +191 -0
  148. package/dist/core/toolchain/locate.js +275 -0
  149. package/dist/core/toolchain/remember.js +84 -0
  150. package/dist/core/toolchain/run.js +318 -0
  151. package/dist/core/toolchain/specs.js +351 -0
  152. package/dist/core/toolchain/types.js +1 -0
  153. package/dist/core/types.js +1 -0
  154. package/dist/core/userconfig.js +66 -0
  155. package/dist/core/validate/context.js +139 -0
  156. package/dist/core/validate/index.js +5 -0
  157. package/dist/core/validate/registry.js +58 -0
  158. package/dist/core/validate/run.js +131 -0
  159. package/dist/core/validate/suppressions.js +91 -0
  160. package/dist/core/validate/types.js +2 -0
  161. package/dist/core/yamledit.js +51 -0
  162. package/dist/index.js +389 -0
  163. package/dist/server.js +1914 -0
  164. package/dist/surfaces/baldursgate3/compat.js +164 -0
  165. package/dist/surfaces/baldursgate3/identity.js +110 -0
  166. package/dist/surfaces/baldursgate3/index/build.js +331 -0
  167. package/dist/surfaces/baldursgate3/index/index.js +17 -0
  168. package/dist/surfaces/baldursgate3/index/info.js +40 -0
  169. package/dist/surfaces/baldursgate3/index/parsers/loca.js +41 -0
  170. package/dist/surfaces/baldursgate3/index/parsers/lsx.js +137 -0
  171. package/dist/surfaces/baldursgate3/index/parsers/stats.js +45 -0
  172. package/dist/surfaces/baldursgate3/index/parsers/treasuretable.js +57 -0
  173. package/dist/surfaces/baldursgate3/index/parsers/xmlutil.js +142 -0
  174. package/dist/surfaces/baldursgate3/index/queries.js +327 -0
  175. package/dist/surfaces/baldursgate3/index/schema.js +62 -0
  176. package/dist/surfaces/baldursgate3/index.js +3 -0
  177. package/dist/surfaces/baldursgate3/logs.js +278 -0
  178. package/dist/surfaces/baldursgate3/modsettings.js +115 -0
  179. package/dist/surfaces/baldursgate3/pak.js +153 -0
  180. package/dist/surfaces/baldursgate3/settings.js +49 -0
  181. package/dist/surfaces/baldursgate3/surface.js +232 -0
  182. package/dist/surfaces/baldursgate3/validators/bridge.js +7 -0
  183. package/dist/surfaces/baldursgate3/validators/files.js +476 -0
  184. package/dist/surfaces/baldursgate3/validators/items.js +1001 -0
  185. package/dist/surfaces/baldursgate3/validators/progression.js +734 -0
  186. package/dist/surfaces/baldursgate3/validators/stats.js +1076 -0
  187. package/dist/surfaces/baldursgate3/validators/story.js +790 -0
  188. package/dist/surfaces/baldursgate3/validators/templates.js +7 -0
  189. package/dist/surfaces/cyberpunk2077/compat.js +223 -0
  190. package/dist/surfaces/cyberpunk2077/index/build.js +231 -0
  191. package/dist/surfaces/cyberpunk2077/index/index.js +17 -0
  192. package/dist/surfaces/cyberpunk2077/index/info.js +51 -0
  193. package/dist/surfaces/cyberpunk2077/index/model.js +52 -0
  194. package/dist/surfaces/cyberpunk2077/index/parsers/tweak.js +483 -0
  195. package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +183 -0
  196. package/dist/surfaces/cyberpunk2077/index/queries.js +504 -0
  197. package/dist/surfaces/cyberpunk2077/index/resource.js +135 -0
  198. package/dist/surfaces/cyberpunk2077/index/schema.js +90 -0
  199. package/dist/surfaces/cyberpunk2077/index.js +3 -0
  200. package/dist/surfaces/cyberpunk2077/logs.js +742 -0
  201. package/dist/surfaces/cyberpunk2077/paths.js +12 -0
  202. package/dist/surfaces/cyberpunk2077/surface.js +218 -0
  203. package/dist/surfaces/cyberpunk2077/validators/bridge.js +7 -0
  204. package/dist/surfaces/cyberpunk2077/validators/cr2w.js +349 -0
  205. package/dist/surfaces/cyberpunk2077/validators/garments.js +1181 -0
  206. package/dist/surfaces/cyberpunk2077/validators/mesh.js +338 -0
  207. package/dist/surfaces/cyberpunk2077/validators/modsettings.js +120 -0
  208. package/dist/surfaces/cyberpunk2077/validators/packaging.js +463 -0
  209. package/dist/surfaces/cyberpunk2077/validators/red4ext.js +318 -0
  210. package/dist/surfaces/cyberpunk2077/validators/redscript.js +193 -0
  211. package/dist/surfaces/cyberpunk2077/validators/templates.js +7 -0
  212. package/dist/surfaces/cyberpunk2077/validators/tweaks.js +768 -0
  213. package/dist/surfaces/cyberpunk2077/validators/vehicles.js +531 -0
  214. package/dist/surfaces/cyberpunk2077/validators/xl.js +308 -0
  215. package/dist/surfaces/eldenring/binders.js +111 -0
  216. package/dist/surfaces/eldenring/compat.js +166 -0
  217. package/dist/surfaces/eldenring/dcx.js +49 -0
  218. package/dist/surfaces/eldenring/index.js +13 -0
  219. package/dist/surfaces/eldenring/loaders.js +132 -0
  220. package/dist/surfaces/eldenring/logs.js +67 -0
  221. package/dist/surfaces/eldenring/paramdef.js +256 -0
  222. package/dist/surfaces/eldenring/params.js +129 -0
  223. package/dist/surfaces/eldenring/surface.js +121 -0
  224. package/dist/surfaces/eldenring/tools.js +52 -0
  225. package/dist/surfaces/eldenring/validators/binders.js +193 -0
  226. package/dist/surfaces/eldenring/validators/packages.js +159 -0
  227. package/dist/surfaces/eldenring/validators/params.js +334 -0
  228. package/dist/surfaces/eldenring/validators/profiles.js +287 -0
  229. package/dist/surfaces/index.js +34 -0
  230. package/dist/surfaces/nivalisnights/compat.js +229 -0
  231. package/dist/surfaces/nivalisnights/index/build.js +310 -0
  232. package/dist/surfaces/nivalisnights/index/index.js +5 -0
  233. package/dist/surfaces/nivalisnights/index/queries.js +231 -0
  234. package/dist/surfaces/nivalisnights/index.js +4 -0
  235. package/dist/surfaces/nivalisnights/logs.js +473 -0
  236. package/dist/surfaces/nivalisnights/surface.js +195 -0
  237. package/dist/surfaces/skyrimse/archives.js +229 -0
  238. package/dist/surfaces/skyrimse/compat.js +163 -0
  239. package/dist/surfaces/skyrimse/index.js +12 -0
  240. package/dist/surfaces/skyrimse/loadorder.js +174 -0
  241. package/dist/surfaces/skyrimse/logs.js +241 -0
  242. package/dist/surfaces/skyrimse/plugins.js +149 -0
  243. package/dist/surfaces/skyrimse/records.js +94 -0
  244. package/dist/surfaces/skyrimse/surface.js +240 -0
  245. package/dist/surfaces/skyrimse/validators/archive-records.js +165 -0
  246. package/dist/surfaces/skyrimse/validators/archives.js +110 -0
  247. package/dist/surfaces/skyrimse/validators/plugins.js +374 -0
  248. package/dist/surfaces/skyrimse/validators/records.js +117 -0
  249. package/dist/surfaces/stardewvalley/compat.js +190 -0
  250. package/dist/surfaces/stardewvalley/config.js +42 -0
  251. package/dist/surfaces/stardewvalley/contentpatcher.js +203 -0
  252. package/dist/surfaces/stardewvalley/index/index.js +229 -0
  253. package/dist/surfaces/stardewvalley/index/wiki.js +190 -0
  254. package/dist/surfaces/stardewvalley/index.js +12 -0
  255. package/dist/surfaces/stardewvalley/logs.js +307 -0
  256. package/dist/surfaces/stardewvalley/mods.js +182 -0
  257. package/dist/surfaces/stardewvalley/surface.js +244 -0
  258. package/dist/surfaces/stardewvalley/validators/content.js +321 -0
  259. package/dist/surfaces/stardewvalley/validators/fields.js +147 -0
  260. package/dist/surfaces/stardewvalley/validators/loadorder.js +69 -0
  261. package/dist/surfaces/stardewvalley/validators/manifest.js +221 -0
  262. package/dist/surfaces/subnautica2/compat.js +138 -0
  263. package/dist/surfaces/subnautica2/index.js +5 -0
  264. package/dist/surfaces/subnautica2/logs.js +129 -0
  265. package/dist/surfaces/subnautica2/paks.js +173 -0
  266. package/dist/surfaces/subnautica2/surface.js +151 -0
  267. package/dist/surfaces/subnautica2/validators/packaging.js +298 -0
  268. package/dist/surfaces/valheim/compat.js +171 -0
  269. package/dist/surfaces/valheim/index/index.js +301 -0
  270. package/dist/surfaces/valheim/index/tables.js +103 -0
  271. package/dist/surfaces/valheim/index.js +11 -0
  272. package/dist/surfaces/valheim/logs.js +197 -0
  273. package/dist/surfaces/valheim/packaging.js +131 -0
  274. package/dist/surfaces/valheim/plugins.js +270 -0
  275. package/dist/surfaces/valheim/references.js +121 -0
  276. package/dist/surfaces/valheim/surface.js +223 -0
  277. package/dist/surfaces/valheim/validators/packaging.js +218 -0
  278. package/dist/surfaces/valheim/validators/plugins.js +287 -0
  279. package/dist/surfaces/valheim/validators/references.js +169 -0
  280. package/knowledge/baldursgate3/animations.gr2.yaml +166 -0
  281. package/knowledge/baldursgate3/audio.voice.yaml +182 -0
  282. package/knowledge/baldursgate3/audio.wwise.yaml +104 -0
  283. package/knowledge/baldursgate3/charactercreation.heads-hair.yaml +193 -0
  284. package/knowledge/baldursgate3/communitylibrary.shared-content.yaml +129 -0
  285. package/knowledge/baldursgate3/compatibilityframework.api.yaml +176 -0
  286. package/knowledge/baldursgate3/crash.causes.yaml +90 -0
  287. package/knowledge/baldursgate3/dialogue.dialogbank.yaml +120 -0
  288. package/knowledge/baldursgate3/dialogue.files.yaml +236 -0
  289. package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +386 -0
  290. package/knowledge/baldursgate3/dialogue.yaml +122 -0
  291. package/knowledge/baldursgate3/feats.classes.yaml +164 -0
  292. package/knowledge/baldursgate3/items.cloth-physics.yaml +167 -0
  293. package/knowledge/baldursgate3/items.roottemplates.yaml +320 -0
  294. package/knowledge/baldursgate3/items.visualbank.yaml +628 -0
  295. package/knowledge/baldursgate3/journal.quests.yaml +214 -0
  296. package/knowledge/baldursgate3/loadorder.modsettings.yaml +128 -0
  297. package/knowledge/baldursgate3/localization.handles.yaml +92 -0
  298. package/knowledge/baldursgate3/misc.encoding-and-practice.yaml +111 -0
  299. package/knowledge/baldursgate3/origins.companions.yaml +144 -0
  300. package/knowledge/baldursgate3/osiris.signatures.yaml +211 -0
  301. package/knowledge/baldursgate3/packaging.meta.yaml +155 -0
  302. package/knowledge/baldursgate3/packaging.paks.yaml +79 -0
  303. package/knowledge/baldursgate3/process.launch.yaml +34 -0
  304. package/knowledge/baldursgate3/progressions.yaml +92 -0
  305. package/knowledge/baldursgate3/publishing.modio-nexus.yaml +170 -0
  306. package/knowledge/baldursgate3/publishing.toolkit-modio.yaml +135 -0
  307. package/knowledge/baldursgate3/races.progression.yaml +228 -0
  308. package/knowledge/baldursgate3/scriptextender.console.yaml +257 -0
  309. package/knowledge/baldursgate3/scriptextender.yaml +252 -0
  310. package/knowledge/baldursgate3/stats.boosts.yaml +293 -0
  311. package/knowledge/baldursgate3/stats.equipment-kits.yaml +69 -0
  312. package/knowledge/baldursgate3/stats.interrupts.yaml +45 -0
  313. package/knowledge/baldursgate3/stats.itemcombos.yaml +65 -0
  314. package/knowledge/baldursgate3/stats.spells.yaml +293 -0
  315. package/knowledge/baldursgate3/stats.weapons.yaml +210 -0
  316. package/knowledge/baldursgate3/toolchain.divine.yaml +179 -0
  317. package/knowledge/baldursgate3/toolchain.lslib-formats.yaml +242 -0
  318. package/knowledge/baldursgate3/toolkit.levels.yaml +177 -0
  319. package/knowledge/baldursgate3/ui.icons-atlas.yaml +78 -0
  320. package/knowledge/baldursgate3/ui.icons.yaml +412 -0
  321. package/knowledge/baldursgate3/ui.mcm.yaml +161 -0
  322. package/knowledge/baldursgate3/vfx.materials.yaml +174 -0
  323. package/knowledge/baldursgate3/visuals.characterfix.yaml +73 -0
  324. package/knowledge/cyberpunk2077/animations.anims.yaml +118 -0
  325. package/knowledge/cyberpunk2077/anims.archivexl.yaml +163 -0
  326. package/knowledge/cyberpunk2077/anims.authoring.yaml +579 -0
  327. package/knowledge/cyberpunk2077/anims.encoder.yaml +156 -0
  328. package/knowledge/cyberpunk2077/anims.graph.yaml +629 -0
  329. package/knowledge/cyberpunk2077/anims.method.yaml +360 -0
  330. package/knowledge/cyberpunk2077/anims.pipeline.yaml +352 -0
  331. package/knowledge/cyberpunk2077/anims.roundtrip.yaml +222 -0
  332. package/knowledge/cyberpunk2077/anims.sets.yaml +176 -0
  333. package/knowledge/cyberpunk2077/archivexl.bodytypes.yaml +149 -0
  334. package/knowledge/cyberpunk2077/archivexl.dynamic-appearances.yaml +218 -0
  335. package/knowledge/cyberpunk2077/archivexl.manifest.yaml +214 -0
  336. package/knowledge/cyberpunk2077/audio.events.yaml +168 -0
  337. package/knowledge/cyberpunk2077/audio.sounds.yaml +164 -0
  338. package/knowledge/cyberpunk2077/audio.voicesets.yaml +146 -0
  339. package/knowledge/cyberpunk2077/cet.sandbox.yaml +219 -0
  340. package/knowledge/cyberpunk2077/clothing.garments.yaml +283 -0
  341. package/knowledge/cyberpunk2077/clothing.refits.yaml +215 -0
  342. package/knowledge/cyberpunk2077/codeware.overview.yaml +111 -0
  343. package/knowledge/cyberpunk2077/codeware.systems.yaml +192 -0
  344. package/knowledge/cyberpunk2077/drones.combat.yaml +416 -0
  345. package/knowledge/cyberpunk2077/drones.control.yaml +552 -0
  346. package/knowledge/cyberpunk2077/entities.appearance.yaml +166 -0
  347. package/knowledge/cyberpunk2077/equipmentex.slots.yaml +151 -0
  348. package/knowledge/cyberpunk2077/fx.effects.yaml +246 -0
  349. package/knowledge/cyberpunk2077/fx.lights.yaml +171 -0
  350. package/knowledge/cyberpunk2077/hair.modding.yaml +145 -0
  351. package/knowledge/cyberpunk2077/items.wiring.yaml +246 -0
  352. package/knowledge/cyberpunk2077/loadorder.archives.yaml +71 -0
  353. package/knowledge/cyberpunk2077/mesh.blender.yaml +209 -0
  354. package/knowledge/cyberpunk2077/nativedb.dump.yaml +98 -0
  355. package/knowledge/cyberpunk2077/npc.appearance.yaml +275 -0
  356. package/knowledge/cyberpunk2077/npc.behaviour.yaml +128 -0
  357. package/knowledge/cyberpunk2077/npc.locomotion.yaml +490 -0
  358. package/knowledge/cyberpunk2077/packaging.deploy.yaml +175 -0
  359. package/knowledge/cyberpunk2077/packaging.variants.yaml +176 -0
  360. package/knowledge/cyberpunk2077/player.control.yaml +918 -0
  361. package/knowledge/cyberpunk2077/process.assetbuild.yaml +154 -0
  362. package/knowledge/cyberpunk2077/process.lessons.yaml +276 -0
  363. package/knowledge/cyberpunk2077/quests.minor.yaml +46 -0
  364. package/knowledge/cyberpunk2077/quests.scenes.yaml +282 -0
  365. package/knowledge/cyberpunk2077/quickhacks.tiers.yaml +41 -0
  366. package/knowledge/cyberpunk2077/red4ext.api.yaml +645 -0
  367. package/knowledge/cyberpunk2077/red4ext.plugins.yaml +124 -0
  368. package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +245 -0
  369. package/knowledge/cyberpunk2077/redscript.compile-log.yaml +76 -0
  370. package/knowledge/cyberpunk2077/redscript.modules.yaml +118 -0
  371. package/knowledge/cyberpunk2077/toolchain.wolvenkit.yaml +163 -0
  372. package/knowledge/cyberpunk2077/tweakdb.loot-recipes.yaml +184 -0
  373. package/knowledge/cyberpunk2077/tweakdb.melee.yaml +352 -0
  374. package/knowledge/cyberpunk2077/tweakdb.weapons.yaml +325 -0
  375. package/knowledge/cyberpunk2077/tweakxl.hotreload.yaml +151 -0
  376. package/knowledge/cyberpunk2077/tweakxl.yaml-syntax.yaml +208 -0
  377. package/knowledge/cyberpunk2077/ui.icons.yaml +259 -0
  378. package/knowledge/cyberpunk2077/ui.inkatlas.yaml +81 -0
  379. package/knowledge/cyberpunk2077/ui.modsettings.yaml +204 -0
  380. package/knowledge/cyberpunk2077/vehicles.control.yaml +475 -0
  381. package/knowledge/cyberpunk2077/vehicles.dashboard-ui.yaml +203 -0
  382. package/knowledge/cyberpunk2077/vehicles.entities.yaml +195 -0
  383. package/knowledge/cyberpunk2077/vehicles.wiring.yaml +137 -0
  384. package/knowledge/cyberpunk2077/wolvenkit.cli.yaml +167 -0
  385. package/knowledge/cyberpunk2077/workspot.system.yaml +55 -0
  386. package/knowledge/cyberpunk2077/world.interactions.yaml +41 -0
  387. package/knowledge/cyberpunk2077/world.loot-container.yaml +167 -0
  388. package/knowledge/cyberpunk2077/world.sectors.yaml +233 -0
  389. package/knowledge/eldenring/formats.binders.yaml +237 -0
  390. package/knowledge/eldenring/launcher.me3.yaml +230 -0
  391. package/knowledge/eldenring/launcher.modengine2.yaml +119 -0
  392. package/knowledge/eldenring/params.paramdef.yaml +149 -0
  393. package/knowledge/eldenring/params.regulation.yaml +142 -0
  394. package/knowledge/eldenring/saves.online.yaml +118 -0
  395. package/knowledge/eldenring/toolchain.cli.yaml +256 -0
  396. package/knowledge/nivalisnights/modding.runtime.yaml +157 -0
  397. package/knowledge/skyrimse/archives.bsa.yaml +212 -0
  398. package/knowledge/skyrimse/install.detection.yaml +41 -0
  399. package/knowledge/skyrimse/loadorder.files.yaml +242 -0
  400. package/knowledge/skyrimse/plugins.esl.yaml +189 -0
  401. package/knowledge/skyrimse/plugins.format.yaml +224 -0
  402. package/knowledge/skyrimse/skse.runtime.yaml +283 -0
  403. package/knowledge/skyrimse/toolchain.xedit-spriggit.yaml +336 -0
  404. package/knowledge/stardewvalley/contentpatcher.format.yaml +338 -0
  405. package/knowledge/stardewvalley/distribution.channels.yaml +236 -0
  406. package/knowledge/stardewvalley/game.versions.yaml +202 -0
  407. package/knowledge/stardewvalley/install.layout.yaml +277 -0
  408. package/knowledge/stardewvalley/smapi.loader.yaml +284 -0
  409. package/knowledge/stardewvalley/smapi.logs.yaml +329 -0
  410. package/knowledge/stardewvalley/smapi.manifest.yaml +293 -0
  411. package/knowledge/stardewvalley/toolchain.build.yaml +261 -0
  412. package/knowledge/stardewvalley/wiki.dataformat.yaml +130 -0
  413. package/knowledge/subnautica2/game.build.yaml +331 -0
  414. package/knowledge/subnautica2/modding.state.yaml +210 -0
  415. package/knowledge/subnautica2/ue5.containers.yaml +72 -0
  416. package/knowledge/subnautica2/ue5.paks.yaml +203 -0
  417. package/knowledge/subnautica2/ue5.tooling.yaml +175 -0
  418. package/knowledge/subnautica2/ue5.ue4ss.yaml +203 -0
  419. package/knowledge/valheim/bepinex.loader.yaml +247 -0
  420. package/knowledge/valheim/bepinex.plugins.yaml +251 -0
  421. package/knowledge/valheim/game.versions.yaml +321 -0
  422. package/knowledge/valheim/install.layout.yaml +216 -0
  423. package/knowledge/valheim/jotunn.library.yaml +334 -0
  424. package/knowledge/valheim/thunderstore.packaging.yaml +217 -0
  425. package/knowledge/valheim/toolchain.build.yaml +111 -0
  426. package/package.json +29 -0
  427. package/scaffolds/baldursgate3/new-class-injection.yaml +148 -0
  428. package/scaffolds/baldursgate3/new-item.yaml +264 -0
  429. package/scaffolds/baldursgate3/new-mod.yaml +174 -0
  430. package/scaffolds/baldursgate3/new-passive.yaml +100 -0
  431. package/scaffolds/baldursgate3/new-project.yaml +118 -0
  432. package/scaffolds/baldursgate3/new-quest-stub.yaml +159 -0
  433. package/scaffolds/baldursgate3/new-spell.yaml +102 -0
  434. package/scaffolds/baldursgate3/new-status.yaml +119 -0
  435. package/scaffolds/baldursgate3/sequences/subclass-kit.yaml +50 -0
  436. package/scaffolds/cyberpunk2077/edit-tweak.yaml +70 -0
  437. package/scaffolds/cyberpunk2077/mesh-export-preset.yaml +170 -0
  438. package/scaffolds/cyberpunk2077/new-entity-patch.yaml +255 -0
  439. package/scaffolds/cyberpunk2077/new-garment-refit.yaml +279 -0
  440. package/scaffolds/cyberpunk2077/new-item-chain.yaml +548 -0
  441. package/scaffolds/cyberpunk2077/new-localization.yaml +130 -0
  442. package/scaffolds/cyberpunk2077/new-mod-settings.yaml +133 -0
  443. package/scaffolds/cyberpunk2077/new-player-replacer.yaml +1003 -0
  444. package/scaffolds/cyberpunk2077/new-project.yaml +157 -0
  445. package/scaffolds/cyberpunk2077/new-sector-node.yaml +156 -0
  446. package/scaffolds/cyberpunk2077/new-tweak.yaml +121 -0
  447. package/scaffolds/cyberpunk2077/new-vehicle-livery.yaml +148 -0
  448. package/scaffolds/cyberpunk2077/new-workspot-entity.yaml +177 -0
  449. package/scaffolds/cyberpunk2077/sequences/iconic-weapon.yaml +72 -0
  450. package/scaffolds/cyberpunk2077/sequences/quickhack-takeover.yaml +213 -0
  451. package/scaffolds/stardewvalley/new-cp-patch.yaml +106 -0
  452. package/scaffolds/stardewvalley/new-csharp-mod.yaml +195 -0
  453. package/scaffolds/stardewvalley/new-project.yaml +184 -0
  454. package/scaffolds/stardewvalley/sequences/content-pack.yaml +31 -0
  455. package/scripts/mw-tool.mjs +184 -0
@@ -0,0 +1,121 @@
1
+ function stripComments(text) {
2
+ let out = "";
3
+ let i = 0;
4
+ while (i < text.length) {
5
+ const c = text[i];
6
+ const next = text[i + 1];
7
+ if (c === "@" && next === '"') {
8
+ let j = i + 2;
9
+ while (j < text.length) {
10
+ if (text[j] === '"' && text[j + 1] === '"') {
11
+ j += 2;
12
+ continue;
13
+ }
14
+ if (text[j] === '"')
15
+ break;
16
+ j++;
17
+ }
18
+ out += text.slice(i, j + 1);
19
+ i = j + 1;
20
+ continue;
21
+ }
22
+ if (c === '"' || c === "'") {
23
+ let j = i + 1;
24
+ while (j < text.length && text[j] !== c) {
25
+ if (text[j] === "\\")
26
+ j++;
27
+ if (text[j] === "\n")
28
+ break;
29
+ j++;
30
+ }
31
+ out += text.slice(i, j + 1);
32
+ i = j + 1;
33
+ continue;
34
+ }
35
+ if (c === "/" && next === "/") {
36
+ while (i < text.length && text[i] !== "\n") {
37
+ out += " ";
38
+ i++;
39
+ }
40
+ continue;
41
+ }
42
+ if (c === "/" && next === "*") {
43
+ const end = text.indexOf("*/", i + 2);
44
+ const stop = end < 0 ? text.length : end + 2;
45
+ out += text.slice(i, stop).replace(/[^\n]/g, " ");
46
+ i = stop;
47
+ continue;
48
+ }
49
+ out += c;
50
+ i++;
51
+ }
52
+ return out;
53
+ }
54
+ function lineAt(text, index) {
55
+ let line = 1;
56
+ for (let i = 0; i < index && i < text.length; i++)
57
+ if (text.charCodeAt(i) === 10)
58
+ line++;
59
+ return line;
60
+ }
61
+ const STR = String.raw `"((?:[^"\\\n]|\\.)*)"`;
62
+ const ASSET_ID_RE = /^[0-9a-f]{32}$/i;
63
+ function all(re, text, fn) {
64
+ let m;
65
+ const g = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
66
+ while ((m = g.exec(text)))
67
+ fn(m);
68
+ }
69
+ function uniq(values) {
70
+ return [...new Set(values)];
71
+ }
72
+ export function scanReferences(source) {
73
+ const text = stripComments(source);
74
+ const refs = [];
75
+ const declares = { prefabs: [], tokens: [], pieceTables: [] };
76
+ const push = (kind, value, index, via) => refs.push({ kind, value, line: lineAt(text, index), via });
77
+ all(new RegExp(String.raw `new\s+(CustomItem|CustomPiece|CustomPrefab)\s*\(\s*${STR}\s*,\s*${STR}`), text, (m) => {
78
+ declares.prefabs.push(m[2]);
79
+ push("prefab", m[3], m.index, `new ${m[1]}(…, "${m[3]}")`);
80
+ });
81
+ all(new RegExp(String.raw `new\s+(CustomItem|CustomPiece|CustomPrefab)\s*\(\s*${STR}\s*,\s*(?:true|false|fixReference)`), text, (m) => {
82
+ declares.prefabs.push(m[2]);
83
+ });
84
+ all(new RegExp(String.raw `new\s+(?:CustomItem|CustomPiece|CustomPrefab)\s*\(\s*[A-Za-z_][A-Za-z0-9_.]*\s*,\s*${STR}\s*,\s*(?:true|false|fixReference)`), text, (m) => {
85
+ declares.prefabs.push(m[1]);
86
+ });
87
+ all(new RegExp(String.raw `new\s+CustomPieceTable\s*\(\s*${STR}`), text, (m) => declares.pieceTables.push(m[1]));
88
+ all(new RegExp(String.raw `new\s+CustomPieceTable\s*\(\s*[A-Za-z_][A-Za-z0-9_.]*\s*,\s*${STR}`), text, (m) => declares.pieceTables.push(m[1]));
89
+ all(new RegExp(String.raw `\.(GetPrefab|GetItemPrefab)\s*\(\s*${STR}\s*\)`), text, (m) => push("prefab", m[2], m.index, `${m[1]}("${m[2]}")`));
90
+ all(new RegExp(String.raw `\.CreateClonedPrefab\s*\(\s*${STR}\s*,\s*${STR}`), text, (m) => push("prefab", m[2], m.index, `CreateClonedPrefab(…, "${m[2]}")`));
91
+ all(new RegExp(String.raw `\.(?:CreateClonedPrefab|CreateEmptyPrefab)\s*\(\s*${STR}`), text, (m) => declares.prefabs.push(m[1]));
92
+ all(new RegExp(String.raw `\.AddRequirement\s*\(\s*${STR}`), text, (m) => push("requirement", m[1], m.index, `AddRequirement("${m[1]}")`));
93
+ all(new RegExp(String.raw `\bItem\s*=\s*${STR}`), text, (m) => push("requirement", m[1], m.index, `Item = "${m[1]}"`));
94
+ all(new RegExp(String.raw `\bPieceTable\s*=\s*${STR}`), text, (m) => push("piece_table", m[1], m.index, `PieceTable = "${m[1]}"`));
95
+ all(new RegExp(String.raw `"(\$[A-Za-z0-9_]+)"`), text, (m) => push("token", m[1], m.index, `"${m[1]}"`));
96
+ all(new RegExp(String.raw `"([0-9a-fA-F]{32})"`), text, (m) => {
97
+ if (ASSET_ID_RE.test(m[1]))
98
+ push("asset", m[1].toLowerCase(), m.index, `"${m[1]}"`);
99
+ });
100
+ all(new RegExp(String.raw `AddTranslation\s*\(\s*${STR}\s*,\s*new\s+Dictionary<\s*string\s*,\s*string\s*>\s*\{([\s\S]*?)\}\s*\)`), text, (m) => {
101
+ all(new RegExp(String.raw `\{\s*${STR}\s*,\s*${STR}\s*\}`), m[2], (pair) => declares.tokens.push(pair[1].replace(/^\$/, "")));
102
+ });
103
+ all(new RegExp(String.raw `AddTranslation\s*\(\s*${STR}\s*,\s*${STR}\s*(?:,\s*${STR}\s*)?\)`), text, (m) => {
104
+ declares.tokens.push((m[3] !== undefined ? m[2] : m[1]).replace(/^\$/, ""));
105
+ });
106
+ declares.prefabs = uniq(declares.prefabs);
107
+ declares.tokens = uniq(declares.tokens);
108
+ declares.pieceTables = uniq(declares.pieceTables);
109
+ return { refs, declares };
110
+ }
111
+ export function translationKeys(jsonText) {
112
+ try {
113
+ const parsed = JSON.parse(jsonText.replace(/^/, ""));
114
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
115
+ return [];
116
+ return Object.keys(parsed).map((k) => k.replace(/^\$/, ""));
117
+ }
118
+ catch {
119
+ return [];
120
+ }
121
+ }
@@ -0,0 +1,223 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { homePath, pathExists, readTextIfExists } from "../../core/fsutil.js";
4
+ import { findInSteam } from "../../core/steam.js";
5
+ import { dedupeByRoot, logs, roots } from "../../core/surfaceutil.js";
6
+ import { valheimCompat } from "./compat.js";
7
+ import { valheimIndex } from "./index/index.js";
8
+ import { SIGNATURES } from "./logs.js";
9
+ import { PACKAGING_VALIDATORS } from "./validators/packaging.js";
10
+ import { PLUGIN_VALIDATORS } from "./validators/plugins.js";
11
+ import { REFERENCE_VALIDATORS } from "./validators/references.js";
12
+ export const valheim = {
13
+ id: "valheim",
14
+ name: "Valheim",
15
+ aliases: ["valheim", "vh"],
16
+ summary: "Unity (Mono) with BepInEx 5: plugin assemblies under BepInEx/plugins, Harmony patches, " +
17
+ "Jötunn as the common library, Thunderstore and r2modman as the distribution channel.",
18
+ async detect() {
19
+ const installs = [];
20
+ for (const [variable, source] of [["MODWRIGHT_VALHEIM_ROOT", "env"], ["VALHEIM_INSTALL", "env"]]) {
21
+ const value = process.env[variable];
22
+ if (value && (await pathExists(value)))
23
+ installs.push({ gameId: "valheim", root: value, source });
24
+ }
25
+ for (const root of await findInSteam(["Valheim", "Valheim dedicated server"])) {
26
+ const kind = await installKind(root);
27
+ if (kind)
28
+ installs.push({ gameId: "valheim", root, source: "steam" });
29
+ }
30
+ return dedupeByRoot(installs);
31
+ },
32
+ modRoots(install) {
33
+ const bepinex = path.join(install.root, "BepInEx");
34
+ return roots([
35
+ {
36
+ id: "plugins",
37
+ label: "BepInEx plugins",
38
+ path: path.join(bepinex, "plugins"),
39
+ layout: "directory",
40
+ conflictScope: "isolated",
41
+ notes: "Each mod in its own subdirectory (loose .dlls directly in plugins/ load too). Real " +
42
+ "collisions are on plugin GUID, not on file path: two plugins with the same GUID in " +
43
+ "different folders silently drop the older one, with one 'Skipping [...] because a " +
44
+ "newer version exists' line in LogOutput.log. Load order is a topological sort of " +
45
+ "[BepInDependency] declarations; there is no order file.",
46
+ },
47
+ {
48
+ id: "patchers",
49
+ label: "BepInEx preloader patchers",
50
+ path: path.join(bepinex, "patchers"),
51
+ layout: "directory",
52
+ conflictScope: "isolated",
53
+ notes: "Assembly patchers that run before any plugin. A failure here breaks everything downstream.",
54
+ },
55
+ {
56
+ id: "config",
57
+ label: "BepInEx config",
58
+ path: path.join(bepinex, "config"),
59
+ layout: "flat",
60
+ conflictScope: "merged",
61
+ notes: "One <GUID>.cfg per plugin plus BepInEx.cfg, created at runtime. Thunderstore's " +
62
+ "ecosystem schema tracks this folder as 'none', which is why it is the one merged " +
63
+ "root a mod actually deploys into: core and loader are merged too, but core is never " +
64
+ "a deploy target and loader holds BepInEx's own files.",
65
+ },
66
+ {
67
+ id: "monomod",
68
+ label: "MonoMod patches",
69
+ path: path.join(bepinex, "monomod"),
70
+ layout: "file",
71
+ extensions: [".mm.dll"],
72
+ conflictScope: "isolated",
73
+ notes: "Declared by Thunderstore's schema; rare on Valheim.",
74
+ },
75
+ {
76
+ id: "core",
77
+ label: "BepInEx core",
78
+ path: path.join(bepinex, "core"),
79
+ layout: "flat",
80
+ conflictScope: "merged",
81
+ notes: "BepInEx's own assemblies. Listed so a mod wrongly shipping into it is visible; never a deploy target.",
82
+ },
83
+ {
84
+ id: "loader",
85
+ label: "Doorstop loader files",
86
+ path: install.root,
87
+ layout: "flat",
88
+ conflictScope: "merged",
89
+ notes: "winhttp.dll, doorstop_config.ini and .doorstop_version beside the executable (the " +
90
+ "Linux run scripts too). Where 'is BepInEx installed' is answered; the ini's keys " +
91
+ "changed at BepInEx 5.4.23 ([UnityDoorstop] camelCase before, [General]/[UnityMono] " +
92
+ "snake_case after), so an old ini under a new BepInEx loads nothing.",
93
+ },
94
+ ]);
95
+ },
96
+ async readManifest(modPath, root) {
97
+ if (root.id !== "plugins")
98
+ return undefined;
99
+ const file = path.join(modPath, "manifest.json");
100
+ const text = await readTextIfExists(file);
101
+ if (text === undefined)
102
+ return undefined;
103
+ try {
104
+ const raw = JSON.parse(text);
105
+ const deps = Array.isArray(raw.dependencies) ? raw.dependencies.filter((d) => typeof d === "string") : undefined;
106
+ return {
107
+ format: "thunderstore-manifest.json",
108
+ ...(typeof raw.name === "string" ? { name: raw.name } : {}),
109
+ ...(typeof raw.version_number === "string" ? { version: raw.version_number } : {}),
110
+ ...(typeof raw.description === "string" ? { description: raw.description } : {}),
111
+ ...(deps ? { dependencies: deps } : {}),
112
+ raw,
113
+ };
114
+ }
115
+ catch {
116
+ return undefined;
117
+ }
118
+ },
119
+ logs(install) {
120
+ const bepinex = path.join(install.root, "BepInEx");
121
+ return logs([
122
+ {
123
+ id: "bepinex",
124
+ label: "BepInEx LogOutput.log",
125
+ candidates: [path.join(bepinex, "LogOutput.log"), ...[1, 2, 3, 4].map((n) => path.join(bepinex, `LogOutput.log.${n}`))],
126
+ rootId: "plugins",
127
+ notes: "One fresh file per run (FileMode.Create unless [Logging.Disk] AppendLog is on), so a " +
128
+ "whole-file read is already one run. Lines are '[Level : Source] message' with no " +
129
+ "timestamp; Debug lines are excluded from disk by default. Unity exceptions are not " +
130
+ "here unless WriteUnityLog is on: see Player.log.",
131
+ },
132
+ {
133
+ id: "player",
134
+ label: "Unity Player.log",
135
+ candidates: [homePath("AppData", "LocalLow", "IronGate", "Valheim", "Player.log")],
136
+ notes: "Unity's own log for the client: engine and script exceptions, including those a " +
137
+ "plugin's patch throws at runtime. The dedicated server's location follows its " +
138
+ "-savedir argument and is not fixed.",
139
+ },
140
+ {
141
+ id: "preloader-crash",
142
+ label: "BepInEx preloader crash log",
143
+ candidates: [path.join(install.root, "preloader_*.log")],
144
+ rootId: "patchers",
145
+ notes: "Written beside the executable only when the preloader itself threw; newest wins.",
146
+ },
147
+ ]);
148
+ },
149
+ async explainMissing(install) {
150
+ const reasons = {};
151
+ const winhttp = path.join(install.root, "winhttp.dll");
152
+ if (!(await pathExists(winhttp))) {
153
+ reasons.bepinex = `${winhttp} was not found beside the executable: BepInEx (the Doorstop loader) is not installed here, so no LogOutput.log can exist. Under r2modman the install lives in a profile folder, not the game folder.`;
154
+ reasons["preloader-crash"] = reasons.bepinex;
155
+ return reasons;
156
+ }
157
+ const cfg = await readTextIfExists(path.join(install.root, "BepInEx", "config", "BepInEx.cfg"));
158
+ if (cfg !== undefined && diskLoggingDisabled(cfg)) {
159
+ reasons.bepinex = "[Logging.Disk] Enabled = false in BepInEx/config/BepInEx.cfg: the loader runs but writes no LogOutput.log. Set it to true.";
160
+ }
161
+ else if (cfg === undefined) {
162
+ reasons.bepinex = "BepInEx/config/BepInEx.cfg does not exist yet: BepInEx creates it on first run, so the loader has not run since it was installed.";
163
+ }
164
+ reasons["preloader-crash"] = "The preloader crash log exists only when the preloader itself threw.";
165
+ return reasons;
166
+ },
167
+ logSignatures: SIGNATURES,
168
+ validators: [...PACKAGING_VALIDATORS, ...PLUGIN_VALIDATORS, ...REFERENCE_VALIDATORS],
169
+ compat: valheimCompat,
170
+ index: valheimIndex,
171
+ toolNeeds: [
172
+ { feature: "index", data: [{ id: "valheim.jotunn", envVar: "MODWRIGHT_VALHEIM_JOTUNN_DATA", what: "a Valheim-Modding/Jotunn checkout (or its JotunnLib/Documentation/data folder)" }], note: "build_index reads JotunnDoc's committed dumps; no game install or tool is needed." },
173
+ { feature: "build", tools: ["dotnet"], note: "dotnet build compiles BepInEx plugins." },
174
+ { feature: "build", tools: ["tcli"], optional: true, note: "Thunderstore packaging only." },
175
+ ],
176
+ processNames: ["valheim.exe", "valheim_server.exe"],
177
+ guidance: "Mods are BepInEx 5 plugins ordered by their own [BepInDependency] declarations; there is " +
178
+ "no order file, so load_order is unsupported here by design and the sequence of 'Loading " +
179
+ "[...]' lines in LogOutput.log is an observation of the last run, not a plan. Plugin " +
180
+ "collisions are on GUID, not path. The community's BepInEx is denikson's BepInExPack_Valheim, " +
181
+ "which differs from stock in ways that matter (SemVer plugin versions accepted, console on); " +
182
+ "an r2modman profile keeps its own BepInEx under AppData, so the game folder can look " +
183
+ "unmodded while the profile is fully modded. A project declares its folders by source kind: " +
184
+ "'package' for the Thunderstore package root (manifest.json, icon.png, README.md and the " +
185
+ "BepInEx routes), 'source' for the plugin's C# tree. Valheim left early access on 2026-09-09 with a " +
186
+ "Unity engine upgrade, and most plugins were broken by it at the time of writing. Nothing " +
187
+ "here has run against an installed game.",
188
+ };
189
+ export async function installKind(root) {
190
+ if (await pathExists(path.join(root, "valheim_server.exe")))
191
+ return "server";
192
+ if (await pathExists(path.join(root, "valheim.exe")))
193
+ return "client";
194
+ try {
195
+ const entries = await fs.readdir(root);
196
+ if (entries.some((e) => e.toLowerCase() === "valheim_server_data"))
197
+ return "server";
198
+ if (entries.some((e) => e.toLowerCase() === "valheim_data"))
199
+ return "client";
200
+ }
201
+ catch {
202
+ }
203
+ return undefined;
204
+ }
205
+ export function diskLoggingDisabled(cfgText) {
206
+ let section = "";
207
+ for (const raw of cfgText.split(/\r?\n/)) {
208
+ const line = raw.trim();
209
+ if (!line || line.startsWith("#"))
210
+ continue;
211
+ const sec = /^\[(.+)\]$/.exec(line);
212
+ if (sec) {
213
+ section = sec[1].trim().toLowerCase();
214
+ continue;
215
+ }
216
+ if (section !== "logging.disk")
217
+ continue;
218
+ const m = /^enabled\s*=\s*(\w+)/i.exec(line);
219
+ if (m)
220
+ return m[1].toLowerCase() === "false";
221
+ }
222
+ return false;
223
+ }
@@ -0,0 +1,218 @@
1
+ import { promises as fs } from "node:fs";
2
+ import * as path from "node:path";
3
+ import { walk } from "../../../core/fsutil.js";
4
+ import { checkThunderstoreToml, ICON_SIZE, PACKAGE_OPTIONAL_ROOT_FILES, PACKAGE_ROOT_FILES, PACKAGE_ROUTES, parseThunderstoreManifest, readPngDimensions, routeOf } from "../packaging.js";
5
+ export const PACKAGE_SOURCE_KIND = "package";
6
+ const EVIDENCE = ["valheim.thunderstore.packaging.required-zip-contents", "valheim.thunderstore.packaging.manifest-fields", "valheim.thunderstore.packaging.dependency-string-format", "valheim.thunderstore.packaging.versions-are-immutable"];
7
+ const ROOT_FILES_EVIDENCE = ["valheim.thunderstore.packaging.required-zip-contents", "valheim.toolchain.build.icon-must-be-a-256-png", "valheim.thunderstore.packaging.versions-are-immutable"];
8
+ const ROUTES_EVIDENCE = ["valheim.thunderstore.packaging.bepinex-folder-routing", "valheim.thunderstore.packaging.ecosystem-install-rules"];
9
+ const TOML_EVIDENCE = ["valheim.thunderstore.packaging.thunderstore-toml-dependency-shape-differs", "valheim.thunderstore.packaging.tcli-does-not-compile"];
10
+ const DIST_EVIDENCE = ["valheim.jotunn.library.no-redistribution-of-game-assemblies", "valheim.install.layout.managed-assembly-set"];
11
+ const appliesToPackages = (target, project) => target.kind === "project" && project.sources.some((s) => s.exists && s.kind === PACKAGE_SOURCE_KIND);
12
+ const APPLIES = `the project, when it has a source of kind "${PACKAGE_SOURCE_KIND}"`;
13
+ function packageSources(project) {
14
+ return project.sources.filter((s) => s.exists && s.kind === PACKAGE_SOURCE_KIND);
15
+ }
16
+ function rootFile(entries, expected) {
17
+ const exact = entries.find((e) => e === expected);
18
+ const nearMisses = entries.filter((e) => e !== expected && e.toLowerCase() === expected.toLowerCase());
19
+ return { ...(exact ? { exact } : {}), nearMisses };
20
+ }
21
+ const manifest = {
22
+ id: "valheim.packaging.manifest",
23
+ game: "valheim",
24
+ groups: ["packaging"],
25
+ modTypes: ["packaging"],
26
+ summary: "manifest.json sits at the package root with that exact spelling, parses, and its five fields follow Thunderstore's rules (name charset and length, description length, three-integer version_number, website_url present, {team}-{package}-{version} dependencies).",
27
+ appliesTo: appliesToPackages,
28
+ appliesToDescription: APPLIES,
29
+ needs: { project: true },
30
+ cost: "edit",
31
+ severity: "block",
32
+ evidence: EVIDENCE,
33
+ async run(ctx) {
34
+ const findings = [];
35
+ for (const source of packageSources(ctx.project)) {
36
+ const entries = await fs.readdir(source.absPath);
37
+ const { exact, nearMisses } = rootFile(entries, "manifest.json");
38
+ for (const miss of nearMisses) {
39
+ findings.push({ validatorId: this.id, severity: "block", file: path.join(source.absPath, miss), message: `Package "${source.id}" has ${miss} where Thunderstore requires manifest.json; file naming is case-sensitive and the upload is refused.`, fix: "Rename it to manifest.json.", evidence: EVIDENCE });
40
+ }
41
+ if (!exact) {
42
+ if (nearMisses.length === 0)
43
+ findings.push({ validatorId: this.id, severity: "block", file: source.absPath, message: `Package "${source.id}" has no manifest.json at its root; Thunderstore requires manifest.json, icon.png and README.md there.`, evidence: EVIDENCE });
44
+ continue;
45
+ }
46
+ const file = path.join(source.absPath, exact);
47
+ let text;
48
+ try {
49
+ text = await ctx.readText(file);
50
+ }
51
+ catch (err) {
52
+ findings.push({ validatorId: this.id, severity: "block", file, message: `manifest.json could not be read: ${err.message}`, evidence: EVIDENCE });
53
+ continue;
54
+ }
55
+ try {
56
+ const { problems } = parseThunderstoreManifest(text);
57
+ for (const p of problems) {
58
+ findings.push({ validatorId: this.id, severity: "block", file, message: `manifest.json in "${source.id}": ${p.message}.`, fix: "Thunderstore rejects the upload; fix the field before publishing, since a published version cannot be edited.", evidence: EVIDENCE });
59
+ }
60
+ }
61
+ catch (err) {
62
+ findings.push({ validatorId: this.id, severity: "block", file, message: `manifest.json in "${source.id}" is not valid JSON: ${err.message}`, evidence: EVIDENCE });
63
+ }
64
+ }
65
+ return findings;
66
+ },
67
+ };
68
+ const rootFiles = {
69
+ id: "valheim.packaging.root-files",
70
+ game: "valheim",
71
+ groups: ["packaging"],
72
+ modTypes: ["packaging"],
73
+ summary: "icon.png (a 256x256 PNG) and README.md sit at the package root with exactly those names; CHANGELOG.md is optional.",
74
+ appliesTo: appliesToPackages,
75
+ appliesToDescription: APPLIES,
76
+ needs: { project: true },
77
+ cost: "edit",
78
+ severity: "block",
79
+ evidence: ROOT_FILES_EVIDENCE,
80
+ async run(ctx) {
81
+ const findings = [];
82
+ for (const source of packageSources(ctx.project)) {
83
+ const entries = await fs.readdir(source.absPath);
84
+ for (const expected of ["icon.png", "README.md"]) {
85
+ const { exact, nearMisses } = rootFile(entries, expected);
86
+ for (const miss of nearMisses) {
87
+ findings.push({ validatorId: this.id, severity: "block", file: path.join(source.absPath, miss), message: `Package "${source.id}" has ${miss} where Thunderstore requires ${expected}; file naming is case-sensitive.`, fix: `Rename it to ${expected}.`, evidence: ROOT_FILES_EVIDENCE });
88
+ }
89
+ if (!exact) {
90
+ if (nearMisses.length === 0)
91
+ findings.push({ validatorId: this.id, severity: "block", file: source.absPath, message: `Package "${source.id}" has no ${expected} at its root; Thunderstore requires it.`, evidence: ROOT_FILES_EVIDENCE });
92
+ continue;
93
+ }
94
+ if (expected === "icon.png") {
95
+ const file = path.join(source.absPath, exact);
96
+ const dims = await readPngDimensions(file).catch(() => undefined);
97
+ if (!dims) {
98
+ findings.push({ validatorId: this.id, severity: "block", file, message: `icon.png in "${source.id}" is not a PNG (no PNG signature and IHDR chunk); Thunderstore requires a PNG exactly ${ICON_SIZE}x${ICON_SIZE}.`, evidence: ROOT_FILES_EVIDENCE });
99
+ }
100
+ else if (dims.width !== ICON_SIZE || dims.height !== ICON_SIZE) {
101
+ findings.push({ validatorId: this.id, severity: "block", file, message: `icon.png in "${source.id}" is ${dims.width}x${dims.height}; Thunderstore requires exactly ${ICON_SIZE}x${ICON_SIZE}.`, fix: `Resize the icon to ${ICON_SIZE}x${ICON_SIZE}.`, evidence: ROOT_FILES_EVIDENCE });
102
+ }
103
+ }
104
+ }
105
+ for (const optional of PACKAGE_OPTIONAL_ROOT_FILES) {
106
+ for (const miss of rootFile(entries, optional).nearMisses) {
107
+ findings.push({ validatorId: this.id, severity: "warn", file: path.join(source.absPath, miss), message: `Package "${source.id}" has ${miss}; Thunderstore reads the optional changelog only as ${optional}.`, evidence: ROOT_FILES_EVIDENCE });
108
+ }
109
+ }
110
+ }
111
+ return findings;
112
+ },
113
+ };
114
+ const routes = {
115
+ id: "valheim.packaging.routes",
116
+ game: "valheim",
117
+ groups: ["packaging"],
118
+ modTypes: ["packaging"],
119
+ summary: `Every file in the package beyond the root files lands under one of the folders the managers preserve (${PACKAGE_ROUTES.join(", ")}, with or without the BepInEx/ prefix); anything else is flattened into plugins, and nothing ships into core.`,
120
+ appliesTo: appliesToPackages,
121
+ appliesToDescription: APPLIES,
122
+ needs: { project: true },
123
+ cost: "build",
124
+ severity: "warn",
125
+ evidence: ROUTES_EVIDENCE,
126
+ async run(ctx) {
127
+ const findings = [];
128
+ const rootNames = new Set([...PACKAGE_ROOT_FILES, ...PACKAGE_OPTIONAL_ROOT_FILES].map((n) => n.toLowerCase()));
129
+ for (const source of packageSources(ctx.project)) {
130
+ const { files, truncated } = await walk(source.absPath);
131
+ if (truncated)
132
+ findings.push({ validatorId: this.id, severity: "info", file: source.absPath, message: `Package "${source.id}" has more files than this scan's limit; the route check may be incomplete.`, evidence: ROUTES_EVIDENCE });
133
+ for (const rel of files) {
134
+ const file = path.join(source.absPath, rel);
135
+ const segments = rel.split(/[\\/]+/);
136
+ if (segments.length === 1) {
137
+ if (!rootNames.has(segments[0].toLowerCase())) {
138
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${rel} sits at the root of package "${source.id}"; the managers flatten it into BepInEx/plugins, which is rarely where it belongs.`, fix: "Move it under plugins/<Mod>/ (or the route it is for), or drop it from the package.", evidence: ROUTES_EVIDENCE });
139
+ }
140
+ continue;
141
+ }
142
+ const route = routeOf(rel);
143
+ if (!route) {
144
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${rel} in package "${source.id}" is outside every route the managers preserve (${PACKAGE_ROUTES.join(", ")}); r2modman and the Thunderstore app flatten it into BepInEx/plugins.`, fix: "Put it under plugins/<Mod>/ if it belongs with the plugin, or config/ for a shipped config file.", evidence: ROUTES_EVIDENCE });
145
+ }
146
+ else if (route.route === "core") {
147
+ findings.push({ validatorId: this.id, severity: "warn", file, message: `${rel} in package "${source.id}" ships into BepInEx/core, which holds BepInEx's own assemblies and is never a mod's deploy target.`, fix: "A mod that needs a library ships it beside its own DLL under plugins/<Mod>/.", evidence: ROUTES_EVIDENCE });
148
+ }
149
+ }
150
+ }
151
+ return findings;
152
+ },
153
+ };
154
+ const thunderstoreToml = {
155
+ id: "valheim.packaging.thunderstore-toml",
156
+ game: "valheim",
157
+ groups: ["packaging"],
158
+ modTypes: ["packaging"],
159
+ summary: "A thunderstore.toml beside the project parses, and its [package.dependencies] is a table of \"Team-Name\" = \"version\", not manifest.json's flat list.",
160
+ appliesTo: (target, project) => target.kind === "file" && path.basename(target.path).toLowerCase() === "thunderstore.toml" && (path.dirname(target.path) === project.root || project.sources.some((s) => s.exists && path.dirname(target.path) === s.absPath)),
161
+ appliesToDescription: "thunderstore.toml at the project root or at a source root",
162
+ needs: { project: true },
163
+ cost: "edit",
164
+ severity: "block",
165
+ evidence: TOML_EVIDENCE,
166
+ async run(ctx) {
167
+ if (ctx.target.kind !== "file")
168
+ return [];
169
+ const file = ctx.target.path;
170
+ const check = checkThunderstoreToml(await ctx.readText(file));
171
+ return check.problems.map((p) => ({
172
+ validatorId: this.id,
173
+ severity: "block",
174
+ file,
175
+ message: `thunderstore.toml: ${p}; tcli build assembles manifest.json from this file and fails on the wrong shape.`,
176
+ evidence: TOML_EVIDENCE,
177
+ }));
178
+ },
179
+ };
180
+ export const GAME_ASSEMBLY_RE = /^(?:assembly_[a-z0-9]+|.+_publicized|SoftReferenceableAssets|gui_framework|UnityEngine(?:\.[A-Za-z0-9]+)*|mscorlib|System(?:\.[A-Za-z0-9]+)*)\.dll$/i;
181
+ const dist = {
182
+ id: "valheim.dist.no-game-assemblies",
183
+ game: "valheim",
184
+ groups: ["packaging"],
185
+ modTypes: ["packaging"],
186
+ summary: "No game or engine assembly (assembly_*.dll, *_publicized.dll, SoftReferenceableAssets.dll, gui_framework.dll, UnityEngine*.dll, mscorlib, System.*) and nothing from valheim_Data/Managed is inside a package.",
187
+ appliesTo: appliesToPackages,
188
+ appliesToDescription: APPLIES,
189
+ needs: { project: true },
190
+ cost: "build",
191
+ severity: "block",
192
+ evidence: DIST_EVIDENCE,
193
+ async run(ctx) {
194
+ const findings = [];
195
+ for (const source of packageSources(ctx.project)) {
196
+ const { files, truncated } = await walk(source.absPath);
197
+ if (truncated)
198
+ findings.push({ validatorId: this.id, severity: "info", file: source.absPath, message: `Package "${source.id}" has more files than this scan's limit; the never-ships sweep may be incomplete for it.`, evidence: DIST_EVIDENCE });
199
+ for (const rel of files) {
200
+ const file = path.join(source.absPath, rel);
201
+ const base = path.basename(file);
202
+ const underManaged = /(^|[\\/])valheim(?:_server)?_Data[\\/]Managed[\\/]/i.test(rel);
203
+ if (!GAME_ASSEMBLY_RE.test(base) && !underManaged)
204
+ continue;
205
+ findings.push({
206
+ validatorId: this.id,
207
+ severity: "block",
208
+ file,
209
+ message: `${rel} in package "${source.id}" is a game or engine assembly; the community's norm is that patchers ship and assemblies do not (the JotunnModStub README calls them copyrighted material), and a publicized copy is a per-install build artifact regenerated after every patch.`,
210
+ fix: "Remove it from the package; reference it from the developer's own valheim_Data/Managed (or publicized_assemblies) at build time only.",
211
+ evidence: DIST_EVIDENCE,
212
+ });
213
+ }
214
+ }
215
+ return findings;
216
+ },
217
+ };
218
+ export const PACKAGING_VALIDATORS = [manifest, rootFiles, routes, thunderstoreToml, dist];