42 Commits
Author SHA1 Message Date
Mikaël Capelle a0ca112ed0 Bump stubs. 2022-05-05 13:15:52 +02:00
Mikaël Capelle 7ef97f047c Merge pull request #4 from ModOrganizer2/update-cmake-common-qt6
Update cmake common qt6
2022-04-28 17:54:50 +02:00
Mikaël Capelle aff1cbb944 Fixes for Qt6. 2022-04-22 13:18:04 +02:00
Mikaël Capelle 6492f84eac Minor fixes. 2022-04-19 23:33:35 +02:00
Mikaël Capelle 7b3dc82f46 Update following cmake_common changes.
# Conflicts:
#	CMakeLists.txt
2022-04-19 22:03:11 +02:00
Jeremy Rimpo 76faacba19 Merge remote-tracking branch 'origin/master' into qt6 2022-04-17 20:17:40 -05:00
Mikaël Capelle 0c8d7261fd Merge pull request #3 from Holt59/update-interpreter
Update interpreter to fix issues with case-sensitive comparisons.
2022-02-05 14:33:38 +01:00
Mikaël Capelle 3cf15300fb Update interpreter to fix issues with case-sensitive comparisons. 2022-02-05 14:31:39 +01:00
Jeremy Rimpo 3bfd181704 Installer image improvements
- Expand size of options / image row
- Resize image with correct aspect ratio
- Add maximize button to titlebar
2021-12-26 01:23:16 -06:00
Jeremy Rimpo bffeb8c05f More PyQt6 enum fixes 2021-12-26 01:16:42 -06:00
Jeremy Rimpo f7a9c95d5b Fix issues with PyQt6 2021-12-25 20:54:10 -06:00
Jeremy Rimpo 793ab6273b Reformat the translations 2021-12-09 19:39:50 -06:00
Jeremy Rimpo 7e5ec2d11a Fix pylupdate parsing 2021-12-06 23:50:35 -06:00
Jeremy Rimpo ca343602ec PyQt6 Compatibility 2021-12-06 05:25:26 -06:00
Jeremy Rimpo e2202073dc Changes to compile with Qt6 + Boost 1.77 2021-12-05 03:42:40 -06:00
Mikaël Capelle fb91a1d2b6 Merge pull request #2 from Holt59/fix-optional-plugins
Fix not-selected plugins
2021-03-13 13:49:42 +01:00
Mikaël Capelle 9f71ac1517 Move not-selected plugins to 'optional'. 2021-03-13 13:48:45 +01:00
Mikaël Capelle 775d764950 Fix translations. 2021-02-06 19:28:26 +01:00
Mikaël Capelle 538a0f73e9 Make placeholder text not-translatable. 2021-02-03 18:35:19 +01:00
Mikaël Capelle ecefc5eb8a Fix FOMOD/OMOD detection. 2020-11-27 20:30:06 +01:00
Mikaël Capelle b9653b8322 Merge pull request #1 from ModOrganizer2/cmake-common
Add CMakeLists for MO2 build.
2020-11-25 17:51:27 +01:00
Mikaël Capelle b39be8c295 Add CMakeLists for MO2 build. 2020-11-23 10:56:59 +01:00
Mikaël Capelle 28deb0ce25 Fix release script. 2020-11-23 09:15:55 +01:00
Mikaël Capelle 1cf557a33c Bump version to 1.0.2. 2020-11-17 22:08:58 +01:00
Mikaël Capelle 753871d62a Fix getPluginStatus. 2020-11-17 22:05:56 +01:00
Mikaël Capelle 8b49e0d3cd Do not move non-enabled ESPs to optional. 2020-11-17 19:38:22 +01:00
Mikaël Capelle 52e20cf676 Update mobase stubs in setup.cfg. 2020-11-15 21:53:05 +01:00
Mikaël Capelle b695baaf1d Bump version to 1.0.1. 2020-11-15 21:42:54 +01:00
Mikaël Capelle c0104b0951 Remember previous install options. Minor fixes. 2020-11-15 21:40:50 +01:00
Mikaël Capelle 759291e6d4 Fix missing import. 2020-11-15 19:48:35 +01:00
Mikaël Capelle 83d39ee762 Handle OBSE INI tweaks. 2020-11-15 19:47:33 +01:00
Mikaël Capelle c1385c7b96 Hide previous pixmap in select options. 2020-11-15 16:46:07 +01:00
Mikaël Capelle a6dadf39eb Remove unused imports. Fix strings. 2020-11-09 22:37:22 +01:00
Mikaël Capelle c2b6d8da43 More Tweaks stuff. 2020-11-09 22:35:25 +01:00
Mikaël Capelle 4fe5299a42 Fix context when going back. 2020-11-09 21:47:05 +01:00
Mikaël Capelle 88ac3075f0 Display INI Tweaks at the end. 2020-11-09 21:46:54 +01:00
Mikaël Capelle a5e896ff1b Allow subpackages containing INI tweaks or docs. 2020-11-09 21:46:07 +01:00
Mikaël Capelle a3722f900b Double-click on item go to next page for SelectOne. 2020-11-09 13:12:24 +01:00
Mikaël Capelle d3df91babb Fix installer complete page. 2020-11-09 13:03:53 +01:00
Mikaël Capelle cdc2f14347 Add prefer_fomod and prefer_omod settings. 2020-11-09 12:32:10 +01:00
Mikaël Capelle 29e4cad187 Fix mypy for CI. 2020-11-09 11:06:48 +01:00
Mikaël Capelle ec01ca3e92 Update README and scripts. 2020-11-09 11:04:07 +01:00
20 changed files with 1088 additions and 398 deletions
+24
View File
@@ -0,0 +1,24 @@
name: linters
on: [push, pull_request]
jobs:
checks:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.8]
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox
- name: Test with tox
run: tox -e py38-lint
+7
View File
@@ -8,5 +8,12 @@ src/ui/*.py
.tox
.vscode
# MO2 build files:
vsbuild
mob.log
# Generated files:
lib
src/lib
installer_wizard
installer_wizard-*.zip
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.16)
if(DEFINED DEPENDENCIES_DIR)
include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/mo2.cmake)
else()
include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake)
endif()
project(installer_wizard LANGUAGES NONE)
add_subdirectory(src)
+67 -2
View File
@@ -2,9 +2,74 @@
This plugin can be used to install BAIN archives containing a wizard script (`wizard.txt`).
## How to use?
## How to install?
Go to the releases page and download the latest release for your MO2 version.
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
Below are the steps to setup a development environment.
1. Clone this repository into the Mod Organizer 2 plugins folder.
```bash
# (Optional) you can change the name of the folder:
git clone https://github.com/Holt59/modorganizer-installer_wizard installer_wizard
```
2. **Requirements:** You need a Python 3.8 installation. The list of requirements is in
[`requirements.txt`](requirements.txt):
```bash
# Those are only the development requirements.
pip install -r requirements
```
3. "Build" the installer:
```bash
# This will install the 3rd party libraries in src/lib (required for the installer) and convert the .ui files into .py files.
make.ps1
```
4. Create a root `__init__.py` - MO2 will not find and load the plugin unless there is a
`__init__.py` file in the root of the folder, so you need to create one:
```python
from .src import createPlugin
```
### Opening a Pull-Request
Once you are satisfied with your changes, you can
[open a pull-request](https://github.com/Holt59/modorganizer-installer_wizard/pulls).
Before doing so, you should check that your code is properly
formatted and clean:
```bash
# The -vv option is mandatory, otherwise tox will crash...
tox -vv -e py38-lint
```
### The interpreter
The interpreter used by the installer is from the
[`bain-wizard-interpreter`](https://github.com/Holt59/bain-wizard-interpreter) package.
For issues related to interpreter (i.e. the script is wrongly parsed), open issues on the interpreter repository.
## License
-163
View File
@@ -1,163 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0">
<context>
<name>WizardInstallerComplete</name>
<message>
<location filename="src/ui/wizardinstallercomplete.py" line="92"/>
<source>Form</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallercomplete.py" line="93"/>
<source>The installer script has finished. The following sub-packages and plugins will be installed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallercomplete.py" line="94"/>
<source>Sub-Packages:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallercomplete.py" line="95"/>
<source>Plugins:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallercomplete.py" line="96"/>
<source>INI Tweaks:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallercomplete.py" line="97"/>
<source>Notes:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>WizardInstallerDialog</name>
<message>
<location filename="src/ui/wizardinstallerdialog.py" line="75"/>
<source>BAIN Wizard Installer</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerdialog.py" line="76"/>
<source>Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerdialog.py" line="77"/>
<source>Manual</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerdialog.py" line="78"/>
<source>Script</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerdialog.py" line="79"/>
<source>Back</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerdialog.py" line="80"/>
<source>Next</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerdialog.py" line="81"/>
<source>Cancel</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>WizardInstallerError</name>
<message>
<location filename="src/ui/wizardinstallererror.py" line="52"/>
<source>Form</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallererror.py" line="54"/>
<source>TextLabel</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>WizardInstallerPage</name>
<message>
<location filename="src/ui/wizardinstallerpage.py" line="91"/>
<source>Form</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerpage.py" line="92"/>
<source>TextLabel</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerpage.py" line="93"/>
<source>Options:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerpage.py" line="94"/>
<source>Description:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>WizardInstallerRequires</name>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="113"/>
<source>Form</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="114"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Warning:&lt;/span&gt; The following version requirements are not met for using this installer.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="115"/>
<source>Requirements</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="116"/>
<source>Graphics Extender</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="117"/>
<source>Wrye Bash</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="118"/>
<source>Need</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="119"/>
<source>Game</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="122"/>
<source>N/A</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="121"/>
<source>Have</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/ui/wizardinstallerrequires.py" line="123"/>
<source>Script Extender</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>
+3 -2
View File
@@ -6,8 +6,9 @@ $target = ".\installer_wizard\"
Remove-Item -Recurse -Force -ErrorAction Ignore $target
New-Item -Path $target -Type Directory | Out-Null
Copy-Item -Recurse -Path .\src\* -Exclude "*.ui", "__pycache__" -Destination .\installer_wizard
Copy-Item .\installer_wizard_en.ts .\README.md .\LICENSE $target
Copy-Item -Recurse -Path .\src\* -Exclude "*.ui" -Destination $target
Get-ChildItem -Recurse $target -Include "__pycache__" | Remove-Item -Recurse -Force
Copy-Item .\installer_wizard_en.ts, .\README.md, .\LICENSE $target
# Find the version:
$ctx = Get-Content .\src\installer.py | Select-String -Pattern "def version\(self\):" -Context 0, 1
+2 -2
View File
@@ -1,5 +1,5 @@
# Install the lib:
pip install --target=.\src\lib --upgrade git+https://github.com/Holt59/bain-wizard-interpreter | Out-Null
pip install --target=.\src\lib --upgrade -r .\plugin-requirements.txt | Out-Null
# Convert ui files:
Get-ChildItem -Recurse -File -Include "*.ui" | ForEach-Object {
@@ -7,4 +7,4 @@ Get-ChildItem -Recurse -File -Include "*.ui" | ForEach-Object {
}
# Generate the .ts file:
pylupdate5 (Get-ChildItem -Exclude lib | Get-ChildItem -Recurse -File -Include "*.py") -ts installer_wizard_en.ts
pylupdate5 (Get-ChildItem src -Exclude lib | Get-ChildItem -Recurse -File -Include "*.py") -ts installer_wizard_en.ts
+1
View File
@@ -0,0 +1 @@
git+https://github.com/Holt59/bain-wizard-interpreter@v0.0.3
+7
View File
@@ -0,0 +1,7 @@
black
PyQt6
mypy
flake8
flake8-black
mobase-stubs
git+https://github.com/Holt59/bain-wizard-interpreter.git@v0.0.3
+16 -3
View File
@@ -6,6 +6,12 @@ extend-ignore =
E203,
exclude = lib,ui
[isort]
profile = black
multi_line_output = 3
known_mobase = mobase
sections=FUTURE,STDLIB,THIRDPARTY,MOBASE,FIRSTPARTY,LOCALFOLDER
[mypy]
warn_return_any = True
warn_unused_configs = True
@@ -17,6 +23,9 @@ ignore_missing_imports = True
[mypy-.ui.*]
ignore_missing_imports = True
[mypy-src.ui.*]
ignore_missing_imports = True
[mypy-wizard.antlr4.*]
ignore_errors = True
@@ -25,17 +34,21 @@ ignore_missing_imports = True
[tox:tox]
skipsdist = true
envlist = py38-lint
envlist = py310-lint
[testenv:py38-lint]
[testenv:py310-lint]
skip_install = true
deps =
git+https://github.com/TilmanK/PyQt6-stubs.git
mobase-stubs==2.5.0.dev5
git+https://github.com/Holt59/bain-wizard-interpreter@v0.0.3
black
mypy
flake8
flake8-black
mobase-stubs
isort
commands =
black src --check --diff --exclude "lib" --exclude "ui"
isort -c src
flake8 src
mypy src
+4
View File
@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.16)
add_custom_target(installer_wizard ALL)
mo2_configure_python(installer_wizard MODULE)
+209 -80
View File
File diff suppressed because it is too large Load Diff
+158 -47
View File
@@ -1,21 +1,23 @@
# -*- encoding: utf-8 -*-
import os
import re
import sys
from collections import defaultdict
from pathlib import Path
from typing import Optional, List, Union
from typing import Dict, List, Optional, Union
# MO2 ships with PyQt5, so you can use it in your plugins:
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtWidgets
from PyQt6 import QtWidgets
# MO2 ships with PyQt6, so you can use it in your plugins:
from PyQt6.QtWidgets import QApplication
from wizard.runner import WizardRunnerState
import mobase
from wizard.runner import WizardRunnerState
from .dialog import WizardInstallerDialog
from .runner import make_interpreter
from .utils import make_ini_tweaks, merge_ini_tweaks
class WizardInstaller(mobase.IPluginInstallerSimple):
@@ -27,8 +29,16 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
it is valid (for this installer) and then modify it if required before extraction.
"""
# Regex used to parse settings:
RE_DESCRIPTION = re.compile(r"select([0-9]+)-description")
RE_OPTION = re.compile(r"select([0-9]+)-option([0-9]+)")
_organizer: mobase.IOrganizer
# List of selected options:
_installerOptions: Dict[str, List[str]]
_installerUsed: bool
def __init__(self):
super().__init__()
@@ -42,14 +52,17 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
def name(self):
return "BAIN Wizard Installer"
def localizedName(self) -> str:
return self.tr("BAIN Wizard Installer")
def author(self):
return "Holt59"
def description(self):
return self._tr("Installer for BAIN archive containing wizard scripts.")
return self.tr("Installer for BAIN archive containing wizard scripts.")
def version(self):
return mobase.VersionInfo(1, 0, 0, mobase.ReleaseType.BETA)
return mobase.VersionInfo(1, 0, 2)
def isActive(self):
return self._organizer.pluginSetting(self.name(), "enabled")
@@ -57,9 +70,18 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
def settings(self):
return [
mobase.PluginSetting("enabled", "check to enable this plugin", True),
# TODO: I think this should be below FOMOD and above OMOD but this is
# currently not possible since OMOD has a priority of 500...
mobase.PluginSetting("priority", "priority of this installer", 80),
mobase.PluginSetting(
"prefer_fomod",
"prefer FOMOD installer over this one when possible",
True,
),
mobase.PluginSetting(
"prefer_omod",
"prefer OMOD installer over this one when possible",
False,
),
# Above FOMOD:
mobase.PluginSetting("priority", "priority of this installer", 120),
]
# Method for IPluginInstallerSimple:
@@ -73,14 +95,51 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
def onInstallationStart(
self, archive: str, reinstallation: bool, mod: Optional[mobase.IModInterface]
):
# TODO: Reload choices.
pass
self._installerUsed = False
self._installerOptions = {}
if mod:
settings = mod.pluginSettings(self.name())
# First extract the description:
descriptions: Dict[int, str] = {}
options: Dict[int, Dict[int, str]] = defaultdict(dict)
for setting, value in settings.items():
mdesc = WizardInstaller.RE_DESCRIPTION.match(setting)
if mdesc:
select = int(mdesc.group(1))
descriptions[select] = str(value)
mopt = WizardInstaller.RE_OPTION.match(setting)
if mopt:
select = int(mopt.group(1))
index = int(mopt.group(2))
options[select][index] = str(value)
for kdesc, desc in descriptions.items():
self._installerOptions[desc] = []
if kdesc in options:
for index in sorted(options[kdesc].keys()):
self._installerOptions[desc].append(options[kdesc][index])
def onInstallationEnd(
self, result: mobase.InstallResult, mod: Optional[mobase.IModInterface]
):
# TODO: Save choices.
pass
if result != mobase.InstallResult.SUCCESS or not self._installerUsed or not mod:
return
mod.clearPluginSettings(self.name())
for i, desc in enumerate(self._installerOptions):
mod.setPluginSetting(self.name(), f"select{i}-description", desc)
for iopt, opt in enumerate(self._installerOptions[desc]):
mod.setPluginSetting(self.name(), f"select{i}-option{iopt}", opt)
def _hasFomodInstaller(self) -> bool:
# Do not consider the NCC installer.
return self._organizer.isPluginEnabled("Fomod Installer")
def _hasOmodInstaller(self) -> bool:
return self._organizer.isPluginEnabled("Omod Installer")
def _getWizardArchiveBase(
self, tree: mobase.IFileTree, data_name: str, checker: mobase.ModDataChecker
@@ -107,17 +166,17 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
return None
def _getImageEntries(
def _getEntriesToExtract(
self,
tree: mobase.IFileTree,
extensions: List[str] = ["png", "jpg", "jpeg", "gif", "bmp"],
extensions: List[str] = ["png", "jpg", "jpeg", "gif", "bmp", "ini"],
) -> List[mobase.FileTreeEntry]:
"""
Retrieve all the entries corresponding to images in the given tree.
Retrieve all the entries to extract from the given tree.
Args:
tree: The tree containing the images.
extensions: The extensions for images.
tree: The tree.
extensions: The extensions of files.
Returns:
A list of entries corresponding to files with the given extensions.
@@ -153,9 +212,24 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
mobase.ModDataChecker # type: ignore
)
# Do the actual check - Convert to bool or do `is not None` to avoid
# issue with Python > C++ conversion:
return bool(self._getWizardArchiveBase(tree, data_name, checker))
# Retrieve the base:
base = self._getWizardArchiveBase(tree, data_name, checker)
if not base:
return False
# Check FOMOD:
fomod = base.exists("fomod/ModuleConfig.xml")
if (
fomod
and self._hasFomodInstaller()
and self._organizer.pluginSetting(self.name(), "prefer_fomod")
):
return False
# TODO: Check OMOD?
return True
def install(
self,
@@ -199,12 +273,11 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
if wizard is None:
return mobase.InstallResult.NOT_ATTEMPTED
images = self._getImageEntries(otree)
to_extract = self._getEntriesToExtract(otree)
# Extract the script:
# TODO: Extract pictures, similar to the FOMOD installer.
paths = self._manager().extractFiles([wizard] + images, silent=False)
if len(paths) != len(images) + 1:
paths = self._manager().extractFiles([wizard] + to_extract, silent=False)
if len(paths) != len(to_extract) + 1:
return mobase.InstallResult.FAILED
interpreter = make_interpreter(base, self._organizer)
@@ -216,15 +289,20 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
interpreter,
interpreter.make_top_level_context(Path(script), WizardRunnerState()),
name,
{Path(entry.path()): Path(path) for entry, path in zip(images, paths[1:])},
{
Path(entry.path()): Path(path)
for entry, path in zip(to_extract, paths[1:])
if not path.endswith(".ini")
},
self._installerOptions,
self._parentWidget(),
)
dialog.scriptButtonClicked.connect(lambda: os.startfile(script))
dialog.scriptButtonClicked.connect(lambda: os.startfile(script)) # type: ignore
# Note: Unlike the official installer, we do not have a "silent" setting,
# but it is really simple to add it.
if dialog.exec() == QtWidgets.QDialog.Accepted:
if dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted:
# We update the name with the user specified one:
name.update(dialog.name(), mobase.GuessQuality.USER)
@@ -245,21 +323,6 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
tree.merge(entry)
# Move all non-selected plugins to "optional":
optionals: List[mobase.FileTreeEntry] = []
for entry in tree:
if entry.isFile() and (
entry.hasSuffix(["esl", "esp", "esm"])
and entry.name() not in dialog.plugins()
):
optionals.append(entry)
# Do not keep empty optional folder:
if optionals:
optional = tree.addDirectory("optional")
for entry in optionals:
optional.insert(entry)
# Handle renames:
for original, new in dialog.renames().items():
# Entry should be at the root:
@@ -271,6 +334,54 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
tree.move(entry, new)
# Move not selected plugins to optional:
for plugin, enabled in dialog.plugins().items():
if not enabled:
entry = tree.find(plugin)
if not entry:
continue # silently fail since the plugin should be disabled
tree.addDirectory("optional").insert(entry)
# TODO: INI Tweaks:
alltweaks = dialog.tweaks()
for filename, tweaks in alltweaks.items():
# Find the original file (if any):
o_entry = tree.find(filename)
o_filename: Optional[str] = None
if o_entry:
# Find the filepath from the list of extracted files:
index = to_extract.index(o_entry)
# +1 because the first one is the script.
o_filename = paths[index + 1]
# If the file existed before, we keep the new one at the same
# place:
if o_entry or Path(filename).parts[0].lower() == "ini tweaks":
entry = tree.addFile(filename, replace_if_exists=True)
# Otherwise we create it in INI Tweaks
else:
entry = tree.addFile(
os.path.join("INI Tweaks", filename), replace_if_exists=True
)
filepath = self._manager().createFile(entry)
if not o_filename:
data = make_ini_tweaks(tweaks)
else:
data = merge_ini_tweaks(tweaks, Path(o_filename))
with open(filepath, "w") as fp:
fp.write(data)
# Mark stuff for saving:
self._installerUsed = True
self._installerOptions = dict(dialog.selectedOptions())
# Return the tree:
return tree
@@ -284,7 +395,7 @@ class WizardInstaller(mobase.IPluginInstallerSimple):
else:
return mobase.InstallResult.CANCELED
def _tr(self, str):
def tr(self, str) -> str:
# We need this to translate string in Python. Check the common documentation
# for more details:
return QApplication.translate("WizardInstaller", str)
+156
View File
@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>WizardInstaller</name>
<message>
<location filename="installer.py" line="56" />
<source>BAIN Wizard Installer</source>
<translation type="unfinished" />
</message>
<message>
<location filename="installer.py" line="62" />
<source>Installer for BAIN archive containing wizard scripts.</source>
<translation type="unfinished" />
</message>
</context>
<context>
<name>WizardInstallerComplete</name>
<message>
<location filename="ui\wizardinstallercomplete.ui" line="0" />
<source>The installer script has finished. The following sub-packages and plugins will be installed.</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallercomplete.ui" line="0" />
<source>Sub-Packages:</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallercomplete.ui" line="0" />
<source>Plugins:</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallercomplete.ui" line="0" />
<source>INI Tweaks:</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallercomplete.ui" line="0" />
<source>Notes:</source>
<translation type="unfinished" />
</message>
</context>
<context>
<name>WizardInstallerDialog</name>
<message>
<location filename="ui\wizardinstallerdialog.ui" line="0" />
<location filename="dialog.py" line="663" />
<source>Next</source>
<translation type="unfinished" />
</message>
<message>
<location filename="dialog.py" line="665" />
<source>Install anyway</source>
<translation type="unfinished" />
</message>
<message>
<location filename="dialog.py" line="669" />
<source>Install</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerdialog.ui" line="0" />
<source>BAIN Wizard Installer</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerdialog.ui" line="0" />
<source>Name</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerdialog.ui" line="0" />
<source>Manual</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerdialog.ui" line="0" />
<source>Script</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerdialog.ui" line="0" />
<source>Back</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerdialog.ui" line="0" />
<source>Cancel</source>
<translation type="unfinished" />
</message>
</context>
<context>
<name>WizardInstallerPage</name>
<message>
<location filename="ui\wizardinstallerpage.ui" line="0" />
<source>Options:</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerpage.ui" line="0" />
<source>Description:</source>
<translation type="unfinished" />
</message>
</context>
<context>
<name>WizardInstallerRequires</name>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=" font-weight:600;"&gt;Warning:&lt;/span&gt; The following version requirements are not met for using this installer.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>Requirements</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>Graphics Extender</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>Wrye Bash</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>Need</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>Game</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>N/A</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>Have</source>
<translation type="unfinished" />
</message>
<message>
<location filename="ui\wizardinstallerrequires.ui" line="0" />
<source>Script Extender</source>
<translation type="unfinished" />
</message>
</context>
</TS>
+27 -9
View File
@@ -1,17 +1,16 @@
# -*- encoding: utf-8 -*-
import sys
from pathlib import Path
from typing import Iterable, List, Optional
import mobase
from wizard.interpreter import WizardInterpreter
from wizard.manager import ManagerModInterface
from wizard.severity import SeverityContext
from wizard.value import SubPackage, SubPackages
from wizard.utils import make_runner_context_factory
from wizard.value import SubPackage, SubPackages
import mobase
class MO2SubPackage(SubPackage):
@@ -73,6 +72,21 @@ class MO2ManagerModInterface(ManagerModInterface):
if checker:
if checker.dataLooksValid(entry) == mobase.ModDataChecker.VALID:
self._subpackages.append(MO2SubPackage(entry))
continue
# Add entry with INI tweaks:
if entry.exists("INI Tweaks") or entry.exists("INI"):
self._subpackages.append(MO2SubPackage(entry))
continue
# We add folder with format "XXX Docs" where "XXX" is a number.
parts = entry.name().split()
if (
len(parts) >= 2
and parts[0].isdigit()
and parts[1].lower().startswith("doc")
):
self._subpackages.append(MO2SubPackage(entry))
@property
def subpackages(self) -> SubPackages:
@@ -129,7 +143,11 @@ class MO2ManagerModInterface(ManagerModInterface):
path = None
else:
path = Path(filepath)
files = self._organizer.findFiles(path.parent.as_posix(), path.name)
parent = path.parent.as_posix()
if parent == ".":
parent = ""
files = self._organizer.findFiles(parent, "*" + path.name)
if files:
path = Path(files[0])
else:
@@ -146,11 +164,11 @@ class MO2ManagerModInterface(ManagerModInterface):
def getPluginStatus(self, filename) -> int:
state = self._organizer.pluginList().state(filename)
if state & mobase.PluginState.MISSING:
return -1
if state & mobase.PluginState.INACTIVE:
if state == mobase.PluginState.ACTIVE:
return 2
if state == mobase.PluginState.INACTIVE:
return 0 # Or 1?
return 2
return -1
def getFilename(self, filepath: str) -> str:
path = self._resolve(filepath)
+203 -82
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
@@ -30,52 +30,32 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>The installer script has finished. The following sub-packages and plugins will be installed.</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Sub-Packages:</string>
</property>
<property name="heading" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="subpackagesList"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Plugins:</string>
</property>
<property name="heading" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="pluginsList"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="tweaksWidget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_7">
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<property name="leftMargin">
<number>0</number>
</property>
@@ -89,58 +69,199 @@
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="tweaksLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="lineWidth">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="packagesWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>15</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<property name="leftMargin">
<number>0</number>
</property>
<property name="text">
<string>INI Tweaks:</string>
<property name="topMargin">
<number>0</number>
</property>
<property name="heading" stdset="0">
<bool>true</bool>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="tweaksLayout">
<item>
<widget class="QPlainTextEdit" name="tweaksTextEdit1"/>
</item>
<item>
<widget class="QPlainTextEdit" name="tweaksTextEdit2"/>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Sub-Packages:</string>
</property>
<property name="heading" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="subpackagesList"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Plugins:</string>
</property>
<property name="heading" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="pluginsList"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tweaksWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="tweaksLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="text">
<string>INI Tweaks:</string>
</property>
<property name="heading" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="tweaksLayout">
<item>
<widget class="QListWidget" name="tweaksList"/>
</item>
<item>
<widget class="QPlainTextEdit" name="tweaksTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="notesWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QLabel" name="notesLabel">
<property name="text">
<string>Notes:</string>
</property>
<property name="heading" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="notesTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="notesLabel">
<property name="text">
<string>Notes:</string>
</property>
<property name="heading" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="notesTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
+13 -3
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
@@ -45,7 +45,7 @@
</size>
</property>
<property name="text">
<string>TextLabel</string>
<string notr="true"/>
</property>
</widget>
</item>
@@ -68,7 +68,7 @@
<item>
<widget class="QLabel" name="titleLabel">
<property name="text">
<string>TextLabel</string>
<string notr="true"/>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -82,6 +82,16 @@
<property name="readOnly">
<bool>true</bool>
</property>
<property name="markdown">
<string notr="true"/>
</property>
<property name="html">
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
+7 -4
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
@@ -27,7 +27,7 @@
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1,0,1">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,2,0,1">
<property name="spacing">
<number>6</number>
</property>
@@ -93,7 +93,7 @@
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>TextLabel</string>
<string notr="true"/>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -136,7 +136,10 @@
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
+1 -1
View File
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
+172
View File
@@ -0,0 +1,172 @@
# -*- encoding: utf-8 -*-
import re
from pathlib import Path
from typing import Dict, List, Mapping
from wizard.tweaks import WizardINISetting, WizardINISettingEdit
def make_obscript_ini_tweaks(tweaks: List[WizardINISetting]) -> str:
lines = []
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: List[WizardINISetting]) -> str:
# Group Tweaks per section:
sections: Mapping[str, List[WizardINISetting]] = {m.section: [] for m in tweaks}
for m in tweaks:
sections[m.section].append(m)
lines = []
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: List[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: 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.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: List[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)