Version 0.1

Created a small PyQt6-based window to manage Lethal Company mods by storing their zip files and extracting them correctly into the BepInEx folder and remove them again.

Creates a small config file which is currently set nearby the main.py as a yaml
dev
Peery 5 months ago
parent 7023135c33
commit 4705da0157
Signed by: pandro
SSH Key Fingerprint: SHA256:iBUZSuDxqYr4hYpe9U3BA9NJmXKpbGt4H0S8hUwIbrA

@ -0,0 +1,359 @@
import glob
import hashlib
import json
import logging
import os
import shutil
import sys
import tempfile
from typing import Tuple, List
from zipfile import ZipFile
from PyQt6.QtWidgets import QApplication
from files.settings import LCSettings
from window.main_window import MainWindow
class ModManager:
VERSION = "0.1"
def __init__(self, log_level: int = logging.INFO):
self.__logger = logging.getLogger("ModManager")
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(logging.Formatter(
fmt="%(asctime)s.%(msecs)03d [%(name)-12.12s] [%(levelname)-5.5s] %(message)s"))
self.__logger.addHandler(sh)
self.__logger.setLevel(log_level)
self.__app = QApplication([])
self.__window = MainWindow(self, self.__logger, version=ModManager.VERSION)
self.__settings = LCSettings()
self.available_mods = dict()
self.installed_mods = dict()
while self.__settings.get_game_folder() is None:
self.__window.on_action_set_game_folder()
self.create_manager_folder()
self.index_stored_mods()
self.index_installed_mods()
def run(self):
self.__logger.debug(f"Starting LCMod Manager {ModManager.VERSION} ...")
self.__window.show()
status = self.__app.exec()
sys.exit(status)
def __adjust_folder_paths(self, path: str) -> str:
"""
Adjusts a given path containing common issues to one compatible with BepInEx
:param input:
:return:
"""
if path.endswith(".dll") and "BepInEx" not in path:
path = os.path.join("BepInEx", "plugins", path)
if path.startswith("config" + os.path.sep) or path.startswith("plugins" + os.path.sep):
path = os.path.join("BepInEx", path)
if path.lower().startswith("bepinex") and not path.startswith("BepInEx"):
path = "BepInEx" + path[len("BepinEx"):]
return path
def create_manager_folder(self):
game_folder = self.__settings.get_game_folder()
if game_folder is not None:
manager_folder = os.path.join(game_folder, LCSettings.FOLDER_NAME)
if not os.path.isdir(manager_folder):
self.__logger.debug(f"Creating folder: {manager_folder}")
os.mkdir(manager_folder)
def is_folder_valid_game_folder(self, dir_path: str) -> bool:
req_files = ["Lethal Company.exe", "doorstop_config.ini"]
req_folders = ["Lethal Company_Data", "MonoBleedingEdge", "BepInEx"]
for entry in os.listdir(dir_path):
fullpath_entry = os.path.join(dir_path, entry)
if os.path.isfile(fullpath_entry):
if entry in req_files:
req_files.remove(entry)
elif os.path.isdir(fullpath_entry):
if entry in req_folders:
req_folders.remove(entry)
if len(req_folders) == len(req_files) == 0:
return True
else:
if len(req_files) == 1 and req_files[0] == "doorstop_config.ini" or \
len(req_folders) == 1 and req_folders[0] == "BepInEx":
self.__logger.error(f"new game path rejected! Following files and folders were "
f"expected but not present: {req_files + req_folders}\nBepInEx not installed?")
return False
self.__logger.error(f"new game path rejected! Following files and folders were "
f"expected but not present: {req_files + req_folders}")
return False
def set_game_folder(self, dir_path: str) -> bool:
"""
Sets the game_path to dir_path in the settings
:param dir_path:
:return: indicates if the new game folder path was accepted
"""
if len(dir_path) == 0:
self.__logger.debug("new game path selection got probably cancelled.")
return True
is_valid = self.is_folder_valid_game_folder(dir_path)
if is_valid:
s = self.__settings.get_settings()
s["game_path"] = dir_path
self.__settings.apply_changes()
self.create_manager_folder()
self.index_stored_mods()
self.index_installed_mods()
return is_valid
def is_mod_installed(self, mod_name: str) -> bool:
"""
Checks if a given mod has been installed to the game already
:param mod_name:
:return:
"""
r = self.available_mods[mod_name]
mod_files = r["mod_files"]
for i in range(len(mod_files)):
file = mod_files[i]
orig_file = r["orig_mod_files"][i]
if not os.path.exists(os.path.join(self.__settings.get_game_folder(), file)):
return False
else:
if os.path.isfile(os.path.join(self.__settings.get_game_folder(), file)):
hash_installed = self.get_file_hash(open(os.path.join(self.__settings.get_game_folder(), file), 'rb'))
modzip = os.path.join(self.__settings.get_mod_folder(), self.available_mods[mod_name]["path"])
hash_in_storage = self.get_file_hash(ZipFile(modzip).open(orig_file, 'r'))
if hash_installed != hash_in_storage:
return False
return True
def is_valid_mod_file(self, file_path: str):
"""
Checks if the given file is a valid mod zip file
:param file_path:
:return:
"""
if not os.path.isfile(file_path):
return False
zip = ZipFile(file_path)
contents = zip.namelist()
if "manifest.json" in contents:
return True
return False
def get_file_hash(self, file_buffer) -> str:
md5_obj = hashlib.md5()
while True:
buffer = file_buffer.read(8096)
if not buffer:
break
md5_obj.update(buffer)
return md5_obj.hexdigest()
def get_mod_hash(self, file_path: str) -> str:
md5_obj = hashlib.md5()
with open(file_path, 'rb') as file:
while True:
buffer = file.read(8096)
if not buffer:
break
md5_obj.update(buffer)
return md5_obj.hexdigest()
def get_mod_info(self, file_path: str) -> Tuple[str, str, List[str]]:
"""
Returns the name and version string of a given mod file
:param file_path:
:return:
"""
if not self.is_valid_mod_file(file_path):
self.__logger.error(f"Tried to get mod info of an invalid file: {file_path}")
raise ValueError(f"Tried to get mod info of an invalid file: {file_path}")
zip = ZipFile(file_path)
f = zip.open('manifest.json')
contents = zip.namelist()
orig_files = []
files = []
for file in contents:
if "icon.png" in file or "manifest.json" in file or file.endswith(".md"):
continue
orig_files.append(file)
file = self.__adjust_folder_paths(file)
files.append(file)
manifest = json.load(f)
return str(manifest["name"]), str(manifest["version_number"]), files, orig_files
def index_installed_mods(self):
"""
Checks all installed mods against the known mod files.
For unknown mods a placeholder is created
:return:
"""
self.installed_mods = dict()
if self.__settings.get_game_folder() is None:
return
files = []
to_search = [self.__settings.get_plugin_folder()]
while len(to_search) > 0:
curr = to_search.pop()
if os.path.isdir(curr):
r = os.listdir(curr)
for i in r:
to_search.append(os.path.join(curr, i))
elif os.path.isfile(curr) and curr.endswith('.dll'):
files.append(curr)
for mod_name in self.available_mods.keys():
if self.is_mod_installed(mod_name):
self.installed_mods[mod_name] = self.available_mods[mod_name]
unknown_mod = dict()
unresolved_files = files.copy()
for file in files:
for mod in self.installed_mods.keys():
if file[len(self.__settings.get_game_folder())+1:] in self.installed_mods[mod]["mod_files"]:
unresolved_files.remove(file)
if file in unresolved_files:
unknown_mod[os.path.basename(file)] = {"mod_files": [file]}
for key in unknown_mod.keys():
self.installed_mods[key] = unknown_mod[key]
self.__window.set_installed_mods(self.installed_mods)
self.__window.set_available_mods(self.available_mods)
def index_stored_mods(self):
"""
Goes through all mods in ModManager.FOLDER_NAME and tries to add them to the available mod list.
Ignores mods that are already on the list
:return:
"""
self.available_mods = dict()
if self.__settings.get_game_folder() is None:
return
for file in os.listdir(os.path.join(self.__settings.get_game_folder(), LCSettings.FOLDER_NAME)):
full_path = os.path.join(self.__settings.get_mod_folder(), str(file))
if not self.is_valid_mod_file(full_path):
self.__logger.warning(f"File {file} is not a valid mod file but inside the mod storage folder!")
mod_name, mod_version, mod_files, orig_mod_files = self.get_mod_info(full_path)
if mod_name is None:
self.__logger.warning(f"Mod \"{full_path}\" did not have the expected path. Ignoring it ...")
continue
self.available_mods[mod_name] = {"path": file, "version": mod_version, "mod_files": mod_files,
"orig_mod_files": orig_mod_files}
self.__window.set_available_mods(self.available_mods)
def add_mod_file(self, file_path: str):
"""
Adds the mod to the mod list
:param file_path:
:return:
"""
if self.__settings.get_settings()["game_path"] is None:
self.__logger.error("Can't add a mod without a valid game path!")
if not self.is_valid_mod_file(file_path):
self.__logger.warning(f"File {file_path} was not a mod file!")
return
dst = os.path.join(self.__settings.get_mod_folder(),
os.path.basename(file_path))
if os.path.isfile(dst):
hash1 = self.get_mod_hash(file_path)
hash2 = self.get_mod_hash(dst)
if hash1 != hash2:
self.__logger.info("Given file is different than the one stored. Overwriting it!")
else:
self.__logger.info("Given file is the same as the one stored. Ignoring it!")
return
self.create_manager_folder()
self.__logger.info(f"File {file_path} added as a mod file ...")
shutil.copy(file_path,
os.path.join(self.__settings.get_mod_folder(), os.path.basename(file_path)))
self.index_stored_mods()
self.index_installed_mods()
def install_mod(self, mod_name: str):
"""
Installs the given mod by extracting all files into the game directory.
This assumes that the .zip is structured like the game folder as all mods should be
:param mod_name:
:return:
"""
if mod_name not in self.available_mods.keys():
self.__logger.critical(f"Tried to install a mod that doesn't exist: {mod_name}")
return
self.__logger.info(f"Installing mod \"{mod_name}\" ...")
mod_zip = self.available_mods[mod_name]["path"]
with ZipFile(os.path.join(self.__settings.get_mod_folder(), mod_zip), 'r') as zip_ref:
#zip_ref.extractall(self.__settings.get_game_folder())
contents = zip_ref.namelist()
with tempfile.TemporaryDirectory() as tmp_dir:
for file in contents:
content = file
if "icon.png" in file or "manifest.json" in file or file.endswith(".md"):
continue
file = self.__adjust_folder_paths(file)
#print("Extracting", content, "to", tmp_dir)
zip_ref.extract(content, tmp_dir)
if content.endswith(os.path.sep):
#print("Skipped moving", os.path.join(tmp_dir, content))
continue
#print("Moving", os.path.join(tmp_dir, content), "to",
# os.path.join(self.__settings.get_game_folder(), file))
parent_dir = os.path.join(self.__settings.get_game_folder(), file).split(os.path.basename(file))[0]
if not os.path.exists(parent_dir):
os.mkdir(parent_dir)
shutil.move(os.path.join(tmp_dir, content), os.path.join(self.__settings.get_game_folder(), file))
self.index_installed_mods()
def uninstall_mod(self, mod_name: str):
"""
Uninstalls the given mod by removing all files (not folders) that the manager is aware of
Note: For untracked mods this will only be the .dll in the plugins folder
:param mod_name:
:return:
"""
self.__logger.info(f"Uninstalling mod \"{mod_name}\" ...")
for file in self.installed_mods[mod_name]["mod_files"]:
full_path = os.path.join(self.__settings.get_game_folder(), file)
if os.path.isfile(full_path):
self.__logger.debug(f"Deleting file \"{full_path}\" ...")
os.remove(full_path)
self.index_installed_mods()
def nuke_manager_files(self):
self.__logger.info("Deleting all manager related files ...")
self.__logger.debug(f"Deleting folder \"{self.__settings.get_mod_folder()}\"")
files = glob.glob(os.path.join(self.__settings.get_mod_folder(), '*.zip'))
for f in files:
self.__logger.debug(f"Deleting file \"{f}\"")
os.remove(f)
os.removedirs(self.__settings.get_mod_folder())
self.__logger.debug(f"Deleting configuration file \"{self.__settings.file_path}\"")
os.remove(self.__settings.file_path)
self.__app.exit(0)

@ -0,0 +1,46 @@
import logging
import os.path
import yaml
class LCSettings:
FOLDER_NAME = "LCModManager"
FILE_NAME = "lc_mod_manager_settings.yaml"
DEFAULT_SETTINGS = {
"game_path": None,
}
def __init__(self):
self.__logger = logging.getLogger("ModManager")
self.file_path = "./"+LCSettings.FILE_NAME
if not os.path.isfile(self.file_path):
self.__logger.warning(f"Couldn't find settings file \"{self.file_path}\" during LCSettings.init() ...")
self.__create_default_settings_file()
self.settings = yaml.safe_load(open(self.file_path, 'r'))
def __create_default_settings_file(self):
self.__logger.warning(f"Writing new default settings file at {self.file_path}")
yaml.safe_dump(LCSettings.DEFAULT_SETTINGS, open(self.file_path, 'w'))
def get_settings(self) -> dict:
return self.settings
def get_game_folder(self) -> str:
return self.settings["game_path"]
def get_mod_folder(self) -> str:
return os.path.join(self.settings["game_path"], LCSettings.FOLDER_NAME)
def get_plugin_folder(self) -> str:
return os.path.join(self.settings["game_path"], "BepInEx", "plugins")
def apply_changes(self):
"""
Writes the current settings to file in case there were changes
:return:
"""
self.__logger.info("Writing settings down to file!")
yaml.safe_dump(self.settings, open(self.file_path, 'w'))

@ -0,0 +1,8 @@
import logging
from ModManager import ModManager
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
m = ModManager(log_level=logging.INFO)
m.run()

@ -0,0 +1,2 @@
PyQt6>=6.6.1
pyyaml>=6.0.1

@ -0,0 +1,178 @@
import logging
import os.path
import sys
from typing import Dict, List
from PyQt6 import QtWidgets
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QStandardItemModel, QStandardItem
from window.mod_manager_window_export import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent, parent_logger: logging.Logger, version: str = ""):
super(MainWindow, self).__init__()
self.parent = parent
self.__logger = logging.getLogger("MainWindow")
for handler in parent_logger.handlers:
self.__logger.addHandler(handler)
self.__logger.setLevel(parent_logger.level)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setWindowTitle(f"LC Mod Manager {version}")
self.ui.actionAdd_new_mod.triggered.connect(self.on_action_add_new_mod)
self.ui.actionrefresh_mods.triggered.connect(self.on_action_refresh_mods)
self.ui.actionCheck_for_Updates.triggered.connect(self.on_action_check_for_updates)
self.ui.actionSet_game_folder.triggered.connect(self.on_action_set_game_folder)
self.ui.actionRemove_ALL_manager_files.triggered.connect(self.on_action_remove_all_files)
self.ui.DeleteModFilesButton.pressed.connect(self.on_pressed_delete_mod_files_button)
self.ui.ApplyChangesButton.pressed.connect(self.on_pressed_apply_changes_button)
self.ui.DisacrdChangesButton.pressed.connect(self.on_pressed_discard_changes_button)
self.ui.TODOButton.hide()
self.ui.ApplyChangesButton.hide()
self.ui.DisacrdChangesButton.hide()
self.ui.DeleteModFilesButton.hide()
self.ui.actionCheck_for_Updates.setDisabled(True)
def set_available_mods(self, available_mods: Dict[str, str]):
"""
Sets the given mods as the list of available mods
:param available_mods:
:return:
"""
item_model = QStandardItemModel(self.ui.AvailableModsList)
for mod_name in available_mods.keys():
item = QStandardItem(mod_name + f" ({available_mods[mod_name]['version']})")
item.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled)
item.setData(Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
item.setData(mod_name, Qt.ItemDataRole.UserRole)
item_model.appendRow(item)
if self.parent.is_mod_installed(mod_name):
item.setCheckState(Qt.CheckState.Checked)
item_model.itemChanged.connect(self.on_available_mod_item_changed)
self.ui.AvailableModsList.setModel(item_model)
def set_installed_mods(self, installed_mods: Dict[str, str]):
"""
Sets the given mods as the list of installed mods
:param installed_mods: string list of all the mods as to be written to the list
:return:
"""
item_model = QStandardItemModel(self.ui.InstalledModsListView)
for mod_name in installed_mods.keys():
if mod_name in self.parent.available_mods.keys():
mod_version = self.parent.available_mods[mod_name]['version']
else:
mod_version = "Not Tracked"
item = QStandardItem(mod_name + f" ({mod_version})")
item.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled)
item.setData(Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole)
item.setData(mod_name, Qt.ItemDataRole.UserRole)
item_model.appendRow(item)
item.setCheckState(Qt.CheckState.Checked)
item_model.itemChanged.connect(self.on_installed_mod_item_changed)
self.ui.InstalledModsListView.setModel(item_model)
# UI Callback functions
## Actions
def on_action_add_new_mod(self):
self.__logger.debug("Action: \"add new mod\" triggered!")
dialog = QtWidgets.QFileDialog(self, "Select Lethal Company mod")
dialog.setFileMode(QtWidgets.QFileDialog.FileMode.ExistingFiles)
result = dialog.getOpenFileName(filter='ZIP (*.zip)')
self.__logger.debug(f"user selected \"{result[0]}\"")
if not os.path.isfile(result[0]):
dialog = QtWidgets.QMessageBox()
dialog.setWindowTitle("Not a file")
dialog.setInformativeText(f"The given file \"{result}\" did not look like a file!")
dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
dialog.exec()
if not self.parent.is_valid_mod_file(result[0]):
dialog = QtWidgets.QMessageBox()
dialog.setWindowTitle("Not a valid mod file")
dialog.setInformativeText(
f"The given file \"{result}\" did not look like a mod file. Is the manifest.json present?")
dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
dialog.exec()
self.parent.add_mod_file(result[0])
def on_action_refresh_mods(self):
self.__logger.debug("Action: \"refresh mods\" triggered!")
self.parent.index_stored_mods()
self.parent.index_installed_mods()
def on_action_check_for_updates(self):
self.__logger.debug("Action: \"check for updates\" triggered!")
def on_action_set_game_folder(self):
self.__logger.debug("Action: \"set game folder\" triggered!")
dialog = QtWidgets.QFileDialog(self, "Select Lethal Company folder")
dialog.setFileMode(QtWidgets.QFileDialog.FileMode.Directory)
dialog.setOptions(QtWidgets.QFileDialog.Option.ShowDirsOnly)
result = dialog.getExistingDirectory()
dir_accepted = self.parent.set_game_folder(result)
while not dir_accepted:
dialog = QtWidgets.QMessageBox()
dialog.setWindowTitle("Invalid game path")
dialog.setInformativeText(f"The given path \"{result}\" did not look like the Lethal Company game folder!\n"
f"If you can't find it. Try using steam and select \"browse local files\"!")
dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning)
dialog.exec()
dialog = QtWidgets.QFileDialog(self, "Select Lethal Company folder")
dialog.setFileMode(QtWidgets.QFileDialog.FileMode.Directory)
dialog.setOptions(QtWidgets.QFileDialog.Option.ShowDirsOnly)
result = dialog.getExistingDirectory()
dir_accepted = self.parent.set_game_folder(result)
def on_action_remove_all_files(self):
self.__logger.debug("Action: \"remove all manager files\" triggered!")
self.parent.nuke_manager_files()
## Buttons
def on_pressed_delete_mod_files_button(self):
self.__logger.debug("Pressed button: \"Delete Mod Files\"")
raise NotImplementedError
def on_pressed_apply_changes_button(self):
self.__logger.debug("Pressed button: \"Apply Changes\"")
raise NotImplementedError
def on_pressed_discard_changes_button(self):
self.__logger.debug("Pressed button: \"Discard Changes\"")
raise NotImplementedError
def on_available_mod_item_changed(self, item: QStandardItem):
self.__logger.debug(f"Available Mod list item \"{item.text()}\" changed to {item.checkState()}")
mod_name = item.data(Qt.ItemDataRole.UserRole)
self.parent.install_mod(mod_name)
def on_installed_mod_item_changed(self, item: QStandardItem):
self.__logger.debug(f"Installed Mod list item \"{item.text()}\" changed to {item.checkState()}")
mod_name = item.data(Qt.ItemDataRole.UserRole)
if item.checkState() == Qt.CheckState.Unchecked: # mod should be uninstalled
accepted = QtWidgets.QMessageBox.question(self, "Really uninstall mod?",
f"Do you really want to uninstall the mod \"{mod_name}\"?\n"
"This could lead to permanent data loss if it wasn't tracked!")
if accepted:
self.parent.uninstall_mod(mod_name)
else:
return

@ -0,0 +1,141 @@
# Form implementation generated from reading ui file 'mod_manager_window.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 594)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.ModListLayout = QtWidgets.QVBoxLayout()
self.ModListLayout.setObjectName("ModListLayout")
self.InstalledModLayout = QtWidgets.QVBoxLayout()
self.InstalledModLayout.setObjectName("InstalledModLayout")
self.InstalledModsLabel = QtWidgets.QLabel(parent=self.centralwidget)
font = QtGui.QFont()
font.setPointSize(14)
font.setBold(True)
font.setUnderline(False)
font.setWeight(75)
font.setKerning(True)
self.InstalledModsLabel.setFont(font)
self.InstalledModsLabel.setObjectName("InstalledModsLabel")
self.InstalledModLayout.addWidget(self.InstalledModsLabel)
self.InstalledModsListView = QtWidgets.QListView(parent=self.centralwidget)
self.InstalledModsListView.setObjectName("InstalledModsListView")
self.InstalledModLayout.addWidget(self.InstalledModsListView)
self.ModListButtonsLayout = QtWidgets.QHBoxLayout()
self.ModListButtonsLayout.setObjectName("ModListButtonsLayout")
self.ApplyChangesButton = QtWidgets.QPushButton(parent=self.centralwidget)
self.ApplyChangesButton.setObjectName("ApplyChangesButton")
self.ModListButtonsLayout.addWidget(self.ApplyChangesButton)
self.DisacrdChangesButton = QtWidgets.QPushButton(parent=self.centralwidget)
self.DisacrdChangesButton.setObjectName("DisacrdChangesButton")
self.ModListButtonsLayout.addWidget(self.DisacrdChangesButton)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.ModListButtonsLayout.addItem(spacerItem)
self.InstalledModLayout.addLayout(self.ModListButtonsLayout)
self.ModListLayout.addLayout(self.InstalledModLayout)
self.horizontalLayout.addLayout(self.ModListLayout)
self.ActionListLayout = QtWidgets.QVBoxLayout()
self.ActionListLayout.setObjectName("ActionListLayout")
self.AvailableModsLabel = QtWidgets.QLabel(parent=self.centralwidget)
font = QtGui.QFont()
font.setPointSize(14)
font.setBold(True)
font.setUnderline(False)
font.setWeight(75)
font.setKerning(True)
self.AvailableModsLabel.setFont(font)
self.AvailableModsLabel.setObjectName("AvailableModsLabel")
self.ActionListLayout.addWidget(self.AvailableModsLabel)
self.AvailableModsList = QtWidgets.QListView(parent=self.centralwidget)
self.AvailableModsList.setObjectName("AvailableModsList")
self.ActionListLayout.addWidget(self.AvailableModsList)
self.AvailableModsButtonLayout = QtWidgets.QHBoxLayout()
self.AvailableModsButtonLayout.setObjectName("AvailableModsButtonLayout")
self.DeleteModFilesButton = QtWidgets.QPushButton(parent=self.centralwidget)
self.DeleteModFilesButton.setObjectName("DeleteModFilesButton")
self.AvailableModsButtonLayout.addWidget(self.DeleteModFilesButton)
self.TODOButton = QtWidgets.QPushButton(parent=self.centralwidget)
self.TODOButton.setObjectName("TODOButton")
self.AvailableModsButtonLayout.addWidget(self.TODOButton)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.AvailableModsButtonLayout.addItem(spacerItem1)
self.ActionListLayout.addLayout(self.AvailableModsButtonLayout)
self.horizontalLayout.addLayout(self.ActionListLayout)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 30))
self.menubar.setObjectName("menubar")
self.menuSettings = QtWidgets.QMenu(parent=self.menubar)
self.menuSettings.setObjectName("menuSettings")
self.menuAdd = QtWidgets.QMenu(parent=self.menubar)
self.menuAdd.setObjectName("menuAdd")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.actionSet_game_folder = QtGui.QAction(parent=MainWindow)
self.actionSet_game_folder.setObjectName("actionSet_game_folder")
self.actionrefresh_detected_mods = QtGui.QAction(parent=MainWindow)
self.actionrefresh_detected_mods.setObjectName("actionrefresh_detected_mods")
self.actionAdd_new_Mod = QtGui.QAction(parent=MainWindow)
self.actionAdd_new_Mod.setObjectName("actionAdd_new_Mod")
self.actionAdd_new_mod = QtGui.QAction(parent=MainWindow)
self.actionAdd_new_mod.setObjectName("actionAdd_new_mod")
self.actionrefresh_mods = QtGui.QAction(parent=MainWindow)
self.actionrefresh_mods.setObjectName("actionrefresh_mods")
self.actionRemove_ALL_manager_files = QtGui.QAction(parent=MainWindow)
self.actionRemove_ALL_manager_files.setObjectName("actionRemove_ALL_manager_files")
self.actionCheck_for_Updates = QtGui.QAction(parent=MainWindow)
self.actionCheck_for_Updates.setObjectName("actionCheck_for_Updates")
self.menuSettings.addAction(self.actionSet_game_folder)
self.menuSettings.addAction(self.actionRemove_ALL_manager_files)
self.menuAdd.addAction(self.actionAdd_new_mod)
self.menuAdd.addAction(self.actionrefresh_mods)
self.menuAdd.addAction(self.actionCheck_for_Updates)
self.menubar.addAction(self.menuAdd.menuAction())
self.menubar.addAction(self.menuSettings.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.InstalledModsLabel.setText(_translate("MainWindow", "Installed Mods:"))
self.ApplyChangesButton.setText(_translate("MainWindow", "Apply Changes"))
self.DisacrdChangesButton.setText(_translate("MainWindow", "Discard Changes"))
self.AvailableModsLabel.setText(_translate("MainWindow", "Available Mods:"))
self.DeleteModFilesButton.setText(_translate("MainWindow", "Delete Mod files"))
self.TODOButton.setText(_translate("MainWindow", "TODO"))
self.menuSettings.setTitle(_translate("MainWindow", "Settings"))
self.menuAdd.setTitle(_translate("MainWindow", "Add"))
self.actionSet_game_folder.setText(_translate("MainWindow", "Set game folder"))
self.actionrefresh_detected_mods.setText(_translate("MainWindow", "Refresh Mods"))
self.actionAdd_new_Mod.setText(_translate("MainWindow", "Add new Mod"))
self.actionAdd_new_mod.setText(_translate("MainWindow", "Add new mod"))
self.actionrefresh_mods.setText(_translate("MainWindow", "Refresh mods"))
self.actionRemove_ALL_manager_files.setText(_translate("MainWindow", "Remove ALL manager files"))
self.actionCheck_for_Updates.setText(_translate("MainWindow", "Check for Updates"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())

@ -0,0 +1,5 @@
#!/bin/sh
set -e
clear && python -m PyQt6.uic.pyuic mod_manager_window.ui -o ../mod_manager_window_export.py -x

@ -0,0 +1,198 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>594</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="ModListLayout">
<item>
<layout class="QVBoxLayout" name="InstalledModLayout">
<item>
<widget class="QLabel" name="InstalledModsLabel">
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
<underline>false</underline>
<kerning>true</kerning>
</font>
</property>
<property name="text">
<string>Installed Mods:</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="InstalledModsListView"/>
</item>
<item>
<layout class="QHBoxLayout" name="ModListButtonsLayout">
<item>
<widget class="QPushButton" name="ApplyChangesButton">
<property name="text">
<string>Apply Changes</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="DisacrdChangesButton">
<property name="text">
<string>Discard Changes</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="ActionListLayout">
<item>
<widget class="QLabel" name="AvailableModsLabel">
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
<underline>false</underline>
<kerning>true</kerning>
</font>
</property>
<property name="text">
<string>Available Mods:</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="AvailableModsList"/>
</item>
<item>
<layout class="QHBoxLayout" name="AvailableModsButtonLayout">
<item>
<widget class="QPushButton" name="DeleteModFilesButton">
<property name="text">
<string>Delete Mod files</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="TODOButton">
<property name="text">
<string>TODO</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>30</height>
</rect>
</property>
<widget class="QMenu" name="menuSettings">
<property name="title">
<string>Settings</string>
</property>
<addaction name="actionSet_game_folder"/>
<addaction name="actionRemove_ALL_manager_files"/>
</widget>
<widget class="QMenu" name="menuAdd">
<property name="title">
<string>Add</string>
</property>
<addaction name="actionAdd_new_mod"/>
<addaction name="actionrefresh_mods"/>
<addaction name="actionCheck_for_Updates"/>
</widget>
<addaction name="menuAdd"/>
<addaction name="menuSettings"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionSet_game_folder">
<property name="text">
<string>Set game folder</string>
</property>
</action>
<action name="actionrefresh_detected_mods">
<property name="text">
<string>Refresh Mods</string>
</property>
</action>
<action name="actionAdd_new_Mod">
<property name="text">
<string>Add new Mod</string>
</property>
</action>
<action name="actionAdd_new_mod">
<property name="text">
<string>Add new mod</string>
</property>
</action>
<action name="actionrefresh_mods">
<property name="text">
<string>Refresh mods</string>
</property>
</action>
<action name="actionRemove_ALL_manager_files">
<property name="text">
<string>Remove ALL manager files</string>
</property>
</action>
<action name="actionCheck_for_Updates">
<property name="text">
<string>Check for Updates</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
Loading…
Cancel
Save