Created
June 13, 2024 10:46
-
-
Save GregLando113/b1b2ef61c8f1aed26ca532214799129a to your computer and use it in GitHub Desktop.
ModOrganizer2 Delta Patcher Plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
@name MO2 Mod Delta Patcher | |
@author KAOS (xILARTH) | |
@description Allows you to automate creating zipfiles containing all mod changes | |
to allow you to keep multiple installs of the same mod playthough up-to-date. | |
This way you dont have to manually cherry-pick the new mods, and you dont have | |
to copy over GB's of redundant assets. | |
@usage - On the install you wish to update, Use the Gen Manifest tool to create a json file | |
of all your mods of that install. | |
- Copy that file over to the device with the install you want to update from. | |
- On the install you are updating from, use the Generate Delta Zipfile tool and | |
point it to the manifest file. The tool will then compare the manifest to your | |
active mods and create a zipfile of the difference, including your profile and | |
overwrite folders. | |
- Copy the zipfile to the install you wish to update, use to copy the mods and | |
profiles over to update as you see fit. | |
''' | |
import os | |
import pathlib | |
import sys | |
from PyQt6.QtCore import QCoreApplication, qCritical, QFileInfo | |
from PyQt6.QtGui import QIcon, QFileSystemModel | |
from PyQt6.QtWidgets import QFileDialog, QMessageBox, QProgressDialog | |
import mobase | |
import json, datetime | |
import zipfile | |
PLUGIN_VERSION = mobase.VersionInfo(1, 0, 0, 0) | |
class UnknownOutputPreferenceException(Exception): | |
"""Thrown if the user hasn't specified whether to output to a separate mod""" | |
pass | |
class MakeDeltaManifestTool(mobase.IPluginTool): | |
def __init__(self): | |
self.__organizer = None | |
self.__parentWidget = None | |
super(MakeDeltaManifestTool, self).__init__() | |
def init(self, organizer): | |
self.__organizer = organizer | |
if sys.version_info < (3, 0): | |
qCritical("Mod Delta Generation plugin requires a Python 3 interpreter, but is running on a Python 2 interpreter.") | |
QMessageBox.critical(self.__parentWidget, "Incompatible Python version.", "This version of the FNIS Integration plugin requires a Python 3 interpreter, but Mod Organizer has provided a Python 2 interpreter. You should check for an updated version, including in the Mod Organizer 2 Development Discord Server.") | |
return False | |
return True | |
def name(self): | |
return "Mod Delta Generation: Gen Manifest" | |
def localizedName(self): | |
return "Mod Delta Generation: Gen Manifest" | |
def author(self): | |
return "xILARTH" | |
def description(self): | |
return "Generate a manifest file that another install can use to know what mods you need." | |
def version(self): | |
return PLUGIN_VERSION | |
def requirements(self): | |
return [] | |
def settings(self): | |
return [] | |
def displayName(self): | |
return "Delta: Gen Manifest" | |
def tooltip(self): | |
return "Generate a manifest file that another install can use to know what mods you need." | |
def icon(self): | |
return QIcon("plugins/MakeDelta.ico") | |
def setParentWidget(self, widget): | |
self.__parentWidget = widget | |
def display(self): | |
output_name, _ = QFileDialog.getSaveFileName(self.__parentWidget, "Save Mod Delta Manifest", filter="*.json") | |
modList = self.__organizer.modList().allMods() | |
manifest = { | |
'version': PLUGIN_VERSION.canonicalString(), | |
'mods': modList, | |
} | |
with open(output_name,'w') as fd: | |
json.dump(manifest, fd, indent=4) | |
# self.__organizer.modList().setActive(logOutputModName, True) | |
pass | |
class MakeDeltaZipfileTool(mobase.IPluginTool): | |
def __init__(self): | |
self.__organizer = None | |
self.__parentWidget = None | |
super(MakeDeltaZipfileTool, self).__init__() | |
def init(self, organizer): | |
self.__organizer = organizer | |
if sys.version_info < (3, 0): | |
qCritical("Mod Delta Generation plugin requires a Python 3 interpreter, but is running on a Python 2 interpreter.") | |
QMessageBox.critical(self.__parentWidget, "Incompatible Python version.", "This version of the FNIS Integration plugin requires a Python 3 interpreter, but Mod Organizer has provided a Python 2 interpreter. You should check for an updated version, including in the Mod Organizer 2 Development Discord Server.") | |
return False | |
return True | |
def name(self): | |
return "Mod Delta Generation: Gen Delta Zipfile" | |
def localizedName(self): | |
return "Mod Delta Generation: Gen Delta Zipfile" | |
def author(self): | |
return "xILARTH" | |
def description(self): | |
return "Generate a zipfile of your profile and mods that another install does not have given their manifest." | |
def version(self): | |
return PLUGIN_VERSION | |
def requirements(self): | |
return [] | |
def settings(self): | |
return [] | |
def displayName(self): | |
return "Delta: Generate Delta Zipfile" | |
def tooltip(self): | |
return "Generate a zipfile of your profile and mods that another install does not have given their manifest." | |
def icon(self): | |
return QIcon("plugins/MakeDelta.ico") | |
def setParentWidget(self, widget): | |
self.__parentWidget = widget | |
def display(self): | |
recvr_manifest, _ = QFileDialog.getOpenFileName(self.__parentWidget, "Select the manifest file of the other device.", filter="JSON Manifest (*.json)") | |
recvr_manifest_path = pathlib.Path(recvr_manifest) | |
with open(recvr_manifest_path,'r') as fd: | |
recvr_manifest_json = json.load(fd) | |
recvr_manifest_version = mobase.VersionInfo(recvr_manifest_json['version']) | |
# TODO: Bound check file is compatible version | |
# recvr version not greater then current version | |
# recvr version major matches | |
# build a delta of your active mods that the reciever does not have given its manifest. | |
recvr_mods = recvr_manifest_json['mods'] | |
my_mod_list = self.__organizer.modList() | |
my_mods = [mod for mod in my_mod_list.allMods() if (my_mod_list.state(mod) & mobase.ModState.ACTIVE)] | |
mod_delta = [ mod for mod in my_mods if mod not in recvr_mods] | |
if not mod_delta: | |
QMessageBox.information(self.__parentWidget, "MO2 Make Delta", "No new mods to add to manifest install.") | |
return | |
#TODO: Build delta zipfile | |
zip_filename, _ = QFileDialog.getSaveFileName(self.__parentWidget, "Delta zipfile save path",filter='*.zip') | |
zip_path = pathlib.Path(zip_filename) | |
zip_mod_base = pathlib.Path('mods') | |
mo_mods_path = pathlib.Path(self.__organizer.modsPath()) | |
progress = QProgressDialog(parent=self.__parentWidget) | |
progress.setLabelText("Starting Copy...") | |
progress.open() | |
with zipfile.ZipFile(zip_path,'w') as zip: | |
for mod in mod_delta: | |
local_mod_path = pathlib.Path(mo_mods_path, mod) | |
zip_mod_path = pathlib.Path(zip_mod_base, mod) | |
modfiles = [x for x in local_mod_path.rglob('*')] | |
copy_prog = 0 | |
progress.reset() | |
progress.setMaximum(len(modfiles)) | |
for modfile in modfiles: | |
savefile = modfile.relative_to(local_mod_path) | |
progress.setLabelText(f'{mod}\n{str(savefile)}') | |
zip.write(modfile, pathlib.Path(zip_mod_path, savefile)) | |
copy_prog += 1 | |
progress.setValue(copy_prog) | |
profile_path = pathlib.Path(self.__organizer.profilePath()) | |
for file in profile_path.rglob('*'): | |
zip.write(file, pathlib.Path('profile', file.relative_to(profile_path))) | |
overwrite_path = pathlib.Path(self.__organizer.overwritePath()) | |
for file in overwrite_path.rglob('*'): | |
zip.write(file, pathlib.Path('overwrite', file.relative_to(overwrite_path))) | |
QMessageBox.information(self.__parentWidget, "MO2 Make Delta", f"Delta file successfully built at {str(zip_path.absolute())}") | |
def createPlugins(): | |
return [ | |
MakeDeltaManifestTool(), | |
MakeDeltaZipfileTool() | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment