Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
15789ca
WIP: Basic oblivion remaster support
Silarn Apr 24, 2025
26a4793
Merge branch 'master' of https://github.com/ModOrganizer2/modorganize…
Silarn Apr 25, 2025
5d9a778
Numerous updates
Silarn Apr 26, 2025
35d08fb
More file map updates
Silarn Apr 26, 2025
78ffffd
Many changes
Silarn Apr 27, 2025
8d55dde
Fix detach parent if directory parent is root
Silarn Apr 27, 2025
96c578d
Fixes for init
Silarn Apr 28, 2025
592acd1
Various updates
Silarn Apr 29, 2025
3586642
Add LOOT support
Silarn May 3, 2025
98cbe87
Reformat
Silarn May 4, 2025
1db99fe
Check all possible LOOT reg entries
Silarn May 4, 2025
d9af1ff
Rework UE4SS to install to Root
Silarn May 4, 2025
b23e892
[pre-commit.ci] Auto fixes from pre-commit.com hooks.
pre-commit-ci[bot] May 4, 2025
eec1cd7
Improved mod handling
Silarn May 5, 2025
e8291de
Handle top level directory being a Paks subdirectory
Silarn May 5, 2025
2b710d1
[pre-commit.ci] Auto fixes from pre-commit.com hooks.
pre-commit-ci[bot] May 5, 2025
146af8b
More datachecker refinements
Silarn May 6, 2025
9ae090c
Merge remote-tracking branch 'origin/obvrem' into obvrem
Silarn May 6, 2025
f2b28ed
Big restructure of datachecker
Silarn May 7, 2025
79a269c
Updates
Silarn May 8, 2025
2f379c9
Updates
Silarn May 12, 2025
3c5ca96
Implement UE4SS mod management tab
Silarn May 15, 2025
e7d5fdc
Paks management and more
Silarn May 20, 2025
6447d3c
Fix linting issues.
Holt59 May 21, 2025
a46b9e4
Pak fixes
Silarn May 22, 2025
ca4d9ad
Don't use private type
Silarn May 22, 2025
5fe88cf
Remove debug lines
Silarn May 22, 2025
5020934
Fix for null entries
Silarn May 22, 2025
86a1a6f
Fix extension removal for game dir
Silarn May 22, 2025
aa535d3
[pre-commit.ci] Auto fixes from pre-commit.com hooks.
pre-commit-ci[bot] May 22, 2025
18f8e23
Alternate check to appease Pyright
Silarn May 22, 2025
da4c0af
Merge remote-tracking branch 'origin/obvrem' into obvrem
Silarn May 22, 2025
d292c1c
Process entries out of the iterator
Silarn May 22, 2025
8751c24
Several improvements
Silarn May 22, 2025
a9b9c88
Documentation first pass
Silarn May 23, 2025
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
443 changes: 443 additions & 0 deletions games/game_oblivion_remaster.py

Large diffs are not rendered by default.

Empty file.
1 change: 1 addition & 0 deletions games/oblivion_remaster/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PLUGIN_NAME = "Oblivion Remastered Support Plugin"
207 changes: 207 additions & 0 deletions games/oblivion_remaster/game_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
from functools import cmp_to_key
from typing import Sequence

from PyQt6.QtCore import (
QByteArray,
QCoreApplication,
QDateTime,
QFile,
QFileInfo,
QStringConverter,
QStringEncoder,
qCritical,
qWarning,
)

import mobase


class OblivionRemasteredGamePlugins(mobase.GamePlugins):
"""
Reimplementation of GameGamebryo "GamePlugins" code, in the Skyrim style.

Should properly account for disabled plugins and the loadorder.txt profile file.
"""

def __init__(self, organizer: mobase.IOrganizer):
super().__init__()
self._last_read = QDateTime().currentDateTime()
self._organizer = organizer
# Not currently used. These plugins exist in the base game but are not enabled by default.
self._plugin_blacklist = ["TamrielLevelledRegion.esp", "AltarGymNavigation.esp"]

def writePluginLists(self, plugin_list: mobase.IPluginList) -> None:
if not self._last_read.isValid():
return
self.writePluginList(
plugin_list, self._organizer.profile().absolutePath() + "/plugins.txt"
)
self.writeLoadOrderList(
plugin_list, self._organizer.profile().absolutePath() + "/loadorder.txt"
)
self._last_read = QDateTime.currentDateTime()

def readPluginLists(self, plugin_list: mobase.IPluginList) -> None:
load_order_path = self._organizer.profile().absolutePath() + "/loadorder.txt"
load_order = self.readLoadOrderList(plugin_list, load_order_path)
plugin_list.setLoadOrder(load_order)
self.readPluginList(plugin_list)
self._last_read = QDateTime.currentDateTime()

def getLoadOrder(self) -> Sequence[str]:
load_order_path = self._organizer.profile().absolutePath() + "/loadorder.txt"
plugins_path = self._organizer.profile().absolutePath() + "/plugins.txt"

load_order_is_new = (
not self._last_read.isValid()
or not QFileInfo(load_order_path).exists()
or QFileInfo(load_order_path).lastModified() > self._last_read
)
plugins_is_new = (
not self._last_read.isValid()
or QFileInfo(plugins_path).lastModified() > self._last_read
)

if load_order_is_new or not plugins_is_new:
return self.readLoadOrderList(self._organizer.pluginList(), load_order_path)
else:
return self.readPluginList(self._organizer.pluginList())

def writePluginList(self, plugin_list: mobase.IPluginList, filePath: str):
self.writeList(plugin_list, filePath, False)

def writeLoadOrderList(self, plugin_list: mobase.IPluginList, filePath: str):
self.writeList(plugin_list, filePath, True)

def writeList(
self, plugin_list: mobase.IPluginList, filePath: str, load_order: bool
):
plugins_file = open(filePath, "w")
encoder = (
QStringEncoder(QStringConverter.Encoding.Utf8)
if load_order
else QStringEncoder(QStringConverter.Encoding.System)
)
plugins_text = "# This file was automatically generated by Mod Organizer.\n"
invalid_filenames = False
written_count = 0
plugins = plugin_list.pluginNames()
plugins_sorted = sorted(
plugins,
key=cmp_to_key(
lambda lhs, rhs: plugin_list.priority(lhs) - plugin_list.priority(rhs)
),
)
for plugin_name in plugins_sorted:
if (
load_order
or plugin_list.state(plugin_name) == mobase.PluginState.ACTIVE
):
result = encoder.encode(plugin_name)
if encoder.hasError():
invalid_filenames = True
qCritical("invalid plugin name %s" % plugin_name)
plugins_text += result.data().decode() + "\n"
written_count += 1

if invalid_filenames:
qCritical(
QCoreApplication.translate(
"MainWindow",
"Some of your plugins have invalid names! These "
+ "plugins can not be loaded by the game. Please see "
+ "mo_interface.log for a list of affected plugins "
+ "and rename them.",
)
)

if written_count == 0:
qWarning(
"plugin list would be empty, this is almost certainly wrong. Not saving."
)
else:
plugins_file.write(plugins_text)
plugins_file.close()

def readLoadOrderList(
self, plugin_list: mobase.IPluginList, file_path: str
) -> list[str]:
plugin_names = [
plugin for plugin in self._organizer.managedGame().primaryPlugins()
]
plugin_lookup: set[str] = set()
for name in plugin_names:
if name.lower() not in plugin_lookup:
plugin_lookup.add(name.lower())

try:
with open(file_path) as file:
for line in file:
if line.startswith("#"):
continue
plugin_file = line.rstrip("\n")
if plugin_file.lower() not in plugin_lookup:
plugin_lookup.add(plugin_file.lower())
plugin_names.append(plugin_file)
except FileNotFoundError:
return self.readPluginList(plugin_list)

return plugin_names

def readPluginList(self, plugin_list: mobase.IPluginList) -> list[str]:
plugins = [plugin for plugin in plugin_list.pluginNames()]
sorted_plugins: list[str] = []
primary = [plugin for plugin in self._organizer.managedGame().primaryPlugins()]
primary_lower = [plugin.lower() for plugin in primary]
for plugin_name in primary:
if plugin_list.state(plugin_name) != mobase.PluginState.MISSING:
plugin_list.setState(plugin_name, mobase.PluginState.ACTIVE)
sorted_plugins.append(plugin_name)
plugin_remove = [
plugin for plugin in plugins if plugin.lower() in primary_lower
]
for plugin in plugin_remove:
plugins.remove(plugin)

plugins_txt_exists = True
file_path = self._organizer.profile().absolutePath() + "/plugins.txt"
file = QFile(file_path)
if not file.open(QFile.OpenModeFlag.ReadOnly):
plugins_txt_exists = False
if file.size() == 0:
plugins_txt_exists = False
if plugins_txt_exists:
while not file.atEnd():
line = file.readLine()
file_plugin_name = QByteArray()
if line.size() > 0 and line.at(0).decode() != "#":
encoder = QStringEncoder(QStringEncoder.Encoding.System)
file_plugin_name = encoder.encode(line.trimmed().data().decode())
if file_plugin_name.size() > 0:
if file_plugin_name.data().decode().lower() in [
plugin.lower() for plugin in plugins
]:
plugin_list.setState(
file_plugin_name.data().decode(), mobase.PluginState.ACTIVE
)
sorted_plugins.append(file_plugin_name.data().decode())
plugins.remove(file_plugin_name.data().decode())

file.close()

for plugin_name in plugins:
plugin_list.setState(plugin_name, mobase.PluginState.INACTIVE)
else:
for plugin_name in plugins:
plugin_list.setState(plugin_name, mobase.PluginState.INACTIVE)

return sorted_plugins + plugins

def lightPluginsAreSupported(self) -> bool:
return False

def mediumPluginsAreSupported(self) -> bool:
return False

def blueprintPluginsAreSupported(self) -> bool:
return False
Loading