UniversalModloader/UniversalModloader.py

241 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
import sys
import os
import shutil
import webbrowser
import configparser
import subprocess
import ctypes
oldexcepthook = sys.excepthook
def newexcepthook(type,value,traceback):
oldexcepthook(type,value,traceback)
input("Press ENTER to quit.")
sys.excepthook = newexcepthook
p = os.path.join
pUp = os.path.dirname
s = False
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
s = os.path.realpath(sys.executable)
else:
s = os.path.realpath(__file__)
sp = pUp(s)
version = (1,2,0,"beta (dev)")
pathGame = ""
pathTemp = ""
pathOrig = ""
pathMods = ""
stateModded = False
listLinked = []
listLinkedFolders = []
config = configparser.ConfigParser()
config["default"] = {
"linkMethod": "hardlink"
}
config.read(p(sp,os.path.splitext(os.path.basename(s))[0] + ".ini"))
if config["default"]["linkMethod"] == "reflink":
fancyReflink = False
try:
from reflink import reflink
fancyReflink = True
except:
print("python3-reflink not installed, using shell for reflink (slow)\n")
def filterDd(text):
while text[-1] in ['"',"'"," ",os.path.sep]: text = text[:-1]
while text[0] in ['"',"'"," "]: text = text[1:]
return text
def questionYesNo(text):
while True:
response = input(text).lower()
if response == "y": return True
if response == "n": return False
def questionMultChoice(options):
length = len(options)
index = 0
while index < length:
print(str(index + 1)+ ": " +options[index])
index = index + 1
response = input("Choice: ")
try:
response = int(response)
except:
return False
if response < 1: return False
if response > index: return False
return options[response - 1]
def getModState():
if os.path.isdir(pathOrig):
return True
return False
def getModlist(path,sort = True):
modList = []
for root,dirs,files in os.walk(path):
for file in dirs:
ffile = p(root,file)
lfile = ffile.replace(path + os.path.sep,"",1)
if lfile[0] == "-": continue
if lfile[0] == "[" and lfile[-1] == "]":
modList = modList + getModlist(ffile,False)
continue
modList.append(ffile)
break
if sort == True: return sorted(modList)
return modList
btrfsdll = False
def linkFile(src,dst,method = False):
if not method: method = config["default"]["linkMethod"]
if method == "hardlink":
os.link(src,dst)
return
if method == "copy":
shutil.copy(src,dst)
return
if method == "reflink":
if fancyReflink == False:
if os.name == "nt":
global btrfsdll
if not btrfsdll: btrfsdll = ctypes.cdll.LoadLibrary("shellbtrfs.dll")
btrfsdll.ReflinkCopyW(0,0,'"' +src+ '" "' +dst+ '"',1)
else:
subprocess.call(["cp","-n","--reflink=always",src,dst])
return
else:
reflink(src,dst)
return
raise Exception("uml", "invalid link method")
def cloneFolder(src,dst):
global listLinked
global listLinkedFolders
for root,dirs,files in os.walk(src):
for file in dirs:
ffile = p(root,file)
lfile = ffile.replace(src + os.path.sep,"",1)
nfile = p(dst,lfile)
nnfile = nfile
if config["default"]["windowsPaths"] == "true": nnfile = nnfile.lower()
if nnfile in listLinkedFolders: continue
os.makedirs(nfile)
listLinkedFolders.append(nnfile)
for file in files:
ffile = p(root,file)
lfile = ffile.replace(src + os.path.sep,"",1)
nfile = p(dst,lfile)
nnfile = nfile
if config["default"]["windowsPaths"] == "true": nnfile = nnfile.lower()
if nnfile in listLinked: continue
linkFile(ffile,nfile)
listLinked.append(nnfile)
def loadMods():
print("")
global listLinked
global listLinkedFolders
global stateModded
os.makedirs(pathTemp)
print("Getting mod-list...")
listMods = getModlist(pathMods)
for ffile in reversed(listMods):
lfile = ffile.replace(pathMods + os.path.sep,"",1)
print("Load mod: " +lfile)
cloneFolder(ffile,pathTemp)
print("Linking game...")
cloneFolder(pathGame,pathTemp)
os.rename(pathGame,pathOrig)
os.rename(pathTemp,pathGame)
listLinked = []
listLinkedFolders = []
stateModded = True
def unloadMods():
global stateModded
print("")
print("Removing modded game...")
shutil.rmtree(pathGame)
print("Renaming original game...")
os.rename(pathOrig,pathGame)
stateModded = False
def main():
global pathGame, pathMods, pathTemp, pathOrig, stateModded
pathGame = ""
if len(sys.argv) > 1:
pathGame = sys.argv[1]
while pathGame == "" or os.path.isdir(pathGame) == False:
pathGame = filterDd(input("Path to game - you may drag & drop:\n"))
pathGame = pathGame.rstrip("/\\")
pathMods = pathGame + " - umlMods"
pathTemp = pathGame + " - umlTemp"
pathOrig = pathGame + " - umlOriginal"
if not os.path.isdir(pathMods):
if questionYesNo("\nIt doesn't look like you modded this game before.\nDo you want to create the mod folder? (y/n)\n") == True:
os.makedirs(pathMods)
else:
sys.exit()
if os.path.isdir(pathTemp):
print("\nRemoving temporary folder...")
shutil.rmtree(pathTemp)
stateModded = getModState()
while True:
print("")
print("Modded: " +str(stateModded))
choice = False
if stateModded == False: choice = questionMultChoice([
"Load mods",
"Open mod-folder"
])
if stateModded == True: choice = questionMultChoice([
"Reload mods",
"Unload mods",
"Open mod-folder"
])
curModState = getModState()
if curModState != stateModded:
stateModded = curModState
print("\nMod-state has changed, refreshing menu.")
continue
if choice == False: continue
if choice == "Load mods": loadMods()
if choice == "Reload mods": unloadMods(); loadMods()
if choice == "Unload mods": unloadMods()
if choice == "Open mod-folder": webbrowser.open("file://" +pathMods)
if __name__ == "__main__":
main()