diff --git a/games/game_silenthill2remake.py b/games/game_silenthill2remake.py new file mode 100644 index 0000000..4683a5d --- /dev/null +++ b/games/game_silenthill2remake.py @@ -0,0 +1,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]