1+ from collections .abc import Mapping
2+ from io import BytesIO
3+ import json
4+ import mobase
5+ import math
16import os
27import shutil
3- import mobase
8+ import struct
9+ import sys
10+ import zlib
411
512from pathlib import Path
613from functools import cached_property
714
15+ from ..basic_features import BasicLocalSavegames
16+ from ..basic_features .basic_save_game_info import (BasicGameSaveGame ,BasicGameSaveGameInfo )
817from ..basic_game import BasicGame
918
10- from PyQt6 .QtCore import QDir , QFileInfo
19+ from PyQt6 .QtCore import QDateTime , QDir , QFile , QFileInfo
1120
1221
1322class CassetteBeastsModDataChecker (mobase .ModDataChecker ):
@@ -47,6 +56,60 @@ def fix(self, filetree: mobase.IFileTree) -> mobase.IFileTree:
4756 return None
4857 return filetree
4958
59+ class CassetteBlock :
60+ def __init__ (self ):
61+ compressed_size = None
62+ data = None
63+
64+ class CassetteBeastsSaveGame (BasicGameSaveGame ):
65+ def __init__ (self , filepath : Path ):
66+ super ().__init__ (filepath )
67+ self .name : str = ""
68+ self .cheated : str = ""
69+ self .lastsave : int = 0
70+ self .elapsed : int = 0
71+ info = bytearray ()
72+ data = bytes ()
73+
74+ with open (filepath , 'rb' ) as infile :
75+ magic_string = infile .read (4 )
76+
77+ compression_mode , blocksize , raw_size = struct .unpack ("III" , infile .read (12 ))
78+
79+ num_blocks = math .ceil (raw_size / blocksize )
80+
81+ blocks = []
82+
83+ for bnum in range (num_blocks ):
84+ block = CassetteBlock ()
85+ block .compressed_size = struct .unpack ("I" , infile .read (4 ))[0 ]
86+ blocks .append (block )
87+
88+ for block in blocks :
89+ block .data = infile .read (block .compressed_size )
90+
91+ magic_string = infile .read (4 )
92+ infile .close ()
93+
94+ for block in blocks :
95+ data = zlib .decompress (block .data , wbits = 40 , bufsize = blocksize )
96+ info = info + data
97+
98+ save_data = json .load (BytesIO (info ))
99+ self .name = save_data ["party" ]["player" ]["custom" ]["name" ]
100+ self .cheated = save_data ["has_cheated" ]
101+
102+ def getName (self ) -> str :
103+ return self .name
104+
105+ def getCheated (self ) -> str :
106+ return self .cheated
107+
108+ def getMetadata (p : Path , save : mobase .ISaveGame ) -> Mapping [str , str ]:
109+ return {
110+ "Character" : save .getName (),
111+ "Cheated" : save .getCheated ()
112+ }
50113
51114class CassetteBeastsGame (BasicGame ):
52115 appdataenv = os .getenv ("APPDATA" )
@@ -60,13 +123,16 @@ class CassetteBeastsGame(BasicGame):
60123 GameBinary = "CassetteBeasts.exe"
61124 GameDataPath = appdataenv + "/CassetteBeasts/mods"
62125 GameDocumentsDirectory = appdataenv + "/CassetteBeasts"
63- GameSavesDirectory = '%GAME_DOCUMENTS%'
64126 GameSaveExtension = "gcpf"
65127
66128 def init (self , organizer : mobase .IOrganizer ) -> bool :
67129 super ().init (organizer )
68130 self .dataChecker = CassetteBeastsModDataChecker (organizer )
69131 self ._register_feature (self .dataChecker )
132+ self ._register_feature (BasicLocalSavegames (self ))
133+ self ._register_feature (
134+ BasicGameSaveGameInfo (None , getMetadata )
135+ )
70136 return True
71137
72138 def executables (self ):
@@ -81,6 +147,13 @@ def executables(self):
81147 ),
82148 ]
83149
150+ def listSaves (self , folder : QDir ) -> list [mobase .ISaveGame ]:
151+ ext = self ._mappings .savegameExtension .get ()
152+ return [
153+ CassetteBeastsSaveGame (path )
154+ for path in Path (folder .absolutePath ()).glob (f"*.{ ext } " )
155+ ]
156+
84157 @cached_property
85158 def _base_dlls (self ) -> set [str ]:
86159 base_dir = Path (self .gameDirectory ().absolutePath ())
0 commit comments