From e2c19a6ef8efeb7a9507e93d34a74a535655555a Mon Sep 17 00:00:00 2001 From: Bryan Date: Tue, 30 Dec 2025 23:20:54 -0600 Subject: [PATCH 1/2] Add support for Schedule I This adds support for Schedule I for most features that are possible currently. I've tested will a number of mods, both new and very old, to validate it mostly handles the random packagings available on Nexus; a validator might be needed if the community doesn't mature around a sensible structure. --- games/game_schedule1.py | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 games/game_schedule1.py diff --git a/games/game_schedule1.py b/games/game_schedule1.py new file mode 100644 index 0000000..5813f20 --- /dev/null +++ b/games/game_schedule1.py @@ -0,0 +1,103 @@ +import mobase +from PyQt6.QtCore import QDir + +from pathlib import Path +import json + +from ..basic_features import BasicLocalSavegames, BasicModDataChecker, GlobPatterns +from ..basic_features.basic_save_game_info import BasicGameSaveGame, BasicGameSaveGameInfo +from ..basic_game import BasicGame + +def parse_schedule1_save_metadata(save_path: Path, save: mobase.ISaveGame): + metadata_file = save_path / "Game.json" + try: + with open(metadata_file) as file: + meta_data = json.load(file) + name = meta_data["OrganisationName"] + if name != (save_name := save.getName()): + name = f"{save_name} ({name})" + return { + "Name": name, + "Game version": meta_data["GameVersion"], + } + except (FileNotFoundError, json.JSONDecodeError): + return None + +class Schedule1SaveGame(BasicGameSaveGame): + def getName(self) -> str: + metadata_file = self._filepath / "Game.json" + try: + with open(metadata_file) as file: + meta_data = json.load(file) + return meta_data["OrganisationName"] + except (FileNotFoundError, json.JSONDecodeError): + return f"[{self.getSaveGroupIdentifier().rstrip('s')}] {self._filepath.stem}" + + def getSaveGroupIdentifier(self) -> str: + return self._filepath.parent.name + +class Schedule1Game(BasicGame): + Name = "Schedule I Support Plugin" + Author = "shellbj" + Version = "1.0.0" + + GameName = "Schedule I" + GameShortName = "scheduleI" + GameNexusName = "schedule1" + GameNexusId = 7381 + GameSteamId = 3164500 + + GameBinary = "Schedule I.exe" + GameValidShortNames = ["schedule1", "scheduleI"] + GameDataPath = "" + GameSavesDirectory = r"%USERPROFILE%/AppData/LocalLow/TVGS/Schedule I/Saves" + GameSupportURL = ( + r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/" + "Game:-Schedule-I" + ) + + def init(self, organizer: mobase.IOrganizer) -> bool: + super().init(organizer) + self._register_feature( + BasicModDataChecker( + GlobPatterns( + unfold=[ + # Fixes bad packaging for select mods + "ModManager&PhoneApp", + "AutoClearCompletedDeals", + ], + valid=[ + "meta.ini", # Included in installed mod folder. + "Mods", + "Plugins", + "UserData", # might not be needed + ], + delete=[ + "*.md", + "icon.png", + "fomod", # not sure this is needed either + ], + move={ + "*.dll": "Mods/", + # If these even exisit outside of mod packs + "*.ini": "UserData/", + "*.cfg": "UserData/", + "*.json": "UserData/", + }, + ) + ) + ) + self._register_feature(BasicLocalSavegames(self.savesDirectory())) + self._register_feature( + BasicGameSaveGameInfo( + None, # no snapshot to add to the widget + parse_schedule1_save_metadata, + ) + ) + return True + + def listSaves(self, folder: QDir) -> list[mobase.ISaveGame]: + return [ + Schedule1SaveGame(path) + for path in Path(folder.absolutePath()).glob("*/SaveGame_[1-5]") + ] From 254329095697197960c715802e135a5ceaf2a620 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 31 Dec 2025 05:25:59 +0000 Subject: [PATCH 2/2] [pre-commit.ci] Auto fixes from pre-commit.com hooks. --- games/game_schedule1.py | 215 +++++++++++++++++++++------------------- 1 file changed, 112 insertions(+), 103 deletions(-) diff --git a/games/game_schedule1.py b/games/game_schedule1.py index 5813f20..2c0193d 100644 --- a/games/game_schedule1.py +++ b/games/game_schedule1.py @@ -1,103 +1,112 @@ -import mobase -from PyQt6.QtCore import QDir - -from pathlib import Path -import json - -from ..basic_features import BasicLocalSavegames, BasicModDataChecker, GlobPatterns -from ..basic_features.basic_save_game_info import BasicGameSaveGame, BasicGameSaveGameInfo -from ..basic_game import BasicGame - -def parse_schedule1_save_metadata(save_path: Path, save: mobase.ISaveGame): - metadata_file = save_path / "Game.json" - try: - with open(metadata_file) as file: - meta_data = json.load(file) - name = meta_data["OrganisationName"] - if name != (save_name := save.getName()): - name = f"{save_name} ({name})" - return { - "Name": name, - "Game version": meta_data["GameVersion"], - } - except (FileNotFoundError, json.JSONDecodeError): - return None - -class Schedule1SaveGame(BasicGameSaveGame): - def getName(self) -> str: - metadata_file = self._filepath / "Game.json" - try: - with open(metadata_file) as file: - meta_data = json.load(file) - return meta_data["OrganisationName"] - except (FileNotFoundError, json.JSONDecodeError): - return f"[{self.getSaveGroupIdentifier().rstrip('s')}] {self._filepath.stem}" - - def getSaveGroupIdentifier(self) -> str: - return self._filepath.parent.name - -class Schedule1Game(BasicGame): - Name = "Schedule I Support Plugin" - Author = "shellbj" - Version = "1.0.0" - - GameName = "Schedule I" - GameShortName = "scheduleI" - GameNexusName = "schedule1" - GameNexusId = 7381 - GameSteamId = 3164500 - - GameBinary = "Schedule I.exe" - GameValidShortNames = ["schedule1", "scheduleI"] - GameDataPath = "" - GameSavesDirectory = r"%USERPROFILE%/AppData/LocalLow/TVGS/Schedule I/Saves" - GameSupportURL = ( - r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/" - "Game:-Schedule-I" - ) - - def init(self, organizer: mobase.IOrganizer) -> bool: - super().init(organizer) - self._register_feature( - BasicModDataChecker( - GlobPatterns( - unfold=[ - # Fixes bad packaging for select mods - "ModManager&PhoneApp", - "AutoClearCompletedDeals", - ], - valid=[ - "meta.ini", # Included in installed mod folder. - "Mods", - "Plugins", - "UserData", # might not be needed - ], - delete=[ - "*.md", - "icon.png", - "fomod", # not sure this is needed either - ], - move={ - "*.dll": "Mods/", - # If these even exisit outside of mod packs - "*.ini": "UserData/", - "*.cfg": "UserData/", - "*.json": "UserData/", - }, - ) - ) - ) - self._register_feature(BasicLocalSavegames(self.savesDirectory())) - self._register_feature( - BasicGameSaveGameInfo( - None, # no snapshot to add to the widget - parse_schedule1_save_metadata, - ) - ) - return True - - def listSaves(self, folder: QDir) -> list[mobase.ISaveGame]: - return [ - Schedule1SaveGame(path) - for path in Path(folder.absolutePath()).glob("*/SaveGame_[1-5]") - ] +import json +from pathlib import Path + +from PyQt6.QtCore import QDir + +import mobase + +from ..basic_features import BasicLocalSavegames, BasicModDataChecker, GlobPatterns +from ..basic_features.basic_save_game_info import ( + BasicGameSaveGame, + BasicGameSaveGameInfo, +) +from ..basic_game import BasicGame + + +def parse_schedule1_save_metadata(save_path: Path, save: mobase.ISaveGame): + metadata_file = save_path / "Game.json" + try: + with open(metadata_file) as file: + meta_data = json.load(file) + name = meta_data["OrganisationName"] + if name != (save_name := save.getName()): + name = f"{save_name} ({name})" + return { + "Name": name, + "Game version": meta_data["GameVersion"], + } + except (FileNotFoundError, json.JSONDecodeError): + return None + + +class Schedule1SaveGame(BasicGameSaveGame): + def getName(self) -> str: + metadata_file = self._filepath / "Game.json" + try: + with open(metadata_file) as file: + meta_data = json.load(file) + return meta_data["OrganisationName"] + except (FileNotFoundError, json.JSONDecodeError): + return ( + f"[{self.getSaveGroupIdentifier().rstrip('s')}] {self._filepath.stem}" + ) + + def getSaveGroupIdentifier(self) -> str: + return self._filepath.parent.name + + +class Schedule1Game(BasicGame): + Name = "Schedule I Support Plugin" + Author = "shellbj" + Version = "1.0.0" + + GameName = "Schedule I" + GameShortName = "scheduleI" + GameNexusName = "schedule1" + GameNexusId = 7381 + GameSteamId = 3164500 + + GameBinary = "Schedule I.exe" + GameValidShortNames = ["schedule1", "scheduleI"] + GameDataPath = "" + GameSavesDirectory = r"%USERPROFILE%/AppData/LocalLow/TVGS/Schedule I/Saves" + GameSupportURL = ( + r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/" + "Game:-Schedule-I" + ) + + def init(self, organizer: mobase.IOrganizer) -> bool: + super().init(organizer) + self._register_feature( + BasicModDataChecker( + GlobPatterns( + unfold=[ + # Fixes bad packaging for select mods + "ModManager&PhoneApp", + "AutoClearCompletedDeals", + ], + valid=[ + "meta.ini", # Included in installed mod folder. + "Mods", + "Plugins", + "UserData", # might not be needed + ], + delete=[ + "*.md", + "icon.png", + "fomod", # not sure this is needed either + ], + move={ + "*.dll": "Mods/", + # If these even exisit outside of mod packs + "*.ini": "UserData/", + "*.cfg": "UserData/", + "*.json": "UserData/", + }, + ) + ) + ) + self._register_feature(BasicLocalSavegames(self.savesDirectory())) + self._register_feature( + BasicGameSaveGameInfo( + None, # no snapshot to add to the widget + parse_schedule1_save_metadata, + ) + ) + return True + + def listSaves(self, folder: QDir) -> list[mobase.ISaveGame]: + return [ + Schedule1SaveGame(path) + for path in Path(folder.absolutePath()).glob("*/SaveGame_[1-5]") + ]