diff --git a/README.md b/README.md index a87fb0b..b8db085 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,16 @@ This plugin can be used to install BAIN archives containing a wizard script (`wi Go to the [releases page](https://github.com/Holt59/modorganizer-installer_wizard/releases) and download the latest release for your MO2 version. +### A few words on INI Tweaks + +Mod Organizer 2 does not currently manage INI Tweaks, so the Wizard installer is partially functional +regarding them. + +- The installer will create proper INI Tweaks when requested, but these will not be applied + to the game INI files automatically. If INI Tweaks are present, a pop-up should appear at + the end of the installation. +- INI Tweaks for OBSE script are directly applied to the OBSE scripts. + ## How to contribute? ### Setting-up the environment diff --git a/src/installer.py b/src/installer.py index 4186967..4e56c41 100644 --- a/src/installer.py +++ b/src/installer.py @@ -311,8 +311,6 @@ class WizardInstaller(mobase.IPluginInstallerSimple): for filename, tweaks in alltweaks.items(): - print(tweaks) - # Find the original file (if any): o_entry = tree.find(filename) o_filename: Optional[str] = None diff --git a/src/utils.py b/src/utils.py index 47cd143..f08ff75 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,7 +1,9 @@ # -*- encoding: utf-8 -*- +import re + from pathlib import Path -from typing import List, Mapping +from typing import Dict, List, Mapping from wizard.tweaks import WizardINISetting, WizardINISettingEdit @@ -47,9 +49,107 @@ def make_standard_ini_tweaks(tweaks: List[WizardINISetting]) -> str: return "\n".join(lines[:-1]) +def merge_standard_ini_tweaks(tweaks: List[WizardINISetting], file: Path) -> str: + print(f"Cannot merge INI Tweaks for {file.name}.", file=sys.stderr) + return make_standard_ini_tweaks(tweaks) + + +def merge_obscript_ini_tweaks(tweaks: List[WizardINISetting], file: Path) -> str: + + # This is from Wrye Bash (and the function is inspired from Wrye Bash): + reComment = re.compile(";.*", re.U) + reDeleted = re.compile("" r";-(\w.*?)$", re.U) + reSet = re.compile("" r"\s*set\s+(.+?)\s+to\s+(.*)", re.I | re.U) + reSetGS = re.compile("" r"\s*setGS\s+(.+?)\s+(.*)", re.I | re.U) + reSetNGS = re.compile("" r"\s*SetNumericGameSetting\s+(.+?)\s+(.*)", re.I | re.U) + + _regex_tuples = ( + (reSet, "set", "set {} to {}"), + (reSetGS, "setgs", "setGS {} {}"), + (reSetNGS, "setnumericgamesetting", "SetNumericGameSetting {} {}"), + ) + + def _parse_obse_line(line): + for regex, sectionKey, format_string in _regex_tuples: + match = regex.match(line) + if match: + return match, sectionKey, format_string + return None, None, None + + # Read the original file: + with open(file, "r") as fp: + olines = fp.readlines() + + # Map setting name to value: + settings: Dict[str, Dict[str, WizardINISettingEdit]] = {} + deleted: Dict[str, Dict[str, WizardINISetting]] = {} + for tweak in tweaks: + if isinstance(tweak, WizardINISettingEdit): + if tweak.section not in settings: + settings[tweak.section.lower()] = {} + settings[tweak.section.lower()][tweak.setting] = tweak + else: + if tweak.section not in deleted: + deleted[tweak.section.lower()] = {} + deleted[tweak.section.lower()][tweak.setting] = tweak + + # Create the lines: + lines = [] + for line in olines: + line = line.rstrip() + maDeleted = reDeleted.match(line) + if maDeleted: + stripped = maDeleted.group(1) + else: + stripped = line + stripped = reComment.sub("", stripped).strip() + + match, section_key, format_string = _parse_obse_line(stripped) + + if match: + setting = match.group(1) + if section_key in settings and setting in settings[section_key]: + value = settings[section_key][setting].value + line = format_string.format(setting, value) + comment = "" + if settings[section_key][setting].comment: + comment = settings[section_key][setting].comment # type: ignore + comment += " " + comment += f"(set by MO2 via Wizard, was {match.group(2)})" + line = f"{line} ; {comment}" + del settings[section_key][setting] + elif ( + not maDeleted + and section_key in deleted + and setting in deleted[section_key] + ): + line = f";-{line}" + + lines.append(line) + + for section in settings.values(): + for setting in section.values(): + if setting.section.lower() == "set": + line = f"{setting.section} {setting.setting} to {setting.value}" + else: + line = f"{setting.section} {setting.setting} {setting.value}" + + if setting.comment: + comment = setting.comment + " (set by MO2 via Wizard)" + else: + comment = "(set by MO2 via Wizard)" + line = f"{line} ; {comment}" + + lines.append(line) + + return "\n".join(lines) + + def make_ini_tweaks(tweaks: List[WizardINISetting]) -> str: - is_set = [tw.section in ("set", "setGS", "SetNumericGameSetting") for tw in tweaks] + is_set = [ + tw.section.lower() in ("set", "setgs", "setnumericgamesetting") for tw in tweaks + ] # Assume there is no mix. if all(is_set): @@ -60,11 +160,12 @@ def make_ini_tweaks(tweaks: List[WizardINISetting]) -> str: def merge_ini_tweaks(tweaks: List[WizardINISetting], file: Path) -> str: - is_set = [tw.section in ("set", "setGS", "SetNumericGameSetting") for tw in tweaks] + is_set = [ + tw.section.lower() in ("set", "setgs", "setnumericgamesetting") for tw in tweaks + ] # Assume there is no mix. if all(is_set): - pass + return merge_obscript_ini_tweaks(tweaks, file) else: - pass - return "" + return merge_standard_ini_tweaks(tweaks, file)