diff --git a/games/game_subnautica-below-zero.py b/games/game_subnautica-below-zero.py index e87e6c0..5b9e3a7 100644 --- a/games/game_subnautica-below-zero.py +++ b/games/game_subnautica-below-zero.py @@ -9,14 +9,14 @@ class SubnauticaBelowZeroGame(game_subnautica.SubnauticaGame): Name = "Subnautica Below Zero Support Plugin" Author = "dekart811, Zash" - Version = "2.0" + Version = "2.1" GameName = "Subnautica: Below Zero" GameShortName = "subnauticabelowzero" GameNexusName = "subnauticabelowzero" GameSteamId = 848450 GameBinary = "SubnauticaZero.exe" - GameDataPath = "" + GameDataPath = "_ROOT" GameDocumentsDirectory = "%GAME_PATH%" GameSupportURL = ( r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/" diff --git a/games/game_subnautica.py b/games/game_subnautica.py index ad59300..33b75a7 100644 --- a/games/game_subnautica.py +++ b/games/game_subnautica.py @@ -1,9 +1,11 @@ from __future__ import annotations +from enum import Enum import os +from collections.abc import Iterable from pathlib import Path -from PyQt5.QtCore import QDir +from PyQt5.QtCore import QDir, qWarning import mobase @@ -30,11 +32,11 @@ class SubnauticaModDataChecker(BasicModDataChecker): } -class SubnauticaGame(BasicGame): +class SubnauticaGame(BasicGame, mobase.IPluginFileMapper): Name = "Subnautica Support Plugin" Author = "dekart811, Zash" - Version = "2.0" + Version = "2.1" GameName = "Subnautica" GameShortName = "subnautica" @@ -42,8 +44,8 @@ class SubnauticaGame(BasicGame): GameSteamId = 264710 GameEpicId = "Jaguar" GameBinary = "Subnautica.exe" - GameDataPath = "" - GameDocumentsDirectory = "%GAME_PATH%" + GameDataPath = "_ROOT" # Custom mappings to actual root folders below. + GameDocumentsDirectory = r"%GAME_PATH%" GameSupportURL = ( r"https://github.com/ModOrganizer2/modorganizer-basic_games/wiki/" "Game:-Subnautica" @@ -57,6 +59,25 @@ class SubnauticaGame(BasicGame): _forced_libraries = ["winhttp.dll"] + _root_blacklist = {GameDataPath.casefold()} + + class MapType(Enum): + FILE = 0 + FOLDER = 1 + + _root_extra_overwrites: dict[str, MapType] = { + "qmodmanager_log-Subnautica.txt": MapType.FILE, + "qmodmanager-config.json": MapType.FILE, + "BepInEx_Shim_Backup": MapType.FOLDER, + } + """Extra files & folders created in game root by mods / BepInEx after game launch, + but not included in the mod archives. + """ + + def __init__(self): + super().__init__() + mobase.IPluginFileMapper.__init__(self) + def init(self, organizer: mobase.IOrganizer) -> bool: super().init(organizer) self._featureMap[mobase.ModDataChecker] = SubnauticaModDataChecker() @@ -80,3 +101,75 @@ class SubnauticaGame(BasicGame): mobase.ExecutableForcedLoadSetting(self.binaryName(), lib).withEnabled(True) for lib in self._forced_libraries ] + + def mappings(self) -> list[mobase.Mapping]: + game = self._organizer.managedGame() + game_path = Path(game.gameDirectory().absolutePath()) + overwrite_path = Path(self._organizer.overwritePath()) + + return [ + *( + # Extra overwrites + self._overwrite_mapping( + overwrite_path / name, + dest, + is_dir=(map_type is self.MapType.FOLDER), + ) + for name, map_type in self._root_extra_overwrites.items() + if not (dest := game_path / name).exists() + ), + *self._root_mappings(game_path, overwrite_path), + ] + + def _root_mappings( + self, game_path: Path, overwrite_path: Path + ) -> Iterable[mobase.Mapping]: + for mod_path in self._active_mod_paths(): + mod_name = mod_path.name + + for child in mod_path.iterdir(): + # Check blacklist + if child.name.casefold() in self._root_blacklist: + qWarning(f"Skipping {child.name} ({mod_name})") + continue + destination = game_path / child.name + # Check existing + if destination.exists(): + qWarning( + f"Overwriting of existing game files/folders is not supported! " + f"{destination.as_posix()} ({mod_name})" + ) + continue + # Mapping: mod -> root + yield mobase.Mapping( + source=str(child), + destination=str(destination), + is_directory=child.is_dir(), + create_target=False, + ) + if child.is_dir(): + # Mapping: overwrite <-> root + yield self._overwrite_mapping( + overwrite_path / child.name, destination, is_dir=True + ) + + def _active_mod_paths(self) -> Iterable[Path]: + mods_parent_path = Path(self._organizer.modsPath()) + modlist = self._organizer.modList().allModsByProfilePriority() + for mod in modlist: + if self._organizer.modList().state(mod) & mobase.ModState.ACTIVE: + yield mods_parent_path / mod + + def _overwrite_mapping( + self, overwrite_source: Path, destination: Path, is_dir: bool + ) -> mobase.Mapping: + """Mapping: overwrite <-> root""" + if is_dir: + # Root folders in overwrite need to exits. + overwrite_source.mkdir(parents=True, exist_ok=True) + return mobase.Mapping( + str(overwrite_source), + str(destination), + is_dir, + create_target=True, + )