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.
- checksums.yaml +7 -0
- data/.rbcli +0 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.md +302 -0
- data/Rakefile +8 -0
- data/exe/silkedit +14 -0
- data/lib/commands/backup_restore.rb +25 -0
- data/lib/commands/cheat.rb +19 -0
- data/lib/commands/edit_diff.rb +47 -0
- data/lib/commands/examples/command.rb +51 -0
- data/lib/commands/examples/script.rb +36 -0
- data/lib/commands/journal.rb +34 -0
- data/lib/commands/permasave_permaload.rb +62 -0
- data/lib/commands/scripts/.keep +0 -0
- data/lib/commands/unpack_repack.rb +27 -0
- data/lib/commands/zone_mkzone.rb +102 -0
- data/lib/config/config.rb +28 -0
- data/lib/config/envvars.rb +23 -0
- data/lib/config/hooks.rb +22 -0
- data/lib/config/logger.rb +36 -0
- data/lib/config/parser.rb +34 -0
- data/lib/config/updatechecker.rb +12 -0
- data/lib/silkedit/cheats/cheatengine.rb +34 -0
- data/lib/silkedit/cheats/hollow_knight_cheats.rb +5 -0
- data/lib/silkedit/cheats/merger.rb +103 -0
- data/lib/silkedit/cheats/silksong_cheats.rb +121 -0
- data/lib/silkedit/cheats/silksong_enemyjournal.rb +119 -0
- data/lib/silkedit/cheats/silksong_zoner.rb +98 -0
- data/lib/silkedit/config/savegame.yaml +13 -0
- data/lib/silkedit/config/silkedit.yaml +6 -0
- data/lib/silkedit/config/silksong/cheatdata.yaml +1145 -0
- data/lib/silkedit/config/silksong/enemylist.yaml +1418 -0
- data/lib/silkedit/config/silksong/old.yaml +4345 -0
- data/lib/silkedit/config/silksong/permasaves.yaml +10 -0
- data/lib/silkedit/config/silksong/unfinished.yaml +628 -0
- data/lib/silkedit/config/silksong/zones.yaml +1686 -0
- data/lib/silkedit/images/.keep +0 -0
- data/lib/silkedit/images/silksong/Aknid.png +0 -0
- data/lib/silkedit/images/silksong/Alita.png +0 -0
- data/lib/silkedit/images/silksong/Barnak.png +0 -0
- data/lib/silkedit/images/silksong/Beastfly.png +0 -0
- data/lib/silkedit/images/silksong/Bell_Beast.png +0 -0
- data/lib/silkedit/images/silksong/Bell_Eater.png +0 -0
- data/lib/silkedit/images/silksong/Bloatroach.png +0 -0
- data/lib/silkedit/images/silksong/Broodmother.png +0 -0
- data/lib/silkedit/images/silksong/Brushflit.png +0 -0
- data/lib/silkedit/images/silksong/Burning_Bug.png +0 -0
- data/lib/silkedit/images/silksong/Caranid.png +0 -0
- data/lib/silkedit/images/silksong/Choir_Bellbearer.png +0 -0
- data/lib/silkedit/images/silksong/Choir_Clapper.png +0 -0
- data/lib/silkedit/images/silksong/Choir_Elder.png +0 -0
- data/lib/silkedit/images/silksong/Choir_Flyer.png +0 -0
- data/lib/silkedit/images/silksong/Choir_Hornhead.png +0 -0
- data/lib/silkedit/images/silksong/Choir_Pouncer.png +0 -0
- data/lib/silkedit/images/silksong/Choristor.png +0 -0
- data/lib/silkedit/images/silksong/Clawmaiden.png +0 -0
- data/lib/silkedit/images/silksong/Clover_Dancers.png +0 -0
- data/lib/silkedit/images/silksong/Cloverstag.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Choirbug.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Clapper.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Cleanser.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Crawler.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Dancers.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Defender.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Hauler.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Spine.png +0 -0
- data/lib/silkedit/images/silksong/Cogwork_Underfly.png +0 -0
- data/lib/silkedit/images/silksong/Cogworker.png +0 -0
- data/lib/silkedit/images/silksong/Conchfly.png +0 -0
- data/lib/silkedit/images/silksong/Coral_Furm.png +0 -0
- data/lib/silkedit/images/silksong/Corrcrust_Karaka.png +0 -0
- data/lib/silkedit/images/silksong/Covetous_Pilgrim.png +0 -0
- data/lib/silkedit/images/silksong/Craggler.png +0 -0
- data/lib/silkedit/images/silksong/Cragglite.png +0 -0
- data/lib/silkedit/images/silksong/Craw.png +0 -0
- data/lib/silkedit/images/silksong/Craw_Juror.png +0 -0
- data/lib/silkedit/images/silksong/Crawfather.png +0 -0
- data/lib/silkedit/images/silksong/Crust_King_Khann.png +0 -0
- data/lib/silkedit/images/silksong/Crustcrag.png +0 -0
- data/lib/silkedit/images/silksong/Crustcrawler.png +0 -0
- data/lib/silkedit/images/silksong/Deep_Diver.png +0 -0
- data/lib/silkedit/images/silksong/Disgraced_Chef_Lugoli.png +0 -0
- data/lib/silkedit/images/silksong/Drapefly.png +0 -0
- data/lib/silkedit/images/silksong/Drapelord.png +0 -0
- data/lib/silkedit/images/silksong/Drapemite.png +0 -0
- data/lib/silkedit/images/silksong/Dreg_Catcher.png +0 -0
- data/lib/silkedit/images/silksong/Dreg_Husk.png +0 -0
- data/lib/silkedit/images/silksong/Dregwheel.png +0 -0
- data/lib/silkedit/images/silksong/Driftlin.png +0 -0
- data/lib/silkedit/images/silksong/Driznarga.png +0 -0
- data/lib/silkedit/images/silksong/Driznit.png +0 -0
- data/lib/silkedit/images/silksong/Ductsucker.png +0 -0
- data/lib/silkedit/images/silksong/Elder_Pilgrim.png +0 -0
- data/lib/silkedit/images/silksong/Envoy.png +0 -0
- data/lib/silkedit/images/silksong/Escalion.png +0 -0
- data/lib/silkedit/images/silksong/Father_of_the_Flame.png +0 -0
- data/lib/silkedit/images/silksong/Fertid.png +0 -0
- data/lib/silkedit/images/silksong/First_Sinner.png +0 -0
- data/lib/silkedit/images/silksong/Flapping_Fertid.png +0 -0
- data/lib/silkedit/images/silksong/Flintbeetle.png +0 -0
- data/lib/silkedit/images/silksong/Flintflame_Flyer.png +0 -0
- data/lib/silkedit/images/silksong/Flintstone_Flyer.png +0 -0
- data/lib/silkedit/images/silksong/Fluttermite.png +0 -0
- data/lib/silkedit/images/silksong/Forebrothers_Signis_&_Gron.png +0 -0
- data/lib/silkedit/images/silksong/Fourth_Chorus.png +0 -0
- data/lib/silkedit/images/silksong/Freshfly.png +0 -0
- data/lib/silkedit/images/silksong/Furm.png +0 -0
- data/lib/silkedit/images/silksong/Gahlia.png +0 -0
- data/lib/silkedit/images/silksong/Gargant_Gloom.png +0 -0
- data/lib/silkedit/images/silksong/Garpid.png +0 -0
- data/lib/silkedit/images/silksong/Giant_Drapemite.png +0 -0
- data/lib/silkedit/images/silksong/Gloomsac.png +0 -0
- data/lib/silkedit/images/silksong/Grand_Mother_Silk.png +0 -0
- data/lib/silkedit/images/silksong/Grand_Reed.png +0 -0
- data/lib/silkedit/images/silksong/Great_Conchfly.png +0 -0
- data/lib/silkedit/images/silksong/Groal_the_Great.png +0 -0
- data/lib/silkedit/images/silksong/Grom.png +0 -0
- data/lib/silkedit/images/silksong/Gromling.png +0 -0
- data/lib/silkedit/images/silksong/Guardfly.png +0 -0
- data/lib/silkedit/images/silksong/Gurr_the_Outcast.png +0 -0
- data/lib/silkedit/images/silksong/Hardbone_Elder.png +0 -0
- data/lib/silkedit/images/silksong/Hardbone_Hopper.png +0 -0
- data/lib/silkedit/images/silksong/Hoker.png +0 -0
- data/lib/silkedit/images/silksong/Huge_Flea.png +0 -0
- data/lib/silkedit/images/silksong/Imoba.png +0 -0
- data/lib/silkedit/images/silksong/Judge.png +0 -0
- data/lib/silkedit/images/silksong/Kai.png +0 -0
- data/lib/silkedit/images/silksong/Kakri.png +0 -0
- data/lib/silkedit/images/silksong/Karak_Gor.png +0 -0
- data/lib/silkedit/images/silksong/Karaka.png +0 -0
- data/lib/silkedit/images/silksong/Kilik.png +0 -0
- data/lib/silkedit/images/silksong/Kindanir.png +0 -0
- data/lib/silkedit/images/silksong/Lace.png +0 -0
- data/lib/silkedit/images/silksong/Lampbearer.png +0 -0
- data/lib/silkedit/images/silksong/Last_Claw.png +0 -0
- data/lib/silkedit/images/silksong/Last_Judge.png +0 -0
- data/lib/silkedit/images/silksong/Lavalarga.png +0 -0
- data/lib/silkedit/images/silksong/Lavalug.png +0 -0
- data/lib/silkedit/images/silksong/Leaf_Glider.png +0 -0
- data/lib/silkedit/images/silksong/Leaf_Roller.png +0 -0
- data/lib/silkedit/images/silksong/Lost_Garmond.png +0 -0
- data/lib/silkedit/images/silksong/Lost_Lace.png +0 -0
- data/lib/silkedit/images/silksong/Maestro.png +0 -0
- data/lib/silkedit/images/silksong/Marrowmaw.png +0 -0
- data/lib/silkedit/images/silksong/Massive_Mossgrub.png +0 -0
- data/lib/silkedit/images/silksong/Mawling.png +0 -0
- data/lib/silkedit/images/silksong/Memoria.png +0 -0
- data/lib/silkedit/images/silksong/Minister.png +0 -0
- data/lib/silkedit/images/silksong/Miremite.png +0 -0
- data/lib/silkedit/images/silksong/Mite.png +0 -0
- data/lib/silkedit/images/silksong/Mitemother.png +0 -0
- data/lib/silkedit/images/silksong/Mnemonid.png +0 -0
- data/lib/silkedit/images/silksong/Mnemonord.png +0 -0
- data/lib/silkedit/images/silksong/Moorwing.png +0 -0
- data/lib/silkedit/images/silksong/Mortician.png +0 -0
- data/lib/silkedit/images/silksong/Moss_Mother.png +0 -0
- data/lib/silkedit/images/silksong/Mossgrub.png +0 -0
- data/lib/silkedit/images/silksong/Mossmir.png +0 -0
- data/lib/silkedit/images/silksong/Mothleaf_Lagnia.png +0 -0
- data/lib/silkedit/images/silksong/Muckmaggot.png +0 -0
- data/lib/silkedit/images/silksong/Muckroach.png +0 -0
- data/lib/silkedit/images/silksong/Nuphar.png +0 -0
- data/lib/silkedit/images/silksong/Nyleth.png +0 -0
- data/lib/silkedit/images/silksong/Overgrown_Pilgrim.png +0 -0
- data/lib/silkedit/images/silksong/Palestag.png +0 -0
- data/lib/silkedit/images/silksong/Pendra.png +0 -0
- data/lib/silkedit/images/silksong/Pendragor.png +0 -0
- data/lib/silkedit/images/silksong/Penitent.png +0 -0
- data/lib/silkedit/images/silksong/Phacia.png +0 -0
- data/lib/silkedit/images/silksong/Phantom.png +0 -0
- data/lib/silkedit/images/silksong/Pharlid.png +0 -0
- data/lib/silkedit/images/silksong/Pharlid_Diver.png +0 -0
- data/lib/silkedit/images/silksong/Pilgrim_Bellbearer.png +0 -0
- data/lib/silkedit/images/silksong/Pilgrim_Groveller.png +0 -0
- data/lib/silkedit/images/silksong/Pilgrim_Guide.png +0 -0
- data/lib/silkedit/images/silksong/Pilgrim_Hiker.png +0 -0
- data/lib/silkedit/images/silksong/Pilgrim_Hornfly.png +0 -0
- data/lib/silkedit/images/silksong/Pilgrim_Hulk.png +0 -0
- data/lib/silkedit/images/silksong/Pilgrim_Pouncer.png +0 -0
- data/lib/silkedit/images/silksong/Pinstress.png +0 -0
- data/lib/silkedit/images/silksong/Plasmid.png +0 -0
- data/lib/silkedit/images/silksong/Plasmidas.png +0 -0
- data/lib/silkedit/images/silksong/Plasmified_Zango.png +0 -0
- data/lib/silkedit/images/silksong/Pokenabbin.png +0 -0
- data/lib/silkedit/images/silksong/Pollenica.png +0 -0
- data/lib/silkedit/images/silksong/Pond_Skipper.png +0 -0
- data/lib/silkedit/images/silksong/Pondcatcher.png +0 -0
- data/lib/silkedit/images/silksong/Puny_Penitent.png +0 -0
- data/lib/silkedit/images/silksong/Reed.png +0 -0
- data/lib/silkedit/images/silksong/Rhinogrund.png +0 -0
- data/lib/silkedit/images/silksong/Roachcatcher.png +0 -0
- data/lib/silkedit/images/silksong/Roachfeeder.png +0 -0
- data/lib/silkedit/images/silksong/Roachkeeper.png +0 -0
- data/lib/silkedit/images/silksong/Roachserver.png +0 -0
- data/lib/silkedit/images/silksong/Sandcarver.png +0 -0
- data/lib/silkedit/images/silksong/Savage_Beastfly.png +0 -0
- data/lib/silkedit/images/silksong/Scabfly.png +0 -0
- data/lib/silkedit/images/silksong/Scrollreader.png +0 -0
- data/lib/silkedit/images/silksong/Second_Sentinel.png +0 -0
- data/lib/silkedit/images/silksong/Servitor_Boran.png +0 -0
- data/lib/silkedit/images/silksong/Servitor_Ignim.png +0 -0
- data/lib/silkedit/images/silksong/Shadow_Charger.png +0 -0
- data/lib/silkedit/images/silksong/Shadow_Creeper_(Silksong).png +0 -0
- data/lib/silkedit/images/silksong/Shardillard.png +0 -0
- data/lib/silkedit/images/silksong/Shellwood_Gnat.png +0 -0
- data/lib/silkedit/images/silksong/Shrine_Guardian_Seth.png +0 -0
- data/lib/silkedit/images/silksong/Silk_Snipper.png +0 -0
- data/lib/silkedit/images/silksong/Sister_Splinter.png +0 -0
- data/lib/silkedit/images/silksong/Skarr_Scout.png +0 -0
- data/lib/silkedit/images/silksong/Skarr_Stalker.png +0 -0
- data/lib/silkedit/images/silksong/Skarrgard.png +0 -0
- data/lib/silkedit/images/silksong/Skarrlid.png +0 -0
- data/lib/silkedit/images/silksong/Skarrsinger_Karmelita.png +0 -0
- data/lib/silkedit/images/silksong/Skarrwing.png +0 -0
- data/lib/silkedit/images/silksong/Skrill.png +0 -0
- data/lib/silkedit/images/silksong/Skull_Brute.png +0 -0
- data/lib/silkedit/images/silksong/Skull_Scuttler.png +0 -0
- data/lib/silkedit/images/silksong/Skull_Tyrant.png +0 -0
- data/lib/silkedit/images/silksong/Skullwing.png +0 -0
- data/lib/silkedit/images/silksong/Slubberlug.png +0 -0
- data/lib/silkedit/images/silksong/Smelt_Shoveller.png +0 -0
- data/lib/silkedit/images/silksong/Smokerock_Sifter.png +0 -0
- data/lib/silkedit/images/silksong/Snitchfly.png +0 -0
- data/lib/silkedit/images/silksong/Spear_Skarr.png +0 -0
- data/lib/silkedit/images/silksong/Spinebeak_Kai.png +0 -0
- data/lib/silkedit/images/silksong/Spit_Squit.png +0 -0
- data/lib/silkedit/images/silksong/Splinter.png +0 -0
- data/lib/silkedit/images/silksong/Splinterbark.png +0 -0
- data/lib/silkedit/images/silksong/Splinterhorn.png +0 -0
- data/lib/silkedit/images/silksong/Squatcraw.png +0 -0
- data/lib/silkedit/images/silksong/Squatcraw_Juror.png +0 -0
- data/lib/silkedit/images/silksong/Squirrm.png +0 -0
- data/lib/silkedit/images/silksong/Steelspine_Kai.png +0 -0
- data/lib/silkedit/images/silksong/Stilkin.png +0 -0
- data/lib/silkedit/images/silksong/Stilkin_Trapper.png +0 -0
- data/lib/silkedit/images/silksong/Summoned_Saviour.png +0 -0
- data/lib/silkedit/images/silksong/Surgeon.png +0 -0
- data/lib/silkedit/images/silksong/Swamp_Squit.png +0 -0
- data/lib/silkedit/images/silksong/Tallcraw.png +0 -0
- data/lib/silkedit/images/silksong/Tallcraw_Juror.png +0 -0
- data/lib/silkedit/images/silksong/Tarmite.png +0 -0
- data/lib/silkedit/images/silksong/The_Unravelled.png +0 -0
- data/lib/silkedit/images/silksong/Thread_Raker.png +0 -0
- data/lib/silkedit/images/silksong/Tormented_Trobbio.png +0 -0
- data/lib/silkedit/images/silksong/Trobbio.png +0 -0
- data/lib/silkedit/images/silksong/Undercrank.png +0 -0
- data/lib/silkedit/images/silksong/Underloft.png +0 -0
- data/lib/silkedit/images/silksong/Underpoke.png +0 -0
- data/lib/silkedit/images/silksong/Underscrub.png +0 -0
- data/lib/silkedit/images/silksong/Undersweep.png +0 -0
- data/lib/silkedit/images/silksong/Underworker.png +0 -0
- data/lib/silkedit/images/silksong/Vaultborn.png +0 -0
- data/lib/silkedit/images/silksong/Vaultkeeper.png +0 -0
- data/lib/silkedit/images/silksong/Verdanir.png +0 -0
- data/lib/silkedit/images/silksong/Vicious_Caranid.png +0 -0
- data/lib/silkedit/images/silksong/Void_Mass.png +0 -0
- data/lib/silkedit/images/silksong/Void_Tendrils_(Silksong).png +0 -0
- data/lib/silkedit/images/silksong/Voltvyrm.png +0 -0
- data/lib/silkedit/images/silksong/Wardenfly.png +0 -0
- data/lib/silkedit/images/silksong/Watcher_at_the_Edge.png +0 -0
- data/lib/silkedit/images/silksong/Widow.png +0 -0
- data/lib/silkedit/images/silksong/Winged_Furm.png +0 -0
- data/lib/silkedit/images/silksong/Winged_Lifeseed.png +0 -0
- data/lib/silkedit/images/silksong/Winged_Pilgrim.png +0 -0
- data/lib/silkedit/images/silksong/Winged_Pilgrim_Bellbearer.png +0 -0
- data/lib/silkedit/images/silksong/Wingmould_(Silksong).png +0 -0
- data/lib/silkedit/images/silksong/Wisp.png +0 -0
- data/lib/silkedit/images/silksong/Wood_Wasp.png +0 -0
- data/lib/silkedit/images/silksong/Wraith.png +0 -0
- data/lib/silkedit/images/silksong/Yago.png +0 -0
- data/lib/silkedit/images/silksong/Yuma.png +0 -0
- data/lib/silkedit/images/silksong/Yumama.png +0 -0
- data/lib/silkedit/savegame/crypto.rb +25 -0
- data/lib/silkedit/savegame/diff.rb +158 -0
- data/lib/silkedit/savegame/packer.rb +75 -0
- data/lib/silkedit/savegame/savefile.rb +153 -0
- data/lib/silkedit/util/system.rb +37 -0
- data/lib/silkedit/version.rb +5 -0
- data/lib/silkedit.rb +9 -0
- data/sig/silkedit.rbs +4 -0
- 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
         | 
    
        data/lib/config/hooks.rb
    ADDED
    
    | @@ -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,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
         |