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
1 change: 1 addition & 0 deletions .github/workflows/pyinstaller-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
pip install pyinstaller
pip install jsoncparser
pip install certifi
pip install pyyaml
- name: Run PyInstaller
run: |
pyinstaller --noconfirm --onedir --console --name "Snark" --add-data "version.txt:." --add-data "activities.txt:." --add-data "logo128.png:." --add-data "icon-win32.ico:." --add-data "icon-linux.png:." --add-data "LICENSE:." --add-data "README.md:." --add-data "save:save/" --add-data "themes:themes/" --add-data "third_party:third_party/" --add-data "images:images/" --add-data "logs:logs/" "GUI.py"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pyinstaller.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
pip install pyinstaller
pip install jsoncparser
pip install certifi
pip install pyyaml
- name: Run PyInstaller
run: |
pyinstaller --noconfirm --onedir --console --name "Snark" --add-data "version.txt:." --add-data "activities.txt:." --add-data "logo128.png:." --add-data "icon-win32.ico:." --add-data "icon-linux.png:." --add-data "LICENSE:." --add-data "README.md:." --add-data "save:save/" --add-data "themes:themes/" --add-data "third_party:third_party/" --add-data "images:images/" --add-data "logs:logs/" "GUI.py"
Expand Down
2 changes: 2 additions & 0 deletions GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,11 +577,13 @@ def changeTheme(self, a):
self.decMenu.changeTheme(thCol)
self.optMenu.changeTheme(thCol)
self.compSetMenu.changeTheme(thCol)
self.scrMenu.changeTheme(thCol)
self.setupMenu.master.config(bg=thCol["bg"])
self.cmpMenu.master.config(bg=thCol["bg"])
self.abtMenu.master.config(bg=thCol["bg"])
self.optMenu.master.config(bg=thCol["bg"])
self.compSetMenu.master.config(bg=thCol["bg"])
self.scrMenu.master.config(bg=thCol["bg"])
self.frame.config(bg=thCol["bg"])
self.header.config(bg=thCol["bg"])
for w in self.header.winfo_children():
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<br>
<h2>Why should I use Snark over Crowbar?</h2>
<h3><i>It's easier to configure and use!</i></h3>
<p>In Crowbar, to decompile a model you would have to fiddle around with settings in order to get your model decompiled properly for GoldSRC. With Snark, you don't need to do any extra configuration, just select an MDL file, select the output folder, set the appropriate preset for the compiler used, hit the decompile button and you're set!</p>
<p>In Crowbar, to decompile a model you would have to fiddle around with settings in order to get your model decompiled properly for GoldSRC. With Snark, you don't need to do any extra configuration, just select an MDL file, select the output folder, set the appropriate preset for the compiler you'd like to use, hit the decompile button and you're set!</p>
<img src="git_images/decompWin.png" width="609" height="523" alt="Screenshot of decompile menu">
<p>On top of that, the compiling GUI includes all the command-line options as toggleable checks with tooltips to tell you what they do, so you don't need to check any sort of documentation, official or unofficial to use the advanced features StudioMDL offers its users.</p>
<img src="git_images/compWin.png" width="609" height="523" alt="Screenshot of compile menu">
Expand Down
36 changes: 36 additions & 0 deletions helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,59 @@ def callback(self, opt):
self.command(opt)
self.name.set(self.text)

"""
Widget class that displays an output of anything, including terminal outputs or logs, while disallowing the user to edit the output.
"""
class Console():

def __init__(self, master, startText:str='', columnn=0, roww=0, widthh=100, heightt=20):
self.column = columnn
self.row = roww
self.width = widthh
self.height = heightt
self.lines = []
self.miniTerminal = Text(master, width=self.width, height=self.height)
ys = Scrollbar(master, orient='vertical', command=self.miniTerminal.yview)
self.miniTerminal['yscrollcommand'] = ys.set
self.miniTerminal.insert('1.0', startText)
if startText.find("\n") != -1:
self.lines = startText.split('\n')
else:
self.lines.append(startText)
self.miniTerminal['state'] = 'disabled'

# Sets the output of the console to something else.
def setOutput(self, replace):
self.miniTerminal['state'] = 'normal'
self.miniTerminal.delete('1.0', 'end')
self.miniTerminal.insert('1.0', replace)
self.miniTerminal['state'] = 'disabled'
if replace.find("\n") != -1:
self.lines = replace.split('\n')
else:
self.lines.append(replace)

# Appends a new line to the bottom of the console.
def append(self, e):
self.miniTerminal['state'] = 'normal'
self.miniTerminal.insert('end', e)
self.lines.append(e)
self.miniTerminal['state'] = 'disabled'

# Clears the output completely.
def clear(self):
self.lines = []
self.miniTerminal['state'] = 'normal'
self.miniTerminal.delete('1.0', 'end')
self.miniTerminal['state'] = 'disabled'

# Checks if the output is empty and returns true if it is.
def isEmpty(self):
if len(self.lines) == 0:
return True
return False

# Helper function that formats outputted text generated by the subprocess library to be usable for the class.
def subprocessHelper(self, query):
tOutputTmp = query.split('\n')
tOutputTmp.pop(0)
Expand All @@ -57,9 +91,11 @@ def subprocessHelper(self, query):
tOutput += tOutputTmp[count] + '\n'
return tOutput

# Show the console
def show(self):
self.miniTerminal.grid(column=self.column, row=self.row, sticky=(N, S, E, W), columnspan=69)

# Hide the console
def hide(self):
self.miniTerminal.grid_remove()

Expand Down
125 changes: 114 additions & 11 deletions interp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import jsonc
from tkinter.filedialog import askopenfilename, askdirectory
import os
import sys
import subprocess
import yaml

# Class that gets information from the script header (the lines of text sandwiched inbetween the dashes)
class SSTVer:
Expand Down Expand Up @@ -33,7 +36,9 @@ class SSTGlobal:

def __init__(self, dat:dict, opt:dict):
self.name = dat["name"]
self.globalOut = dat["globalOutput"]
self.defProf = dat.get("defComp", None)
self.defDec = dat.get("defDecPreset", None)
self.globalOut = dat.get("globalOutput", None)
if self.globalOut.startswith('askFolder'):
# Getting start directory
startDir = opt["startFolder"]
Expand All @@ -48,10 +53,11 @@ def __init__(self, dat:dict, opt:dict):
# Reader for SST (Snark ScripT) files
class SSTReader:

def __init__(self, sst:str, opt:dict):
def __init__(self, sst:str, opt:dict, logger, options:dict):
sstf = open(sst, 'r')
scr = sstf.readlines()
count = -1
error = False
for l in scr:
count += 1
scr[count] = l.replace('\n', '')
Expand All @@ -63,12 +69,109 @@ def __init__(self, sst:str, opt:dict):
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}")
elif header.format == 'yaml' or header.format == 'yml':
print(header.script)
parsedSCR = yaml.safe_load(header.script)
else:
logger.append("Fatal error: Format specified in header is not json, jsonc, or yaml/yml.")
error = True
if not error:
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}")
intrp = SSTInterp(logger, parsedSCR["tasks"], scrG, header, options)


class SSTInterp:

def __init__(self, logger, dat, globalVs, header, options):
self.logger = logger
self.dat = dat
self.globalVs = globalVs
self.header = header
self.options = options
self.tskCnt = 0
# This variable stores all available tasks and tells the Interpreter how to handle them
self.cmds = {
# This command is for compiling models
"compile": {
"func": self.cmpHnd,
# First array: Type?, Accepted inputs, Options
# Second array:
"args": [["file", "file", ".mdl"], ["output", "folder"]]
},
# This command is for decompiling models
"decompile": {
"func": self.dCMPHnd,
"args": [["file", "file", ".mdl"], ["output", "folder"]]
},
"view": {
"func": self.hlmvHnd,
"args": [["file", "file+task", ".mdl"]]
}
}
logger.append(f"Starting Script \'{globalVs.name}\'")

def taskToValue(self, tsk:str, grab:str):
for t in self.dat:
if t["name"] == tsk:
return t[grab]
# If a value could not be found, return nothing.
return None

# Functions for each instruction defined in self.cmds
def cmpHnd(self):
# This is a stub for now.
pass

def dCOMPHnd(self):
# This is a stub for now.
pass

def hlmvHnd(self, dat):
file = ""
genByTask = False
if dat["file"].endswith(".mdl"):
file = dat["file"]
else:
file = self.taskToValue(dat["file"], "output")
genByTask = True

if file != None:
if not genByTask:
self.hlmvTsk(file, False)
else:
self.hlmvTsk(file, True, dat["file"])
else:
self.logger.append(f"Error: Couldn't find model generated by compile task \'{dat["file"]}\'")
self.logger.append(f"From: view task \'{dat["name"]}\' (index {self.tskCnt})")

def hlmvTsk(self, f, genByTask:bool, task=""):
if genByTask:
self.logger.append(f"Viewing model generated by compile task: \'{task}\'")
else:
self.logger.append(f"Viewing model \'{os.path.basename(f)}\'")
# If "Half-Life Asset Manager" is selected
if self.options["gsMV"]["selectedMV"] == 1:
if sys.platform == "linux":
a = subprocess.getoutput(f"XDG_SESSION_TYPE=x11 hlam \"{f}\"")
else:
a = subprocess.getoutput(f"\"C:/Program Files (x86)/Half-Life Asset Manager/hlam.exe\" \"{f}\"")
# If "Other" option is selected
elif self.options["gsMV"]["selectedMV"] > 1:
if sys.platform == "linux":
path = self.options["gsMV"]["csPath"]
path = os.path.expanduser(path)
if path.endswith(".exe"):
a = subprocess.getoutput(f"wine \"{path}\" \"{f}\"")
else:
a = subprocess.getoutput(f"\"{path}\" \"{f}\"")
else:
path = self.options["gsMV"]["csPath"]
a = subprocess.getoutput(f"\"{path}\" \"{f}\"")
11 changes: 6 additions & 5 deletions menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,9 +647,9 @@ def startDecomp(self):
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 -a {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 -a \" \"{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 Down Expand Up @@ -815,7 +815,7 @@ def __init__(self, template, master, startHidden:bool=False):
self.angleSBtt = ToolTip(self.angleChk, "Overrides the blend angle of vertex normals, Valve recommends keeping this value at 2 (the default) according to the HLSDK docs.", background=thme["tt"], foreground=thme["txt"])
self.angleChkTT = ToolTip(self.hitboxChk, "Dumps hitbox information to the console when enabled.", background=thme["tt"], foreground=thme["txt"])
self.keepBonesChkTT = ToolTip(self.keepBonesChk, "Tells the compiler to keep all bones, including unweighted bones.", background=thme["tt"], foreground=thme["txt"])
self.ignoreChkTT = ToolTip(self.ignoreChk, "Tells the compiler to ignore all warnings, useful for if you want to quickly test a model that isn't complete yet.", background=thme["tt"], foreground=thme["txt"])
self.ignoreChkTT = ToolTip(self.ignoreChk, "Tells the compiler to ignore all warnings, useful for when you want to quickly test a model that isn't complete yet.", background=thme["tt"], foreground=thme["txt"])
self.bNormChkTT = ToolTip(self.bNormChk, "Tags bad normals in the console.", background=thme["tt"], foreground=thme["txt"])
self.flipChkTT = ToolTip(self.flipChk, "Tells the compiler to flip all triangles in the model.", background=thme["tt"], foreground=thme["txt"])
self.groupChkTT = ToolTip(self.groupChk, "Sets the maximum group size for sequences in KB", background=thme["tt"], foreground=thme["txt"])
Expand Down Expand Up @@ -1847,7 +1847,8 @@ def __init__(self, template, master, startHidden:bool=False):
EXE_LOCATION = os.path.dirname( os.path.realpath( __file__ ) )
self.scr_dir = os.path.join(EXE_LOCATION, "scripts")
for s in os.listdir(self.scr_dir):
self.scripts.append(s)
if not s.startswith("template"):
self.scripts.append(s)

self.scr_list = Listbox(master, width=self.widthFix, selectmode=SINGLE)
count = -1
Expand All @@ -1864,7 +1865,7 @@ def __init__(self, template, master, startHidden:bool=False):

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)
a = SSTReader(os.path.join(self.scr_dir, selected_scr), self.options, self.console)

def applyTheme(self, master):
style= ttk.Style()
Expand Down
3 changes: 2 additions & 1 deletion scripts/batch-compile.sst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ format json
-
{
"name": "Batch Compile",
"globalOutput": "askFolder",
"globalOutput": "askFolder,Select a folder full of compilable models",
"defComp": "ask",
"tasks": [
{
"name": "Compile MDL",
Expand Down
3 changes: 2 additions & 1 deletion scripts/batch-decompile.sst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ format json
-
{
"name": "Batch Decompile",
"globalOutput": "askFolder,Select a folder full of models",
"globalOutput": "askFolder,Select a folder full of compiled models",
"defDecPreset": "ask",
"tasks": [
{
"name": "Decompile MDL",
Expand Down
23 changes: 20 additions & 3 deletions scripts/template.sst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ format jsonc
-
{
"name": "Template Script",
// You can specify a global output folder for every task
// You can specify a global output folder for every task.
"globalOutput": "path/to/output/folder",
// You can also specify a default compiler for the compile task to use,
"defComp": "Svengine",
// and a default preset for the decompiler to use.
"defDecPreset": "Svengine",
"tasks": [
{
// This specifies the name of the task, this will show up in the console when the task has been started.
Expand All @@ -15,14 +19,27 @@ format jsonc
"task": "decompile",
"file": "absolute/path/to/mdl/file",
// You can use this key to specify an output folder for this specific task.
"output": "path/to/output/folder"
"output": "path/to/output/folder",
// Specify arguments for the decompiler, write them how you would if you were interfacing with it through a terminal.
// Supported options: -l, -m, -t, -u, -V
// More info for them available on the wiki's Scripting 101 page.
"args": "-m -t",
// Specify a decompiler preset just for this task, you can do this instead of specifying arguments directly.
// Available options are: GoldSRC, Svengine, DoomMusic and Xash3D
"usePreset": "Svengine"
},
{
"name": "Compile MDL",
"task": "compile",
"file": "absolute/path/to/qc/file",
// Please note that with compile tasks, the output will not be put in a subfolder of the output folder.
"output": "path/to/output/folder"
"output": "path/to/output/folder",
// Specify arguments for the compiler, write them how you would if you were interfacing with it through a terminal.
// Supported options: -t, -r, -a, -h, -i, -n, -f, -g, -p, -k (only for Sven Co-op's StudioMDL).
// More info for them available on the wiki's Scripting 101 page.
"args": "-k",
// Specify a compiler to use just for this task.
"useComp": "Svengine"
},
{
"name": "View Compiled Model",
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.2.2-(OS)-alpha
v0.2.3-(OS)-alpha