Skip to content

Commit 3fbeff6

Browse files
committed
Fixes for bug and edgecases. Rewrote Zuma to use compliant XML.
1 parent 951adf5 commit 3fbeff6

10 files changed

Lines changed: 564 additions & 403 deletions

games/game_cassettebeasts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def __init__(self, filepath: Path):
9494
with open(filepath, "rb") as infile:
9595
infile.read(4)
9696

97-
blocksize, raw_size = struct.unpack("III", infile.read(12))
97+
_, blocksize, raw_size = struct.unpack("III", infile.read(12))
9898

9999
num_blocks = math.ceil(raw_size / blocksize)
100100

games/game_emuvr.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,30 @@ def fix(self, filetree: mobase.IFileTree) -> mobase.IFileTree | None:
2727
GameDataUGCMods = (
2828
getattr(self.organizer.managedGame(), "GameDataUGCMods", "") + "/"
2929
)
30-
for branch in filetree:
31-
mod_name = filetree.name()
32-
if mod_name == "":
33-
mod_name = branch.name()
34-
mod_path = os.path.join(self.organizer.modsPath(), mod_name)
35-
if (
36-
not filetree.createOrphanTree("OrphanTree")
37-
and os.path.exists(mod_path)
38-
and branch.suffix().casefold() == "ugc"
39-
):
40-
os.makedirs(os.path.join(mod_path, GameDataUGCMods), exist_ok=True)
41-
shutil.move(
42-
os.path.join(mod_path, branch.name()),
43-
os.path.join(mod_path, GameDataUGCMods, branch.name()),
44-
)
45-
else:
30+
31+
# If the tree has no name, MO2 is working with a normal virtual tree and
32+
# entries can be moved virtually. Non-zipped installer paths set a tree name,
33+
# so those files need to be moved on disk instead.
34+
if filetree.name() == "":
35+
for branch in list(filetree):
4636
if isinstance(branch, mobase.IFileTree):
47-
for e in branch:
37+
for e in list(branch):
4838
if e.isFile() and e.suffix().casefold() == "ugc":
4939
filetree.move(e, GameDataUGCMods, mobase.IFileTree.MERGE)
50-
elif branch.suffix().casefold() == "ugc":
40+
elif branch.isFile() and branch.suffix().casefold() == "ugc":
5141
filetree.move(branch, GameDataUGCMods, mobase.IFileTree.MERGE)
42+
else:
43+
mod_name = filetree.name()
44+
mod_path = os.path.join(self.organizer.modsPath(), mod_name)
45+
target_dir = os.path.join(mod_path, GameDataUGCMods)
46+
47+
for branch in list(filetree):
48+
if branch.isFile() and branch.suffix().casefold() == "ugc":
49+
os.makedirs(target_dir, exist_ok=True)
50+
src = os.path.join(mod_path, branch.name())
51+
dst = os.path.join(target_dir, branch.name())
52+
shutil.move(src, dst)
53+
5254
return filetree
5355

5456

@@ -126,7 +128,9 @@ def executableForcedLoads(self) -> list[mobase.ExecutableForcedLoadSetting]:
126128
return efls
127129

128130
def initializeProfile(self, directory: QDir, settings: mobase.ProfileSetting):
129-
modsPath = self.dataDirectory().absolutePath()
130-
if not os.path.exists(modsPath):
131-
os.mkdir(modsPath)
131+
modsPath = os.path.join(
132+
self.dataDirectory().absolutePath(),
133+
self.GameDataUGCMods,
134+
)
135+
os.makedirs(modsPath, exist_ok=True)
132136
super().initializeProfile(directory, settings)

games/game_hitman3.py

Lines changed: 124 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,33 @@ def moveOverwriteMerge(self, source: str, destination: str):
3131
self.moveOverwriteMerge(s_item, d_item)
3232
os.rmdir(source)
3333

34+
def readManifestId(self, manifest_path: str) -> str | None:
35+
try:
36+
with open(manifest_path, encoding="utf-8") as manifest_file:
37+
mod_data = json.load(manifest_file)
38+
except (OSError, json.JSONDecodeError):
39+
return None
40+
41+
mod_id = mod_data.get("id")
42+
if isinstance(mod_id, str) and mod_id:
43+
return mod_id
44+
return None
45+
3446
def fixInstalledMod(self, mod: mobase.IModInterface):
3547
if not self.needsNameFix:
3648
return
3749
GameSMMPath = getattr(self.organizer.managedGame(), "GameSMMPath", "")
3850
filetree: mobase.IFileTree = mod.fileTree()
3951
fixed = False
40-
if filetree.exists(
41-
GameSMMPath + "/Mods/FOLDERNAME", mobase.IFileTree.DIRECTORY
42-
):
52+
foldername_path = GameSMMPath + "/Mods/FOLDERNAME"
53+
if filetree.exists(foldername_path, mobase.IFileTree.DIRECTORY):
4354
path = mod.absolutePath()
44-
json_path = os.path.join(
45-
path, GameSMMPath + "/Mods/FOLDERNAME/manifest.json"
46-
)
47-
mod_data = json.load(open(json_path, encoding="utf-8"))
48-
modname = mod_data["id"]
49-
old_path = os.path.join(path, GameSMMPath + "/Mods/FOLDERNAME")
50-
new_path = os.path.join(path, GameSMMPath + f"/Mods/{modname}")
55+
json_path = os.path.join(path, foldername_path, "manifest.json")
56+
modname = self.readManifestId(json_path)
57+
if not modname:
58+
return
59+
old_path = os.path.join(path, foldername_path)
60+
new_path = os.path.join(path, GameSMMPath, "Mods", modname)
5161
self.moveOverwriteMerge(old_path, new_path)
5262
fixed = True
5363
if not fixed:
@@ -57,30 +67,26 @@ def fixInstalledMod(self, mod: mobase.IModInterface):
5767
def dataLooksValid(
5868
self, filetree: mobase.IFileTree
5969
) -> mobase.ModDataChecker.CheckReturn:
70+
validList = {"simple mod framework"}
71+
for e in filetree:
72+
if isinstance(e, mobase.IFileTree) and e.isDir():
73+
if e.name().casefold() not in validList:
74+
return mobase.ModDataChecker.FIXABLE
6075
if filetree.exists("Simple Mod Framework", mobase.IFileTree.DIRECTORY):
6176
return mobase.ModDataChecker.VALID
6277
return mobase.ModDataChecker.FIXABLE
6378

64-
def fileExistsInNextSubDir(self, filetree: mobase.IFileTree, name: str):
65-
for branch in filetree:
66-
if isinstance(branch, mobase.IFileTree):
67-
for e in branch:
68-
if e.name() == name:
69-
return True
70-
return False
71-
7279
def allMoveTo(
7380
self, sourcetree: mobase.IFileTree, targettree: mobase.IFileTree, toMoveTo: str
74-
):
81+
) -> bool:
7582
entriesToMove: list[mobase.FileTreeEntry] = []
76-
retVal = 0
7783
for e in sourcetree:
7884
entriesToMove.append(e)
7985
for e in entriesToMove:
8086
targettree.move(e, toMoveTo, mobase.IFileTree.MERGE)
81-
retVal = 1
82-
targettree.remove(sourcetree)
83-
return retVal
87+
if sourcetree is not targettree:
88+
targettree.remove(sourcetree)
89+
return bool(entriesToMove)
8490

8591
def firstTree(self, filetree: mobase.IFileTree) -> mobase.IFileTree | None:
8692
for e in filetree:
@@ -90,22 +96,28 @@ def firstTree(self, filetree: mobase.IFileTree) -> mobase.IFileTree | None:
9096

9197
def fix(self, filetree: mobase.IFileTree) -> mobase.IFileTree | None:
9298
GameSMMPath = getattr(self.organizer.managedGame(), "GameSMMPath", "")
93-
treefixed = 0
99+
94100
if filetree.exists("manifest.json", mobase.IFileTree.FILE):
95-
treefixed = self.allMoveTo(
96-
filetree, filetree, GameSMMPath + "/Mods/FOLDERNAME/"
97-
)
98-
if treefixed == 1:
101+
if self.allMoveTo(
102+
filetree,
103+
filetree,
104+
GameSMMPath + "/Mods/FOLDERNAME/",
105+
):
99106
self.needsNameFix = True
107+
100108
elif len(filetree) == 1:
101-
firsttreelayer: mobase.IFileTree | None = self.firstTree(filetree)
102-
if firsttreelayer is not None:
103-
if firsttreelayer.exists("manifest.json", mobase.IFileTree.FILE):
104-
treefixed = self.allMoveTo(
105-
firsttreelayer, filetree, GameSMMPath + "/Mods/FOLDERNAME/"
106-
)
107-
if treefixed == 1:
108-
self.needsNameFix = True
109+
firsttreelayer = self.firstTree(filetree)
110+
111+
if firsttreelayer is not None and firsttreelayer.exists(
112+
"manifest.json", mobase.IFileTree.FILE
113+
):
114+
if self.allMoveTo(
115+
firsttreelayer,
116+
filetree,
117+
GameSMMPath + "/Mods/FOLDERNAME/",
118+
):
119+
self.needsNameFix = True
120+
109121
return filetree
110122

111123

@@ -129,72 +141,84 @@ def init(self, organizer: mobase.IOrganizer) -> bool:
129141

130142
def updateSmmMeta(self, mods: dict[str, mobase.ModState]):
131143
SMM_Path = os.path.join(self.dataDirectory().absolutePath(), self.GameSMMPath)
132-
SMM_Config_Json = SMM_Path + "/config.json"
144+
SMM_Config_Json = os.path.join(SMM_Path, "config.json")
145+
146+
if not os.path.exists(SMM_Config_Json):
147+
return None
148+
133149
for key, value in mods.items():
134-
key = self._organizer.modList().getMod(key)
135-
tree = key.fileTree()
150+
mod = self._organizer.modList().getMod(key)
151+
tree = mod.fileTree()
136152
subtree = tree.find(self.GameSMMPath + "/Mods", mobase.IFileTree.DIRECTORY)
137-
if isinstance(subtree, mobase.IFileTree):
138-
for e in subtree:
139-
if isinstance(e, mobase.IFileTree):
140-
if e.exists("manifest.json", mobase.IFileTree.FILE):
141-
json_path = (
142-
key.absolutePath() + "/" + e.path() + "/manifest.json"
143-
)
144-
mod_data = json.load(open(json_path, encoding="utf-8"))
145-
modname = mod_data["id"]
146-
if value == 35:
147-
with open(SMM_Config_Json, "r") as config_json:
148-
config_json_content = config_json.read()
149-
config_json.close()
150-
good_code = '"knownMods": []'
151-
if good_code in config_json_content:
152-
bad_code = "{runtimePath:'..\\Runtime',retailPath:'..\\Retail',skipIntro:false,outputToSeparateDirectory:false,loadOrder:[''],modOptions:{},outputConfigToAppDataOnDeploy:true,knownMods:[''],developerMode:false,reportErrors:false}"
153-
config_json_content = bad_code
154-
if modname not in config_json_content:
155-
substr = "knownMods:["
156-
config_json_content = config_json_content.replace(
157-
substr, substr + "'" + modname + "',"
158-
)
159-
substr = "loadOrder:["
160-
config_json_content = config_json_content.replace(
161-
substr, substr + "'" + modname + "',"
162-
)
163-
substr = ",],modOptions"
164-
config_json_content = config_json_content.replace(
165-
substr, "],modOptions"
166-
)
167-
substr = ",],developer"
168-
config_json_content = config_json_content.replace(
169-
substr, "],developer"
170-
)
171-
with open(SMM_Config_Json, "w") as config_json:
172-
config_json.write(config_json_content)
173-
config_json.close()
174-
return None
175-
if value == 33:
176-
with open(SMM_Config_Json, "r") as config_json:
177-
config_json_content = config_json.read()
178-
config_json.close()
179-
if modname in config_json_content:
180-
config_json_content = config_json_content.replace(
181-
"'" + modname + "',", ""
182-
)
183-
config_json_content = config_json_content.replace(
184-
",,", ","
185-
)
186-
substr = ",],modOptions"
187-
config_json_content = config_json_content.replace(
188-
substr, "],modOptions"
189-
)
190-
substr = ",],developer"
191-
config_json_content = config_json_content.replace(
192-
substr, "],developer"
193-
)
194-
with open(SMM_Config_Json, "w") as config_json:
195-
config_json.write(config_json_content)
196-
config_json.close()
197-
return None
153+
if not isinstance(subtree, mobase.IFileTree):
154+
continue
155+
156+
for e in subtree:
157+
if not isinstance(e, mobase.IFileTree):
158+
continue
159+
if not e.exists("manifest.json", mobase.IFileTree.FILE):
160+
continue
161+
162+
json_path = os.path.join(mod.absolutePath(), e.path(), "manifest.json")
163+
try:
164+
with open(json_path, encoding="utf-8") as manifest_file:
165+
mod_data = json.load(manifest_file)
166+
except (OSError, json.JSONDecodeError):
167+
continue
168+
169+
modname = mod_data.get("id")
170+
if not isinstance(modname, str) or not modname:
171+
continue
172+
173+
try:
174+
with open(SMM_Config_Json, "r", encoding="utf-8") as config_json:
175+
config_json_content = config_json.read()
176+
except OSError:
177+
return None
178+
179+
good_code = '"knownMods": []'
180+
if good_code in config_json_content:
181+
config_json_content = "{runtimePath:'..\\Runtime',retailPath:'..\\Retail',skipIntro:false,outputToSeparateDirectory:false,loadOrder:[''],modOptions:{},outputConfigToAppDataOnDeploy:true,knownMods:[''],developerMode:false,reportErrors:false}"
182+
183+
quoted_modname = "'" + modname + "'"
184+
changed = False
185+
if value == mobase.ModState.ACTIVE:
186+
if quoted_modname not in config_json_content:
187+
substr = "knownMods:["
188+
config_json_content = config_json_content.replace(
189+
substr, substr + quoted_modname + ","
190+
)
191+
substr = "loadOrder:["
192+
config_json_content = config_json_content.replace(
193+
substr, substr + quoted_modname + ","
194+
)
195+
changed = True
196+
else:
197+
old_content = config_json_content
198+
config_json_content = config_json_content.replace(
199+
quoted_modname + ",", ""
200+
)
201+
config_json_content = config_json_content.replace(
202+
"," + quoted_modname, ""
203+
)
204+
changed = config_json_content != old_content
205+
206+
if changed:
207+
config_json_content = config_json_content.replace(",,", ",")
208+
config_json_content = config_json_content.replace(
209+
",],modOptions", "],modOptions"
210+
)
211+
config_json_content = config_json_content.replace(
212+
",],developer", "],developer"
213+
)
214+
try:
215+
with open(
216+
SMM_Config_Json, "w", encoding="utf-8"
217+
) as config_json:
218+
config_json.write(config_json_content)
219+
except OSError:
220+
return None
221+
return None
198222

199223
def executables(self):
200224
return [

0 commit comments

Comments
 (0)