Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
74 changes: 74 additions & 0 deletions interp.py
Original file line number Diff line number Diff line change
@@ -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}")
88 changes: 69 additions & 19 deletions menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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)
Expand All @@ -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()

Expand All @@ -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":
Expand All @@ -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()]
Expand Down Expand Up @@ -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"):
Expand Down Expand Up @@ -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):
Expand All @@ -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("~"):
Expand All @@ -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)
Expand All @@ -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':
Expand All @@ -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):
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
jsonc-parser
scikit-build
tksvg
certifi
pyyaml
15 changes: 15 additions & 0 deletions scripts/batch-compile.sst
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
15 changes: 15 additions & 0 deletions scripts/batch-decompile.sst
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
Binary file modified third_party/mdldec
Binary file not shown.
Binary file modified third_party/mdldec.exe
Binary file not shown.
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.2.0-(OS)-alpha
v0.2.1-(OS)-alpha