Update for GOG IDs and handling of multiple Steam/Gog IDs.

This commit is contained in:
Mikaël Capelle
2020-08-06 12:06:25 +02:00
parent 836595d2a2
commit 53e86a68a4
4 changed files with 162 additions and 24 deletions
+112 -18
View File
@@ -2,6 +2,7 @@
import shutil
from pathlib import Path
from typing import List, Union, Optional, TypeVar, Callable, Generic, Dict
@@ -47,7 +48,7 @@ class BasicGameMapping(Generic[T]):
_required: bool
# Callable returning a default value (if not required):
_default: Optional[Callable[["BasicGame"], T]]
_default: Callable[["BasicGame"], T]
# Function to apply to the value:
_apply_fn: Optional[Callable[[Union[T, str]], T]]
@@ -64,7 +65,6 @@ class BasicGameMapping(Generic[T]):
self._game = game
self._exposed_name = exposed_name
self._internal_method_name = internal_method
self._default = default
self._apply_fn = apply_fn
if hasattr(game, self._exposed_name):
@@ -80,9 +80,8 @@ class BasicGameMapping(Generic[T]):
)
)
self._default = lambda game: value # type: ignore
elif self._default is not None:
# Not required, ok!
pass
elif default is not None:
self._default = default # type: ignore
elif getattr(game.__class__, self._internal_method_name) is getattr(
BasicGame, self._internal_method_name
):
@@ -104,6 +103,68 @@ class BasicGameMapping(Generic[T]):
return value
class BasicGameOptionsMapping(BasicGameMapping[List[T]]):
"""
Represents a game mappings for which multiple options are possible. The game
plugin is responsible to choose the right option depending on the context.
"""
_index: int
def __init__(
self,
game,
exposed_name,
internal_method,
default: Optional[Callable[["BasicGame"], T]] = None,
apply_fn: Optional[Callable[[Union[List[T], str]], List[T]]] = None,
):
super().__init__(game, exposed_name, internal_method, lambda g: [], apply_fn)
self._index = -1
self._current_default = default
def set_index(self, index: int):
"""
Set the index of the option to use.
Args:
index: Index of the option to use.
"""
self._index = index
def set_value(self, value: T):
"""
Set the index corresponding of the given value. If the value is not present,
the index is set to -1.
Args:
value: The value to set the index to.
"""
try:
self._index = self.get().index(value)
except ValueError:
self._index = -1
def current(self) -> T:
values = self._default(self._game) # type: ignore
if not values:
return self._current_default(self._game) # type: ignore
if self._index == -1:
value = values[0]
else:
value = values[self._index]
if isinstance(value, str):
return replace_variables(value, self._game) # type: ignore
elif isinstance(value, QDir):
return QDir(replace_variables(value.path(), self._game)) # type: ignore
return value
class BasicGameMappings:
name: BasicGameMapping[str]
@@ -121,7 +182,8 @@ class BasicGameMappings:
documentsDirectory: BasicGameMapping[QDir]
savesDirectory: BasicGameMapping[QDir]
savegameExtension: BasicGameMapping[str]
steamAPPId: BasicGameMapping[str]
steamAPPId: BasicGameOptionsMapping[str]
gogAPPId: BasicGameOptionsMapping[str]
@staticmethod
def _default_documents_directory(game):
@@ -200,8 +262,18 @@ class BasicGameMappings:
self.savegameExtension = BasicGameMapping(
game, "GameSaveExtension", "savegameExtension", default=lambda g: "save"
)
self.steamAPPId = BasicGameMapping(
game, "GameSteamId", "steamAPPId", default=lambda g: "", apply_fn=str
# Convert Union[int, str, List[Union[int, str]]] to List[str].
def ids_apply(v) -> List[str]:
if isinstance(v, (int, str)):
v = [v]
return [str(x) for x in v]
self.steamAPPId = BasicGameOptionsMapping(
game, "GameSteamId", "steamAPPId", default=lambda g: "", apply_fn=ids_apply
)
self.gogAPPId = BasicGameOptionsMapping(
game, "GameGogId", "gogAPPId", default=lambda g: "", apply_fn=ids_apply
)
@@ -211,14 +283,17 @@ class BasicGame(mobase.IPluginGame):
to make it easier to create game plugins without having to implement
all the methods of mobase.IPluginGame."""
# List of steam games:
steam_games: Dict[str, str]
# List of steam and GOG games:
steam_games: Dict[str, Path]
gog_games: Dict[str, Path]
@staticmethod
def setup():
from .steam_utils import find_games
from .steam_utils import find_games as find_steam_games
from .gog_utils import find_games as find_gog_games
BasicGame.steam_games = find_games()
BasicGame.steam_games = find_steam_games()
BasicGame.gog_games = find_gog_games()
# File containing the plugin:
_fromName: str
@@ -293,7 +368,10 @@ class BasicGame(mobase.IPluginGame):
return self.mappings.nexusGameId.get()
def steamAPPId(self) -> str:
return self.mappings.steamAPPId.get()
return self.mappings.steamAPPId.current()
def gogAPPId(self) -> str:
return self.mappings.gogAPPId.current()
def binaryName(self) -> str:
return self.mappings.binaryName.get()
@@ -373,9 +451,15 @@ class BasicGame(mobase.IPluginGame):
return aQDir.exists(self.binaryName())
def isInstalled(self) -> bool:
if self.steamAPPId() in BasicGame.steam_games:
self.setGamePath(BasicGame.steam_games[self.steamAPPId()])
return True
for steam_id in self.mappings.steamAPPId.get():
if steam_id in BasicGame.steam_games:
self.setGamePath(BasicGame.steam_games[steam_id])
return True
for gog_id in self.mappings.gogAPPId.get():
if gog_id in BasicGame.gog_games:
self.setGamePath(BasicGame.gog_games[gog_id])
return True
return False
@@ -390,8 +474,18 @@ class BasicGame(mobase.IPluginGame):
self.gameDirectory().absoluteFilePath(self.mappings.dataDirectory.get())
)
def setGamePath(self, pathStr: str):
self._gamePath = pathStr
def setGamePath(self, path: Union[Path, str]):
self._gamePath = str(path)
path = Path(path)
# Check if we have a matching steam ID or GOG id and set the index accordingly:
for steamid, steampath in BasicGame.steam_games.items():
if steampath == path:
self.mappings.steamAPPId.set_value(steamid)
for gogid, gogpath in BasicGame.gog_games.items():
if gogpath == path:
self.mappings.steamAPPId.set_value(gogid)
def documentsDirectory(self) -> QDir:
return self.mappings.documentsDirectory.get()
+2 -1
View File
@@ -18,7 +18,8 @@ class Witcher3Game(BasicGame):
GameShortName = "witcher3"
GaneNexusHame = "witcher3"
GameNexusId = 952
GameSteamId = 292030
GameSteamId = [499450, 292030]
GameGogId = [1640424747, 1495134320, 1207664663, 1207664643]
GameBinary = "bin/x64/witcher3.exe"
GameDataPath = "Mods"
GameSaveExtension = "sav"
+40
View File
@@ -0,0 +1,40 @@
# -*- encoding: utf-8 -*-
# Code adapted from EzioTheDeadPoet / erri120:
# https://github.com/ModOrganizer2/modorganizer-basic_games/pull/5
import winreg # type: ignore
from pathlib import Path
from typing import Dict
def find_games() -> Dict[str, Path]:
# List the game IDs from the registry:
game_ids = []
try:
with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE, r"Software\Wow6432Node\GOG.com\Games"
) as key:
nkeys = winreg.QueryInfoKey(key)[0]
for ik in range(nkeys):
game_key = winreg.EnumKey(key, ik)
if game_key.isdigit():
game_ids.append(game_key)
except FileNotFoundError:
return {}
# For each game, query the path:
games: Dict[str, Path] = {}
for game_id in game_ids:
try:
with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
f"Software\\Wow6432Node\\GOG.com\\Games\\{game_id}",
) as key:
games[game_id] = Path(winreg.QueryValueEx(key, "path")[0])
except FileNotFoundError:
pass
return games
+8 -5
View File
@@ -5,6 +5,7 @@
import os
import winreg # type: ignore
from pathlib import Path
from typing import Dict
@@ -27,7 +28,9 @@ class LibraryFolder:
self.games = []
for filename in os.listdir(os.path.join(path, "steamapps")):
if filename.startswith("appmanifest"):
with open(os.path.join(path, "steamapps", filename), "r", encoding="utf-8") as fp:
with open(
os.path.join(path, "steamapps", filename), "r", encoding="utf-8"
) as fp:
i, n = None, None
for line in fp:
line = line.strip()
@@ -95,7 +98,7 @@ def parse_library_info(library_vdf_path):
return library_folders
def find_games() -> Dict[str, str]:
def find_games() -> Dict[str, Path]:
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Valve\\Steam") as key:
value = winreg.QueryValueEx(key, "SteamExe")
@@ -110,11 +113,11 @@ def find_games() -> Dict[str, str]:
library_folders = parse_library_info(library_vdf_path)
library_folders.append(LibraryFolder(os.path.dirname(steam_path)))
games: Dict[str, str] = {}
games: Dict[str, Path] = {}
for library in library_folders:
for game in library.games:
games[game.appid] = os.path.join(
library.path, "steamapps", "common", game.installdir
games[game.appid] = Path(library.path).joinpath(
"steamapps", "common", game.installdir
)
return games