1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
from typing import Tuple
import mobase
from ..basic_features import BasicModDataChecker, GlobPatterns
from ..basic_game import BasicGame
class SilentHill2RemakeModDataChecker(BasicModDataChecker):
def __init__(self):
super().__init__(
GlobPatterns(
delete=[
"*.txt",
"*.md",
"manifest.json",
"icon.png",
],
)
)
self.mod_path = ["SHProto", "Content", "Paks", "~mod"]
self.mod_path_lower = [name.lower() for name in self.mod_path]
def _find_tree(
self, filetree: mobase.IFileTree
) -> Tuple[str | None, mobase.FileTreeEntry | None]:
"""
Search the given filetree for a directory name that matches any component
of self.mod_path (case-insensitive).
Returns:
(prefix, entry)
prefix: The missing part before the match (e.g. 'SHProto/Content/')
entry: The IFileTree entry that matched (e.g. the 'Paks' directory)
(None, None) if nothing matches.
"""
for entry in filetree:
if not entry.isDir():
continue
name_lower = entry.name().lower()
for i, component in enumerate(self.mod_path_lower):
if name_lower == component:
# Build the prefix string for everything *before* this match
prefix_parts = self.mod_path[:i]
prefix = "/".join(prefix_parts) + ("/" if prefix_parts else "")
return (prefix, entry)
# No matches found
return (None, None)
def dataLooksValid(
self, filetree: mobase.IFileTree
) -> mobase.ModDataChecker.CheckReturn:
# Check for fully valid layout
has_entry, _ = self._find_tree(filetree)
if has_entry is None:
# in this case we check to make sure there's a .pak file
for entry in filetree:
if entry.name().lower().endswith(".pak") and entry.isFile():
return mobase.ModDataChecker.FIXABLE
elif has_entry == "":
return mobase.ModDataChecker.VALID
else:
return mobase.ModDataChecker.FIXABLE
# Otherwise, not recognizable
return mobase.ModDataChecker.INVALID
def fix(self, filetree: mobase.IFileTree) -> mobase.IFileTree:
filetree = super().fix(filetree)
prefix, item = self._find_tree(filetree)
if prefix is None:
foundAPak = False
# Move all top-level items to BepInEx/plugins/
items_to_move = list(filetree)
for cur_item in items_to_move:
if cur_item.name().lower().endswith(".pak"):
foundAPak = True
filetree.move(cur_item, f"SHProto/Content/Paks/~mod/{cur_item.name()}")
# foundAPack MUST be true because if 'prefix' returned None then
# there must be a .pak file or dataLooksValid wouldn't have returned
# a FIXABLE. This is therefore just a sanity check
assert foundAPak
return filetree
elif prefix == "":
return filetree
else:
# if prefix is not None then item cannot be None
assert item is not None
filetree.move(item, f"{prefix}{item.name()}")
return filetree
class SilentHill2RemakeGame(BasicGame):
def init(self, organizer: mobase.IOrganizer) -> bool:
super().init(organizer)
self._register_feature(SilentHill2RemakeModDataChecker())
return True
Name = "Silent Hill 2 Remake Support Plugin"
Author = "HomerSimpleton Returns"
Version = "1.0"
GameName = "Silent Hill 2 Remake"
GameShortName = "silenthill2"
GameNexusName = "silenthill2"
GameBinary = "SHProto/Binaries/Win64/SHProto-Win64-Shipping.exe"
GameLauncher = "SHProto.exe"
GameDataPath = "%GAME_PATH%"
GameSupportURL = "https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/Game:-Silent-Hill-2-Remake"
GameGogId = [1225972913, 2051029707]
|