Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Extended ini support #126

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class Witcher3Game(BasicGame):
| GameLauncher | Name of the game launcher, relative to the game path (Optional) | `getLauncherName` | `str` |
| GameDataPath | Name of the folder containing mods, relative to game folder| `dataDirectory` | |
| GameDocumentsDirectory | Documents directory (Optional) | `documentsDirectory` | `str` or `QDir` |
| GameIniFiles | Config files in documents, for profile specific config (Optional) | `iniFiles` | `str` or `List[str]` |
| GameSavesDirectory | Directory containing saves (Optional, default to `GameDocumentsDirectory`) | `savesDirectory` | `str` or `QDir` |
| GameSaveExtension | Save file extension (Optional) `savegameExtension` | `str` |
| GameSteamId | Steam ID of the game (Optional) | `steamAPPId` | `List[str]` or `str` or `int` |
Expand Down
16 changes: 14 additions & 2 deletions basic_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class BasicGameMappings:
launcherName: BasicGameMapping[str]
dataDirectory: BasicGameMapping[str]
documentsDirectory: BasicGameMapping[QDir]
iniFiles: BasicGameMapping[list[str]]
savesDirectory: BasicGameMapping[QDir]
savegameExtension: BasicGameMapping[str]
steamAPPId: BasicGameOptionsMapping[str]
Expand Down Expand Up @@ -290,6 +291,15 @@ def __init__(self, game: BasicGame):
apply_fn=lambda s: QDir(s) if isinstance(s, str) else s,
default=BasicGameMappings._default_documents_directory,
)
self.iniFiles = BasicGameMapping(
game,
"GameIniFiles",
"iniFiles",
lambda g: [],
apply_fn=lambda value: [c.strip() for c in value.split(",")]
if isinstance(value, str)
else value,
)
self.savesDirectory = BasicGameMapping(
game,
"GameSavesDirectory",
Expand Down Expand Up @@ -406,7 +416,7 @@ def __init__(self):
self._fromName = self.__class__.__name__

self._gamePath = ""
self._featureMap = {}
self._featureMap = {mobase.SaveGameInfo: BasicGameSaveGameInfo()}

self._mappings: BasicGameMappings = BasicGameMappings(self)

Expand All @@ -430,7 +440,6 @@ def is_eadesktop(self) -> bool:

def init(self, organizer: mobase.IOrganizer) -> bool:
self._organizer = organizer
self._featureMap[mobase.SaveGameInfo] = BasicGameSaveGameInfo()
if self._mappings.originWatcherExecutables.get():
from .origin_utils import OriginWatcher

Expand Down Expand Up @@ -543,6 +552,9 @@ def getLauncherName(self) -> str:
def getSupportURL(self) -> str:
return self._mappings.supportURL.get()

def iniFiles(self) -> list[str]:
return self._mappings.iniFiles.get()

def executables(self) -> list[mobase.ExecutableInfo]:
execs: list[mobase.ExecutableInfo] = []
if self.getLauncherName():
Expand Down
62 changes: 59 additions & 3 deletions basic_game_ini.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,77 @@
import configparser
import os

import mobase
from PyQt6.QtCore import QDir

from .basic_features import (
BasicGameSaveGameInfo,
BasicLocalSavegames,
BasicModDataChecker,
GlobPatterns,
)
from .basic_game import BasicGame


def get_section_as_dict(
config: configparser.ConfigParser, section: str
) -> dict[str, str]:
try:
return dict(config[section])
except KeyError:
return {}


class BasicIniGame(BasicGame):
def __init__(self, path: str):
# Set the _fromName to get more "correct" errors:
self._fromName = os.path.basename(path)

# Read the file:
config = configparser.ConfigParser()
config = configparser.ConfigParser(
interpolation=configparser.ExtendedInterpolation()
)
config.optionxform = str # type: ignore
config.read(path)

# Just fill the class with values:
for k, v in config["DEFAULT"].items():
# Fill the class with values:
main_section = (
config["BasicGame"] if "BasicGame" in config else config["DEFAULT"]
)
for k, v in main_section.items():
setattr(self, k, v)

super().__init__()

# Add features
if "Features" in config:
features = config["Features"]
# BasicLocalSavegames
try:
# LocalSavegames = True
if features.getboolean("LocalSavegames"):
self._featureMap[mobase.LocalSavegames] = BasicLocalSavegames(
self.savesDirectory()
)
except ValueError:
# LocalSavegames = path
self._featureMap[mobase.LocalSavegames] = BasicLocalSavegames(
QDir(features["LocalSavegames"])
)
except KeyError:
pass

# SaveGamePreview = BasicGameSaveGameInfo
if preview := features.get("SaveGamePreview"):
self._featureMap[mobase.SaveGameInfo] = BasicGameSaveGameInfo(
get_preview=lambda p: p / preview
)

# BasicModDataChecker
if patterns := get_section_as_dict(config, "BasicModDataChecker"):
self._featureMap[mobase.ModDataChecker] = BasicModDataChecker(
GlobPatterns(
**{key: value.split(",") for key, value in patterns.items()},
move=get_section_as_dict(config, "BasicModDataChecker.move"),
)
)
59 changes: 59 additions & 0 deletions game_template.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
; Template for a BasicGame config
; Copy to game/game_{GameName}.ini
; Add all required sections and replace the values
; <- indicates comments and/or optional key/value pairs

; Required section and key/value pairs

[BasicGame]
Name = Name of the plugin
Author = Author of the plugin
Version = Version of the plugin
; Description = Description (Optional)
GameName = Name of the game, as displayed by MO2
GameShortName = Short name of the game
; GameNexusName = Nexus name of the game (Optional, default to `GameShortName`)
; GameValidShortNames = Other valid short names (Optional, comma-separated list)
; GameNexusId = Nexus ID of the game (Optional)
GameBinary = Name of the game executable, relative to the game path
; GameLauncher = Name of the game launcher, relative to the game path (Optional)
GameDataPath = Name of the folder containing mods, relative to game folder
; GameDocumentsDirectory = Documents directory (Optional)
; GameIniFiles = Config files in documents, for profile specific config (Optional, comma-separated list)
; GameSavesDirectory = Directory containing saves (Optional, default to `GameDocumentsDirectory`)
; GameSaveExtension = Save file extension (Optional)
; TODO: GameSaveAdditionalFiles = Additional save files, with %SAVE_NAME% replaced by the save file with GameSaveExtension, e.g. %SAVE_NAME%.png
; GameSteamId = Steam ID of the game (Optional): comma-separated list of values
; GameGogId = GOG ID of the game (Optional): comma-separated list of values
; GameOriginManifestIds = Origin Manifest ID of the game (Optional, comma-separated list)
; GameOriginWatcherExecutables = Executables to watch for Origin DRM (Optional, comma-separated list)
; GameEpicId = Epic ID (`AppName`) of the game (Optional, comma-separated list)
; GameEaDesktopId = EA Desktop ID of the game (Optional, comma-separated list)


; Optional sections

; All of the following sections and key / value pairs are optional
; No features are provided by default

[Features]
# TODO ForceLoadLibraries = list of .dll files to force load with the game binary for better compatiblity
; Profile specific save games
LocalSavegames = True (defaults to GameSavesDirectory) OR the path to the
; A preview image for the savegamesg
SaveGamePreview = ../image.ext (relative to the main save game file, see GameSavesDirectory and GameSaveExtension above)
; TODO: add a %SAVE_NAME% var to allow e.g. %SAVE_NAME%.png

[BasicModDataChecker]
; Comma-separated list of glob patterns, e.g. *.ext
valid =
invalid =
delete =


[BasicModDataChecker.move]
; Move matched files / folder to target ( or into target/ )
file1 = target_folder/renamed_file1
folder2 = target_folder/ => target_folder/folder2
; *.ext = target_folder/ => target_folder/*.ext
; TODO: subfolder globbing */*.ext = target_folder/ => target_folder/*.ext
Loading