silkedit 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 (282) hide show
  1. checksums.yaml +7 -0
  2. data/.rbcli +0 -0
  3. data/CHANGELOG.md +3 -0
  4. data/CODE_OF_CONDUCT.md +132 -0
  5. data/README.md +302 -0
  6. data/Rakefile +8 -0
  7. data/exe/silkedit +14 -0
  8. data/lib/commands/backup_restore.rb +25 -0
  9. data/lib/commands/cheat.rb +19 -0
  10. data/lib/commands/edit_diff.rb +47 -0
  11. data/lib/commands/examples/command.rb +51 -0
  12. data/lib/commands/examples/script.rb +36 -0
  13. data/lib/commands/journal.rb +34 -0
  14. data/lib/commands/permasave_permaload.rb +62 -0
  15. data/lib/commands/scripts/.keep +0 -0
  16. data/lib/commands/unpack_repack.rb +27 -0
  17. data/lib/commands/zone_mkzone.rb +102 -0
  18. data/lib/config/config.rb +28 -0
  19. data/lib/config/envvars.rb +23 -0
  20. data/lib/config/hooks.rb +22 -0
  21. data/lib/config/logger.rb +36 -0
  22. data/lib/config/parser.rb +34 -0
  23. data/lib/config/updatechecker.rb +12 -0
  24. data/lib/silkedit/cheats/cheatengine.rb +34 -0
  25. data/lib/silkedit/cheats/hollow_knight_cheats.rb +5 -0
  26. data/lib/silkedit/cheats/merger.rb +103 -0
  27. data/lib/silkedit/cheats/silksong_cheats.rb +121 -0
  28. data/lib/silkedit/cheats/silksong_enemyjournal.rb +119 -0
  29. data/lib/silkedit/cheats/silksong_zoner.rb +98 -0
  30. data/lib/silkedit/config/savegame.yaml +13 -0
  31. data/lib/silkedit/config/silkedit.yaml +6 -0
  32. data/lib/silkedit/config/silksong/cheatdata.yaml +1145 -0
  33. data/lib/silkedit/config/silksong/enemylist.yaml +1418 -0
  34. data/lib/silkedit/config/silksong/old.yaml +4345 -0
  35. data/lib/silkedit/config/silksong/permasaves.yaml +10 -0
  36. data/lib/silkedit/config/silksong/unfinished.yaml +628 -0
  37. data/lib/silkedit/config/silksong/zones.yaml +1686 -0
  38. data/lib/silkedit/images/.keep +0 -0
  39. data/lib/silkedit/images/silksong/Aknid.png +0 -0
  40. data/lib/silkedit/images/silksong/Alita.png +0 -0
  41. data/lib/silkedit/images/silksong/Barnak.png +0 -0
  42. data/lib/silkedit/images/silksong/Beastfly.png +0 -0
  43. data/lib/silkedit/images/silksong/Bell_Beast.png +0 -0
  44. data/lib/silkedit/images/silksong/Bell_Eater.png +0 -0
  45. data/lib/silkedit/images/silksong/Bloatroach.png +0 -0
  46. data/lib/silkedit/images/silksong/Broodmother.png +0 -0
  47. data/lib/silkedit/images/silksong/Brushflit.png +0 -0
  48. data/lib/silkedit/images/silksong/Burning_Bug.png +0 -0
  49. data/lib/silkedit/images/silksong/Caranid.png +0 -0
  50. data/lib/silkedit/images/silksong/Choir_Bellbearer.png +0 -0
  51. data/lib/silkedit/images/silksong/Choir_Clapper.png +0 -0
  52. data/lib/silkedit/images/silksong/Choir_Elder.png +0 -0
  53. data/lib/silkedit/images/silksong/Choir_Flyer.png +0 -0
  54. data/lib/silkedit/images/silksong/Choir_Hornhead.png +0 -0
  55. data/lib/silkedit/images/silksong/Choir_Pouncer.png +0 -0
  56. data/lib/silkedit/images/silksong/Choristor.png +0 -0
  57. data/lib/silkedit/images/silksong/Clawmaiden.png +0 -0
  58. data/lib/silkedit/images/silksong/Clover_Dancers.png +0 -0
  59. data/lib/silkedit/images/silksong/Cloverstag.png +0 -0
  60. data/lib/silkedit/images/silksong/Cogwork_Choirbug.png +0 -0
  61. data/lib/silkedit/images/silksong/Cogwork_Clapper.png +0 -0
  62. data/lib/silkedit/images/silksong/Cogwork_Cleanser.png +0 -0
  63. data/lib/silkedit/images/silksong/Cogwork_Crawler.png +0 -0
  64. data/lib/silkedit/images/silksong/Cogwork_Dancers.png +0 -0
  65. data/lib/silkedit/images/silksong/Cogwork_Defender.png +0 -0
  66. data/lib/silkedit/images/silksong/Cogwork_Hauler.png +0 -0
  67. data/lib/silkedit/images/silksong/Cogwork_Spine.png +0 -0
  68. data/lib/silkedit/images/silksong/Cogwork_Underfly.png +0 -0
  69. data/lib/silkedit/images/silksong/Cogworker.png +0 -0
  70. data/lib/silkedit/images/silksong/Conchfly.png +0 -0
  71. data/lib/silkedit/images/silksong/Coral_Furm.png +0 -0
  72. data/lib/silkedit/images/silksong/Corrcrust_Karaka.png +0 -0
  73. data/lib/silkedit/images/silksong/Covetous_Pilgrim.png +0 -0
  74. data/lib/silkedit/images/silksong/Craggler.png +0 -0
  75. data/lib/silkedit/images/silksong/Cragglite.png +0 -0
  76. data/lib/silkedit/images/silksong/Craw.png +0 -0
  77. data/lib/silkedit/images/silksong/Craw_Juror.png +0 -0
  78. data/lib/silkedit/images/silksong/Crawfather.png +0 -0
  79. data/lib/silkedit/images/silksong/Crust_King_Khann.png +0 -0
  80. data/lib/silkedit/images/silksong/Crustcrag.png +0 -0
  81. data/lib/silkedit/images/silksong/Crustcrawler.png +0 -0
  82. data/lib/silkedit/images/silksong/Deep_Diver.png +0 -0
  83. data/lib/silkedit/images/silksong/Disgraced_Chef_Lugoli.png +0 -0
  84. data/lib/silkedit/images/silksong/Drapefly.png +0 -0
  85. data/lib/silkedit/images/silksong/Drapelord.png +0 -0
  86. data/lib/silkedit/images/silksong/Drapemite.png +0 -0
  87. data/lib/silkedit/images/silksong/Dreg_Catcher.png +0 -0
  88. data/lib/silkedit/images/silksong/Dreg_Husk.png +0 -0
  89. data/lib/silkedit/images/silksong/Dregwheel.png +0 -0
  90. data/lib/silkedit/images/silksong/Driftlin.png +0 -0
  91. data/lib/silkedit/images/silksong/Driznarga.png +0 -0
  92. data/lib/silkedit/images/silksong/Driznit.png +0 -0
  93. data/lib/silkedit/images/silksong/Ductsucker.png +0 -0
  94. data/lib/silkedit/images/silksong/Elder_Pilgrim.png +0 -0
  95. data/lib/silkedit/images/silksong/Envoy.png +0 -0
  96. data/lib/silkedit/images/silksong/Escalion.png +0 -0
  97. data/lib/silkedit/images/silksong/Father_of_the_Flame.png +0 -0
  98. data/lib/silkedit/images/silksong/Fertid.png +0 -0
  99. data/lib/silkedit/images/silksong/First_Sinner.png +0 -0
  100. data/lib/silkedit/images/silksong/Flapping_Fertid.png +0 -0
  101. data/lib/silkedit/images/silksong/Flintbeetle.png +0 -0
  102. data/lib/silkedit/images/silksong/Flintflame_Flyer.png +0 -0
  103. data/lib/silkedit/images/silksong/Flintstone_Flyer.png +0 -0
  104. data/lib/silkedit/images/silksong/Fluttermite.png +0 -0
  105. data/lib/silkedit/images/silksong/Forebrothers_Signis_&_Gron.png +0 -0
  106. data/lib/silkedit/images/silksong/Fourth_Chorus.png +0 -0
  107. data/lib/silkedit/images/silksong/Freshfly.png +0 -0
  108. data/lib/silkedit/images/silksong/Furm.png +0 -0
  109. data/lib/silkedit/images/silksong/Gahlia.png +0 -0
  110. data/lib/silkedit/images/silksong/Gargant_Gloom.png +0 -0
  111. data/lib/silkedit/images/silksong/Garpid.png +0 -0
  112. data/lib/silkedit/images/silksong/Giant_Drapemite.png +0 -0
  113. data/lib/silkedit/images/silksong/Gloomsac.png +0 -0
  114. data/lib/silkedit/images/silksong/Grand_Mother_Silk.png +0 -0
  115. data/lib/silkedit/images/silksong/Grand_Reed.png +0 -0
  116. data/lib/silkedit/images/silksong/Great_Conchfly.png +0 -0
  117. data/lib/silkedit/images/silksong/Groal_the_Great.png +0 -0
  118. data/lib/silkedit/images/silksong/Grom.png +0 -0
  119. data/lib/silkedit/images/silksong/Gromling.png +0 -0
  120. data/lib/silkedit/images/silksong/Guardfly.png +0 -0
  121. data/lib/silkedit/images/silksong/Gurr_the_Outcast.png +0 -0
  122. data/lib/silkedit/images/silksong/Hardbone_Elder.png +0 -0
  123. data/lib/silkedit/images/silksong/Hardbone_Hopper.png +0 -0
  124. data/lib/silkedit/images/silksong/Hoker.png +0 -0
  125. data/lib/silkedit/images/silksong/Huge_Flea.png +0 -0
  126. data/lib/silkedit/images/silksong/Imoba.png +0 -0
  127. data/lib/silkedit/images/silksong/Judge.png +0 -0
  128. data/lib/silkedit/images/silksong/Kai.png +0 -0
  129. data/lib/silkedit/images/silksong/Kakri.png +0 -0
  130. data/lib/silkedit/images/silksong/Karak_Gor.png +0 -0
  131. data/lib/silkedit/images/silksong/Karaka.png +0 -0
  132. data/lib/silkedit/images/silksong/Kilik.png +0 -0
  133. data/lib/silkedit/images/silksong/Kindanir.png +0 -0
  134. data/lib/silkedit/images/silksong/Lace.png +0 -0
  135. data/lib/silkedit/images/silksong/Lampbearer.png +0 -0
  136. data/lib/silkedit/images/silksong/Last_Claw.png +0 -0
  137. data/lib/silkedit/images/silksong/Last_Judge.png +0 -0
  138. data/lib/silkedit/images/silksong/Lavalarga.png +0 -0
  139. data/lib/silkedit/images/silksong/Lavalug.png +0 -0
  140. data/lib/silkedit/images/silksong/Leaf_Glider.png +0 -0
  141. data/lib/silkedit/images/silksong/Leaf_Roller.png +0 -0
  142. data/lib/silkedit/images/silksong/Lost_Garmond.png +0 -0
  143. data/lib/silkedit/images/silksong/Lost_Lace.png +0 -0
  144. data/lib/silkedit/images/silksong/Maestro.png +0 -0
  145. data/lib/silkedit/images/silksong/Marrowmaw.png +0 -0
  146. data/lib/silkedit/images/silksong/Massive_Mossgrub.png +0 -0
  147. data/lib/silkedit/images/silksong/Mawling.png +0 -0
  148. data/lib/silkedit/images/silksong/Memoria.png +0 -0
  149. data/lib/silkedit/images/silksong/Minister.png +0 -0
  150. data/lib/silkedit/images/silksong/Miremite.png +0 -0
  151. data/lib/silkedit/images/silksong/Mite.png +0 -0
  152. data/lib/silkedit/images/silksong/Mitemother.png +0 -0
  153. data/lib/silkedit/images/silksong/Mnemonid.png +0 -0
  154. data/lib/silkedit/images/silksong/Mnemonord.png +0 -0
  155. data/lib/silkedit/images/silksong/Moorwing.png +0 -0
  156. data/lib/silkedit/images/silksong/Mortician.png +0 -0
  157. data/lib/silkedit/images/silksong/Moss_Mother.png +0 -0
  158. data/lib/silkedit/images/silksong/Mossgrub.png +0 -0
  159. data/lib/silkedit/images/silksong/Mossmir.png +0 -0
  160. data/lib/silkedit/images/silksong/Mothleaf_Lagnia.png +0 -0
  161. data/lib/silkedit/images/silksong/Muckmaggot.png +0 -0
  162. data/lib/silkedit/images/silksong/Muckroach.png +0 -0
  163. data/lib/silkedit/images/silksong/Nuphar.png +0 -0
  164. data/lib/silkedit/images/silksong/Nyleth.png +0 -0
  165. data/lib/silkedit/images/silksong/Overgrown_Pilgrim.png +0 -0
  166. data/lib/silkedit/images/silksong/Palestag.png +0 -0
  167. data/lib/silkedit/images/silksong/Pendra.png +0 -0
  168. data/lib/silkedit/images/silksong/Pendragor.png +0 -0
  169. data/lib/silkedit/images/silksong/Penitent.png +0 -0
  170. data/lib/silkedit/images/silksong/Phacia.png +0 -0
  171. data/lib/silkedit/images/silksong/Phantom.png +0 -0
  172. data/lib/silkedit/images/silksong/Pharlid.png +0 -0
  173. data/lib/silkedit/images/silksong/Pharlid_Diver.png +0 -0
  174. data/lib/silkedit/images/silksong/Pilgrim_Bellbearer.png +0 -0
  175. data/lib/silkedit/images/silksong/Pilgrim_Groveller.png +0 -0
  176. data/lib/silkedit/images/silksong/Pilgrim_Guide.png +0 -0
  177. data/lib/silkedit/images/silksong/Pilgrim_Hiker.png +0 -0
  178. data/lib/silkedit/images/silksong/Pilgrim_Hornfly.png +0 -0
  179. data/lib/silkedit/images/silksong/Pilgrim_Hulk.png +0 -0
  180. data/lib/silkedit/images/silksong/Pilgrim_Pouncer.png +0 -0
  181. data/lib/silkedit/images/silksong/Pinstress.png +0 -0
  182. data/lib/silkedit/images/silksong/Plasmid.png +0 -0
  183. data/lib/silkedit/images/silksong/Plasmidas.png +0 -0
  184. data/lib/silkedit/images/silksong/Plasmified_Zango.png +0 -0
  185. data/lib/silkedit/images/silksong/Pokenabbin.png +0 -0
  186. data/lib/silkedit/images/silksong/Pollenica.png +0 -0
  187. data/lib/silkedit/images/silksong/Pond_Skipper.png +0 -0
  188. data/lib/silkedit/images/silksong/Pondcatcher.png +0 -0
  189. data/lib/silkedit/images/silksong/Puny_Penitent.png +0 -0
  190. data/lib/silkedit/images/silksong/Reed.png +0 -0
  191. data/lib/silkedit/images/silksong/Rhinogrund.png +0 -0
  192. data/lib/silkedit/images/silksong/Roachcatcher.png +0 -0
  193. data/lib/silkedit/images/silksong/Roachfeeder.png +0 -0
  194. data/lib/silkedit/images/silksong/Roachkeeper.png +0 -0
  195. data/lib/silkedit/images/silksong/Roachserver.png +0 -0
  196. data/lib/silkedit/images/silksong/Sandcarver.png +0 -0
  197. data/lib/silkedit/images/silksong/Savage_Beastfly.png +0 -0
  198. data/lib/silkedit/images/silksong/Scabfly.png +0 -0
  199. data/lib/silkedit/images/silksong/Scrollreader.png +0 -0
  200. data/lib/silkedit/images/silksong/Second_Sentinel.png +0 -0
  201. data/lib/silkedit/images/silksong/Servitor_Boran.png +0 -0
  202. data/lib/silkedit/images/silksong/Servitor_Ignim.png +0 -0
  203. data/lib/silkedit/images/silksong/Shadow_Charger.png +0 -0
  204. data/lib/silkedit/images/silksong/Shadow_Creeper_(Silksong).png +0 -0
  205. data/lib/silkedit/images/silksong/Shardillard.png +0 -0
  206. data/lib/silkedit/images/silksong/Shellwood_Gnat.png +0 -0
  207. data/lib/silkedit/images/silksong/Shrine_Guardian_Seth.png +0 -0
  208. data/lib/silkedit/images/silksong/Silk_Snipper.png +0 -0
  209. data/lib/silkedit/images/silksong/Sister_Splinter.png +0 -0
  210. data/lib/silkedit/images/silksong/Skarr_Scout.png +0 -0
  211. data/lib/silkedit/images/silksong/Skarr_Stalker.png +0 -0
  212. data/lib/silkedit/images/silksong/Skarrgard.png +0 -0
  213. data/lib/silkedit/images/silksong/Skarrlid.png +0 -0
  214. data/lib/silkedit/images/silksong/Skarrsinger_Karmelita.png +0 -0
  215. data/lib/silkedit/images/silksong/Skarrwing.png +0 -0
  216. data/lib/silkedit/images/silksong/Skrill.png +0 -0
  217. data/lib/silkedit/images/silksong/Skull_Brute.png +0 -0
  218. data/lib/silkedit/images/silksong/Skull_Scuttler.png +0 -0
  219. data/lib/silkedit/images/silksong/Skull_Tyrant.png +0 -0
  220. data/lib/silkedit/images/silksong/Skullwing.png +0 -0
  221. data/lib/silkedit/images/silksong/Slubberlug.png +0 -0
  222. data/lib/silkedit/images/silksong/Smelt_Shoveller.png +0 -0
  223. data/lib/silkedit/images/silksong/Smokerock_Sifter.png +0 -0
  224. data/lib/silkedit/images/silksong/Snitchfly.png +0 -0
  225. data/lib/silkedit/images/silksong/Spear_Skarr.png +0 -0
  226. data/lib/silkedit/images/silksong/Spinebeak_Kai.png +0 -0
  227. data/lib/silkedit/images/silksong/Spit_Squit.png +0 -0
  228. data/lib/silkedit/images/silksong/Splinter.png +0 -0
  229. data/lib/silkedit/images/silksong/Splinterbark.png +0 -0
  230. data/lib/silkedit/images/silksong/Splinterhorn.png +0 -0
  231. data/lib/silkedit/images/silksong/Squatcraw.png +0 -0
  232. data/lib/silkedit/images/silksong/Squatcraw_Juror.png +0 -0
  233. data/lib/silkedit/images/silksong/Squirrm.png +0 -0
  234. data/lib/silkedit/images/silksong/Steelspine_Kai.png +0 -0
  235. data/lib/silkedit/images/silksong/Stilkin.png +0 -0
  236. data/lib/silkedit/images/silksong/Stilkin_Trapper.png +0 -0
  237. data/lib/silkedit/images/silksong/Summoned_Saviour.png +0 -0
  238. data/lib/silkedit/images/silksong/Surgeon.png +0 -0
  239. data/lib/silkedit/images/silksong/Swamp_Squit.png +0 -0
  240. data/lib/silkedit/images/silksong/Tallcraw.png +0 -0
  241. data/lib/silkedit/images/silksong/Tallcraw_Juror.png +0 -0
  242. data/lib/silkedit/images/silksong/Tarmite.png +0 -0
  243. data/lib/silkedit/images/silksong/The_Unravelled.png +0 -0
  244. data/lib/silkedit/images/silksong/Thread_Raker.png +0 -0
  245. data/lib/silkedit/images/silksong/Tormented_Trobbio.png +0 -0
  246. data/lib/silkedit/images/silksong/Trobbio.png +0 -0
  247. data/lib/silkedit/images/silksong/Undercrank.png +0 -0
  248. data/lib/silkedit/images/silksong/Underloft.png +0 -0
  249. data/lib/silkedit/images/silksong/Underpoke.png +0 -0
  250. data/lib/silkedit/images/silksong/Underscrub.png +0 -0
  251. data/lib/silkedit/images/silksong/Undersweep.png +0 -0
  252. data/lib/silkedit/images/silksong/Underworker.png +0 -0
  253. data/lib/silkedit/images/silksong/Vaultborn.png +0 -0
  254. data/lib/silkedit/images/silksong/Vaultkeeper.png +0 -0
  255. data/lib/silkedit/images/silksong/Verdanir.png +0 -0
  256. data/lib/silkedit/images/silksong/Vicious_Caranid.png +0 -0
  257. data/lib/silkedit/images/silksong/Void_Mass.png +0 -0
  258. data/lib/silkedit/images/silksong/Void_Tendrils_(Silksong).png +0 -0
  259. data/lib/silkedit/images/silksong/Voltvyrm.png +0 -0
  260. data/lib/silkedit/images/silksong/Wardenfly.png +0 -0
  261. data/lib/silkedit/images/silksong/Watcher_at_the_Edge.png +0 -0
  262. data/lib/silkedit/images/silksong/Widow.png +0 -0
  263. data/lib/silkedit/images/silksong/Winged_Furm.png +0 -0
  264. data/lib/silkedit/images/silksong/Winged_Lifeseed.png +0 -0
  265. data/lib/silkedit/images/silksong/Winged_Pilgrim.png +0 -0
  266. data/lib/silkedit/images/silksong/Winged_Pilgrim_Bellbearer.png +0 -0
  267. data/lib/silkedit/images/silksong/Wingmould_(Silksong).png +0 -0
  268. data/lib/silkedit/images/silksong/Wisp.png +0 -0
  269. data/lib/silkedit/images/silksong/Wood_Wasp.png +0 -0
  270. data/lib/silkedit/images/silksong/Wraith.png +0 -0
  271. data/lib/silkedit/images/silksong/Yago.png +0 -0
  272. data/lib/silkedit/images/silksong/Yuma.png +0 -0
  273. data/lib/silkedit/images/silksong/Yumama.png +0 -0
  274. data/lib/silkedit/savegame/crypto.rb +25 -0
  275. data/lib/silkedit/savegame/diff.rb +158 -0
  276. data/lib/silkedit/savegame/packer.rb +75 -0
  277. data/lib/silkedit/savegame/savefile.rb +153 -0
  278. data/lib/silkedit/util/system.rb +37 -0
  279. data/lib/silkedit/version.rb +5 -0
  280. data/lib/silkedit.rb +9 -0
  281. data/sig/silkedit.rbs +4 -0
  282. metadata +504 -0
@@ -0,0 +1,34 @@
1
+ Rbcli.command 'journal' do
2
+ description 'Manages the journal of enemy kills'
3
+ usage '[list|listmissing|complete|killsonly]'
4
+ parameter :showimages, 'Show images of the enemies', short: 'i', type: :bool, default: false
5
+ action do |opts, params, args, config, env|
6
+ command = args.first
7
+ if command.nil? || command.empty? || !%w[list listmissing complete killsonly].include?(command)
8
+ Rbcli.log.error "Must specify an action as one of: listall, listmissing, complete, killsonly"
9
+ next
10
+ end
11
+
12
+ s = Silkedit::Savegame::SaveFile.new(:silksong, opts[:savenum])
13
+ s.load_from_dat
14
+ c = Silkedit::Cheat::Engine.new(s.data)
15
+
16
+ case command
17
+ when 'listall'
18
+ c.enemy_list(only_missing: false, show_images: params[:showimages])
19
+ when 'listmissing'
20
+ c.enemy_list(only_missing: true, show_images: params[:showimages])
21
+ when 'complete'
22
+ c.update_journal(should_update_kills_only: false)
23
+ s.direct_backup
24
+ s.save_to_dat
25
+ when 'killsonly'
26
+ c.update_journal(should_update_kills_only: true)
27
+ s.direct_backup
28
+ s.save_to_dat
29
+ else
30
+ raise "Unknown command: #{command}"
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ Rbcli.command 'permasave' do
2
+ description 'Saves a local copy of a game into the config to restore later.'
3
+ parameter :name, 'Name to give the savegame', short: :n, type: :string, required: true
4
+ action do |opts, params, args, config, env|
5
+ permasave_file = config[:permasave_file_location][Silkedit::Sys.os]
6
+ permasave_file = permasave_file.gsub('%APPDATA%', ENV['APPDATA'] || ENV['LOCALAPPDATA']) if Silkedit::Sys.os == :windows
7
+ permasave_file = File.expand_path(permasave_file)
8
+ unless File.exist?(permasave_file)
9
+ FileUtils.cp(File.join(Silkedit::LIBDIR, 'config', 'silksong', 'permasaves.yaml'), permasave_file)
10
+ end
11
+ permasaves = YAML.safe_load_file(permasave_file)
12
+ Rbcli.log.fatal('Permasaves file is corrupt. Please delete or fix it and try again.', exit_status: 1) unless permasaves.is_a?(Hash)
13
+
14
+ params[:name] = params[:name].downcase
15
+ if permasaves.key?(params[:name])
16
+ Rbcli.log.warn "Found existing permasave: #{params[:name]}"
17
+ next unless Silkedit::Sys.yes_no?('Overwrite?')
18
+ end
19
+
20
+ s = Silkedit::Savegame::SaveFile.new(:silksong, opts[:savenum])
21
+ s.load_from_dat
22
+ permasaves[params[:name]] = YAML.safe_dump(s.data).compress
23
+ File.write(permasave_file, YAML.safe_dump(permasaves))
24
+
25
+ Rbcli.log.info "Permasave #{params[:name]} saved from slot #{opts[:savenum]}"
26
+ end
27
+ end
28
+
29
+ Rbcli.command 'permaload' do
30
+ description 'Restores a permasave into the slot'
31
+ parameter :name, 'Name of the permasave to load', short: :n, type: :string, required: false
32
+ parameter :list, 'List all permasaves', short: :l, type: :bool, default: false
33
+ action do |opts, params, args, config, env|
34
+ permasave_file = config[:permasave_file_location][Silkedit::Sys.os]
35
+ permasave_file = permasave_file.gsub('%APPDATA%', ENV['APPDATA'] || ENV['LOCALAPPDATA']) if Silkedit::Sys.os == :windows
36
+ permasave_file = File.expand_path(permasave_file)
37
+ unless File.exist?(permasave_file)
38
+ FileUtils.cp(File.join(Silkedit::LIBDIR, 'config', 'silksong', 'permasaves.yaml'), permasave_file)
39
+ end
40
+ permasaves = YAML.safe_load_file(permasave_file)
41
+ Rbcli.log.fatal('Permasaves file is corrupt. Please delete or fix it and try again.', exit_status: 1) unless permasaves.is_a?(Hash)
42
+
43
+ if !params[:name] && !params[:list]
44
+ Rbcli.log.warn 'Must provide a permasave name to load, or use --(l)ist to list all permasaves.'
45
+ next
46
+ elsif params[:list]
47
+ Rbcli.log.info permasaves.keys.join("\n")
48
+ next
49
+ end
50
+ params[:name] = params[:name].downcase
51
+
52
+ s = Silkedit::Savegame::SaveFile.new(:silksong, opts[:savenum])
53
+ if permasaves.is_a?(Hash) && permasaves.key?(params[:name])
54
+ s.data = YAML.safe_load(permasaves[params[:name]].decompress)
55
+ s.data['profileID'] = opts[:savenum].to_i
56
+ s.save_to_dat
57
+ Rbcli.log.info "Permasave #{params[:name]} loaded to slot #{opts[:savenum]}."
58
+ else
59
+ Rbcli.log.warn "Permasave #{params[:name]} does not exist. Exiting."
60
+ end
61
+ end
62
+ end
File without changes
@@ -0,0 +1,27 @@
1
+ Rbcli.command 'unpack' do
2
+ description 'Unpacks a save file or backup to JSON/YAML for manual editing'
3
+ parameter :backup_seq, 'Sequence number for the backup to unpack', short: :b, type: :integer
4
+ action do |opts, params, args, config, env|
5
+ s = Silkedit::Savegame::SaveFile.new(:silksong, opts[:savenum])
6
+ if params[:backup_seq].nil?
7
+ s.load_from_dat
8
+ s.save_to_json
9
+ Rbcli.log.info 'Unpacked savefile to json/yaml'
10
+ else
11
+ s.load_from_backup(seq_number: params[:backup_seq])
12
+ s.save_to_json
13
+ Rbcli.log.info "Unpacked backup ##{params[:backup_seq]}"
14
+ end
15
+ end
16
+ end
17
+
18
+ Rbcli.command 'repack' do
19
+ description 'Packs the JSON/YAML to the savefile'
20
+ action do |opts, params, args, config, env|
21
+ s = Silkedit::Savegame::SaveFile.new(:silksong, opts[:savenum])
22
+ s.load_from_json
23
+ s.direct_backup
24
+ s.save_to_dat
25
+ Rbcli.log.info 'Repacked savefile from json/yaml'
26
+ end
27
+ end
@@ -0,0 +1,102 @@
1
+ Rbcli.command 'zone' do
2
+ description 'Zones the character to a different respawn point'
3
+ usage '<zone> (--(f)orce)'
4
+ parameter :force, 'Force select a spawn point even when requirements are not met. Changes may be made to your savegave.', type: :bool, default: false
5
+ parameter :list, 'Display the full list of zones to select from', type: :bool, default: false
6
+ action do |opts, params, args, config, env|
7
+ s = Silkedit::Savegame::SaveFile.new(:silksong, opts[:savenum])
8
+ s.load_from_dat
9
+ c = Silkedit::Cheat::Engine.new(s.data)
10
+
11
+ display_simple_zone_list = lambda do |list, cols|
12
+ display_set = []
13
+ rows = (c.list_shortcuts.length / cols.to_f).ceil
14
+ row_idx = 0
15
+ max_rows = rows - 1
16
+ c.list_shortcuts.length.times do |i|
17
+ display_set[row_idx] ||= []
18
+ display_set[row_idx] << c.list_shortcuts.keys[i]
19
+ row_idx += 1
20
+ row_idx = 0 if row_idx > max_rows
21
+ end
22
+ display_set.map { |row| row.map { |z| z.rjust(15) }.join(' ') }.join("\n")
23
+ end
24
+
25
+ display_detailed_zone_list = lambda do |list|
26
+ max_shortcut_length = list.map { |zone| (zone[:shortcut] || '').length }.max
27
+ formatter = "%-#{max_shortcut_length}s %3s %s"
28
+ final_string = ''
29
+ final_string << format(formatter, 'Shortcut', 'Act', 'Zone') + "\n"
30
+ final_string << list.map { |zone| format(formatter, zone[:shortcut], zone[:min_act], zone[:slug]) }.join("\n")
31
+ final_string
32
+ end
33
+
34
+ if args.empty? || params[:list]
35
+ Rbcli.log.info 'Shortcuts:'
36
+ Rbcli.log.info display_simple_zone_list.call(c.list_shortcuts, 5)
37
+ end
38
+
39
+ if params[:list]
40
+ Rbcli.log.info ''
41
+ Rbcli.log.info 'Zones:'
42
+ Rbcli.log.info display_detailed_zone_list.call(c.list_zones)
43
+ end
44
+
45
+ Rbcli.exit(0) if args.empty? || params[:list]
46
+
47
+ status = c.zone_to(args.first, force_soft_reqs: params[:force], enforce_min_act: !params[:force])
48
+ case status
49
+ when :no_zone
50
+ Rbcli.log.info "Could not zone to #{args.first}: Specified spawn point does not exist"
51
+ Rbcli.log.info 'Did you mean one of these?'
52
+ possible_zones = c.list_zones.select { |zone| zone[:slug].include?(args.first) || !zone[:shortcut].nil? && zone[:shortcut].include?(args.first) }
53
+ Rbcli.log.info display_detailed_zone_list.call(possible_zones)
54
+ when :failed_act_check
55
+ Rbcli.log.warn "Could not zone to #{args.first}: The player is in the wrong act. Use --(f)orce to override."
56
+ when :failed_soft_reqs
57
+ Rbcli.log.warn "Could not zone to #{args.first}: Soft requirements not met. Use --(f)orce to apply the required changes."
58
+ when :failed_hard_reqs
59
+ Rbcli.log.error "Could not zone to #{args.first}: Hard requirements not met. Zoning here would cause errors."
60
+ when :success
61
+ s.direct_backup
62
+ s.save_to_dat
63
+ Rbcli.log.info "Zoned to #{args.first}"
64
+ else
65
+ raise "Unknown status: #{status}"
66
+ end
67
+ end
68
+ end
69
+
70
+ Rbcli.command 'mkzone' do
71
+ description 'Adds a new spawn point to the library'
72
+ usage '<slug>'
73
+ parameter :slug, 'The slug of the zone to add', type: :string
74
+ parameter :act, 'Override default act detection', type: :integer
75
+ parameter :shortcut, 'Provide a shorter slug as a shortcut for the zone', short: 'o', type: :string
76
+ parameter :force, 'Force overwrite of existing zone', type: :bool, default: false
77
+ action do |opts, params, args, config, env|
78
+ if params[:slug].nil?
79
+ Rbcli.log.error 'Must provide a zone slug'
80
+ next
81
+ end
82
+
83
+ s = Silkedit::Savegame::SaveFile.new(:silksong, opts[:savenum])
84
+ s.load_from_dat
85
+ c = Silkedit::Cheat::Engine.new(s.data)
86
+
87
+ status = c.save_current_zone(params[:slug], params[:shortcut], params[:act], overwrite: params[:force])
88
+
89
+ case status
90
+ when :badname
91
+ Rbcli.log.error 'Invalid zone name. Zones must be formatted as: region.target'
92
+ when :badact
93
+ Rbcli.log.error 'Invalid act number. Act must be between 1 and 3, or leave blank for autodetection.'
94
+ when :badshortcut
95
+ Rbcli.log.error 'Invalid shortcut. Shortcuts must not have a period (.) in them.'
96
+ when :success
97
+ Rbcli.log.info "Added #{params[:slug]} to zonelist."
98
+ else
99
+ Rbcli.log.error "Duplicate zone found: #{status}. Use --(f)orce to overwrite."
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,28 @@
1
+ Rbcli::Configurate.config do
2
+ ##### Config (Optional) #####
3
+ # The built-in config will automatically pull in a config file to a hash and vice versa
4
+ # It can either be used immutably (as user-defined configuration) and/or to store application state
5
+ #
6
+ # file: <string> or <array> (Required) Provide either a specific config file location or a hierarchy to search. If creating a :null type of config this can be omitted.
7
+ # type: (:yaml|:json|:ini|:toml|:null) (Optional) Select which backend/file format to use. If the file has an associated extension (i.e. '.yaml') then this can be omitted.
8
+ # schema_file: <string> (Optional) The file location of a JSON Schema (https://json-schema.org). If provided, the config will automatically be validated. (default: nil)
9
+ # schema_hash: <string> (Optional) If you'd like to provide a JSON schema hash directly instead, do it here. May not use `schema_hash` and `schema_file` together. (default: nil)
10
+ # save_on_exit: (true|false) (Optional) Save changes to the config file on exit (default: false)
11
+ # create_if_not_exists: (true|false) (Optional) Create the file using default values if it is not found on the system (default: false)
12
+ # suppress_errors: (true|false) (Optional) If set to false, the application will halt on any errors in loading the config. If set to true, defaults will be provided (default: true)
13
+ # banner: <string> (Optional) Define a banner to be placed at the top of the config file on disk. Note that it will only be written to backends that support comments
14
+ # defaults: <hash> (Optional) Defaults set here will be provided to your application if any values are missing in the config.
15
+ # They will also be used to create new config files when the flag `create_if_not_exists` is set to true.
16
+ # skeleton: <string> (Optional) If a skeleton is provided, it will be used as the config source when creating new config files on disk instead of the defaults. (default: nil)`
17
+ # Please provide the text exactly as you want the user to see it in the config file (plus the banner if provided).
18
+ file [File.join(Silkedit::LIBDIR, 'config', 'silkedit.yaml')]
19
+ type :yaml
20
+ # schema_file nil
21
+ # schema_hash nil
22
+ save_on_exit false
23
+ create_if_not_exists false
24
+ suppress_errors true
25
+ # banner nil
26
+ # defaults({ editor_command: "vi '%FILE%'", permasaves: {} })
27
+ # skeleton "eJzT1dXlSsvPt1JISiziAhFWSYlVAD1hBjs=".decompress
28
+ end
@@ -0,0 +1,23 @@
1
+ Rbcli::Configurate.envvars do
2
+ ##### Environment Variable Parsing (Optional) #####
3
+ # The envvars module can pull in all environment variables with a given
4
+ # prefix and organize them into a hash structure based on name.
5
+ # It will also parse the string values and convert them to the proper type (Integer, Boolean, etc)
6
+ # Any values set here will be treated as defaults and made available when a variable is missing
7
+ #
8
+ # For example, these two environment variables:
9
+ # SILKEDIT_TERM_HEIGHT=40
10
+ # SILKEDIT_TERM_WIDTH=120
11
+ # Would be declared here as:
12
+ # prefix 'SILKEDIT'
13
+ # envvar 'TERM_HEIGHT', 40
14
+ # envvar 'TERM_WIDTH', 120
15
+ # And get loaded into the env hash as:
16
+ # { term: { height: 40, width: 120 } }
17
+ #
18
+ # If the prefix is unset or equal to nil, the environment variables specified here will
19
+ # be loaded without one.
20
+ prefix 'SILKEDIT'
21
+ envvar 'SAVENUM', nil
22
+ envvar 'DEVELOPMENT', false
23
+ end
@@ -0,0 +1,22 @@
1
+ Rbcli::Configurate.hooks do
2
+ ##### Hooks (Optional) #####
3
+ # These hooks are scheduled on the Rbcli execution engine to run
4
+ # Pre- and Post- the command specified. They are executed after
5
+ # everything else has been initialized, so the runtime configuration
6
+ # values are all made available, as they will appear to the command.
7
+ #
8
+ # These are good for parsing and/or doing transformations on the provided
9
+ # configuration before passing them to the command, and for cleaning
10
+ # up your environment afterwards.
11
+ pre_execute do |opts, params, args, config, env|
12
+ opts[:savenum] = env[:savenum] if opts[:savenum].nil? && !env[:savenum].nil?
13
+
14
+ if !opts[:savenum].is_a?(Integer) || opts[:savenum] < 1 || opts[:savenum] > 4
15
+ Rbcli.log.fatal 'A savegame index between 1-4 must be specified either via the command line (-s #) or environment variable (SILKEDIT_SAVENUM=#). See help (-h) for details.', exit_status: 24
16
+ end
17
+ end
18
+
19
+ # post_execute do |opts, params, args, config, env|
20
+ # Rbcli.log.info "I'm done running the command!"
21
+ # end
22
+ end
@@ -0,0 +1,36 @@
1
+ Rbcli::Configurate.logger do
2
+ ##### Logger (Optional) #####
3
+ # The Rbcli logger is a wrapper around the standard Ruby `Logger` library
4
+ # It is recommended that - unless you want to use specialized output libraries (such as ncurses) - that all
5
+ # of your application output is directed through the logger. This will allow for easy formatting and redirection
6
+ # no matter how your application is run.
7
+ #
8
+ # Valid targets are: :stdout, :stderr, "/path/to/a/file", or an IO or StringIO object (Default: :stdout)
9
+ # Valid levels are: :debug, :info, :warn, :error, :fatal, :unknown, :any (Default: :info)
10
+ # Valid formats are: :display, :simple, :full, :ruby, :json, :apache, :lolcat (Default: :display)
11
+ #
12
+ # The first three formats are designed to cover most use cases:
13
+ # display: For display on a terminal, with user interaction
14
+ # simple: For display on a terminal, for a developer
15
+ # full: For logging to a file, or using log collection software
16
+ # The next three are for specialized integrations in case you already have log collection configured:
17
+ # ruby: Default ruby logger format
18
+ # json: Each line is a JSON object
19
+ # apache: Follows the standard Apache log format
20
+ # As for :lolcat... if you can't guess what it does then give it a shot
21
+ logger target: :stdout, level: :info, format: :display
22
+ # Custom formats can also be defined here.
23
+ # Declaring a format here will not switch to it. However, if
24
+ # the `format` keyword is used without providing a Proc, it will switch
25
+ # to that format.
26
+ #
27
+ # Format switch: format :json
28
+ # Format definition: format :foo, Proc.new { |severity, datetime, progname, msg| "Foo! " + msg }
29
+ #
30
+ # You can also switch formats mid-execution by calling this method on
31
+ # the logger object itself.
32
+ #
33
+ # Rbcli.log.format :json
34
+ #
35
+ # format :foo, Proc.new { |severity, datetime, progname, msg| "Foo! " + msg }
36
+ end
@@ -0,0 +1,34 @@
1
+ Rbcli::Configurate.cli do
2
+ ##### Core Configuration (Required) #####
3
+ # appname (Optional) - Defaults to the name of the executable
4
+ # author (Optional) - A name or array of names
5
+ # email (Optional) - An email for users to contact
6
+ # version (Optional) - major.minor.patch notation, required if using update checks
7
+ # copyright_year (Optional) - Self explanatory
8
+ # compatibility (Optional) - Array of Operating Systems, devices, or other targets (For example: %w[MacOS Linux Ubuntu Windows Raspberry\ Pi]
9
+ # license (Optional) - Convention is to use an identifier from here: https://spdx.org/licenses/
10
+ # helptext (Optional) - Text that gets shown with --help or -h
11
+ appname nil
12
+ author ['Andrew Khoury']
13
+ email nil
14
+ version Silkedit::VERSION
15
+ copyright_year 2025
16
+ compatibility %w[MacOS Linux]
17
+ license 'GPL-3.0'
18
+ helptext 'This is a tool to quickly and easily edit savefiles for SilkSong.'
19
+ ##### CLI Options (Optional) #####
20
+ # These appear to commands as `opts`.
21
+ # Format:
22
+ # opt :name, "Description"[, optional arguments ]
23
+ # Optional Arguments:
24
+ # long: Specify the long form (--long) version of an argument. (Default: same as the name)
25
+ # short: Specify the short form (-s) version of an argument. (Default: first letter of the name)
26
+ # type: Specify the type. Valid options are :boolean, :float, :integer, :string, :io, :date. (Default: :boolean)
27
+ # If the plural form of any of the above are used (i.e. ':strings') then the user can provide a comma-delimited list on the command line (--param=foo,bar)
28
+ # required: If set to true, requires that this option is provided by the user. (Default: false)
29
+ # multi: If set to true, allows the option to be provided multiple times (--param --param, or in short form, -pp). (Default: false)
30
+ # When using this with a :boolean type, rather than returning `true` or `false`, it will return a count of the number of times the parameter was passed.
31
+ # permitted: If set to an array, restricts input to the values within that array. (Default: nil (no restrictions))
32
+ opt :verbose, "Twice or more enables very-verbose output", multi: true
33
+ opt :savenum, "A number from 1-4, indicating which game save you'd like to address", short: 's', type: :integer
34
+ end
@@ -0,0 +1,12 @@
1
+ Rbcli::Configurate.updatechecker do
2
+ ##### Update Check (Optional) #####
3
+ # The application can warn users when a new version is released
4
+ # Checks can be done either by rubygems or by a Github repo
5
+ # Private servers for Github (enterprise) are supported
6
+ #
7
+ # Setting force_update to true will halt execution until it is updated
8
+ #
9
+ # For Github, an access_token is required for private repos. See: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
10
+ # gem 'silkedit', force_update: false, message: 'Please run `gem update silkedit` to upgrade to the latest version.'
11
+ # github 'repo/name', access_token: nil, enterprise_hostname: nil, force_update: false, message: 'Please download the latest version from Github'
12
+ end
@@ -0,0 +1,34 @@
1
+ module Silkedit::Cheat
2
+ class Engine
3
+ def initialize(savedata)
4
+ @data = savedata
5
+ game = detect_game(@data)
6
+ @modules = %w[Cheats Zoner Journaler].map { |type| [type, Silkedit::Cheat.const_get("#{game}#{type}")]}.to_h
7
+ @modules.each_value { |mod| self.extend(mod) }
8
+ end
9
+
10
+ def apply_cheat(cheat, *args)
11
+ return false unless cheat_exists?(cheat.to_s)
12
+ self.send(cheat.to_sym, *args)
13
+ true
14
+ end
15
+
16
+ def apply_cheats(cheats)
17
+ cheats.map { |cheat| apply_cheat(cheat) }
18
+ end
19
+
20
+ def list_cheats
21
+ @modules['Cheats'].instance_methods.map(&:to_s).sort
22
+ end
23
+
24
+ def cheat_exists?(cheat)
25
+ list_cheats.include?(cheat)
26
+ end
27
+
28
+ private
29
+
30
+ def detect_game(data = nil)
31
+ (data || @data).key?('firstGeo') ? 'HollowKnight' : 'Silksong'
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module Silkedit::Cheat
2
+ module HollowKnightCheats
3
+
4
+ end
5
+ end
@@ -0,0 +1,103 @@
1
+ module Silkedit::Cheat
2
+ def self.merge_cheat(data, cheat, should_merge_arrays: true, force_soft_reqs: false, enforce_min_act: false)
3
+ # 1. Check act requirement
4
+ game_act = self.get_game_act(data)
5
+ Rbcli.log.debug "Game act is #{game_act}, required act is #{cheat['min_act']}", 'CHEATS'
6
+ return :failed_act_check if enforce_min_act && cheat.key?('min_act') && game_act < cheat['min_act']
7
+
8
+ # 2. Check hard reqs and abort if not met
9
+ if cheat.key?('hard_reqs') && !cheat['hard_reqs'].nil? && !cheat['hard_reqs'].empty?
10
+ return :failed_hard_reqs unless verify_hash(data, cheat['hard_reqs'])
11
+ end
12
+
13
+ # 3. Check soft reqs, and either queue them to be applied or abort
14
+ queue_soft_reqs = false
15
+ if cheat.key?('soft_reqs') && !cheat['soft_reqs'].nil? && !cheat['soft_reqs'].empty?
16
+ unless verify_hash(data, cheat['soft_reqs'])
17
+ return :failed_soft_reqs unless force_soft_reqs
18
+ queue_soft_reqs = true
19
+ end
20
+ end
21
+
22
+ # 4. Apply cheat
23
+ self.merge_hash(data, cheat['soft_reqs'], should_merge_arrays: true) if queue_soft_reqs
24
+ self.merge_hash(data, cheat['data'], should_merge_arrays: should_merge_arrays)
25
+ :success
26
+ end
27
+
28
+ def self.merge_hash(data, cheat, should_merge_arrays: true)
29
+ cheat.each_key do |k|
30
+ if cheat[k].is_a?(Hash) && data[k].is_a?(Hash)
31
+ merge_hash(data[k], cheat[k])
32
+ elsif should_merge_arrays && cheat[k].is_a?(Array) && data[k].is_a?(Array)
33
+ if cheat[k][0].is_a?(Hash)
34
+ merge_arrays_by_hash_keys(data[k], cheat[k])
35
+ else
36
+ data[k].merge!(cheat[k])
37
+ data[k].uniq!
38
+ end
39
+ else
40
+ data[k] = cheat[k]
41
+ end
42
+ end
43
+ true
44
+ end
45
+
46
+ def self.merge_arrays_by_hash_keys(data, cheat)
47
+ return nil unless data.is_a?(Array) && cheat.is_a?(Array) && data.first.is_a?(Hash)
48
+ # First determine the primary key(s) for the object
49
+ pkeys = []
50
+ [%w[Name], %w[sceneData ID]].each do |pkey_arr|
51
+ pkeys = pkey_arr if pkey_arr.all? { |k| data.first.key?(k) || cheat.first.key?(k) }
52
+ end
53
+ cheat.each do |c|
54
+ idx = data.find_index { |d| d.is_a?(Hash) && pkeys.all? { |k| d.key?(k) && d[k] == c[k] } }
55
+ if idx
56
+ self.merge_hash(data[idx], c, should_merge_arrays: true)
57
+ else
58
+ data << c
59
+ end
60
+ end
61
+ true
62
+ end
63
+
64
+ def self.verify_hash(data, reqs)
65
+ reqs.each_key do |k|
66
+ if reqs[k].is_a?(Hash) && data[k].is_a?(Hash)
67
+ return false unless verify_hash(data[k], reqs[k])
68
+ elsif reqs[k].is_a?(Array) && data[k].is_a?(Array)
69
+ if reqs[k].first.is_a?(Hash)
70
+ return false unless verify_array_of_hashes(data[k], reqs[k])
71
+ else
72
+ return false unless reqs[k].all? { |r| data[k].include?(r) }
73
+ end
74
+ else
75
+ return false unless data[k] == reqs[k]
76
+ end
77
+ end
78
+ true
79
+ end
80
+
81
+ def self.verify_array_of_hashes(data, reqs)
82
+ # First determine the primary key(s) for the object
83
+ pkeys = []
84
+ [%w[Name], %w[SceneName ID]].each do |pkey_arr|
85
+ pkeys = pkey_arr if pkey_arr.all? { |k| data.first.key?(k) || reqs.first.key?(k) }
86
+ end
87
+ reqs.each do |r|
88
+ idx = data.find_index { |d| d.is_a?(Hash) && pkeys.all? { |k| d.key?(k) && d[k] == r[k] } }
89
+ return false unless idx && self.verify_hash(data[idx], r)
90
+ end
91
+ true
92
+ end
93
+
94
+ def self.get_game_act(data)
95
+ if data['playerData']['act3MapUpdated'] || data['playerData']['act3_wokeUp']
96
+ 3
97
+ elsif data['playerData']['act2Started'] || data['playerData']['defeatedLastJudge']
98
+ 2
99
+ else
100
+ 1
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,121 @@
1
+ require 'yaml'
2
+
3
+ module Silkedit::Cheat
4
+ module SilksongCheats
5
+ @cheatdata = YAML.safe_load_file(File.join(Silkedit::LIBDIR, 'config', 'silksong', 'cheatdata.yaml'), symbolize_names: false)
6
+ @cheatdata['cheats'].each do |cheat, cht_data|
7
+ next if self.instance_methods.include?(cheat.to_sym)
8
+ self.define_method(cheat.to_sym) do
9
+ Rbcli.log.info "Applying cheat #{cheat}", 'CHEATS'
10
+ Silkedit::Cheat.merge_cheat(@data, cht_data, should_merge_arrays: true)
11
+ end
12
+ end
13
+
14
+ def unkill
15
+ Rbcli.log.info 'Reviving from death', 'CHEATS'
16
+ if @data['playerData']['permadeathMode'] == 2
17
+ @data['playerData']['permadeathMode'] = 1
18
+ end
19
+ @data['playerData']['geo'] += @data['playerData']['HeroCorpseMoneyPool']
20
+ @data['playerData']['HeroDeathScenePos'] = { 'x' => 0.0, 'y' => 0.0 }
21
+ @data['playerData']['HeroDeathSceneSize'] = { 'x' => 0.0, 'y' => 0.0 }
22
+ @data['playerData']['HeroCorpseType'] = 0
23
+ @data['playerData']['HeroCorpseMoneyPool'] = 0
24
+ @data['playerData']['hazardRespawnFacing'] = 0
25
+ @data['playerData']['IsSilkSpoolBroken'] = false
26
+ @data['playerData'].delete('HeroCorpseMarkerGuid')
27
+ @data['playerData'].delete('HeroCorpseScene')
28
+ end
29
+
30
+ def refresh
31
+ self.max_shards
32
+ self.max_liquids
33
+ end
34
+
35
+ def max_everything
36
+ %w[
37
+ max_health
38
+ max_silk
39
+ max_weapon
40
+ max_tool_upgrades
41
+ max_liquids
42
+ all_abilities
43
+ all_crests
44
+ all_crest_unlocks
45
+ all_eva_upgrades
46
+ all_spells
47
+ all_tools
48
+ give_consumables
49
+ max_rosaries
50
+ max_shards
51
+ ].each { |cht| self.send(cht.to_sym) }
52
+ end
53
+
54
+ def max_shards
55
+ Rbcli.log.info 'Applying cheat max_shards', 'CHEATS'
56
+ @data['playerData']['ShellShards'] = 400 + (@data['playerData']['ToolPouchUpgrades'] || 0) * 100
57
+ end
58
+
59
+ def all_crest_unlocks
60
+ Rbcli.log.info 'Applying cheat all_crest_unlocks', 'CHEATS'
61
+ cheatdata = Silkedit::Cheat::SilksongCheats.module_eval { @cheatdata }
62
+ @data['playerData']['ToolEquips']['savedData'].each do |crest|
63
+ if crest['Data']['Slots'].nil? || crest['Data']['Slots'].empty?
64
+ newcrest = cheatdata['reference']['full_crests']['playerData']['ToolEquips']['savedData'].find { |c| c['Name'] == crest['Name'] }
65
+ next if newcrest.nil?
66
+ crest['Data'] = {} if crest['Data'].nil?
67
+ crest['Data']['Slots'] = newcrest['Data']['Slots'] if newcrest.key?('Data') && newcrest['Data'].key?('Slots')
68
+ end
69
+ crest['Data']['Slots'].each { |slot| slot['IsUnlocked'] = true }
70
+ end
71
+ end
72
+
73
+ def toggle_map_reveal
74
+ @data['playerData']['mapAllRooms'] = !@data['playerData']['mapAllRooms']
75
+ Rbcli.log.info "Full map reveal is #{@data['playerData']['mapAllRooms'] ? 'enabled' : 'disabled'}", 'CHEATS'
76
+ end
77
+
78
+ def toggle_flea_reveal
79
+ flea_keys = @data['playerData'].keys.select { |k| k.start_with?('hasPinFlea') }
80
+ is_enabled = flea_keys.all? { |k| @data['playerData'][k] }
81
+ flea_keys.each { |k| @data[k] = !is_enabled }
82
+ Rbcli.log.info "Flea locations are #{is_enabled ? 'hidden' : 'revealed'} on map", 'CHEATS'
83
+ end
84
+
85
+ def toggle_cloakless
86
+ if @data['playerData']['CurrentCrestID'] != 'Cloakless'
87
+ Rbcli.log.info 'Going cloakless', 'CHEATS'
88
+ @data['playerData']['TempGeoStore'] = @data['playerData']['geo']
89
+ @data['playerData']['geo'] = 0
90
+ @data['playerData']['TempShellShardStore'] = @data['playerData']['ShellShards']
91
+ @data['playerData']['ShellShards'] = 0
92
+ @data['playerData']['PreviousCrestID'] = @data['playerData']['CurrentCrestID']
93
+ @data['playerData']['CurrentCrestID'] = 'Cloakless'
94
+ @data['playerData']['slab_cloak_battle_encountered'] = false
95
+ @data['playerData']['slab_cloak_battle_completed'] = false
96
+ @data['playerData']['IsSilkSpoolBroken'] = true
97
+ unless @data['playerData']['ToolEquips']['savedData'].find { |e| e['Name'] == 'Cloakless' }
98
+ cheatdata = Silkedit::Cheat::SilksongCheats.module_eval { @cheatdata }
99
+ @data['playerData']['ToolEquips']['savedData'].append(cheatdata['reference']['cloakless']['playerData']['ToolEquips']['savedData'].first)
100
+ end
101
+ else
102
+ Rbcli.log.info 'Recovering cloak', 'CHEATS'
103
+ @data['playerData']['geo'] = @data['playerData']['TempGeoStore']
104
+ @data['playerData']['TempGeoStore'] = 0
105
+ @data['playerData']['ShellShards'] = @data['playerData']['TempShellShardStore']
106
+ @data['playerData']['TempShellShardStore'] = 0
107
+ @data['playerData']['CurrentCrestID'] = @data['playerData']['PreviousCrestID']
108
+ @data['playerData']['PreviousCrestID'] = 'Cloakless'
109
+ # @data['playerData']['slab_cloak_battle_encountered'] = true
110
+ # @data['playerData']['slab_cloak_battle_completed'] = true
111
+ @data['playerData']['IsSilkSpoolBroken'] = false
112
+ end
113
+ end
114
+
115
+ def toggle_permadeath_mode
116
+ toggle = @data['playerData']['permadeathMode'] == 0
117
+ Rbcli.log.info "Toggling permadeath mode #{toggle ? 'ON' : 'OFF'}", 'CHEATS'
118
+ @data['playerData']['permadeathMode'] = toggle ? 1 : 0
119
+ end
120
+ end
121
+ end