mirror of
https://github.com/RedDeadDepresso/KKAFIO.git
synced 2025-12-22 09:20:02 +00:00
revert: modules
This commit is contained in:
@@ -36,17 +36,17 @@ class Config(QConfig):
|
||||
""" Config of application """
|
||||
|
||||
# core
|
||||
gamePath = ConfigItem("Core", "GamePath", "", FolderValidator())
|
||||
gamePath = ConfigItem("Core", "GamePath", "C:/Program Files (x86)/Steam/steamapps/common/Koikatsu Party", FolderValidator())
|
||||
|
||||
# createBackup
|
||||
backupEnable = ConfigItem(
|
||||
"CreateBackup", "Enable", False, BoolValidator()
|
||||
)
|
||||
backupPath = ConfigItem(
|
||||
"CreateBackup", "OutputPath", "", FolderValidator()
|
||||
"CreateBackup", "OutputPath", "C:/Backup", FolderValidator()
|
||||
)
|
||||
filename = ConfigItem(
|
||||
"CreateBackup", "Filename", "",
|
||||
"CreateBackup", "Filename", "koikatsu_backup",
|
||||
)
|
||||
userData = ConfigItem(
|
||||
"CreateBackup", "UserData", False, BoolValidator()
|
||||
@@ -73,7 +73,7 @@ class Config(QConfig):
|
||||
installEnable = ConfigItem(
|
||||
"InstallChara", "Enable", False, BoolValidator()
|
||||
)
|
||||
gamePath = ConfigItem(
|
||||
installPath = ConfigItem(
|
||||
"InstallChara", "InputPath", "", FolderValidator())
|
||||
fileConflicts = OptionsConfigItem(
|
||||
"InstallChara", "FileConflicts", "Skip", OptionsValidator(["Skip", "Replace", "Rename"])
|
||||
@@ -86,8 +86,8 @@ class Config(QConfig):
|
||||
removeEnable = ConfigItem(
|
||||
"RemoveChara", "Enable", False, BoolValidator()
|
||||
)
|
||||
gamePath = ConfigItem(
|
||||
"InstallChara", "InputPath", "", FolderValidator())
|
||||
removePath = ConfigItem(
|
||||
"RemoveChara", "InputPath", "", FolderValidator())
|
||||
|
||||
# main window
|
||||
micaEnabled = ConfigItem("MainWindow", "MicaEnabled", isWin11(), BoolValidator())
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
import os
|
||||
import shutil
|
||||
import datetime
|
||||
import patoolib
|
||||
import customtkinter
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from app.common.logger import logger
|
||||
|
||||
|
||||
class FileManager:
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def findAllFiles(self, directory):
|
||||
fileList = []
|
||||
archiveList = []
|
||||
archiveExtensions = [".rar", ".zip", ".7z"]
|
||||
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
|
||||
filePath = os.path.join(root, file)
|
||||
fileSize = os.path.getsize(filePath)
|
||||
_, fileExtension = os.path.splitext(filePath)
|
||||
|
||||
if fileExtension in archiveExtensions:
|
||||
archiveList.append((filePath, fileSize, fileExtension))
|
||||
else:
|
||||
fileList.append((filePath, fileSize, fileExtension))
|
||||
|
||||
fileList.sort(key=lambda x: x[1])
|
||||
archiveList.sort(key=lambda x: x[1])
|
||||
return fileList, archiveList
|
||||
|
||||
def copyAndPaste(self, type, sourcePath, destinationFolder):
|
||||
sourcePath = sourcePath[0]
|
||||
baseName = os.path.basename(sourcePath)
|
||||
destinationPath = os.path.join(destinationFolder, baseName)
|
||||
conflicts = self.config.install_chara["FileConflicts"]
|
||||
alreadyExists = os.path.exists(destinationPath)
|
||||
|
||||
if alreadyExists and conflicts == "Skip":
|
||||
logger.skipped(type, baseName)
|
||||
return
|
||||
|
||||
elif alreadyExists and conflicts == "Replace":
|
||||
logger.replaced(type, baseName)
|
||||
|
||||
elif alreadyExists and conflicts == "Rename":
|
||||
maxRetries = 3
|
||||
for attempt in range(maxRetries):
|
||||
try:
|
||||
filename, fileExtension = os.path.splitext(sourcePath)
|
||||
newName = datetime.datetime.now().strftime('%Y%m%d%H%M%S%f')
|
||||
newSourcePath = f"{filename}_{newName}{fileExtension}"
|
||||
os.rename(sourcePath, newSourcePath)
|
||||
sourcePath = newSourcePath
|
||||
logger.renamed(type, baseName)
|
||||
|
||||
filename, fileExtension = os.path.splitext(destinationPath)
|
||||
destinationPath = f"{filename}_{newName}{fileExtension}"
|
||||
break # Exit the loop if renaming is successful
|
||||
except PermissionError:
|
||||
if attempt < maxRetries - 1:
|
||||
time.sleep(1) # Wait for 1 second before retrying
|
||||
else:
|
||||
logger.error(type, f"Failed to rename {baseName} after {maxRetries} attempts.")
|
||||
return
|
||||
|
||||
try:
|
||||
shutil.copy(sourcePath, destinationPath)
|
||||
print(f"File copied successfully from {sourcePath} to {destinationPath}")
|
||||
if not alreadyExists:
|
||||
logger.success(type, baseName)
|
||||
except FileNotFoundError:
|
||||
logger.error(type, f"{baseName} does not exist.")
|
||||
except PermissionError:
|
||||
logger.error(type, f"Permission denied for {baseName}")
|
||||
except Exception as e:
|
||||
logger.error(type, f"An error occurred: {e}")
|
||||
|
||||
def findAndRemove(self, type, sourcePath, destinationFolder):
|
||||
sourcePath = sourcePath[0]
|
||||
baseName = os.path.basename(sourcePath)
|
||||
destinationPath = os.path.join(destinationFolder, baseName)
|
||||
if os.path.exists(destinationPath):
|
||||
try:
|
||||
os.remove(destinationPath)
|
||||
logger.removed(type, baseName)
|
||||
except OSError as e:
|
||||
logger.error(type, baseName)
|
||||
|
||||
def createArchive(self, folders, archivePath):
|
||||
# Specify the full path to the 7zip executable
|
||||
pathTo7zip = patoolib.util.find_program("7z")
|
||||
if not pathTo7zip:
|
||||
logger.error("SCRIPT", "7zip not found. Unable to create backup")
|
||||
raise Exception()
|
||||
|
||||
if os.path.exists(archivePath+".7z"):
|
||||
os.remove(archivePath+".7z")
|
||||
|
||||
pathTo7zip = f'"{pathTo7zip}"'
|
||||
archivePath = f'"{archivePath}"'
|
||||
excludeFolders = [
|
||||
'"Sideloader Modpack"',
|
||||
'"Sideloader Modpack - Studio"',
|
||||
'"Sideloader Modpack - KK_UncensorSelector"',
|
||||
'"Sideloader Modpack - Maps"',
|
||||
'"Sideloader Modpack - KK_MaterialEditor"',
|
||||
'"Sideloader Modpack - Fixes"',
|
||||
'"Sideloader Modpack - Exclusive KK KKS"',
|
||||
'"Sideloader Modpack - Exclusive KK"',
|
||||
'"Sideloader Modpack - Animations"'
|
||||
]
|
||||
|
||||
# Create a string of folder names to exclude
|
||||
excludeString = ''
|
||||
for folder in excludeFolders:
|
||||
excludeString += f'-xr!{folder} '
|
||||
|
||||
# Create a string of folder names to include
|
||||
includeString = ''
|
||||
for folder in folders:
|
||||
includeString += f'"{folder}" '
|
||||
|
||||
# Construct the 7zip command
|
||||
command = f'{pathTo7zip} a -t7z {archivePath} {includeString} {excludeString}'
|
||||
# Call the command
|
||||
process = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
# Print the output
|
||||
for line in process.stdout.decode().split('\n'):
|
||||
if line.strip() != "":
|
||||
logger.info("7-Zip", line)
|
||||
# Check the return code
|
||||
if process.returncode not in [0, 1]:
|
||||
raise Exception()
|
||||
|
||||
def extractArchive(self, archivePath):
|
||||
try:
|
||||
archiveName = os.path.basename(archivePath)
|
||||
logger.info("ARCHIVE", f"Extracting {archiveName}")
|
||||
extractPath = os.path.join(f"{os.path.splitext(archivePath)[0]}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S%f')}")
|
||||
patoolib.extract_archive(archivePath, outdir=extractPath)
|
||||
return extractPath
|
||||
|
||||
except patoolib.util.PatoolError as e:
|
||||
text = f"There is an error with the archive {archiveName} but it is impossible to detect the cause. Maybe it requires a password?"
|
||||
while self.config.install_chara["Password"] == "Request Password":
|
||||
try:
|
||||
dialog = customtkinter.CTkInputDialog(text=text, title="Enter Password")
|
||||
password = dialog.get_input()
|
||||
|
||||
if password is not None or "":
|
||||
patoolib.extract_archive(archivePath, outdir=extractPath, password=password)
|
||||
return extractPath
|
||||
else:
|
||||
break
|
||||
except:
|
||||
text = f"Wrong password or {archiveName} is corrupted. Please enter password again or click Cancel"
|
||||
|
||||
logger.skipped("ARCHIVE", archiveName)
|
||||
|
||||
|
||||
fileManager = FileManager()
|
||||
@@ -29,8 +29,8 @@ class Logger:
|
||||
# Status Text: INFO, SUCCESS, ERROR, SKIPPED, REPLACED, RENAMED, REMOVED
|
||||
self.status = [' INFO', ' SUCCESS', ' ERROR',
|
||||
' SKIPPED', ' REPLACED', ' RENAMED', ' REMOVED']
|
||||
# Status Color: Blue, Red, Green, Orange,
|
||||
self.statusColor = ['#2d8cf0', '#ed3f14', '#f90', '#f90', '#f90', '#f90', '#00c12b']
|
||||
# Status Color: Blue, Red, Green, Orange
|
||||
self.statusColor = ['#2d8cf0', '#00c12b', '#ed3f14', '#f90', '#f90', '#f90', '#f90']
|
||||
|
||||
# Status HTML: <b style="color:$color">status</b>
|
||||
self.statusHtml = [
|
||||
@@ -69,17 +69,20 @@ class Logger:
|
||||
|
||||
def colorize(self, line):
|
||||
adding = line
|
||||
print(line)
|
||||
for i, s in enumerate(self.text):
|
||||
if s in line:
|
||||
print(s)
|
||||
print(self.statusColor[i])
|
||||
adding = (f'''
|
||||
<div style="font-family: Consolas, monospace;color:{self.statusColor[i]};">
|
||||
{line}
|
||||
</div>
|
||||
|
||||
''')
|
||||
break
|
||||
self.logs += adding
|
||||
self.logger_signal.emit(adding)
|
||||
return
|
||||
|
||||
def info(self, category: str, message: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -32,6 +32,7 @@ class ThreadManager:
|
||||
logger.colorize(line)
|
||||
|
||||
def stop(self):
|
||||
if self._script is not None:
|
||||
self._script.terminate()
|
||||
self._script = None
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import os
|
||||
|
||||
from app.common.file_manager import fileManager
|
||||
from app.modules.handler import Handler
|
||||
|
||||
|
||||
class CreateBackup(Handler):
|
||||
def __str__(self) -> str:
|
||||
return "Create Backup"
|
||||
|
||||
def loadConfig(self, config):
|
||||
super().loadConfig(config)
|
||||
folders = ["mods", "UserData", "BepInEx"]
|
||||
self.folders = [self.gamePath[f] for f in folders if self.config[f]]
|
||||
self.outputPath = self.config["OutputPath"]
|
||||
self.filename = self.config["Filename"]
|
||||
|
||||
def handle(self, request):
|
||||
outputPath = os.path.join(self.outputPath, self.filename)
|
||||
fileManager.createArchive(self.folders, outputPath)
|
||||
|
||||
self.setNext(request)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import os
|
||||
import re as regex
|
||||
import codecs
|
||||
import shutil
|
||||
|
||||
from app.common.logger import logger
|
||||
from app.modules.handler import Handler
|
||||
|
||||
|
||||
class FilterConvertKKS(Handler):
|
||||
def __str__(self) -> str:
|
||||
return "Filter Convert KKS"
|
||||
|
||||
def loadConfig(self, config):
|
||||
super().loadConfig(config)
|
||||
self.convert = self.config["Convert"]
|
||||
|
||||
def getList(self, folderPath):
|
||||
newList = []
|
||||
for root, dirs, files in os.walk(folderPath):
|
||||
for filename in files:
|
||||
if regex.match(r".*(\.png)$", filename):
|
||||
newList.append(os.path.join(root, filename))
|
||||
return newList
|
||||
|
||||
def checkPng(self, cardPath):
|
||||
with codecs.open(cardPath, "rb") as card:
|
||||
data = card.read()
|
||||
cardType = 0
|
||||
if data.find(b"KoiKatuChara") != -1:
|
||||
cardType = 1
|
||||
if data.find(b"KoiKatuCharaSP") != -1:
|
||||
cardType = 2
|
||||
elif data.find(b"KoiKatuCharaSun") != -1:
|
||||
cardType = 3
|
||||
logger.info(f"[{cardType}]", f"{cardPath}")
|
||||
return cardType
|
||||
|
||||
def convertKk(self, cardName, cardPath, destinationPath):
|
||||
with codecs.open(cardPath, mode="rb") as card:
|
||||
data = card.read()
|
||||
|
||||
replaceList = [
|
||||
[b"\x15\xe3\x80\x90KoiKatuCharaSun", b"\x12\xe3\x80\x90KoiKatuChara"],
|
||||
[b"Parameter\xa7version\xa50.0.6", b"Parameter\xa7version\xa50.0.5"],
|
||||
[b"version\xa50.0.6\xa3sex", b"version\xa50.0.5\xa3sex"],
|
||||
]
|
||||
|
||||
for text in replaceList:
|
||||
data = data.replace(text[0], text[1])
|
||||
|
||||
newFilePath = os.path.normpath(os.path.join(destinationPath, f"KKS2KK_{cardName}"))
|
||||
|
||||
with codecs.open(newFilePath, "wb") as newCard:
|
||||
newCard.write(data)
|
||||
|
||||
def handle(self, request):
|
||||
path = self.config.fc_kks["InputPath"]
|
||||
kksCardList = []
|
||||
kksFolder = "_KKS_card_"
|
||||
kksFolder2 = "_KKS_to_KK_"
|
||||
|
||||
pngList = self.getList(path)
|
||||
|
||||
count = len(pngList)
|
||||
if count > 0:
|
||||
logger.info("SCRIPT", "0: unknown / 1: kk / 2: kksp / 3: kks")
|
||||
for png in pngList:
|
||||
if self.checkPng(png) == 3:
|
||||
kksCardList.append(png)
|
||||
else:
|
||||
logger.success("SCRIPT", f"no PNG found")
|
||||
return
|
||||
|
||||
count = len(kksCardList)
|
||||
if count > 0:
|
||||
print(kksCardList)
|
||||
|
||||
targetFolder = os.path.normpath(os.path.join(path, kksFolder))
|
||||
targetFolder2 = os.path.normpath(os.path.join(path, kksFolder2))
|
||||
if not os.path.isdir(targetFolder):
|
||||
os.mkdir(targetFolder)
|
||||
|
||||
if self.convert:
|
||||
logger.info("SCRIPT", f"Conversion to KK is [{self.convert}]")
|
||||
if not os.path.isdir(targetFolder2):
|
||||
os.mkdir(targetFolder2)
|
||||
|
||||
for cardPath in kksCardList:
|
||||
source = cardPath
|
||||
card = os.path.basename(cardPath)
|
||||
target = os.path.normpath(os.path.join(targetFolder, card))
|
||||
|
||||
# copy & convert before move
|
||||
if self.convert:
|
||||
self.convertKk(card, source, targetFolder2)
|
||||
|
||||
# move file
|
||||
shutil.move(source, target)
|
||||
|
||||
if self.convert:
|
||||
logger.success("SCRIPT", f"[{count}] cards moved to [{kksFolder}] folder, converted and save to [{kksFolder2}] folder")
|
||||
else:
|
||||
logger.success("SCRIPT", f"[{count}] cards moved to [{kksFolder}] folder")
|
||||
else:
|
||||
logger.success("SCRIPT", f"no KKS card found")
|
||||
|
||||
self.setNext(request)
|
||||
@@ -1,16 +0,0 @@
|
||||
class Handler:
|
||||
def __str__(self) -> str:
|
||||
return "Handler"
|
||||
|
||||
def loadConfig(self, config):
|
||||
self.gamePath = config.get("Core", "GamePath")
|
||||
key = str(self).replace(" ", "")
|
||||
self.config = config.get(key)
|
||||
|
||||
def handle(self, request):
|
||||
pass
|
||||
|
||||
def setNext(self, request):
|
||||
request.removeHandler()
|
||||
request.process()
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import os
|
||||
import codecs
|
||||
|
||||
from app.common.file_manager import fileManager
|
||||
from app.common.logger import logger
|
||||
from app.modules.handler import Handler
|
||||
|
||||
|
||||
class InstallChara(Handler):
|
||||
def __str__(self) -> str:
|
||||
return "Install Chara"
|
||||
|
||||
def loadConfig(self, config):
|
||||
super().loadConfig(config)
|
||||
self.inputPath = self.config["InputPath"]
|
||||
|
||||
def resolvePng(self, imagePath):
|
||||
with codecs.open(imagePath[0], "rb") as card:
|
||||
data = card.read()
|
||||
if data.find(b"KoiKatuChara") != -1:
|
||||
if data.find(b"KoiKatuCharaSP") != -1 or data.find(b"KoiKatuCharaSun") != -1:
|
||||
basename = os.path.basename(imagePath[0])
|
||||
logger.error("CHARA", f"{basename} is a KKS card")
|
||||
return
|
||||
fileManager.copyAndPaste("CHARA", imagePath, self.gamePath["chara"])
|
||||
elif data.find(b"KoiKatuClothes") != -1:
|
||||
fileManager.copyAndPaste("COORD",imagePath, self.gamePath["coordinate"])
|
||||
else:
|
||||
fileManager.copyAndPaste("OVERLAYS", imagePath, self.gamePath["Overlays"])
|
||||
|
||||
def handle(self, request, folderPath=None):
|
||||
if folderPath is None:
|
||||
folderPath = self.inputPath
|
||||
foldername = os.path.basename(folderPath)
|
||||
logger.log_msg("FOLDER", foldername)
|
||||
fileList, archiveList = fileManager.findAllFiles(folderPath)
|
||||
|
||||
for file in fileList:
|
||||
extension = file[2]
|
||||
match extension:
|
||||
case ".zipmod":
|
||||
fileManager.copyAndPaste("MODS", file, self.gamePath["mods"])
|
||||
case ".png":
|
||||
self.resolvePng(file)
|
||||
case _:
|
||||
basename = os.path.basename(file[0])
|
||||
logger.error("UKNOWN", f"Cannot classify {basename}")
|
||||
|
||||
for archive in archiveList:
|
||||
extractPath = fileManager.extractArchive(archive[0])
|
||||
if extractPath is not None:
|
||||
self.handle(extractPath)
|
||||
|
||||
self.setNext(request)
|
||||
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import os
|
||||
import codecs
|
||||
|
||||
from app.common.file_manager import fileManager
|
||||
from app.common.logger import logger
|
||||
from app.modules.handler import Handler
|
||||
|
||||
|
||||
class RemoveChara(Handler):
|
||||
def __str__(self) -> str:
|
||||
return "Remove Chara"
|
||||
|
||||
def loadConfig(self, config):
|
||||
super().configLoad(config)
|
||||
self.inputPath = self.config["InputPath"]
|
||||
|
||||
def resolvePng(self, imagePath):
|
||||
with codecs.open(imagePath[0], "rb") as card:
|
||||
data = card.read()
|
||||
if data.find(b"KoiKatuChara") != -1:
|
||||
if data.find(b"KoiKatuCharaSP") != -1 or data.find(b"KoiKatuCharaSun") != -1:
|
||||
return
|
||||
fileManager.findAndRemove("CHARA", imagePath, self.gamePath["chara"])
|
||||
elif data.find(b"KoiKatuClothes") != -1:
|
||||
fileManager.findAndRemove("COORD",imagePath, self.gamePath["coordinate"])
|
||||
else:
|
||||
fileManager.findAndRemove("OVERLAYS", imagePath, self.gamePath["Overlays"])
|
||||
|
||||
def handle(self, request):
|
||||
foldername = os.path.basename(self.inputPath)
|
||||
logger.info("FOLDER", foldername)
|
||||
fileList, archiveList = fileManager.findAllFiles(self.inputPath)
|
||||
|
||||
for file in fileList:
|
||||
extension = file[2]
|
||||
match extension:
|
||||
case ".zipmod":
|
||||
fileManager.findAndRemove("MODS", file, self.game_path["mods"])
|
||||
case ".png":
|
||||
self.resolvePng(file)
|
||||
case _:
|
||||
pass
|
||||
|
||||
logger.line()
|
||||
self.setNext(request)
|
||||
|
||||
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import os
|
||||
|
||||
from app.common.config import cfg
|
||||
from app.common.logger import logger
|
||||
|
||||
from app.modules.handler import Handler
|
||||
from app.modules.create_backup import CreateBackup
|
||||
from app.modules.fc_kks import FilterConvertKKS
|
||||
from app.modules.install_chara import InstallChara
|
||||
from app.modules.remove_chara import RemoveChara
|
||||
|
||||
|
||||
class Request:
|
||||
def __init__(self):
|
||||
self._handlers = [CreateBackup(), FilterConvertKKS(), InstallChara(), RemoveChara()]
|
||||
self._config = cfg.toDict()
|
||||
self._isValid = True
|
||||
|
||||
logger.info("SCRIPT", "Validating config")
|
||||
self.validateGamepath()
|
||||
self._handlers = [x for x in self.handlers if self.isTaskEnabled(x)]
|
||||
|
||||
if not self._handlers:
|
||||
logger.error("SCRIPT", "No task enabled")
|
||||
if not self._isValid:
|
||||
raise Exception()
|
||||
|
||||
def validatePath(self, path, errorMsg):
|
||||
if not os.path.exists(path):
|
||||
logger.error("SCRIPT", errorMsg)
|
||||
self._isValid = False
|
||||
return False
|
||||
return True
|
||||
|
||||
def validateGamepath(self):
|
||||
base = self.config['Core']['GamePath']
|
||||
self.config['Core']['GamePath'] = {
|
||||
"base": base,
|
||||
"UserData": os.path.join(base, "UserData"),
|
||||
"BepInEx": os.path.join(base, "BepInEx"),
|
||||
"mods": os.path.join(base, "mods"),
|
||||
"chara": os.path.join(base, "UserData\\chara\\female"),
|
||||
"coordinate": os.path.join(base, "UserData\\coordinate"),
|
||||
"Overlays": os.path.join(base, "UserData\\Overlays")
|
||||
}
|
||||
|
||||
for directory, path in self.config['Core']['GamePath'].items():
|
||||
self.validatePath(path, f"Game path not valid. Missing {directory} directory.")
|
||||
|
||||
def isTaskEnabled(self, handler: Handler):
|
||||
task = str(handler).replace(" ", "")
|
||||
taskConfig = self.config[task]
|
||||
|
||||
if not taskConfig["Enable"]:
|
||||
return False
|
||||
|
||||
if (path := taskConfig.get("InputPath")):
|
||||
self.validatePath(path, f"Invalid path for {str(handler)}: {path}")
|
||||
|
||||
if (path := taskConfig.get("OutputPath")):
|
||||
self.validatePath(path, f"Invalid path for {str(handler)}: {path}")
|
||||
|
||||
if self._isValid:
|
||||
handler.loadConfig(self.config)
|
||||
|
||||
return True
|
||||
|
||||
def removeHandler(self) -> Handler:
|
||||
if len(self._handlers) > 1:
|
||||
return self._handlers.pop(0)
|
||||
|
||||
def process(self):
|
||||
if self._handlers:
|
||||
self._handlers[0].handle()
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from qfluentwidgets import FluentIcon as FIF
|
||||
from qfluentwidgets import InfoBar, DisplayLabel
|
||||
from PySide6.QtCore import Qt, QUrl
|
||||
from PySide6.QtGui import QDesktopServices
|
||||
from PySide6.QtWidgets import QWidget
|
||||
from PySide6.QtWidgets import QWidget, QFileDialog
|
||||
|
||||
from ..common.config import cfg, HELP_URL, FEEDBACK_URL, AUTHOR, VERSION, YEAR, isWin11
|
||||
from ..components.line_edit_card import LineEditSettingCard
|
||||
@@ -307,23 +307,29 @@ class SettingInterface(ScrollArea):
|
||||
parent=self
|
||||
)
|
||||
|
||||
# def __onDownloadFolderCardClicked(self):
|
||||
# """ download folder card clicked slot """
|
||||
# folder = QFileDialog.getExistingDirectory(
|
||||
# self, self.tr("Choose folder"), "./")
|
||||
# if not folder or cfg.get(cfg.downloadFolder) == folder:
|
||||
# return
|
||||
def __onFolderCardClicked(self, item, card):
|
||||
""" download folder card clicked slot """
|
||||
folder = QFileDialog.getExistingDirectory(
|
||||
self, self.tr("Choose folder"), "./")
|
||||
if not folder or cfg.get(item) == folder:
|
||||
return
|
||||
|
||||
# cfg.set(cfg.downloadFolder, folder)
|
||||
# self.downloadFolderCard.setContent(folder)
|
||||
cfg.set(item, folder)
|
||||
card.setContent(folder)
|
||||
|
||||
def __connectSignalToSlot(self):
|
||||
""" connect signal to slot """
|
||||
cfg.appRestartSig.connect(self.__showRestartTooltip)
|
||||
|
||||
# music in the pc
|
||||
# self.downloadFolderCard.clicked.connect(
|
||||
# self.__onDownloadFolderCardClicked)
|
||||
# gamePath
|
||||
self.gamePathCard.clicked.connect(lambda: self.__onFolderCardClicked(cfg.gamePath, self.gamePathCard))
|
||||
# backup
|
||||
self.backupPathCard.clicked.connect(lambda: self.__onFolderCardClicked(cfg.backupPath, self.backupPathCard))
|
||||
# fckks
|
||||
self.fckksPathCard.clicked.connect(lambda: self.__onFolderCardClicked(cfg.fccksPath, self.fckksPathCard))
|
||||
# install
|
||||
self.installPathCard.clicked.connect(lambda: self.__onFolderCardClicked(cfg.installPath, self.installPathCard))
|
||||
# remove
|
||||
self.removePathCard.clicked.connect(lambda: self.__onFolderCardClicked(cfg.removePath, self.removePathCard))
|
||||
|
||||
# personalization
|
||||
self.themeCard.optionChanged.connect(lambda ci: setTheme(cfg.get(ci)))
|
||||
|
||||
26
modules/create_backup.py
Normal file
26
modules/create_backup.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
|
||||
class CreateBackup:
|
||||
def __init__(self, config, file_manager):
|
||||
"""Initializes the Bounty module.
|
||||
|
||||
Args:
|
||||
config (Config): BAAuto Config instance
|
||||
"""
|
||||
self.config = config
|
||||
self.file_manager = file_manager
|
||||
self.game_path = self.config.game_path
|
||||
folders = ["mods", "UserData", "BepInEx"]
|
||||
self.folders = [self.game_path[f] for f in folders if self.config.create_backup[f]]
|
||||
self.filename = self.config.create_backup["Filename"]
|
||||
self.output_path = self.config.create_backup["OutputPath"]
|
||||
|
||||
def logic_wrapper(self):
|
||||
output_path = os.path.join(self.output_path, self.filename)
|
||||
self.file_manager.create_archive(self.folders, output_path)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
107
modules/fc_kks.py
Normal file
107
modules/fc_kks.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import os
|
||||
import re as regex
|
||||
import codecs
|
||||
import shutil
|
||||
from util.logger import Logger
|
||||
|
||||
class FilterConvertKKS:
|
||||
def __init__(self, config, file_manager):
|
||||
"""Initializes the Bounty module.
|
||||
|
||||
Args:
|
||||
config (Config): BAAuto Config instance
|
||||
"""
|
||||
self.config = config
|
||||
self.file_manager = file_manager
|
||||
self.convert = self.config.fc_kks["Convert"]
|
||||
|
||||
def get_list(self, folder_path):
|
||||
new_list = []
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
for filename in files:
|
||||
if regex.match(r".*(\.png)$", filename):
|
||||
new_list.append(os.path.join(root, filename))
|
||||
return new_list
|
||||
|
||||
def check_png(self, card_path):
|
||||
with codecs.open(card_path, "rb") as card:
|
||||
data = card.read()
|
||||
card_type = 0
|
||||
if data.find(b"KoiKatuChara") != -1:
|
||||
card_type = 1
|
||||
if data.find(b"KoiKatuCharaSP") != -1:
|
||||
card_type = 2
|
||||
elif data.find(b"KoiKatuCharaSun") != -1:
|
||||
card_type = 3
|
||||
Logger.log_info(f"[{card_type}]", f"{card_path}")
|
||||
return card_type
|
||||
|
||||
def convert_kk(self, card_name, card_path, destination_path):
|
||||
with codecs.open(card_path, mode="rb") as card:
|
||||
data = card.read()
|
||||
|
||||
replace_list = [
|
||||
[b"\x15\xe3\x80\x90KoiKatuCharaSun", b"\x12\xe3\x80\x90KoiKatuChara"],
|
||||
[b"Parameter\xa7version\xa50.0.6", b"Parameter\xa7version\xa50.0.5"],
|
||||
[b"version\xa50.0.6\xa3sex", b"version\xa50.0.5\xa3sex"],
|
||||
]
|
||||
|
||||
for text in replace_list:
|
||||
data = data.replace(text[0], text[1])
|
||||
|
||||
new_file_path = os.path.normpath(os.path.join(destination_path, f"KKS2KK_{card_name}"))
|
||||
# print(f"new_file_path {new_file_path}")
|
||||
|
||||
with codecs.open(new_file_path, "wb") as new_card:
|
||||
new_card.write(data)
|
||||
|
||||
def logic_wrapper(self):
|
||||
path = self.config.fc_kks["InputPath"]
|
||||
kks_card_list = []
|
||||
kks_folder = "_KKS_card_"
|
||||
kks_folder2 = "_KKS_to_KK_"
|
||||
|
||||
png_list = self.get_list(path)
|
||||
|
||||
count = len(png_list)
|
||||
if count > 0:
|
||||
Logger.log_info("SCRIPT", "0: unknown / 1: kk / 2: kksp / 3: kks")
|
||||
for png in png_list:
|
||||
if self.check_png(png) == 3:
|
||||
kks_card_list.append(png)
|
||||
else:
|
||||
Logger.log_success("SCRIPT", f"no PNG found")
|
||||
return
|
||||
|
||||
count = len(kks_card_list)
|
||||
if count > 0:
|
||||
print(kks_card_list)
|
||||
|
||||
target_folder = os.path.normpath(os.path.join(path, kks_folder))
|
||||
target_folder2 = os.path.normpath(os.path.join(path, kks_folder2))
|
||||
if not os.path.isdir(target_folder):
|
||||
os.mkdir(target_folder)
|
||||
|
||||
if self.convert:
|
||||
Logger.log_info("SCRIPT", f"Conversion to KK is [{self.convert}]")
|
||||
if not os.path.isdir(target_folder2):
|
||||
os.mkdir(target_folder2)
|
||||
|
||||
for card_path in kks_card_list:
|
||||
source = card_path
|
||||
card = os.path.basename(card_path)
|
||||
target = os.path.normpath(os.path.join(target_folder, card))
|
||||
|
||||
# copy & convert before move
|
||||
if self.convert:
|
||||
self.convert_kk(card, source, target_folder2)
|
||||
|
||||
# move file
|
||||
shutil.move(source, target)
|
||||
|
||||
if self.convert:
|
||||
Logger.log_success("SCRIPT", f"[{count}] cards moved to [{kks_folder}] folder, converted and save to [{kks_folder2}] folder")
|
||||
else:
|
||||
Logger.log_success("SCRIPT", f"[{count}] cards moved to [{kks_folder}] folder")
|
||||
else:
|
||||
Logger.log_success("SCRIPT", f"no KKS card found")
|
||||
56
modules/install_chara.py
Normal file
56
modules/install_chara.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import os
|
||||
import codecs
|
||||
from util.logger import Logger
|
||||
|
||||
class InstallChara:
|
||||
def __init__(self, config, file_manager):
|
||||
"""Initializes the Bounty module.
|
||||
|
||||
Args:
|
||||
config (Config): BAAuto Config instance
|
||||
"""
|
||||
self.config = config
|
||||
self.file_manager = file_manager
|
||||
self.game_path = self.config.game_path
|
||||
self.input_path = self.config.install_chara["InputPath"]
|
||||
|
||||
def resolve_png(self, image_path):
|
||||
with codecs.open(image_path[0], "rb") as card:
|
||||
data = card.read()
|
||||
if data.find(b"KoiKatuChara") != -1:
|
||||
if data.find(b"KoiKatuCharaSP") != -1 or data.find(b"KoiKatuCharaSun") != -1:
|
||||
basename = os.path.basename(image_path[0])
|
||||
Logger.log_error("CHARA", f"{basename} is a KKS card")
|
||||
return
|
||||
self.file_manager.copy_and_paste("CHARA", image_path, self.game_path["chara"])
|
||||
elif data.find(b"KoiKatuClothes") != -1:
|
||||
self.file_manager.copy_and_paste("COORD",image_path, self.game_path["coordinate"])
|
||||
else:
|
||||
self.file_manager.copy_and_paste("OVERLAYS", image_path, self.game_path["Overlays"])
|
||||
|
||||
def logic_wrapper(self, folder_path=None):
|
||||
if folder_path is None:
|
||||
folder_path = self.input_path
|
||||
foldername = os.path.basename(folder_path)
|
||||
Logger.log_msg("FOLDER", foldername)
|
||||
file_list, compressed_file_list = self.file_manager.find_all_files(folder_path)
|
||||
|
||||
for file in file_list:
|
||||
file_extension = file[2]
|
||||
match file_extension:
|
||||
case ".zipmod":
|
||||
self.file_manager.copy_and_paste("MODS", file, self.game_path["mods"])
|
||||
case ".png":
|
||||
self.resolve_png(file)
|
||||
case _:
|
||||
basename = os.path.basename(file[0])
|
||||
Logger.log_error("UKNOWN", f"Cannot classify {basename}")
|
||||
print("[MSG]")
|
||||
|
||||
for compressed in compressed_file_list:
|
||||
extract_path = self.file_manager.extract_archive(compressed[0])
|
||||
if extract_path is not None:
|
||||
self.logic_wrapper(extract_path)
|
||||
|
||||
|
||||
|
||||
47
modules/remove_chara.py
Normal file
47
modules/remove_chara.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import codecs
|
||||
from util.logger import Logger
|
||||
|
||||
class RemoveChara:
|
||||
def __init__(self, config, file_manager):
|
||||
"""Initializes the Bounty module.
|
||||
|
||||
Args:
|
||||
config (Config): BAAuto Config instance
|
||||
"""
|
||||
self.config = config
|
||||
self.file_manager = file_manager
|
||||
self.game_path = self.config.game_path
|
||||
self.input_path = self.config.remove_chara["InputPath"]
|
||||
|
||||
def resolve_png(self, image_path):
|
||||
with codecs.open(image_path[0], "rb") as card:
|
||||
data = card.read()
|
||||
if data.find(b"KoiKatuChara") != -1:
|
||||
if data.find(b"KoiKatuCharaSP") != -1 or data.find(b"KoiKatuCharaSun") != -1:
|
||||
return
|
||||
self.file_manager.find_and_remove("CHARA", image_path, self.game_path["chara"])
|
||||
elif data.find(b"KoiKatuClothes") != -1:
|
||||
self.file_manager.find_and_remove("COORD",image_path, self.game_path["coordinate"])
|
||||
else:
|
||||
self.file_manager.find_and_remove("OVERLAYS", image_path, self.game_path["Overlays"])
|
||||
|
||||
def logic_wrapper(self):
|
||||
foldername = os.path.basename(self.input_path)
|
||||
Logger.log_msg("FOLDER", foldername)
|
||||
file_list, archive_list = self.file_manager.find_all_files(self.input_path)
|
||||
|
||||
for file in file_list:
|
||||
file_extension = file[2]
|
||||
match file_extension:
|
||||
case ".zipmod":
|
||||
self.file_manager.find_and_remove("MODS", file, self.game_path["mods"])
|
||||
case ".png":
|
||||
self.resolve_png(file)
|
||||
case _:
|
||||
pass
|
||||
print("[MSG]")
|
||||
|
||||
|
||||
|
||||
|
||||
70
script.py
70
script.py
@@ -1,5 +1,69 @@
|
||||
from app.modules.request import Request
|
||||
import sys
|
||||
import traceback
|
||||
try:
|
||||
with open('traceback.log', 'w') as f:
|
||||
pass
|
||||
|
||||
from util.config import Config
|
||||
from util.logger import Logger
|
||||
from util.file_manager import FileManager
|
||||
from modules.install_chara import InstallChara
|
||||
from modules.remove_chara import RemoveChara
|
||||
from modules.fc_kks import FilterConvertKKS
|
||||
from modules.create_backup import CreateBackup
|
||||
|
||||
|
||||
request = Request()
|
||||
request.process()
|
||||
class Script:
|
||||
def __init__(self, config, file_manager):
|
||||
"""Initializes the primary azurlane-auto instance with the passed in
|
||||
Config instance;
|
||||
|
||||
Args:
|
||||
config (Config): BAAuto Config instance
|
||||
"""
|
||||
self.config = config
|
||||
self.file_manager = file_manager
|
||||
self.modules = {
|
||||
'InstallChara': None,
|
||||
'RemoveChara': None,
|
||||
'CreateBackup': None,
|
||||
'FilterConvertKKS': None,
|
||||
}
|
||||
if self.config.install_chara['Enable']:
|
||||
self.modules['InstallChara'] = InstallChara(self.config, self.file_manager)
|
||||
if self.config.remove_chara['Enable']:
|
||||
self.modules['RemoveChara'] = RemoveChara(self.config, self.file_manager)
|
||||
if self.config.create_backup['Enable']:
|
||||
self.modules['CreateBackup'] = CreateBackup(self.config, self.file_manager)
|
||||
if self.config.fc_kks["Enable"]:
|
||||
self.modules['FilterConvertKKS'] = FilterConvertKKS(self.config, self.file_manager)
|
||||
|
||||
def run(self):
|
||||
for task in self.config.tasks:
|
||||
if self.modules[task]:
|
||||
Logger.log_info("SCRIPT", f'Start Task: {task}')
|
||||
try:
|
||||
self.modules[task].logic_wrapper()
|
||||
except:
|
||||
Logger.log_error("SCRIPT", f'Task error: {task}. For more info, check the traceback.log file.')
|
||||
with open('traceback.log', 'a') as f:
|
||||
f.write(f'[{task}]\n')
|
||||
traceback.print_exc(None, f, True)
|
||||
f.write('\n')
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
|
||||
except:
|
||||
print(f'[ERROR] Script Initialisation Error. For more info, check the traceback.log file.')
|
||||
with open('traceback.log', 'w') as f:
|
||||
f.write(f'Script Initialisation Error\n')
|
||||
traceback.print_exc(None, f, True)
|
||||
f.write('\n')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = Config('app/config/config.json')
|
||||
file_manager = FileManager(config)
|
||||
script = Script(config, file_manager)
|
||||
script.run()
|
||||
|
||||
0
util/__init__.py
Normal file
0
util/__init__.py
Normal file
90
util/config.py
Normal file
90
util/config.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
from util.logger import Logger
|
||||
|
||||
class Config:
|
||||
def __init__(self, config_file):
|
||||
Logger.log_info("SCRIPT", "Initializing config module")
|
||||
self.config_file = config_file
|
||||
self.ok = False
|
||||
self.initialized = False
|
||||
self.config_data = None
|
||||
self.changed = False
|
||||
self.read()
|
||||
|
||||
def read(self):
|
||||
backup_config = self._deepcopy_dict(self.__dict__)
|
||||
|
||||
try:
|
||||
with open(self.config_file, 'r') as json_file:
|
||||
self.config_data = json.load(json_file)
|
||||
except FileNotFoundError:
|
||||
Logger.log_error("SCRIPT", f"Config file '{self.config_file}' not found.")
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError:
|
||||
Logger.log_error("SCRIPT", f"Invalid JSON format in '{self.config_file}'.")
|
||||
sys.exit(1)
|
||||
|
||||
self.validate()
|
||||
|
||||
if self.ok and not self.initialized:
|
||||
Logger.log_info("SCRIPT", "Starting KKAFIO!")
|
||||
self.initialized = True
|
||||
self.changed = True
|
||||
elif not self.ok and not self.initialized:
|
||||
Logger.log_error("SCRIPT", "Invalid config. Please check your config file.")
|
||||
sys.exit(1)
|
||||
elif not self.ok and self.initialized:
|
||||
Logger.log_warning("SCRIPT", "Config change detected, but with problems. Rolling back config.")
|
||||
self._rollback_config(backup_config)
|
||||
elif self.ok and self.initialized:
|
||||
if backup_config != self.__dict__:
|
||||
Logger.log_warning("SCRIPT", "Config change detected. Hot-reloading.")
|
||||
self.changed = True
|
||||
|
||||
def validate(self):
|
||||
Logger.log_info("SCRIPT", "Validating config")
|
||||
self.ok = True
|
||||
self.tasks = ["CreateBackup", "FilterConvertKKS", "InstallChara", "RemoveChara"]
|
||||
self.create_gamepath()
|
||||
|
||||
for task in self.tasks:
|
||||
if self.config_data[task]["Enable"]:
|
||||
if "InputPath" in self.config_data[task]:
|
||||
path = self.config_data[task]["InputPath"]
|
||||
elif "OutputPath" in self.config_data[task]:
|
||||
path = self.config_data[task]["OutputPath"]
|
||||
if not os.path.exists(path):
|
||||
Logger.log_error("SCRIPT", f"Path invalid for task {task}")
|
||||
raise Exception()
|
||||
|
||||
self.install_chara = self.config_data.get("InstallChara", {})
|
||||
self.create_backup = self.config_data.get("CreateBackup", {})
|
||||
self.remove_chara = self.config_data.get("RemoveChara", {})
|
||||
self.fc_kks = self.config_data.get("FilterConvertKKS", {})
|
||||
|
||||
def create_gamepath(self):
|
||||
base = self.config_data["Core"]["GamePath"]
|
||||
self.game_path = {
|
||||
"base": base,
|
||||
"UserData": os.path.join(base, "UserData"),
|
||||
"BepInEx": os.path.join(base, "BepInEx"),
|
||||
"mods": os.path.join(base, "mods"),
|
||||
"chara": os.path.join(base, "UserData\\chara\\female"),
|
||||
"coordinate": os.path.join(base, "UserData\coordinate"),
|
||||
"Overlays": os.path.join(base, "UserData\Overlays")
|
||||
}
|
||||
|
||||
for path in self.game_path.values():
|
||||
if not os.path.exists(path):
|
||||
Logger.log_error("SCRIPT", "Game path not valid")
|
||||
raise Exception()
|
||||
|
||||
def _deepcopy_dict(self, dictionary):
|
||||
from copy import deepcopy
|
||||
return deepcopy(dictionary)
|
||||
|
||||
def _rollback_config(self, config):
|
||||
for key, value in config.items():
|
||||
setattr(self, key, value)
|
||||
165
util/file_manager.py
Normal file
165
util/file_manager.py
Normal file
@@ -0,0 +1,165 @@
|
||||
import os
|
||||
import shutil
|
||||
import datetime
|
||||
import patoolib
|
||||
import customtkinter
|
||||
import subprocess
|
||||
import time
|
||||
from util.logger import Logger
|
||||
|
||||
class FileManager:
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def find_all_files(self, directory):
|
||||
file_list = []
|
||||
compressed_file_list = []
|
||||
compressed_extensions = [".rar", ".zip", ".7z"]
|
||||
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
|
||||
file_path = os.path.join(root, file)
|
||||
file_size = os.path.getsize(file_path)
|
||||
_, file_extension = os.path.splitext(file_path)
|
||||
|
||||
if file_extension in compressed_extensions:
|
||||
compressed_file_list.append((file_path, file_size, file_extension))
|
||||
else:
|
||||
file_list.append((file_path, file_size, file_extension))
|
||||
|
||||
file_list.sort(key=lambda x: x[1])
|
||||
compressed_file_list.sort(key=lambda x: x[1])
|
||||
return file_list, compressed_file_list
|
||||
|
||||
def copy_and_paste(self, type, source_path, destination_folder):
|
||||
source_path = source_path[0]
|
||||
base_name = os.path.basename(source_path)
|
||||
destination_path = os.path.join(destination_folder, base_name)
|
||||
conflicts = self.config.install_chara["FileConflicts"]
|
||||
already_exists = os.path.exists(destination_path)
|
||||
|
||||
if already_exists and conflicts == "Skip":
|
||||
Logger.log_skipped(type, base_name)
|
||||
return
|
||||
|
||||
elif already_exists and conflicts == "Replace":
|
||||
Logger.log_replaced(type, base_name)
|
||||
|
||||
elif already_exists and conflicts == "Rename":
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
filename, file_extension = os.path.splitext(source_path)
|
||||
new_name = datetime.datetime.now().strftime('%Y%m%d%H%M%S%f')
|
||||
new_source_path = f"{filename}_{new_name}{file_extension}"
|
||||
os.rename(source_path, new_source_path)
|
||||
source_path = new_source_path
|
||||
Logger.log_renamed(type, base_name)
|
||||
|
||||
filename, file_extension = os.path.splitext(destination_path)
|
||||
destination_path = f"{filename}_{new_name}{file_extension}"
|
||||
break # Exit the loop if renaming is successful
|
||||
except PermissionError:
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(1) # Wait for 1 second before retrying
|
||||
else:
|
||||
Logger.log_error(type, f"Failed to rename {base_name} after {max_retries} attempts.")
|
||||
return
|
||||
|
||||
try:
|
||||
shutil.copy(source_path, destination_path)
|
||||
print(f"File copied successfully from {source_path} to {destination_path}")
|
||||
if not already_exists:
|
||||
Logger.log_success(type, base_name)
|
||||
except FileNotFoundError:
|
||||
Logger.log_error(type, f"{base_name} does not exist.")
|
||||
except PermissionError:
|
||||
Logger.log_error(type, f"Permission denied for {base_name}")
|
||||
except Exception as e:
|
||||
Logger.log_error(type, f"An error occurred: {e}")
|
||||
|
||||
def find_and_remove(self, type, source_path, destination_folder):
|
||||
source_path = source_path[0]
|
||||
base_name = os.path.basename(source_path)
|
||||
destination_path = os.path.join(destination_folder, base_name)
|
||||
if os.path.exists(destination_path):
|
||||
try:
|
||||
os.remove(destination_path)
|
||||
Logger.log_removed(type, base_name)
|
||||
except OSError as e:
|
||||
Logger.log_error(type, base_name)
|
||||
|
||||
def create_archive(self, folders, archive_path):
|
||||
# Specify the full path to the 7zip executable
|
||||
path_to_7zip = patoolib.util.find_program("7z")
|
||||
if not path_to_7zip:
|
||||
Logger.log_error("SCRIPT", "7zip not found. Unable to create backup")
|
||||
raise Exception()
|
||||
|
||||
if os.path.exists(archive_path+".7z"):
|
||||
os.remove(archive_path+".7z")
|
||||
|
||||
path_to_7zip = f'"{path_to_7zip}"'
|
||||
archive_path = f'"{archive_path}"'
|
||||
exclude_folders = [
|
||||
'"Sideloader Modpack"',
|
||||
'"Sideloader Modpack - Studio"',
|
||||
'"Sideloader Modpack - KK_UncensorSelector"',
|
||||
'"Sideloader Modpack - Maps"',
|
||||
'"Sideloader Modpack - KK_MaterialEditor"',
|
||||
'"Sideloader Modpack - Fixes"',
|
||||
'"Sideloader Modpack - Exclusive KK KKS"',
|
||||
'"Sideloader Modpack - Exclusive KK"',
|
||||
'"Sideloader Modpack - Animations"'
|
||||
]
|
||||
|
||||
# Create a string of folder names to exclude
|
||||
exclude_string = ''
|
||||
for folder in exclude_folders:
|
||||
exclude_string += f'-xr!{folder} '
|
||||
|
||||
# Create a string of folder names to include
|
||||
include_string = ''
|
||||
for folder in folders:
|
||||
include_string += f'"{folder}" '
|
||||
|
||||
# Construct the 7zip command
|
||||
command = f'{path_to_7zip} a -t7z {archive_path} {include_string} {exclude_string}'
|
||||
# Call the command
|
||||
process = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
# Print the output
|
||||
for line in process.stdout.decode().split('\n'):
|
||||
if line.strip() != "":
|
||||
Logger.log_info("7-Zip", line)
|
||||
# Check the return code
|
||||
if process.returncode not in [0, 1]:
|
||||
raise Exception()
|
||||
|
||||
def extract_archive(self, archive_path):
|
||||
try:
|
||||
archive_name = os.path.basename(archive_path)
|
||||
Logger.log_info("ARCHIVE", f"Extracting {archive_name}")
|
||||
extract_path = os.path.join(f"{os.path.splitext(archive_path)[0]}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S%f')}")
|
||||
patoolib.extract_archive(archive_path, outdir=extract_path)
|
||||
return extract_path
|
||||
|
||||
except patoolib.util.PatoolError as e:
|
||||
text = f"There is an error with the archive {archive_name} but it is impossible to detect the cause. Maybe it requires a password?"
|
||||
while self.config.install_chara["Password"] == "Request Password":
|
||||
try:
|
||||
dialog = customtkinter.CTkInputDialog(text=text, title="Enter Password")
|
||||
password = dialog.get_input()
|
||||
|
||||
if password is not None or "":
|
||||
patoolib.extract_archive(archive_path, outdir=extract_path, password=password)
|
||||
return extract_path
|
||||
else:
|
||||
break
|
||||
except:
|
||||
text = f"Wrong password or {archive_name} is corrupted. Please enter password again or click Cancel"
|
||||
|
||||
Logger.log_skipped("ARCHIVE", archive_name)
|
||||
|
||||
34
util/logger.py
Normal file
34
util/logger.py
Normal file
@@ -0,0 +1,34 @@
|
||||
class Logger(object):
|
||||
|
||||
@classmethod
|
||||
def log_msg(cls, type, msg):
|
||||
print(f"[MSG][{type}] {msg}")
|
||||
|
||||
@classmethod
|
||||
def log_success(cls, type, msg):
|
||||
print(f"[SUCCESS][{type}] {msg}")
|
||||
|
||||
@classmethod
|
||||
def log_skipped(cls, type, msg):
|
||||
print(f"[SKIPPED][{type}] {msg}")
|
||||
|
||||
@classmethod
|
||||
def log_replaced(cls, type, msg):
|
||||
print(f"[REPLACED][{type}] {msg}")
|
||||
|
||||
@classmethod
|
||||
def log_renamed(cls, type, msg):
|
||||
print(f"[RENAMED][{type}] {msg}")
|
||||
|
||||
@classmethod
|
||||
def log_removed(cls, type, msg):
|
||||
print(f"[REMOVED][{type}] {msg}")
|
||||
|
||||
@classmethod
|
||||
def log_error(cls, type, msg):
|
||||
print(f"[ERROR][{type}] {msg}")
|
||||
|
||||
@classmethod
|
||||
def log_info(cls, type, msg):
|
||||
print(f"[INFO][{type}] {msg}")
|
||||
|
||||
Reference in New Issue
Block a user