gsgbackup/sgbackup/game.py

1095 lines
38 KiB
Python

###############################################################################
# sgbackup - The SaveGame Backup tool #
# Copyright (C) 2024 Christian Moser #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
###############################################################################
from gi.repository.GObject import Property,GObject,Signal,SignalFlags
from gi.repository import GLib
import os
import json
import re
import fnmatch
from enum import StrEnum
import sys
import logging
import pathlib
logger = logging.getLogger(__name__)
from .settings import settings
if sys.platform.lower() == "win32":
PLATFORM_WIN32 = True
import winreg
else:
PLATFORM_WIN32 = False
if sys.platform.lower() in ['linux','freebsd','netbsd','openbsd','dragonfly']:
PLATFORM_LINUX = True
else:
PLATFORM_LINUX = False
class SavegameType(StrEnum):
UNSET = "unset"
OTHER = "other"
WINDOWS = "windows"
LINUX = "linux"
MACOS = "macos"
STEAM_WINDOWS = "steam_windows"
STEAM_LINUX = "steam_linux"
STEAM_MACOS = "steam_macos"
GOG_WINDOWS = "gog_windows"
GOG_LINUX = "gog_linux"
EPIC_WINDOWS = "epic_windows"
EPIC_LINUX = "epic_linux"
@staticmethod
def from_string(typestring:str):
st=SavegameType
s=typestring.lower()
if (s == 'other'):
return st.OTHER
elif (s == 'windows'):
return st.WINDOWS
elif (s == 'linux'):
return st.LINUX
elif (s == 'macos'):
return st.MACOS
elif (s == 'steam_windows' or s == 'steamwindows' or s == 'steam.windows'):
return st.STEAM_WINDOWS
elif (s == 'steam_linux' or s == 'steamlinux' or s == 'steam.linux'):
return st.STEAM_LINUX
elif (s == 'steam_macos' or s == 'steammacos' or s == 'steam.macos'):
return st.STEAM_MACOS
elif (s == 'gog_winows' or s == 'gogwindows' or s == 'gog.windows'):
return st.GOG_WINDOWS
elif (s == 'gog_linux' or s == 'goglinux' or s == 'gog.linux'):
return st.GOG_LINUX
elif (s == 'epic_windows' or s == 'epicwindows' or s == 'epic.windows'):
return st.EPIC_WINDOWS
elif (s == 'epic_linux' or s == 'epiclinux' or s == 'epic.linux'):
return st.EPIC_LINUX
return st.UNSET
class GameFileType(StrEnum):
GLOB = "glob"
REGEX = "regex"
FILENAME = "filename"
@staticmethod
def from_string(typestring:str):
s = typestring.lower()
if (s == 'glob'):
return GameFileType.GLOB
elif (s == 'regex'):
return GameFileType.REGEX
elif (s == 'filename'):
return GameFileType.FILENAME
raise ValueError("Unknown GameFileType \"{}\"!".fomrat(typestring))
class GameFileMatcher(GObject):
__gtype_name__ = "GameFileMatcher"
def __init__(self,match_type:GameFileType,match_file:str):
GObject.__init__(self)
self.match_type = type
self.match_file = match_file
@Property
def match_type(self)->GameFileType:
return self.__match_type
@match_type.setter
def match_type(self,type:GameFileType):
if not isinstance(type,GameFileType):
raise TypeError("match_type is not a GameFileType instance!")
self.__match_type = type
@Property(type=str)
def match_file(self)->str:
return self.__match_file
@match_file.setter
def match_file(self,file:str):
self.__match_file = file
## @}
def match(self,rel_filename:str):
def match_glob(filename):
return fnmatch.fnmatch(filename,self.match_file)
# match_glob()
def match_filename(filename):
if (PLATFORM_WIN32):
fn = filename.replace("/","\\")
if (self.match_file.endswith("\\")):
if fn == self.match_file[:-1] or fn.startswith(self.match_file):
return True
elif fn == self.match_file:
return True
else:
if (self.match_file.endswith('/')):
if fn == self.match_file[:-1] or fn.startswith(self.match_file):
return True
elif fn == self.match_file:
return True
return False
# match_filename()
def match_regex(filename):
return (re.search(self.match_file,filename) is not None)
# match_filename()
if (self.match_type == GameFileType.FILENAME):
return match_filename(rel_filename)
elif (self.match_type == GameFileType.GLOB):
return match_glob(rel_filename)
elif (self.match_type == GameFileType.REGEX):
return match_regex(rel_filename)
return False
class GameData(GObject):
__gtype_name__ = 'GameData'
"""
:class: GameData
:brief: Base class for platform specific data.
"""
def __init__(self,
savegame_type:SavegameType,
savegame_root:str,
savegame_dir:str,
variables:dict|None=None,
file_match:list|None=None,
ignore_match:list|None=None):
GObject.__init__(self)
self.__savegame_type = savegame_type
self.__savegame_root = savegame_root
self.__savegame_dir = savegame_dir
self.__variables = {}
self.__filematch = []
self.__ignorematch = []
if variables is not None:
variables.update(variables)
if file_match is not None:
for fm in file_match:
self.add_file_match(fm)
if ignore_match is not None:
for fm in ignore_match:
self.add_ignore_match(fm)
@Property
def savegame_type(self)->SavegameType:
"""
:attr: savegame_type
:brief: Type of the class.
"""
return self.__savegame_type
@Property(type=str)
def savegame_root(self)->str:
"""
:attr: savegame_root
"""
return self.__savegame_root
@savegame_root.setter
def savegame_root(self,sgroot:str):
self.__savegame_root = sgroot
@Property
def savegame_dir(self)->str:
"""
:attr: savegame_dir
"""
return self.__savegame_dir
@savegame_dir.setter
def savegame_dir(self,sgdir:str):
self.__savegame_dir = sgdir
@Property
def variables(self)->dict:
return self.__variables
@variables.setter
def variables(self,vars:dict|None):
if not vars:
self.__variables = {}
else:
self.__variables = dict(vars)
@Property
def file_match(self):
return self.__filematch
@file_match.setter
def file_match(self,fm:list[GameFileMatcher]|None):
if not fm:
self.__filematch = []
else:
for matcher in fm:
if not isinstance(matcher,GameFileMatcher):
raise TypeError("\"file_match\" needs to be \"None\" or a list of \"GameFileMatcher\" instances!")
self.__filematch = list(fm)
@Property
def ignore_match(self):
return self.__ignorematch
@file_match.setter
def file_match(self,im:list[GameFileMatcher]|None):
if not im:
self.__ignorematch = []
else:
for matcher in im:
if not isinstance(matcher,GameFileMatcher):
raise TypeError("\"ignore_match\" needs to be \"None\" or a list of \"GameFileMatcher\" instances!")
self.__ignorematch = list(im)
def has_variable(self,name:str)->bool:
return (name in self.__variables)
def get_variable(self,name:str)->str:
if name not in self.__variables:
return ""
return self.__variables[name]
def set_variable(self,name:str,value:str):
self.__variables[name] = value
def delete_variable(self,name:str):
if name in self.__variables:
del self.__variables[name]
def get_variables(self):
return self.variables
def match_file(self,rel_filename:str):
if not self.__filematch:
return True
for fm in self.__filematch:
if fm.match(rel_filename):
return True
return False
def match_ignore(self,rel_filename:str):
if not self.__ignorematch:
return False
for fm in self.__ignorematch:
if fm.match(rel_filename):
return True
return False
def match(self,rel_filename:str):
if self.match_file(rel_filename) and not self.match_ignore(rel_filename):
return True
return False
def add_file_match(self,matcher:GameFileMatcher):
if not isinstance(matcher,GameFileMatcher):
raise TypeError("matcher is not a \"GameFileMatcher\" instance!")
self.__filematch.append(matcher)
def remove_file_match(self,matcher:GameFileMatcher):
for i in reversed(range(len(self.__filematch))):
if (matcher == self.__filematch[i]):
del self.__filematch[i]
def add_ignore_match(self,matcher:GameFileMatcher):
if not isinstance(matcher,GameFileMatcher):
raise TypeError("matcher is not a \"GameFileMatcher\" instance!")
self.__ignorematch.append(matcher)
def remove_ignore_match(self,matcher:GameFileMatcher):
for i in reversed(range(len(self.__ignorematch))):
if (matcher == self.__ignorematch[i]):
del self.__ignorematch[i]
def serialize(self)->dict:
ret = {
'savegame_root': self.savegame_root,
'savegame_dir': self.savegame_dir,
}
if (self.__variables):
ret['variables'] = self.variables
if (self.file_match):
fm = []
for matcher in self.file_match:
fm.append({'type':matcher.match_type.value,'match':matcher.match_file})
ret['file_match'] = fm
if (self.add_ignore_match):
im = []
for matcher in self.ignore_match:
im.append({'type':matcher.match_type.value,'match':matcher.match_file})
ret['ignore_match'] = im
return ret
class WindowsGame(GameData):
def __init__(self,
savegame_root:str,
savegame_dir:str,
variables:dict|None=None,
installdir:str|None=None,
game_registry_keys:str|list|None=None,
installdir_registry_keys:str|list|None=None,
file_match:list|None=None,
ignore_match:list|None=None):
GameData.__init__(self,
SavegameType.WINDOWS,
savegame_root,
savegame_dir,
variables,
file_match,
ignore_match)
if not installdir:
self.__installdir = None
else:
self.__installdir = installdir
if not game_registry_keys:
self.__game_registry_keys = []
elif isinstance(game_registry_keys,str):
self.__game_registry_keys = [game_registry_keys]
else:
self.__game_registry_keys = list(game_registry_keys)
if not installdir_registry_keys:
self.__installdir_registry_keys = []
elif isinstance(installdir_registry_keys,str):
self.__installdir_registry_keys = [installdir_registry_keys]
else:
self.__installdir_registry_keys = list(installdir_registry_keys)
def __get_hkey(self,regkey:str):
regvec = regkey.split("\\")
hkstr = regvec[0]
if (hkstr == 'HKLM' or hkstr == 'HKEY_LOCAL_MACHINE'):
return winreg.HKEY_LOCAL_MACHINE
elif (hkstr == 'HKCU' or hkstr == 'HKEY_CURRENT_USER'):
return winreg.HKEY_CURRENT_USER
elif (hkstr == 'HKCC' or hkstr == 'HKEY_CURRENT_CONFIG'):
return winreg.HKEY_CURRENT_CONFIG
elif (hkstr == 'HKCR' or hkstr == 'HKEY_CLASSES_ROOT'):
return winreg.HKEY_CLASSES_ROOT
elif (hkstr == 'HKU' or hkstr == 'HKEY_USERS'):
return winreg.HKEY_USERS
return None
@Property
def installdir(self)->str|None:
return self.__installdir
@installdir.setter
def installdir(self,installdir:str|None):
self.__installdir = installdir
@Property
def game_registry_keys(self)->list:
return self.__game_registry_keys
@Property
def installdir_registry_keys(self)->list:
return self.__installdir_registry_keys
@Property
def is_installed(self)->bool|None:
if not PLATFORM_WIN32 or not self.game_registry_keys:
return None
for regkey in self.__game_registry_keys:
hkey = self.__get_hkey(regkey)
regvec = regkey.split('\\')
if (regvec > 1):
key = '\\'.join(regvec[1:])
try:
rkey = winreg.OpenKeyEx(hkey,key)
winreg.CloseKey(rkey)
return True
except OSError as ex:
continue
return False
@Property
def registry_installdir(self)->str|None:
if not PLATFORM_WIN32 or not (self.installdir_registry_keys):
return None
for regkey in self.__game_registry_keys:
hkey = self.__get_hkey(regkey)
regvec = regkey.split('\\')
if (regvec > 2):
key = '\\'.join(regvec[1:-1])
try:
rkey = None
rkey = winreg.OpenKeyEx(hkey,key)
retval = winreg.QueryValue(rkey,regvec[-1])
winreg.CloseKey(rkey)
if retval:
return str(retval[0])
except OSError as ex:
if (rkey):
winreg.CloseKey(rkey)
continue
return None
def get_variables(self):
variables = super().get_variables()
variables["INSTALLDIR"] = self.installdir if self.installdir else ""
def serialize(self):
ret = super().serialize()
if (self.installdir):
ret['installdir'] = self.installdir
if (self.game_registry_keys):
ret['game_registry_keys'] = self.game_registry_keys
if (self.installdir_registry_keys):
ret['installdir_registry_keys'] = self.installdir_registry_keys
return ret
class LinuxGame(GameData):
def __init__(self,
savegame_root:str,
savegame_dir:str,
variables:dict|None=None,
binary:str|None=None,
file_match:list|None=None,
ignore_match:list|None=None):
GameData.__init__(self,
SavegameType.LINUX,
savegame_root,
savegame_dir,
variables,
file_match,
ignore_match)
self.__binary = binary
@Property
def binary(self)->str|None:
return self.__binary
@binary.setter
def binary(self,bin:str):
self.__binary = bin
@Property
def is_installed(self)->bool|None:
if PLATFORM_LINUX and self.binary:
return bool(GLib.find_program_in_path(self.binary))
else:
return None
def serialize(self):
ret = super().serialize()
if self.binary:
ret['binary'] = self.binary
return ret
class MacOSGame(GameData):
def __init__(self,
savegame_root:str,
savegame_dir:str,
variables:dict|None=None,
binary:str|None=None,
file_match:list|None=None,
ignore_match:list|None=None):
GameData.__init__(self,
SavegameType.MACOS,
savegame_root,
savegame_dir,
variables,
file_match,
ignore_match)
self.__binary = binary
@Property
def binary(self)->str|None:
return self.__binary
@binary.setter
def binary(self,bin:str):
self.__binary = bin
@Property
def is_installed(self)->bool|None:
if PLATFORM_LINUX and self.binary:
return bool(GLib.find_program_in_path(self.binary))
else:
return None
def serialize(self):
ret = super().serialize()
if self.binary:
ret['binary'] = self.binary
return ret
class SteamGame(GameData):
def __init__(self,
sgtype:SavegameType,
appid:int,
savegame_root:str,
savegame_dir:str,
variables:dict|None=None,
installdir:str|None=None,
file_match:list|None=None,
ignore_match:list|None=None):
if sgtype not in (SavegameType.STEAM_WINDOWS,
SavegameType.STEAM_LINUX,
SavegameType.STEAM_MACOS):
raise TypeError("SaveGameType")
GameData.__init__(self,
sgtype,
savegame_root,
savegame_dir,
variables,
file_match,
ignore_match)
self.appid = int(appid)
self.installdir = installdir
def get_variables(self):
vars = super().get_variables()
vars["INSTALLDIR"] = self.installdir if self.installdir else ""
@Property(type=int)
def appid(self):
return self.__appid
@appid.setter
def appid(self,appid):
self.__appid = appid
@Property
def installdir(self):
return self.__installdir
@installdir.setter
def installdir(self,installdir:str|None):
self.__installdir = installdir
def serialize(self):
ret = super().serialize()
ret['appid'] = self.appid
if self.installdir:
ret['installdir'] = self.installdir
return ret
class SteamWindowsGame(SteamGame):
def __init__(self,
appid:int,
savegame_root:str,
savegame_dir:str,
variables:dict|None=None,
installdir:str|None=None,
file_match:list|None=None,
ignore_match:list|None=None):
SteamGame.__init__(self,
SavegameType.STEAM_WINDOWS,
appid,
savegame_root,
savegame_dir,
variables,
installdir,
file_match,
ignore_match)
class SteamLinuxGame(SteamGame):
def __init__(self,
appid:int,
savegame_root:str,
savegame_dir:str,
variables:dict|None=None,
installdir:str|None=None,
file_match:list|None=None,
ignore_match:list|None=None):
SteamGame.__init__(self,
SavegameType.STEAM_LINUX,
appid,
savegame_root,
savegame_dir,
variables,
installdir,
file_match,
ignore_match)
class SteamMacOSGame(SteamGame):
def __init__(self,
appid:int,
savegame_root:str,
savegame_dir:str,
variables:dict|None=None,
installdir:str|None=None,
file_match:list|None=None,
ignore_match:list|None=None):
SteamGame.__init__(self,
SavegameType.STEAM_MACOS,
appid,
savegame_root,
savegame_dir,
variables,
installdir,
file_match,
ignore_match)
class Game(GObject):
__gtype_name__ = "Game"
@staticmethod
def new_from_dict(config:str):
logger = logger.getChild("Game.new_from_dict()")
def get_file_match(conf:dict):
conf_fm = conf['file_match'] if 'file_match' in conf else None
conf_im = conf['ignore_match'] if 'ignore_match' in conf else None
if (conf_fm):
file_match = []
for cfm in conf_fm:
if ('type' in cfm and 'match' in cfm):
try:
file_match.append(GameFileMatcher(GameFileType.from_string(cfm['type'],cfm['match'])))
except Exception as ex:
logger.error("Adding GameFileMatcher to file_match failed! ({})!".format(ex))
else:
logger.error("Illegal file_match settings! (\"type\" or \"match\" missing!)")
else:
file_match = None
if (conf_im):
ignore_match = []
for cim in conf_im:
if ('type' in cim and 'match' in cim):
try:
file_match.append(GameFileMatcher(GameFileType.from_string(cim['type'],cim['match'])))
except Exception as ex:
logger.error("Adding GameFileMatcher to ignore_match failed! ({})!".format(ex))
else:
logger.error("Illegal ignore_match settings! (\"type\" or \"match\" missing!)")
else:
ignore_match = None
return (file_match,ignore_match)
def new_steam_game(conf,cls:SteamGame):
appid = conf['appid'] if 'appid' in conf else None
sgroot = conf['savegame_root'] if 'savegame_root' in conf else None
sgdir = conf['savegame_dir'] if 'savegame_dir' in conf else None
vars = conf['variables'] if 'variables' in conf else None
installdir = conf['installdir'] if 'installdir' in conf else None
file_match,ignore_match = get_file_match(conf)
if appid is not None and sgroot and sgdir:
cls(appid,sgroot,sgdir,vars,installdir,file_match,ignore_match)
return None
# new_steam_game()
if not 'id' in config or not 'name' in config:
return None
id = config['id']
name = config['name']
sgname = config['savegame_name'] if 'savegame_name' in config else id
sgtype = config['savegame_type'] if 'savegame_type' in config else SavegameType.UNSET
game = Game(id,name,sgname)
game.savegame_type = sgtype
game.is_active = config['is_active'] if 'is_active' in config else False
game.is_live = config['is_live'] if 'is_live' in config else True
if 'windows' in config:
winconf = config['windows']
sgroot = winconf['savegame_root'] if 'savegame_root' in winconf else None
sgdir = winconf['savegame_dir'] if 'savegame_dir' in winconf else None
vars = winconf['variables'] if 'variables' in winconf else None
installdir = winconf['installdir'] if 'installdir' in winconf else None
game_regkeys = winconf['game_registry_keys'] if 'game_registry_keys' in winconf else None
installdir_regkeys = winconf['installdir_registry_keys'] if 'installdir_registry_keys' in winconf else None
file_match,ignore_match = get_file_match(winconf)
if (sgroot and sgdir):
game.windows = WindowsGame(sgroot,
sgdir,
vars,
installdir,
game_regkeys,
installdir_regkeys,
file_match,
ignore_match)
if 'linux' in config:
linconf = config['linux']
sgroot = linconf['savegame_root'] if 'savegame_root' in linconf else None
sgdir = linconf['savegame_dir'] if 'savegame_dir' in linconf else None
vars = linconf['variables'] if 'variables' in linconf else None
binary = linconf['binary'] if 'binary' in linconf else None
file_match,ignore_match = get_file_match(linconf)
if (sgroot and sgdir):
game.linux = LinuxGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
if 'macos' in config:
macconf = config['macos']
sgroot = macconf['savegame_root'] if 'savegame_root' in macconf else None
sgdir = macconf['savegame_dir'] if 'savegame_dir' in macconf else None
vars = macconf['variables'] if 'variables' in macconf else None
binary = macconf['binary'] if 'binary' in macconf else None
file_match,ignore_match = get_file_match(macconf)
if (sgroot and sgdir):
game.macos = MacOSGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
if 'steam_windows' in config:
game.steam_windows = new_steam_game(config['steam_windows'],SteamWindowsGame)
if 'steam_linux' in config:
game.steam_linux = new_steam_game(config['steam_linux'],SteamLinuxGame)
if 'steam_macos' in config:
game.steam_macos = new_steam_game(config['steam_macos'],SteamMacOSGame)
return game
@staticmethod
def new_from_json_file(filename:str):
if not os.path.isfile(filename):
raise FileNotFoundError("Filename \"{filename}\" not found!".format(filename=filename))
with open(filename,'rt',encoding="UTF-8") as ifile:
return Game.new_from_dict(json.loads(ifile.read()))
def __init__(self,key:str,name:str,savegame_name:str):
GObject.__init__(self)
self.__dbid = None
self.__key = key
self.__name = name
self.__filename = None
self.__savegame_name = savegame_name
self.__savegame_type = SavegameType.UNSET
self.__active = False
self.__live = True
self.__variables = dict()
self.__windows = None
self.__linux = None
self.__macos = None
self.__steam_windows = None
self.__steam_linux = None
self.__steam_macos = None
self.__gog_windows = None
self.__gog_linux = None
self.__epic_windows = None
self.__epic_linux = None
@Property(type=str)
def dbid(self)->str:
return self.__id
@dbid.setter
def id(self,id:str):
self.__id = id
@Property(type=str)
def key(self)->str:
return self.__key
@key.setter
def key(self,key:str):
set_game = False
if self.__key in GAMES:
del GAMES[self.__key]
set_game = True
self.__key = key
if set_game:
GAMES[self.__key] = self
@Property(type=str)
def name(self)->str:
return self.__name
@name.setter
def name(self,name:str):
self.__name = name
@Property(type=str)
def savegame_name(self)->str:
return self.__savegame_name
@savegame_name.setter
def savegame_name(self,sgname:str):
self.__savegame_name = sgname
@Property
def savegame_type(self)->SavegameType:
return self.__savegame_type
@savegame_type.setter
def savegame_type(self,sgtype:SavegameType):
self.__savegame_type = sgtype
@Property(type=bool,default=False)
def is_active(self)->bool:
return self.__active
@is_active.setter
def is_active(self,active:bool):
self.__active = bool(active)
@Property(type=bool,default=True)
def is_live(self)->bool:
return self.__live
@is_live.setter
def is_live(self,live:bool):
self.__live = bool(live)
@Property
def filename(self)->str|None:
if not self.__id:
return None
if not self.__filename:
GLib.build_filename(settings.gameconf_dir,'.'.join((self.id,'gameconf')))
return self.__filename
@filename.setter
def filename(self,fn:str):
if not os.path.isabs(fn):
self.__filename = GLib.build_filename(settings.gameconf_dir,fn)
else:
self.__filename = fn
@Property
def variables(self):
return self.__variables
@variables.setter
def variables(self,vars:dict|None):
if not vars:
self.__variables = {}
else:
self.__variables = dict(vars)
@Property
def game_data(self):
sgtype = self.savegame_type
if (sgtype == SavegameType.WINDOWS):
return self.windows
elif (sgtype == SavegameType.LINUX):
return self.linux
elif (sgtype == SavegameType.MACOS):
return self.macos
elif (sgtype == SavegameType.STEAM_WINDOWS):
return self.steam_windows
elif (sgtype == SavegameType.STEAM_LINUX):
return self.steam_linux
elif (sgtype == SavegameType.STEAM_MACOS):
return self.steam_macos
elif (sgtype == SavegameType.GOG_WINDOWS):
return self.__gog_windows
elif (sgtype == SavegameType.GOG_LINUX):
return self.__gog_linux
elif (sgtype == SavegameType.EPIC_WINDOWS):
return self.__epic_windows
elif (sgtype == SavegameType.EPIC_LINUX):
return self.__epic_linux
return None
@Property
def windows(self)->WindowsGame|None:
return self.__windows
@windows.setter
def windows(self,data:WindowsGame|None):
if not data:
self.__windows = None
else:
if not isinstance(data,WindowsGame):
raise TypeError("WindowsGame")
self.__windows = data
@Property
def linux(self)->LinuxGame|None:
return self.__linux
@linux.setter
def linux(self,data:LinuxGame):
if not data:
self.__linux = None
else:
if not isinstance(data,LinuxGame):
raise TypeError("LinuxGame")
self.__linux = data
@Property
def macos(self)->MacOSGame|None:
return self.__macos
@macos.setter
def macos(self,data:MacOSGame|None):
if not data:
self.__macos = None
else:
if not isinstance(data,MacOSGame):
raise TypeError("MacOSGame")
self.__macos = data
@Property
def steam_windows(self)->SteamWindowsGame|None:
return self.__steam_windows
@steam_windows.setter
def steam_windows(self,data:SteamWindowsGame|None):
if not data:
self.__steam_windows = None
else:
if not isinstance(data,SteamWindowsGame):
raise TypeError("SteamWindowsGame")
self.__steam_windows = data
@Property
def steam_linux(self)->SteamLinuxGame|None:
return self.__steam_linux
@steam_linux.setter
def steam_windows(self,data:SteamLinuxGame|None):
if not data:
self.__steam_linux = None
else:
if not isinstance(data,SteamLinuxGame):
raise TypeError("SteamWindowsGame")
self.__steam_linux = data
@Property
def steam_macos(self)->SteamMacOSGame|None:
return self.__steam_macos
@steam_macos.setter
def steam_macos(self,data:SteamMacOSGame|None):
if not data:
self.__steam_macos = None
else:
if not isinstance(data,SteamMacOSGame):
raise TypeError("SteamWindowsGame")
self.__steam_macos = data
def add_variable(self,name:str,value:str):
self.__variables[str(name)] = str(value)
def delete_variable(self,name):
if name in self.__variables:
del self.__variables[name]
def get_variable(self,name):
vars = dict(os.environ)
#vars.update(settings.variables)
vars.update(self.__variables)
game_data = self.game_data
if (game_data is not None):
vars.update(game_data.variables)
def serialize(self)->dict:
ret = {
'id': self.id,
'name': self.name,
'savegame_name': self.savegame_name,
'savegame_type': self.savegame_type.value,
'is_active': self.is_active,
'is_live': self.is_live,
}
if (self.windows):
ret['windows'] = self.windows.serialize()
if (self.linux):
ret['linux'] = self.linux.serialize()
if (self.macos):
ret['macos'] = self.macos.serialize()
if (self.steam_windows):
ret['steam_windows'] = self.steam_windows.serialize()
if (self.steam_linux):
ret['steam_linux'] = self.steam_linux.serialize()
if (self.steam_macos):
ret['steam_macos'] = self.steam_macos.serialize()
#if self.gog_windows:
# ret['gog_windows'] = self.gog_windows.serialize()
#if self.gog_linux:
# ret['gog_linux'] = self.gog_linux.serialize()
#if self.epic_windows:
# ret['epic_windows'] = self.epic_windows.serialize()
#if self.epic_linux:
# ret['epic_linux'] = self.epic_linux.serialize()
return ret
def save(self):
old_path = pathlib.Path(self.filename).resolve()
new_path = pathlib.Path(settings.gameconf_dir / '.'.join(self.id,'gameconf')).resolve()
if (str(old_path) != str(new_path)) and old_path.is_file():
os.unlink(old_path)
if not new_path.parent.is_dir():
os.makedirs(new_path.parent)
with open(new_path,'wt',encoding='utf-8') as ofile:
ofile.write(json.dumps(self.serialize(),ensure_ascii=False,indent=4))
GAMES={}
STEAM_GAMES={}
STEAM_LINUX_GAMES={}
STEAM_WINDOWS_GAMES={}
STEAM_MACOS_GAMES={}
def __init_games():
gameconf_dir = settings.gameconf_dir
if not os.path.isdir(gameconf_dir):
return
for gcf in (os.path.join(gameconf_dir,i) for i in os.listdir(gameconf_dir)):
if not os.path.isfile(gcf) or not gcf.endswith('.gameconf'):
continue
try:
game = Game.new_from_json_file(gcf)
if not game:
continue
except:
continue
GAMES[game.key] = game
if (game.steam_windows):
if not game.steam_windows.appid in STEAM_GAMES:
STEAM_GAMES[game.steam_windows.appid] = game
STEAM_WINDOWS_GAMES[game.steam_windows.appid] = game
if (game.steam_linux):
if not game.steam_linux.appid in STEAM_GAMES:
STEAM_GAMES[game.steam_linux.appid] = game
STEAM_LINUX_GAMES[game.steam_linux.appid] = game
if (game.steam_macos):
if not game.steam_macos.appid in STEAM_GAMES:
STEAM_GAMES[game.steam_macos.appid] = game
STEAM_MACOS_GAMES[game.steam_macos.appid] = game
__init_games()
def add_game(game:Game):
GAMES[game.key] = game
if game.steam_windows:
if not game.steam_windows.appid in STEAM_GAMES:
STEAM_GAMES[game.steam_windows.appid] = game
STEAM_WINDOWS_GAMES[game.steam_windows.appid] = game
if (game.steam_linux):
if not game.steam_linux.appid in STEAM_GAMES:
STEAM_GAMES[game.steam_linux.appid] = game
STEAM_LINUX_GAMES[game.steam_linux.appid] = game
if (game.steam_macos):
if not game.steam_macos.appid in STEAM_GAMES:
STEAM_GAMES[game.steam_macos.appid] = game
STEAM_MACOS_GAMES[game.steam_macos.appid] = game