diff --git a/GUI.py b/GUI.py index 1c70971..d366fcd 100644 --- a/GUI.py +++ b/GUI.py @@ -29,7 +29,7 @@ def __init__(self): # This will allow me to disable the functionality for it until I come up with a proper implementation self.allowScripts = False # Another flag for Snark, it disables booting to the "games" menu and makes the menu show a "This menu will be completed soon" message - # instead of showing the (very much) incomplete menu + # instead of showing the menu self.allowGames = True class Interp: @@ -489,6 +489,7 @@ def __init__(self): pass if not self.flags.devMode: + print("Attempting to connect to update server...") try: self.check_version() except: diff --git a/interp.py b/interp.py new file mode 100644 index 0000000..49192d3 --- /dev/null +++ b/interp.py @@ -0,0 +1,74 @@ +import json +import jsonc +from tkinter.filedialog import askopenfilename, askdirectory +import os + +# Class that gets information from the script header (the lines of text sandwiched inbetween the dashes) +class SSTVer: + + def __init__(self, dat:list): + self.version = 0 + self.format = 'json' + count = -1 + end = dat.index('-', 2) + print(end) + for l in dat: + count += 1 + if count == 0: + continue + elif l.startswith('version'): + # Getting the last letter in the line, which is the version number. + self.version = int(l[len(l)-1:]) + elif l.startswith('format'): + frmt = l.replace('format ', '') + self.format = frmt + elif l == '-': + break + self.script = dat[end+1:] + # Converting the list into a readable string + self.script = "\n".join(self.script) + +# Class for reading the non-functional part of the script, the 'Global variables'. +class SSTGlobal: + + def __init__(self, dat:dict, opt:dict): + self.name = dat["name"] + self.globalOut = dat["globalOutput"] + if self.globalOut.startswith('askFolder'): + # Getting start directory + startDir = opt["startFolder"] + if startDir.startswith("~"): + startDir = os.path.expanduser(startDir) + # Checking if user has specified a title + oTitle = "Select Output Folder" + if self.globalOut.find(',') != -1: + oTitle = self.globalOut.split(',')[1] + self.globalOut = askdirectory(title=oTitle, initialdir=startDir) + +# Reader for SST (Snark ScripT) files +class SSTReader: + + def __init__(self, sst:str, opt:dict): + sstf = open(sst, 'r') + scr = sstf.readlines() + count = -1 + for l in scr: + count += 1 + scr[count] = l.replace('\n', '') + header = SSTVer(scr) + parsedSCR = None + if header.format == 'jsonc': + print(header.script) + parsedSCR = jsonc.loads(header.script) + elif header.format == 'json': + print(header.script) + parsedSCR = json.loads(header.script) + scrG = SSTGlobal(parsedSCR, opt) + print("Data from SST file: ") + print(f"Version: {header.version}") + print(f"Format: {header.format}") + print(f"Name: {scrG.name}") + suffix = "" + if parsedSCR["globalOutput"].startswith("askFolder"): + suffix = "(generated path from askFolder)" + print(f"Global Output Folder: {scrG.globalOut} {suffix}") \ No newline at end of file diff --git a/menus.py b/menus.py index 719bdb8..94bf843 100644 --- a/menus.py +++ b/menus.py @@ -12,6 +12,7 @@ import json import sys import jsonc +from interp import SSTReader # To make things easier for myself, I'm making a new class that contains common values that won't (or usually doesn't) change for each menu. class MenuTemp(): @@ -395,6 +396,7 @@ def __init__(self, template, master, startHidden:bool=False): self.widthFix, self.conFix = self.widthFix-n, self.conFix-n self.quick = Frame(master, borderwidth=2, bg=thme["bg"]) self.advOpt = Frame(master, borderwidth=2, bg=thme["bg"], relief="sunken") + self.advOptR2 = Frame(self.advOpt, borderwidth=2, bg=thme["bg"]) # Setting up options js = open("save/options.json", 'r') self.options = json.loads(js.read()) @@ -445,7 +447,7 @@ def __init__(self, template, master, startHidden:bool=False): self.advOptLabel = Label(self.advOpt, text="Advanced Options") self.decomp = Button(master, text='Decompile', command=self.startDecomp, cursor="hand2") self.hlmv = Button(master, text='Open model in HLMV', command=self.openHLAM, cursor="hand2") - self.console = Console(master, 'Start a decompile and the terminal output will appear here!', 0, 5, self.conFix, 13) + self.console = Console(master, 'Start a decompile and the terminal output will appear here!', 0, 5, self.conFix, 12) # Advanced options self.logVal = BooleanVar(self.advOpt, value=False) self.logChk = Checkbutton(self.advOpt, text="Write log to file", variable=self.logVal, command=self.setLog) @@ -455,6 +457,8 @@ def __init__(self, template, master, startHidden:bool=False): self.uChk = Checkbutton(self.advOpt, text="Shift model UVs", variable=self.uVal) self.vVal = BooleanVar(self.advOpt, value=self.presetDat["-V"]) self.vChk = Checkbutton(self.advOpt, text="Ignore checks", variable=self.vVal) + self.tVal = BooleanVar(self.advOpt, value=True) + self.tChk = Checkbutton(self.advOptR2, text="Place textures in subfolder", variable=self.tVal) if not startHidden: self.show() @@ -463,17 +467,20 @@ def __init__(self, template, master, startHidden:bool=False): self.mChkTT = ToolTip(self.mChk, "By default, the decompiler outputs .qc files with features for Xash3D that GoldSRC does not support, enabling this makes the output GoldSRC compatible.", background=thme["tt"], foreground=thme["txt"]) self.uChkTT = ToolTip(self.uChk, "Enabling this will make the decompiler shift the model UVs, this fixes UV errors with models compiled by some modern compilers like DoomMusic and Sven Co-op's StudioMDL.", background=thme["tt"], foreground=thme["txt"]) self.vChkTT = ToolTip(self.vChk, "Enabling this will make the decompiler ignore validity checks, which might allow you to decompile some broken models", background=thme["tt"], foreground=thme["txt"]) + self.tChkTT = ToolTip(self.tChk, "Disabling this will make the decompiler place textures in the same location as your models, this can fix issues with importing the model in MilkShape3D or Fragmotion.", background=thme["tt"], foreground=thme["txt"]) self.mdlTT = ToolTip(self.mdlBrowse, "REQUIRED, specifies the MDL file used to decompile a model, you cannot leave this blank.", background=thme["tt"], foreground=thme["txt"]) self.outputTT = ToolTip(self.outBrowse, "OPTIONAL, if an output folder is not specified, then it will place the decompiled model in a subfolder of where the MDL file is located.", background=thme["tt"], foreground=thme["txt"]) # Applying theme self.applyTheme(master) self.applyTheme(self.advOpt) + self.applyTheme(self.advOptR2) self.applyTheme(self.quick) def setLog(self): self.logOutput = self.logVal.get() def openHLAM(self): + print("Opening model in HLMV") # If "Half-Life Asset Manager" is selected if self.options["gsMV"]["selectedMV"] == 1: if sys.platform == "linux": @@ -496,7 +503,7 @@ def openHLAM(self): def inputHandler(self, e=False): self.name.set(self.nameEntry.get()) if not self.name.get() == "" and self.options["gsMV"]["selectedMV"] > 0: - self.hlmv.grid(column=1, row=3, pady=(27,0), sticky="w") + self.hlmv.grid(column=1, row=4, pady=(10,0), sticky="w") def chPreset(self, e=False): self.presetDat = self.presets["presets"][self.presetSel.get()] @@ -541,9 +548,15 @@ def changeTheme(self, newTheme): self.thme = newTheme self.applyTheme(self.master) self.applyTheme(self.advOpt) + self.applyTheme(self.advOptR2) + self.applyTheme(self.quick) self.mdlTT.changeTheme(newTheme["tt"], newTheme["txt"]) self.outputTT.changeTheme(newTheme["tt"], newTheme["txt"]) self.logChkTT.changeTheme(newTheme["tt"], newTheme["txt"]) + self.mChkTT.changeTheme(newTheme["tt"], newTheme["txt"]) + self.uChkTT.changeTheme(newTheme["tt"], newTheme["txt"]) + self.vChkTT.changeTheme(newTheme["tt"], newTheme["txt"]) + self.tChkTT.changeTheme(newTheme["tt"], newTheme["txt"]) def updateOpt(self, key, value): if not key.startswith("gsMV"): @@ -571,14 +584,16 @@ def show(self): self.quickStpLbl.grid(column=0, row=2, sticky="w") self.presetSel.grid(column=1,row=2) self.advOpt.grid(column=0, row=3, sticky="nsew", columnspan=10, pady=(20,0)) + self.advOptR2.grid(column=0, row=2, sticky="nsew", columnspan=10) self.advOptLabel.grid(column=0, row=0, sticky="w") self.logChk.grid(column=0, row=1, sticky="w") self.mChk.grid(column=1, row=1, sticky="w") self.uChk.grid(column=2, row=1, sticky="w") self.vChk.grid(column=3, row=1, sticky="w") - self.decomp.grid(column=0, row=4, pady=(24,0)) + self.tChk.grid(column=0, row=1, sticky="w") + self.decomp.grid(column=0, row=4, pady=(10,0)) if not self.name.get() == "" and self.options["gsMV"]["selectedMV"] > 0: - self.hlmv.grid(column=1, row=4, pady=(24,0), sticky="w") + self.hlmv.grid(column=1, row=4, pady=(10,0), sticky="w") self.console.show() def findMDL(self): @@ -588,7 +603,7 @@ def findMDL(self): fileTypes = [("GoldSRC Model", "*.mdl"), ("All Files", "*.*")] self.name.set(askopenfilename(title="Select MDL", initialdir=startDir, filetypes=fileTypes)) if not self.name.get() == "" and self.options["gsMV"]["selectedMV"] > 0: - self.hlmv.grid(column=1, row=3, pady=(27,0), sticky="w") + self.hlmv.grid(column=1, row=4, pady=(10,0), sticky="w") def output(self): startDir = self.options["startFolder"] if startDir.startswith("~"): @@ -603,6 +618,8 @@ def getArgs(self): args.append("-u") if self.vVal.get(): args.append("-V") + if self.tVal.get(): + args.append("-t") cmdArgs = " ".join(args) print(cmdArgs) return(cmdArgs) @@ -621,14 +638,14 @@ def startDecomp(self): tOutput = '' if sys.platform == 'linux': if gotArgs: - tOutput = subprocess.getoutput(f'./third_party/mdldec -d {cmdArgs} \"{mdl}\"') + tOutput = subprocess.getoutput(f'./third_party/mdldec -a {cmdArgs} \"{mdl}\"') else: - tOutput = subprocess.getoutput(f'./third_party/mdldec -d \"{mdl}\"') + tOutput = subprocess.getoutput(f'./third_party/mdldec -a \"{mdl}\"') elif sys.platform == 'win32': if gotArgs: - tOutput = subprocess.getoutput(f'\"{os.getcwd()}/third_party/mdldec.exe -d {cmdArgs} \" \"{mdl}\"') + tOutput = subprocess.getoutput(f'\"{os.getcwd()}/third_party/mdldec.exe -a {cmdArgs} \" \"{mdl}\"') else: - tOutput = subprocess.getoutput(f'\"{os.getcwd()}/third_party/mdldec.exe -d \" \"{mdl}\"') + tOutput = subprocess.getoutput(f'\"{os.getcwd()}/third_party/mdldec.exe -a \" \"{mdl}\"') # I don't have a Mac so I can't compile mdldec to Mac targets :( # So instead I have to use wine for Mac systems """elif sys.platform == 'darwin': @@ -648,19 +665,46 @@ def startDecomp(self): texFolder = os.path.join(mdlFolder, 'textures/') for f in os.listdir(mdlFolder): print(f) - if f.endswith("smd") or f.endswith("qc"): + if f.endswith("smd"): + shutil.copy(f"{mdlFolder}/{f}", os.path.join(output, f)) + os.remove(f"{mdlFolder}/{f}") + elif f.endswith("bmp") and not self.tVal.get(): + shutil.copy(f"{mdlFolder}/{f}", os.path.join(output, f)) + os.remove(f"{mdlFolder}/{f}") + elif f.endswith("qc") and self.mVal.get(): + # Doing yet another workaround for a MDLDec bug where motion types are left blank when -m is used. + qc = open(f"{mdlFolder}/{f}", 'r') + nqc = qc.readlines() + qc.close() + + # T&Cs apply (not really, this is a joke) + tnC = nqc.count("\t\n") + # Using the index function to more efficiently search for blank motion type lines + count = 0 + while count < tnC: + count += 1 + bl = nqc.index("\t\n") + # Using X instead of Y or Z as X is the most commonly used value. + nqc[bl] = "\tX\n" + # Writing the new qc to the directory and deleting the original qc output from MDLDec + nqcf = open(os.path.join(output, f), "w") + nqcf.write("".join(nqc)) + nqcf.close() + os.remove(f"{mdlFolder}/{f}") + elif f.endswith("qc"): shutil.copy(f"{mdlFolder}/{f}", os.path.join(output, f)) os.remove(f"{mdlFolder}/{f}") shutil.copytree(anims, os.path.join(output, 'anims/')) - shutil.copytree(texFolder, os.path.join(output, 'textures/')) + if self.tVal.get(): + shutil.copytree(texFolder, os.path.join(output, 'textures/')) + try: + shutil.rmtree(texFolder) + except: + pass try: shutil.rmtree(anims) except: pass - try: - shutil.rmtree(texFolder) - except: - pass class CompMenu(): def __init__(self, template, master, startHidden:bool=False): @@ -1672,6 +1716,8 @@ def changeTheme(self, newTheme): self.startFolderTT.changeTheme(newTheme["tt"], newTheme["txt"]) self.startFolderTT2.changeTheme(newTheme["tt"], newTheme["txt"]) self.forceDefTT.changeTheme(newTheme["tt"], newTheme["txt"]) + self.hlmvTT.changeTheme(newTheme["tt"], newTheme["txt"]) + self.setMVPtt.changeTheme(newTheme["tt"], newTheme["txt"]) def chSF(self): path = askdirectory(title="Set starting directory for this file explorer") @@ -1818,22 +1864,26 @@ def __init__(self, template, master, startHidden:bool=False): EXE_LOCATION = os.path.dirname( sys.executable ) else: EXE_LOCATION = os.path.dirname( os.path.realpath( __file__ ) ) - scr_dir = os.path.join(EXE_LOCATION, "scripts") - for s in os.listdir(scr_dir): + self.scr_dir = os.path.join(EXE_LOCATION, "scripts") + for s in os.listdir(self.scr_dir): self.scripts.append(s) - self.scr_list = Listbox(master, width=self.widthFix) + self.scr_list = Listbox(master, width=self.widthFix, selectmode=SINGLE) count = -1 while count < len(self.scripts)-1: count += 1 self.scr_list.insert(count, self.scripts[count]) - self.runBtn = Button(master, text="Run script", cursor="hand2") + self.runBtn = Button(master, text="Run script", cursor="hand2", command=self.readScript) self.console = Console(master, 'Run a script and an output of the script\'s progress will appear here!', 0, 2, self.conFix, self.conHeight) if not startHidden: self.show() # Applying theme self.applyTheme(master) + + def readScript(self): + selected_scr = self.scripts[int(self.scr_list.curselection()[0])] + a = SSTReader(os.path.join(self.scr_dir, selected_scr), self.options) def applyTheme(self, master): style= ttk.Style() diff --git a/requirements.txt b/requirements.txt index baf9bba..b58fd7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ jsonc-parser scikit-build tksvg +certifi +pyyaml diff --git a/scripts/batch-compile.sst b/scripts/batch-compile.sst new file mode 100644 index 0000000..a78929a --- /dev/null +++ b/scripts/batch-compile.sst @@ -0,0 +1,15 @@ +- +version 1 +format json +- +{ + "name": "Batch Compile", + "globalOutput": "askFolder", + "tasks": [ + { + "name": "Compile MDL", + "task": "compile", + "file": "askFolder,Select a folder full of compilable models" + } + ] +} diff --git a/scripts/batch-decompile.sst b/scripts/batch-decompile.sst new file mode 100644 index 0000000..a7fec6f --- /dev/null +++ b/scripts/batch-decompile.sst @@ -0,0 +1,15 @@ +- +version 1 +format json +- +{ + "name": "Batch Decompile", + "globalOutput": "askFolder,Select a folder full of models", + "tasks": [ + { + "name": "Decompile MDL", + "task": "decompile", + "file": "askFolder" + } + ] +} diff --git a/third_party/mdldec b/third_party/mdldec index 2dd19dd..9ecbc66 100755 Binary files a/third_party/mdldec and b/third_party/mdldec differ diff --git a/third_party/mdldec.exe b/third_party/mdldec.exe index ca7b872..4be9f85 100755 Binary files a/third_party/mdldec.exe and b/third_party/mdldec.exe differ diff --git a/version.txt b/version.txt index f18af9a..6beabb9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v0.2.0-(OS)-alpha +v0.2.1-(OS)-alpha