diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /libs/installer_wizard/src/utils.py | |
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem,
Proton/umu-run integration, and Flatpak packaging.
Key features:
- FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak)
- Proton/GE-Proton/umu-run launcher with env var forwarding
- Flatpak support (sandbox-aware VFS, NXM handler, umu-run)
- Wine prefix management UI
- Case-insensitive path resolution for Linux filesystems
- QSettings-safe INI handling (avoids Bethesda INI corruption)
- Portable instance support with auto-generated launcher scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/installer_wizard/src/utils.py')
| -rw-r--r-- | libs/installer_wizard/src/utils.py | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/libs/installer_wizard/src/utils.py b/libs/installer_wizard/src/utils.py new file mode 100644 index 0000000..da85c48 --- /dev/null +++ b/libs/installer_wizard/src/utils.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import re +from collections.abc import Sequence +from pathlib import Path +from typing import cast + +from wizard.tweaks import WizardINISetting, WizardINISettingEdit + + +def make_obscript_ini_tweaks(tweaks: Sequence[WizardINISetting]) -> str: + lines: list[str] = [] + + for tweak in tweaks: + if not isinstance(tweak, WizardINISettingEdit): + continue + line = f"{tweak.section} {tweak.setting} to {tweak.value}" + if tweak.comment: + line += f" ; {tweak.comment}" + + lines.append(line) + + return "\n".join(["; Generated by Mod Organizer 2 via Wizard"] + sorted(lines)) + + +def make_standard_ini_tweaks(tweaks: Sequence[WizardINISetting]) -> str: + # group Tweaks per section + sections: dict[str, list[WizardINISetting]] = {m.section: [] for m in tweaks} + for m in tweaks: + sections[m.section].append(m) + + lines: list[str] = [] + + for k in sorted(sections): + s_tweaks = sorted(sections[k], key=lambda m: m.setting) + lines.append(f"[{k}]") + for tw in s_tweaks: + if isinstance(tw, WizardINISettingEdit): + line = f"{tw.setting} = {tw.value}" + if tw.comment: + line += f" # {tw.comment}" + else: + line = f"# {tw.setting} - disabled" + lines.append(line) + lines.append("\n") + + return "\n".join(lines[:-1]) + + +def merge_standard_ini_tweaks(tweaks: Sequence[WizardINISetting], file: Path) -> str: + import sys + + print(f"Cannot merge INI Tweaks for {file.name}.", file=sys.stderr) + return make_standard_ini_tweaks(tweaks) + + +def merge_obscript_ini_tweaks(tweaks: Sequence[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: str): + 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: list[str] = [] + 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: + assert format_string is not None + 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 = cast(str, settings[section_key][setting].comment) + 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(): + line = "" + 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: Sequence[WizardINISetting]) -> str: + is_set = [ + tw.section.lower() in ("set", "setgs", "setnumericgamesetting") for tw in tweaks + ] + + # assume there is no mix + if all(is_set): + return make_obscript_ini_tweaks(tweaks) + else: + return make_standard_ini_tweaks(tweaks) + + +def merge_ini_tweaks(tweaks: Sequence[WizardINISetting], file: Path) -> str: + is_set = [ + tw.section.lower() in ("set", "setgs", "setnumericgamesetting") for tw in tweaks + ] + + # assume there is no mix + if all(is_set): + return merge_obscript_ini_tweaks(tweaks, file) + else: + return merge_standard_ini_tweaks(tweaks, file) |
