Skip to content

Commit dd51459

Browse files
authored
Added Save Error Handling, Additional Save Meta and Local Save Fix
Authored by Cuteness with small edits by Floofytsuna
1 parent 224c6a0 commit dd51459

1 file changed

Lines changed: 88 additions & 39 deletions

File tree

games/game_cassettebeasts.py

Lines changed: 88 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
from collections.abc import Mapping
2-
from io import BytesIO
31
import json
4-
import mobase
52
import math
63
import os
74
import shutil
85
import struct
9-
import sys
106
import zlib
7+
import mobase
118

12-
from pathlib import Path
9+
from collections.abc import Mapping, Sequence
10+
from datetime import datetime
1311
from functools import cached_property
12+
from io import BytesIO
13+
from typing import Any, Optional
14+
from pathlib import Path
1415

1516
from ..basic_features import BasicLocalSavegames
1617
from ..basic_features.basic_save_game_info import (BasicGameSaveGame,BasicGameSaveGameInfo)
@@ -19,6 +20,13 @@
1920
from PyQt6.QtCore import QDateTime, QDir, QFile, QFileInfo
2021

2122

23+
def json_get_me(value: Any, path: Sequence[str | int], /, default: Any) -> Any:
24+
for part in path:
25+
if type(part) not in (str, int) or type(value) not in (dict, list):
26+
return default
27+
value = value[part]
28+
return value
29+
2230
class CassetteBeastsModDataChecker(mobase.ModDataChecker):
2331
def __init__(self, organizer: mobase.IOrganizer):
2432
super().__init__()
@@ -64,72 +72,113 @@ def __init__(self):
6472
class CassetteBeastsSaveGame(BasicGameSaveGame):
6573
def __init__(self, filepath: Path):
6674
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()
75+
self.name: str = "(unknown)"
76+
self.cheated: str = "(unknown)"
77+
self.lastsave: str = "(unknown)"
78+
self.elapsed: str = "(unknown)"
79+
# This doesn't state wether the game would load it,
80+
# only if the data was properly parsed.
81+
self.errorMessage: str = ""
82+
83+
save_data = None
84+
try:
85+
info = bytearray()
86+
data = bytes()
87+
with open(filepath, 'rb') as infile:
88+
magic_string = infile.read(4)
7389

74-
with open(filepath, 'rb') as infile:
75-
magic_string = infile.read(4)
90+
compression_mode, blocksize, raw_size = struct.unpack("III", infile.read(12))
7691

77-
compression_mode, blocksize, raw_size = struct.unpack("III", infile.read(12))
92+
num_blocks = math.ceil(raw_size / blocksize)
7893

79-
num_blocks = math.ceil(raw_size / blocksize)
94+
blocks = []
8095

81-
blocks = []
96+
for bnum in range(num_blocks):
97+
block = CassetteBlock()
98+
block.compressed_size = struct.unpack("I", infile.read(4))[0]
99+
blocks.append(block)
82100

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)
101+
for block in blocks:
102+
block.data = infile.read(block.compressed_size)
87103

104+
magic_string = infile.read(4)
105+
infile.close()
88106
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"]
107+
data = zlib.decompress(block.data, wbits=40, bufsize=blocksize)
108+
info = info + data
109+
save_data = json.load(BytesIO(info))
110+
except (OSError, struct.error, ValueError) as err:
111+
s = str(err)
112+
self.errorMessage = ('{0}: {1}' if s else '{0}').format(
113+
err.__class__.__name__, s
114+
)
115+
return
116+
x = json_get_me(save_data, ["party", "player", "custom", "name"], None)
117+
if type(x) is str:
118+
self.name = x
119+
x = json_get_me(save_data, ["saved_datetime"], None)
120+
if type(x) in (int, float):
121+
try:
122+
dt = datetime.fromtimestamp(float(x))
123+
except OSError:
124+
pass
125+
else:
126+
self.lastsave = "{0:d}-{1:02d}-{2:02d} at {3:02d}:{4:02d}:{5:02d}".format(
127+
dt.year, dt.month, dt.day,
128+
dt.hour, dt.minute, dt.second
129+
)
130+
x = json_get_me(save_data, ["play_time"], None)
131+
if type(x) in (int, float):
132+
a = [ 0, 0, 0, int(x * 10) ]
133+
a[2:4] = divmod(a[3], 10)
134+
a[1:3] = divmod(a[2], 60)
135+
a[0:2] = divmod(a[1], 60)
136+
self.elapsed = "{0:02d}:{1:02d}:{2:02d}.{3:01d}".format(*a)
137+
x = json_get_me(save_data, ["has_cheated"], None)
138+
if type(x) is bool:
139+
self.cheated = "Yes" if x else "No"
101140

102141
def getName(self) -> str:
103142
return self.name
104143

105144
def getCheated(self) -> str:
106145
return self.cheated
107146

147+
def getLastSaved(self) -> str:
148+
return self.lastsave
149+
150+
def getPlayTime(self) -> str:
151+
return self.elapsed
152+
108153
def getMetadata(p: Path, save: mobase.ISaveGame) -> Mapping[str, str]:
154+
if not save.errorMessage:
155+
return {
156+
"Character": save.getName(),
157+
"Last Saved": save.getLastSaved(),
158+
"Play Time": save.getPlayTime(),
159+
"Cheated": save.getCheated()
160+
}
109161
return {
110-
"Character": save.getName(),
111-
"Cheated": save.getCheated()
162+
"Error loading file:": save.errorMessage
112163
}
113164

114165
class CassetteBeastsGame(BasicGame):
115-
appdataenv = os.getenv("APPDATA")
116-
117166
Name = "Cassette Beasts Support Plugin"
118167
Author = "modworkshop"
119168
Version = "1"
120169
GameName = "Cassette Beasts"
121170
GameShortName = "cassette-beasts"
122171
GameSteamId = 1321440
123172
GameBinary = "CassetteBeasts.exe"
124-
GameDataPath = appdataenv + "/CassetteBeasts/mods"
125-
GameDocumentsDirectory = appdataenv + "/CassetteBeasts"
173+
GameDataPath = os.getenv("APPDATA") + "/CassetteBeasts/mods"
174+
GameDocumentsDirectory = os.getenv("APPDATA") + "/CassetteBeasts"
126175
GameSaveExtension = "gcpf"
127176

128177
def init(self, organizer: mobase.IOrganizer) -> bool:
129178
super().init(organizer)
130179
self.dataChecker = CassetteBeastsModDataChecker(organizer)
131180
self._register_feature(self.dataChecker)
132-
self._register_feature(BasicLocalSavegames(self))
181+
self._register_feature(BasicLocalSavegames(QDir(self.GameDocumentsDirectory)))
133182
self._register_feature(
134183
BasicGameSaveGameInfo(None, getMetadata)
135184
)

0 commit comments

Comments
 (0)