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
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| Binary file | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            require 'openssl'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Silkedit::Savegame
         | 
| 4 | 
            +
              module Crypto
         | 
| 5 | 
            +
                AES_KEY = YAML.safe_load_file(File.join(Silkedit::LIBDIR, 'config', 'savegame.yaml'), symbolize_names: true)[:aes_key]
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(key)
         | 
| 8 | 
            +
                  @aes_key = key
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.aes_encrypt(data)
         | 
| 12 | 
            +
                  cipher = OpenSSL::Cipher.new('AES-256-ECB')
         | 
| 13 | 
            +
                  cipher.encrypt
         | 
| 14 | 
            +
                  cipher.key = AES_KEY
         | 
| 15 | 
            +
                  cipher.update(data) + cipher.final
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def self.aes_decrypt(data)
         | 
| 19 | 
            +
                  cipher = OpenSSL::Cipher.new('AES-256-ECB')
         | 
| 20 | 
            +
                  cipher.decrypt
         | 
| 21 | 
            +
                  cipher.key = AES_KEY
         | 
| 22 | 
            +
                  cipher.update(data) + cipher.final
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,158 @@ | |
| 1 | 
            +
            require 'pp'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Silkedit::Savegame
         | 
| 4 | 
            +
              module Diff
         | 
| 5 | 
            +
                def self.mkdiff (old, new)
         | 
| 6 | 
            +
                  return { old: old, new: new } if old.class != new.class
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  diff = nil
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  if old.is_a?(Hash)
         | 
| 11 | 
            +
                    diff = {}
         | 
| 12 | 
            +
                    (old.keys + new.keys).uniq.each do |k|
         | 
| 13 | 
            +
                      itemdiff = mkdiff(old[k], new[k])
         | 
| 14 | 
            +
                      diff[k] = itemdiff unless itemdiff.nil? || itemdiff.empty?
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  elsif old.is_a?(Array)
         | 
| 17 | 
            +
                    diff = []
         | 
| 18 | 
            +
                    if old.first.is_a?(Hash)
         | 
| 19 | 
            +
                      if old.first.key?('Name')
         | 
| 20 | 
            +
                        keycomps = %w[Name]
         | 
| 21 | 
            +
                      elsif old.first.key?('SceneName') && old.first.key?('ID')
         | 
| 22 | 
            +
                        keycomps = %w[SceneName ID]
         | 
| 23 | 
            +
                      elsif old.first.key?('SceneName') && old.first.key?('EventType')
         | 
| 24 | 
            +
                        keycomps = %w[SceneName EventType]
         | 
| 25 | 
            +
                      else
         | 
| 26 | 
            +
                        keycomps = nil
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                      if keycomps.nil?
         | 
| 29 | 
            +
                        diff = { old: old.reject { |e| new.include?(e) }, new: new.reject { |e| old.include?(e) } }
         | 
| 30 | 
            +
                      else
         | 
| 31 | 
            +
                        old.each do |o|
         | 
| 32 | 
            +
                          n = new.find { |e| keycomps.map { |k| e[k] == o[k] }.reduce { |a, b| a && b } }
         | 
| 33 | 
            +
                          itemdiff = mkdiff(
         | 
| 34 | 
            +
                            o.reject { |k, _v| keycomps.include?(k) },
         | 
| 35 | 
            +
                            n.nil? ? nil : n.reject { |k, _v| keycomps.include?(k) }
         | 
| 36 | 
            +
                          )
         | 
| 37 | 
            +
                          next if itemdiff.empty?
         | 
| 38 | 
            +
                          newobj = {}
         | 
| 39 | 
            +
                          keycomps.each { |k| newobj[k] = o[k] }
         | 
| 40 | 
            +
                          diff.append(newobj.merge(itemdiff))
         | 
| 41 | 
            +
                        end
         | 
| 42 | 
            +
                        new.each do |n|
         | 
| 43 | 
            +
                          o = old.find { |e| keycomps.map { |k| e[k] == n[k] }.reduce { |a, b| a && b } }
         | 
| 44 | 
            +
                          next unless o.nil?
         | 
| 45 | 
            +
                          itemdiff = mkdiff(
         | 
| 46 | 
            +
                            nil,
         | 
| 47 | 
            +
                            n.reject { |k, _v| keycomps.include?(k) }
         | 
| 48 | 
            +
                          )
         | 
| 49 | 
            +
                          newobj = {}
         | 
| 50 | 
            +
                          keycomps.each { |k| newobj[k] = n[k] }
         | 
| 51 | 
            +
                          diff.append(newobj.merge(itemdiff))
         | 
| 52 | 
            +
                        end
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
                  elsif old != new
         | 
| 56 | 
            +
                    diff = { old: old, new: new }
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                  return diff
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def self.parse_diff(obj, yaml_output: false)
         | 
| 62 | 
            +
                  if yaml_output
         | 
| 63 | 
            +
                    sio = StringIO.new
         | 
| 64 | 
            +
                    YAML.dump(obj, sio)
         | 
| 65 | 
            +
                    colored_string = ''
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    sio.string.each_line do |line|
         | 
| 68 | 
            +
                      i = 0
         | 
| 69 | 
            +
                      while i < line.length
         | 
| 70 | 
            +
                        blank_leadup = line[0..i-1].chars.map { |c| c == ' ' }.all?
         | 
| 71 | 
            +
                        if line[i] == '-' && blank_leadup
         | 
| 72 | 
            +
                          colored_string += line[i].colorize(:yellow)
         | 
| 73 | 
            +
                          i += 1
         | 
| 74 | 
            +
                        elsif line[i] == ':' && blank_leadup
         | 
| 75 | 
            +
                          j = i + line[i+1..-1].index(':')
         | 
| 76 | 
            +
                          colored_string += line[i..j].colorize(:magenta)
         | 
| 77 | 
            +
                          i += j - i + 1
         | 
| 78 | 
            +
                        elsif line[i].match(%r{[\"\w]}) && line[i+1..-1].include?(':')
         | 
| 79 | 
            +
                          j = i + line[i+1..-1].index(':')
         | 
| 80 | 
            +
                          colored_string += line[i..j].colorize(:red)
         | 
| 81 | 
            +
                          i += j - i + 1
         | 
| 82 | 
            +
                        elsif line[i].match(%r{\d}) && line[i..-2].chars.map { |c| c.match(%r{\d}) }.all?
         | 
| 83 | 
            +
                          colored_string += line[i..-2].colorize(:blue)
         | 
| 84 | 
            +
                          i = line.length - 1
         | 
| 85 | 
            +
                        elsif line[i..i + 2] == 'nil'
         | 
| 86 | 
            +
                          colored_string += line[i..i + 2].colorize(:yellow)
         | 
| 87 | 
            +
                          i += 3
         | 
| 88 | 
            +
                        elsif line[i..i + 3] == 'true'
         | 
| 89 | 
            +
                          colored_string += line[i..i + 3].colorize(:yellow)
         | 
| 90 | 
            +
                          i += 4
         | 
| 91 | 
            +
                        elsif line[i..i + 4] == 'false'
         | 
| 92 | 
            +
                          colored_string += line[i..i + 4].colorize(:yellow)
         | 
| 93 | 
            +
                          i += 5
         | 
| 94 | 
            +
                        elsif line[i].match(%r{[\"\w]})
         | 
| 95 | 
            +
                          colored_string += line[i..-2].colorize(:green)
         | 
| 96 | 
            +
                          i = line.length - 1
         | 
| 97 | 
            +
                        elsif line[i] == ':'
         | 
| 98 | 
            +
                          colored_string += line[i].colorize(:yellow)
         | 
| 99 | 
            +
                          i += 1
         | 
| 100 | 
            +
                        else
         | 
| 101 | 
            +
                          colored_string += line[i]
         | 
| 102 | 
            +
                          i += 1
         | 
| 103 | 
            +
                        end
         | 
| 104 | 
            +
                      end
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    return colored_string
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  sio = StringIO.new
         | 
| 111 | 
            +
                  PP.pp(obj, sio)
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  colored_string = ''
         | 
| 114 | 
            +
                  i = 0
         | 
| 115 | 
            +
                  while i < sio.string.length
         | 
| 116 | 
            +
                    if ['{', '}', '[', ']', ','].include?(sio.string[i])
         | 
| 117 | 
            +
                      colored_string += sio.string[i]
         | 
| 118 | 
            +
                      i += 1
         | 
| 119 | 
            +
                    elsif sio.string[i] == '"'
         | 
| 120 | 
            +
                      capture = '"'
         | 
| 121 | 
            +
                      cnt = 1
         | 
| 122 | 
            +
                      while sio.string[i + cnt] != '"'
         | 
| 123 | 
            +
                        capture += sio.string[i + cnt]
         | 
| 124 | 
            +
                        cnt += 1
         | 
| 125 | 
            +
                      end
         | 
| 126 | 
            +
                      capture += '"'
         | 
| 127 | 
            +
                      color = sio.string[i + cnt + 2] == '=' ? :red : :green
         | 
| 128 | 
            +
                      colored_string += capture.colorize(color)
         | 
| 129 | 
            +
                      i += cnt + 1
         | 
| 130 | 
            +
                    elsif sio.string[i..i + 1] == '=>'
         | 
| 131 | 
            +
                      colored_string += sio.string[i..i + 1].colorize(:yellow)
         | 
| 132 | 
            +
                      i += 2
         | 
| 133 | 
            +
                    elsif sio.string[i].to_i.to_s == sio.string[i]
         | 
| 134 | 
            +
                      colored_string += sio.string[i].colorize(:light_blue)
         | 
| 135 | 
            +
                      i += 1
         | 
| 136 | 
            +
                    elsif sio.string[i..i + 2] == 'nil'
         | 
| 137 | 
            +
                      colored_string += sio.string[i..i + 2].colorize(:yellow)
         | 
| 138 | 
            +
                      i += 3
         | 
| 139 | 
            +
                    elsif sio.string[i..i + 3] == 'true'
         | 
| 140 | 
            +
                      colored_string += sio.string[i..i + 3].colorize(:yellow)
         | 
| 141 | 
            +
                      i += 4
         | 
| 142 | 
            +
                    elsif sio.string[i..i + 4] == 'false'
         | 
| 143 | 
            +
                      colored_string += sio.string[i..i + 4].colorize(:yellow)
         | 
| 144 | 
            +
                      i += 5
         | 
| 145 | 
            +
                    elsif ['new:', 'old:'].include?(sio.string[i..i + 3])
         | 
| 146 | 
            +
                      colored_string += sio.string[i..i + 2].colorize(:magenta)
         | 
| 147 | 
            +
                      colored_string += sio.string[i + 3].colorize(:yellow)
         | 
| 148 | 
            +
                      i += 4
         | 
| 149 | 
            +
                    else
         | 
| 150 | 
            +
                      colored_string += sio.string[i]
         | 
| 151 | 
            +
                      i += 1
         | 
| 152 | 
            +
                    end
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
                  return colored_string
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
              end
         | 
| 158 | 
            +
            end
         | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            require 'base64'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Silkedit::Savegame
         | 
| 4 | 
            +
              module Packer
         | 
| 5 | 
            +
                RAWHEADER = "\u0000\u0001\u0000\u0000\u0000\xFF\xFF\xFF\xFF\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0006\u0001\u0000\u0000\u0000"
         | 
| 6 | 
            +
                C_SHARP_HEADER = [0, 1, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 6, 1, 0, 0, 0]
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def self.can_unpack?(string)
         | 
| 9 | 
            +
                  return string.start_with?(RAWHEADER)
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def self.pack(string)
         | 
| 13 | 
            +
                  string = Crypto.aes_encrypt(string)
         | 
| 14 | 
            +
                  string = Base64.strict_encode64(string)
         | 
| 15 | 
            +
                  string = self.add_header(string)
         | 
| 16 | 
            +
                  string
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def self.unpack(bytes)
         | 
| 20 | 
            +
                  bytes = self.strip_header(bytes)
         | 
| 21 | 
            +
                  bytes = Base64.decode64(bytes)
         | 
| 22 | 
            +
                  bytes = Crypto.aes_decrypt(bytes)
         | 
| 23 | 
            +
                  string = bytes.force_encoding('UTF-8')
         | 
| 24 | 
            +
                  string
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def self.generate_length_prefixed_string(length)
         | 
| 28 | 
            +
                  length = [length, 0x7FFFFFFF].min
         | 
| 29 | 
            +
                  bytes = []
         | 
| 30 | 
            +
                  while true
         | 
| 31 | 
            +
                    if length >> 7 != 0
         | 
| 32 | 
            +
                      bytes << ((length & 0x7F) | 0x80)
         | 
| 33 | 
            +
                      length >>= 7
         | 
| 34 | 
            +
                    else
         | 
| 35 | 
            +
                      bytes << (length & 0x7F)
         | 
| 36 | 
            +
                      length >>= 7
         | 
| 37 | 
            +
                      break
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                  bytes << length if length != 0
         | 
| 41 | 
            +
                  bytes.pack("C*")
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def self.add_header(data)
         | 
| 45 | 
            +
                  # Ensure data is a binary string
         | 
| 46 | 
            +
                  data = data.b if data.respond_to?(:b)
         | 
| 47 | 
            +
                  length_data = generate_length_prefixed_string(data.bytesize)
         | 
| 48 | 
            +
                  result = String.new
         | 
| 49 | 
            +
                  result << RAWHEADER.dup.force_encoding("ASCII-8BIT")
         | 
| 50 | 
            +
                  result << length_data
         | 
| 51 | 
            +
                  result << data
         | 
| 52 | 
            +
                  result << [11].pack("C")
         | 
| 53 | 
            +
                  result
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def self.strip_header(data)
         | 
| 57 | 
            +
                  # Remove fixed header and ending byte (11)
         | 
| 58 | 
            +
                  start = C_SHARP_HEADER.length
         | 
| 59 | 
            +
                  finish = data.bytesize - 1
         | 
| 60 | 
            +
                  data = data.byteslice(start...finish)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  # Remove variable-length LengthPrefixedString header
         | 
| 63 | 
            +
                  bytes = data.bytes
         | 
| 64 | 
            +
                  length_count = 0
         | 
| 65 | 
            +
                  bytes.each do |byte|
         | 
| 66 | 
            +
                    length_count += 1
         | 
| 67 | 
            +
                    break if (byte & 0x80) == 0
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  # Slice off the length prefix header
         | 
| 71 | 
            +
                  stripped = bytes[length_count..-1] || []
         | 
| 72 | 
            +
                  stripped.pack("C*")
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
| @@ -0,0 +1,153 @@ | |
| 1 | 
            +
            module Silkedit::Savegame
         | 
| 2 | 
            +
              class SaveFile
         | 
| 3 | 
            +
                def initialize(game, savegame_index)
         | 
| 4 | 
            +
                  # Rbcli::Warehouse.get(:config, :parsedopts)
         | 
| 5 | 
            +
                  Rbcli.log.fatal('Running on unknown OS. Exiting.', exit_status: 25) if Silkedit::Sys.os.nil?
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  config = YAML.safe_load_file(File.join(Silkedit::LIBDIR, 'config', 'savegame.yaml'), symbolize_names: true)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  @max_backups = config[:max_backups]
         | 
| 10 | 
            +
                  @use_yaml = config[:use_yaml]
         | 
| 11 | 
            +
                  @game = game
         | 
| 12 | 
            +
                  @data = nil
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  basename = "user#{savegame_index}"
         | 
| 15 | 
            +
                  @dirs = {
         | 
| 16 | 
            +
                    saves: File.expand_path(config[:savegame_directory][@game][Silkedit::Sys.os]),
         | 
| 17 | 
            +
                    work: File.join(Silkedit::Sys.tmpdir, @game.to_s),
         | 
| 18 | 
            +
                    backups: File.join(Silkedit::Sys.tmpdir, @game.to_s)
         | 
| 19 | 
            +
                  }
         | 
| 20 | 
            +
                  if @dirs[:saves].include?('%STEAMID%')
         | 
| 21 | 
            +
                    @dirs[:saves] = Dir.glob(File.join(File.dirname(@dirs[:saves]), '*')).select { |f| File.basename(f).match(%r{\d+}) }.first
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                  @dirs.each_value { |dir| FileUtils.mkdir_p(dir) unless File.directory?(dir) }
         | 
| 24 | 
            +
                  @filenames = {
         | 
| 25 | 
            +
                    dat: File.join(@dirs[:saves], "#{basename}.dat"),
         | 
| 26 | 
            +
                    json: File.join(@dirs[:work], "#{basename}.#{@use_yaml ? 'yaml' : 'json'}"),
         | 
| 27 | 
            +
                    backup: File.join(@dirs[:backups], "#{basename}.bak")
         | 
| 28 | 
            +
                  }
         | 
| 29 | 
            +
                  @filenames[:backups] = self.get_backup_file_list
         | 
| 30 | 
            +
                  Rbcli.log.debug "Savegame directory: #{@dirs[:saves]}", 'SAVEGAME'
         | 
| 31 | 
            +
                  Rbcli.log.debug "Working directory:  #{@dirs[:work]}", 'SAVEGAME'
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                attr_accessor :data
         | 
| 35 | 
            +
                attr_reader :dirs, :filenames, :game
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def loaded?
         | 
| 38 | 
            +
                  !@data.nil?
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def load_from_dat
         | 
| 42 | 
            +
                  load_from_disk(@filenames[:dat])
         | 
| 43 | 
            +
                  self
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def save_to_dat
         | 
| 47 | 
            +
                  save_to_disk(@filenames[:dat], pack: true)
         | 
| 48 | 
            +
                  self
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def load_from_json
         | 
| 52 | 
            +
                  load_from_disk(@filenames[:json])
         | 
| 53 | 
            +
                  self
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def save_to_json
         | 
| 57 | 
            +
                  save_to_disk(@filenames[:json], pack: false)
         | 
| 58 | 
            +
                  self
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def del_json
         | 
| 62 | 
            +
                  FileUtils.rm(@filenames[:json])
         | 
| 63 | 
            +
                  self
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def load_from_backup(seq_number: nil)
         | 
| 67 | 
            +
                  all_backup_files = self.get_backup_file_list
         | 
| 68 | 
            +
                  return false if all_backup_files.empty?
         | 
| 69 | 
            +
                  selected_file = seq_number.nil? ? all_backup_files.last : "#{@filenames[:backup]}#{seq_number}"
         | 
| 70 | 
            +
                  return false unless File.exist?(selected_file)
         | 
| 71 | 
            +
                  load_from_disk(selected_file)
         | 
| 72 | 
            +
                  self
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def save_to_backup(backup_name: nil)
         | 
| 76 | 
            +
                  if backup_name.nil?
         | 
| 77 | 
            +
                    return false if @max_backups <= 0
         | 
| 78 | 
            +
                    self.remove_old_backups
         | 
| 79 | 
            +
                    backup_filename = "#{@filenames[:backup]}#{self.next_backup_seq_number}"
         | 
| 80 | 
            +
                  else
         | 
| 81 | 
            +
                    backup_filename = "#{@filenames[:backup]}.#{backup_name}"
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                  save_to_disk(backup_filename, pack: true)
         | 
| 84 | 
            +
                  backup_filename
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def direct_backup(backup_name: nil)
         | 
| 88 | 
            +
                  if backup_name.nil?
         | 
| 89 | 
            +
                    return false if @max_backups <= 0
         | 
| 90 | 
            +
                    self.remove_old_backups
         | 
| 91 | 
            +
                    backup_filename = "#{@filenames[:backup]}#{self.next_backup_seq_number}"
         | 
| 92 | 
            +
                  else
         | 
| 93 | 
            +
                    backup_filename = "#{@filenames[:backup]}.#{backup_name}"
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                  FileUtils.cp(@filenames[:dat], backup_filename)
         | 
| 96 | 
            +
                  @filenames[:backups] = self.get_backup_file_list
         | 
| 97 | 
            +
                  Rbcli.log.info "Backed up savefile to #{backup_filename}", 'SAVEGAME'
         | 
| 98 | 
            +
                  backup_filename
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                def direct_restore(seq_number: nil, backup_name: nil)
         | 
| 102 | 
            +
                  if seq_number.nil? && backup_name.nil?
         | 
| 103 | 
            +
                    all_backup_files = self.get_backup_file_list
         | 
| 104 | 
            +
                    return false if all_backup_files.empty?
         | 
| 105 | 
            +
                    selected_file = all_backup_files.last
         | 
| 106 | 
            +
                  elsif !seq_number.nil?
         | 
| 107 | 
            +
                    all_seq_numbers = self.get_backup_file_list.map { |f| File.extname(f).scan(/\d+$/).first }.reject(&:nil?).map { |n| n.to_i }
         | 
| 108 | 
            +
                    return false unless all_seq_numbers.include?(seq_number)
         | 
| 109 | 
            +
                    selected_file = "#{@filenames[:backup]}#{seq_number}"
         | 
| 110 | 
            +
                  elsif !backup_name.nil?
         | 
| 111 | 
            +
                    return false unless File.exist?("#{@filenames[:backup]}.#{backup_name}")
         | 
| 112 | 
            +
                    selected_file = "#{@filenames[:backup]}.#{backup_name}"
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                  FileUtils.cp(selected_file, @filenames[:dat])
         | 
| 115 | 
            +
                  Rbcli.log.info "Restored savefile from #{selected_file}", 'SAVEGAME'
         | 
| 116 | 
            +
                  selected_file
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                private
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                def load_from_disk(filename)
         | 
| 122 | 
            +
                  data = File.read(filename)
         | 
| 123 | 
            +
                  data = Packer.unpack(data) if Packer.can_unpack?(data)
         | 
| 124 | 
            +
                  Rbcli.log.info "Loaded from #{File.basename(filename)}", 'SAVEGAME'
         | 
| 125 | 
            +
                  @data = YAML.safe_load(data)
         | 
| 126 | 
            +
                  # @data = JSON.parse(data)
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                def save_to_disk(filename, pack: false)
         | 
| 130 | 
            +
                  data = (@use_yaml && !pack) ? YAML.safe_dump(@data) : JSON.pretty_generate(@data)
         | 
| 131 | 
            +
                  data = Packer.pack(data) if pack
         | 
| 132 | 
            +
                  File.write(filename, data)
         | 
| 133 | 
            +
                  Rbcli.log.info "Saved to #{File.basename(filename)}", 'SAVEGAME'
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def get_backup_file_list
         | 
| 137 | 
            +
                  Dir.glob("#{@filenames[:backup]}*").select { |f| f.match?(/#{File.basename(@filenames[:backup])}\d+$/) }.sort_by { |f| File.extname(f).scan(/\d+$/).first.to_i }
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                def remove_old_backups
         | 
| 141 | 
            +
                  all_backup_files = self.get_backup_file_list
         | 
| 142 | 
            +
                  while !all_backup_files.empty? && all_backup_files.length >= @max_backups
         | 
| 143 | 
            +
                    FileUtils.rm(all_backup_files.shift)
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                def next_backup_seq_number
         | 
| 148 | 
            +
                  all_backup_files = self.get_backup_file_list
         | 
| 149 | 
            +
                  return 0 if all_backup_files.empty?
         | 
| 150 | 
            +
                  all_backup_files.map { |f| File.extname(f).scan(/\d+$/).first }.reject(&:nil?).map { |n| n.to_i }.max + 1
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
              end
         | 
| 153 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            require 'io/console'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Silkedit::Sys
         | 
| 4 | 
            +
              def self.os
         | 
| 5 | 
            +
                case RbConfig::CONFIG['host_os'].to_s
         | 
| 6 | 
            +
                when /mswin|msys|mingw|cygwin|bccwin|wince|windows/i
         | 
| 7 | 
            +
                  :windows
         | 
| 8 | 
            +
                when /darwin|mac os|macos|osx/i
         | 
| 9 | 
            +
                  :macos
         | 
| 10 | 
            +
                when /linux/i
         | 
| 11 | 
            +
                  :linux
         | 
| 12 | 
            +
                else
         | 
| 13 | 
            +
                  nil
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def self.tmpdir
         | 
| 18 | 
            +
                case Silkedit::Sys.os
         | 
| 19 | 
            +
                when :windows
         | 
| 20 | 
            +
                  File.join(ENV['TEMP'] || ENV['TMP'], 'silkedit')
         | 
| 21 | 
            +
                when :macos
         | 
| 22 | 
            +
                  File.join(ENV['TMPDIR'], 'silkedit')
         | 
| 23 | 
            +
                when :linux
         | 
| 24 | 
            +
                  File.join(ENV['TMPDIR'], 'silkedit')
         | 
| 25 | 
            +
                else
         | 
| 26 | 
            +
                  Dir.tmpdir
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def self.yes_no? question, &block
         | 
| 31 | 
            +
                print "#{question} (y/n): "
         | 
| 32 | 
            +
                answer = STDIN.getch.chomp.downcase until %w[y n].include?(answer)
         | 
| 33 | 
            +
                print "#{answer}\n"
         | 
| 34 | 
            +
                yield if block_given? && answer == 'y'
         | 
| 35 | 
            +
                return answer == 'y'
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
    
        data/lib/silkedit.rb
    ADDED
    
    
    
        data/sig/silkedit.rbs
    ADDED