-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/master' into dev/bg3_support
- Loading branch information
Showing
3 changed files
with
240 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import mobase | ||
from PyQt6.QtCore import QDir, QFileInfo | ||
|
||
from ..basic_features import BasicLocalSavegames | ||
from ..basic_game import BasicGame | ||
from ..steam_utils import find_steam_path | ||
|
||
|
||
# Lifted from https://github.com/ModOrganizer2/modorganizer-basic_games/blob/71dbb8c557d43cba9d290674a332e7ecd1650261/games/game_darkestdungeon.py | ||
class ArkhamCityModDataChecker(mobase.ModDataChecker): | ||
def __init__(self): | ||
super().__init__() | ||
self.validDirNames = [ | ||
"config", | ||
"cookedpcconsole", | ||
"localization", | ||
"movies", | ||
"moviesstereo", | ||
"splash", | ||
] | ||
|
||
def dataLooksValid( | ||
self, filetree: mobase.IFileTree | ||
) -> mobase.ModDataChecker.CheckReturn: | ||
for entry in filetree: | ||
if not entry.isDir(): | ||
continue | ||
if entry.name().casefold() in self.validDirNames: | ||
return mobase.ModDataChecker.VALID | ||
return mobase.ModDataChecker.INVALID | ||
|
||
|
||
class ArkhamCityGame(BasicGame): | ||
Name = "Batman: Arkham City Plugin" | ||
Author = "Paynamia" | ||
Version = "0.5.3" | ||
|
||
GameName = "Batman: Arkham City" | ||
GameShortName = "batmanarkhamcity" | ||
GameNexusId = 372 | ||
GameSteamId = 200260 | ||
GameGogId = 1260066469 | ||
GameEpicId = "Egret" | ||
GameBinary = "Binaries/Win32/BatmanAC.exe" | ||
GameLauncher = "Binaries/Win32/BmLauncher.exe" | ||
GameDataPath = "BmGame" | ||
GameDocumentsDirectory = ( | ||
"%DOCUMENTS%/WB Games/Batman Arkham City GOTY/BmGame/Config" | ||
) | ||
GameIniFiles = ["UserEngine.ini", "UserGame.ini", "UserInput.ini"] | ||
GameSaveExtension = "sgd" | ||
|
||
# This will only detect saves from the earliest-created Steam profile on the user's PC. | ||
def savesDirectory(self) -> QDir: | ||
docSaves = QDir(self.documentsDirectory().cleanPath("../../SaveData")) | ||
if self.is_steam(): | ||
if (steamDir := find_steam_path()) is None: | ||
return docSaves | ||
for child in steamDir.joinpath("userdata").iterdir(): | ||
if not child.is_dir() or child.name == "0": | ||
continue | ||
steamSaves = child.joinpath("200260", "remote") | ||
if steamSaves.is_dir(): | ||
return QDir(str(steamSaves)) | ||
else: | ||
return docSaves | ||
else: | ||
return docSaves | ||
|
||
def init(self, organizer: mobase.IOrganizer) -> bool: | ||
super().init(organizer) | ||
self._register_feature(ArkhamCityModDataChecker()) | ||
self._register_feature(BasicLocalSavegames(self.savesDirectory())) | ||
return True | ||
|
||
def executables(self): | ||
return [ | ||
mobase.ExecutableInfo( | ||
"Batman: Arkham City", | ||
QFileInfo(self.gameDirectory(), "Binaries/Win32/BatmanAC.exe"), | ||
), | ||
mobase.ExecutableInfo( | ||
"Arkham City Launcher", | ||
QFileInfo(self.gameDirectory(), "Binaries/Win32/BmLauncher.exe"), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import re | ||
|
||
import mobase | ||
from mobase import FileTreeEntry, IFileTree, ModDataChecker | ||
|
||
from ..basic_features import BasicModDataChecker | ||
from ..basic_game import BasicGame | ||
|
||
_extention_pattern = re.compile("\\.(upk|umap|u|int|dll|exe)$", re.I) | ||
_mapslot_pattern = re.compile("^Mapslot\\d\\d?\\.umap$", re.I) | ||
|
||
_mod_dirs = { | ||
"Binaries".casefold(): "/", | ||
"WillowGame".casefold(): "/", | ||
"CookedPC".casefold(): "WillowGame/", | ||
"Localization".casefold(): "WillowGame/", | ||
"Maps".casefold(): "WillowGame/CookedPC/", | ||
} | ||
|
||
_slots_path = "WillowGame/CookedPC/Maps/MapSlots" | ||
|
||
|
||
def _check_filetree( | ||
filetree: IFileTree | FileTreeEntry, recurse: bool | ||
) -> ModDataChecker.CheckReturn: | ||
if filetree.isFile(): | ||
if _extention_pattern.search(filetree.name()): | ||
if _mapslot_pattern.search(filetree.name()): | ||
return ModDataChecker.FIXABLE | ||
else: | ||
return ModDataChecker.INVALID | ||
elif recurse and isinstance(filetree, IFileTree): | ||
status = ModDataChecker.VALID | ||
for child in filetree: | ||
child_status = _check_filetree(child, True) | ||
if child_status is ModDataChecker.INVALID: | ||
return ModDataChecker.INVALID | ||
elif child_status is ModDataChecker.FIXABLE: | ||
status = ModDataChecker.FIXABLE | ||
return status | ||
|
||
return ModDataChecker.VALID | ||
|
||
|
||
def _get_nest(filetree: IFileTree) -> IFileTree | None: | ||
children = tuple(filetree) | ||
if ( | ||
len(children) == 1 | ||
and children[0].isDir() | ||
and isinstance(children[0], IFileTree) | ||
and _mod_dirs.get(children[0].name().casefold()) is None | ||
): | ||
return children[0] | ||
return None | ||
|
||
|
||
def _get_slotstree(roottree: IFileTree) -> IFileTree: | ||
slotstree: IFileTree | None = roottree.find(_slots_path) # type: ignore | ||
if slotstree is None: | ||
slotstree = roottree.addDirectory(_slots_path) | ||
return slotstree | ||
|
||
|
||
def _fix_mapslots(filetree: IFileTree, roottree: IFileTree) -> None: | ||
for child in filetree: | ||
if child.isDir() and isinstance(child, IFileTree): | ||
_fix_mapslots(child, roottree) | ||
elif _mapslot_pattern.search(child.name()): | ||
child.moveTo(_get_slotstree(roottree)) | ||
|
||
|
||
class Borderlands1ModDataChecker(BasicModDataChecker): | ||
def dataLooksValid(self, filetree: IFileTree) -> ModDataChecker.CheckReturn: | ||
parent = filetree.parent() | ||
if parent is not None: | ||
return self.dataLooksValid(parent) | ||
|
||
status = ModDataChecker.VALID | ||
|
||
nest = _get_nest(filetree) | ||
if nest is not None: | ||
status = ModDataChecker.FIXABLE | ||
filetree = nest | ||
|
||
if _check_filetree(filetree, False) is ModDataChecker.INVALID: | ||
return ModDataChecker.INVALID | ||
|
||
slotstree = filetree.find(_slots_path) | ||
if slotstree is not None and not slotstree.isDir(): | ||
return ModDataChecker.INVALID | ||
|
||
for child in filetree: | ||
if child.isDir(): | ||
destination = _mod_dirs.get(child.name().casefold()) | ||
if destination is None: | ||
child_status = _check_filetree(child, True) | ||
if child_status is ModDataChecker.INVALID: | ||
return ModDataChecker.INVALID | ||
elif child_status is ModDataChecker.FIXABLE: | ||
status = ModDataChecker.FIXABLE | ||
elif destination != "/": | ||
status = ModDataChecker.FIXABLE | ||
elif _mapslot_pattern.search(child.name()): | ||
status = ModDataChecker.FIXABLE | ||
elif _extention_pattern.search(child.name()): | ||
return ModDataChecker.INVALID | ||
return status | ||
|
||
def fix(self, filetree: IFileTree) -> IFileTree: | ||
nest = _get_nest(filetree) | ||
if nest is not None: | ||
conflict: FileTreeEntry | None = None | ||
for child in tuple(nest): | ||
if not child.moveTo(filetree): | ||
conflict = child | ||
conflict.detach() | ||
filetree.remove(nest) | ||
if conflict is not None: | ||
conflict.moveTo(filetree) | ||
|
||
for child in tuple(filetree): | ||
if child.isDir() and isinstance(child, IFileTree): | ||
destination = _mod_dirs.get(child.name().casefold()) | ||
if destination is None: | ||
_fix_mapslots(child, filetree) | ||
elif destination != "/": | ||
filetree.move(child, destination) | ||
elif _mapslot_pattern.search(child.name()): | ||
child.moveTo(_get_slotstree(filetree)) | ||
|
||
return filetree | ||
|
||
|
||
class Borderlands1Game(BasicGame): | ||
Name = "Borderlands 1 Support Plugin" | ||
Author = "Miner Of Worlds, RedxYeti, mopioid" | ||
Version = "1.0.0" | ||
|
||
def init(self, organizer: mobase.IOrganizer) -> bool: | ||
super().init(organizer) | ||
self._register_feature(Borderlands1ModDataChecker()) | ||
return True | ||
|
||
GameName = "Borderlands" | ||
GameShortName = "Borderlands" | ||
GameNexusName = "Borderlands GOTY" | ||
GameSteamId = 8980 | ||
GameBinary = "Binaries/Borderlands.exe" | ||
GameDataPath = "." | ||
GameSaveExtension = "sav" | ||
GameDocumentsDirectory = "%DOCUMENTS%/My Games/Borderlands/" | ||
GameSavesDirectory = "%GAME_DOCUMENTS%/savedata" | ||
GameIniFiles = "%GAME_DOCUMENTS%/WillowGame/Config" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters