[Scummvm-git-logs] scummvm-tools master -> 3927ad0d1c4891eb9b18219c4fdd303512394950
aquadran
noreply at scummvm.org
Fri Nov 15 13:19:02 UTC 2024
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm-tools' repo located at https://github.com/scummvm/scummvm-tools .
Summary:
3927ad0d1c WINTERMUTE: Import script decompiler
Commit: 3927ad0d1c4891eb9b18219c4fdd303512394950
https://github.com/scummvm/scummvm-tools/commit/3927ad0d1c4891eb9b18219c4fdd303512394950
Author: PaweÅ KoÅodziejski (aquadran at gmail.com)
Date: 2024-11-15T14:18:58+01:00
Commit Message:
WINTERMUTE: Import script decompiler
Changed paths:
A engines/wintermute/decompile_script.py
diff --git a/engines/wintermute/decompile_script.py b/engines/wintermute/decompile_script.py
new file mode 100755
index 00000000..17e12fb6
--- /dev/null
+++ b/engines/wintermute/decompile_script.py
@@ -0,0 +1,752 @@
+#! /usr/bin/env python3
+
+# ScummVM Tools
+#
+# ScummVM Tools is the legal property of its developers, whose names
+# are too numerous to list here. Please refer to the COPYRIGHT
+# file distributed with this source distribution.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# WinterMute Engine scripts decompiler v1.1
+#
+# Copied from https://raw.githubusercontent.com/lolbot-iichan/decompile.wintermute/refs/heads/master/decompile_wintermute.py
+# Made by lolbot
+#
+# Pipeline is like this:
+# 1. wmeasm: transform binary format to asm-like format
+# 2. wmemedium: evaluate stack operations to transform non-branching code to "actor.GoTo(this.WalkToX, this.WalkToY);"
+# 3. wmehigh: use some rules of thumb to detect while/switch/break/else constructions
+# 4. final result: transform internal structs to readable code
+
+from struct import unpack
+from copy import deepcopy
+
+import re
+import os
+import sys
+import traceback
+
+if len(sys.argv) == 1 or len(sys.argv) > 3:
+ print("Usage: %s <path-to-compiled-script> [<path-to-inc-folder>]")
+ sys.exit(-1)
+fn = sys.argv[1]
+inc = {}
+if len(sys.argv) == 3:
+ for i in os.listdir(sys.argv[2]):
+ with open(sys.argv[2]+os.sep+i,"r") as f:
+ txt = f.read().rstrip()
+ if txt:
+ inc[i] = [t+"\n" for t in txt.split("\n")]
+
+LB_WRITE_HEADERS = False
+LB_WRITE_DISASM = False
+LB_WRITE_MEDIUM = False
+LB_WRITE_HIGHLVL = False
+LB_WRITE_RESULT = True
+
+class WinterMuteDecompiler:
+ opcodes = [
+ "II_DEF_VAR", #0
+ "II_DEF_GLOB_VAR",
+ "II_RET",
+ "II_RET_EVENT",
+ "II_CALL",
+ "II_CALL_BY_EXP", #5
+ "II_EXTERNAL_CALL",
+ "II_SCOPE",
+ "II_CORRECT_STACK",
+ "II_CREATE_OBJECT",
+ "II_POP_EMPTY", #10
+ "II_PUSH_VAR",
+ "II_PUSH_VAR_REF",
+ "II_POP_VAR",
+ "II_PUSH_VAR_THIS",
+ "II_PUSH_INT", #15
+ "II_PUSH_BOOL",
+ "II_PUSH_FLOAT",
+ "II_PUSH_STRING",
+ "II_PUSH_NULL",
+ "II_PUSH_THIS_FROM_STACK", #20
+ "II_PUSH_THIS",
+ "II_POP_THIS",
+ "II_PUSH_BY_EXP",
+ "II_POP_BY_EXP",
+ "II_JMP", #25
+ "II_JMP_FALSE",
+ "II_ADD",
+ "II_SUB",
+ "II_MUL",
+ "II_DIV", #30
+ "II_MODULO",
+ "II_NOT",
+ "II_AND",
+ "II_OR",
+ "II_CMP_EQ", #35
+ "II_CMP_NE",
+ "II_CMP_L",
+ "II_CMP_G",
+ "II_CMP_LE",
+ "II_CMP_GE", #40
+ "II_CMP_STRICT_EQ",
+ "II_CMP_STRICT_NE",
+ "II_DBG_LINE",
+ "II_POP_REG1",
+ "II_PUSH_REG1", #45
+ "II_DEF_CONST_VAR"
+ ]
+
+ varops = {
+ "II_DEF_VAR": "var",
+ "II_DEF_GLOB_VAR": "global",
+ "II_DEF_CONST_VAR": "const",
+ }
+
+ binops = {
+ "II_ADD": "+",
+ "II_SUB": "-",
+ "II_MUL": "*",
+ "II_DIV": "/",
+ "II_MODULO": "%",
+ "II_AND": "&&",
+ "II_OR": "||",
+ "II_CMP_EQ": "==",
+ "II_CMP_NE": "!=",
+ "II_CMP_L": "<",
+ "II_CMP_G": ">",
+ "II_CMP_LE": "<=",
+ "II_CMP_GE": ">=",
+ "II_CMP_STRICT_EQ": "===",
+ "II_CMP_STRICT_NE": "!==",
+ }
+
+ exttypes = ["","bool","long","char","string","float","double","membuffer"]
+
+ table_names = ["function","symbol","event","external","method"]
+
+ def __init__(self, data):
+ self.data = data
+ self.offsets = unpack("LLLLLLLL",data[:32])
+
+ def read_int(self):
+ result = unpack("L",self.data[self.ptr:self.ptr+4])[0]
+ self.ptr += 4
+ return result
+
+ def read_float(self):
+ result = unpack("d",self.data[self.ptr:self.ptr+8])[0]
+ self.ptr += 8
+ return result
+
+ def read_string(self):
+ l = self.data[self.ptr:].find(b"\0")
+ result = self.data[self.ptr:self.ptr+l].decode('cp1251')
+ self.ptr += l+1
+ return result
+
+ def read_header(self):
+ self.fname = self.data[32:self.offsets[2]].strip().decode("utf-8")
+ self.tables = {}
+ self.externals = []
+ for i,title in enumerate(self.table_names):
+ self.tables[title] = {}
+ self.ptr = self.offsets[i+3]
+ l = self.read_int()
+ if title != "external":
+ for i in range(l):
+ idx = self.read_int()
+ self.tables[title][idx] = self.read_string()
+ else:
+ for i in range(l):
+ dllname = self.read_string()
+ fname = self.read_string()
+ ftype = ["stdcall","cdecl","thiscall"][self.read_int()]
+ fret = self.exttypes[self.read_int()]
+ fargs = [self.exttypes[self.read_int()] for i in range(self.read_int())]
+ self.externals += [(dllname, fname, ftype, fret, fargs)]
+
+ def table_lookup(self, id):
+ for t in self.table_names:
+ if id in self.tables[t]:
+ return [t,self.tables[t][id]]
+
+ def read_asm(self):
+ self.disasm = {}
+
+ self.ptr = self.offsets[2]
+ while self.ptr < self.offsets[3]:
+ ptr_old = self.ptr
+ op = self.opcodes[self.read_int()]
+
+ if op in ["II_PUSH_FLOAT"]:
+ param = self.read_float()
+ elif op in ["II_DBG_LINE","II_PUSH_INT","II_PUSH_BOOL","II_JMP","II_JMP_FALSE","II_CORRECT_STACK"]:
+ param = self.read_int()
+ elif op in self.varops or op in ["II_PUSH_VAR_REF","II_POP_VAR","II_PUSH_VAR","II_PUSH_THIS","II_EXTERNAL_CALL"]:
+ param = self.tables["symbol"][self.read_int()]
+ elif op in ["II_CALL"]:
+ param = self.table_lookup(self.read_int())[1]
+ elif op in ["II_SCOPE"]:
+ param = self.table_lookup(ptr_old)
+ elif op in ["II_PUSH_STRING"]:
+ param = self.read_string()
+ else:
+ param = None
+
+ self.disasm[ptr_old] = [op, param]
+
+ def access(self,x,y):
+ if y[0] == '"' and y[-1] == '"' and not '"' in y[1:-1]:
+ return x+"."+y[1:-1]
+ return x+"["+y+"]"
+
+ def create_medium(self):
+ self.medium = {}
+ stack_var = []
+ stack_this = []
+ reg1 = "<<<ERROR>>>"
+ for ptr,(op,param) in sorted(self.disasm.items()):
+ if op in ["II_DBG_LINE"]:
+ pass
+ elif op in self.varops:
+ self.medium[ptr] = ["III_DEF",(self.varops[op],param)]
+ elif op in ["II_SCOPE"]:
+ self.medium[ptr] = ["III_SCOPE",param]
+ elif op in ["II_CORRECT_STACK"]:
+ stack_var = ["<<<PARAM%d>>>"%(param-i-1) for i in range(param)]
+ self.medium[ptr] = ["III_CORRECT_STACK",param]
+ elif op in ["II_RET_EVENT"]:
+ self.medium[ptr] = ["III_RET",None]
+ elif op in ["II_RET"]:
+ if ptr != self.offsets[3]-4:
+ self.medium[ptr] = ["III_RET",stack_var.pop()]
+ else:
+ self.medium[ptr] = ["III_RET_EOF",None]
+ elif op in ["II_POP_BY_EXP","II_POP_VAR","II_POP_EMPTY"]:
+ if op == "II_POP_BY_EXP":
+ key = stack_var.pop()
+ param = self.access(stack_var.pop(),key)
+ self.medium[ptr] = ["III_POP",(param,stack_var.pop())]
+ elif op in ["II_JMP_FALSE"]:
+ self.medium[ptr] = ["III_JMP_FALSE",[stack_var.pop(),param-1]]
+ elif op in ["II_JMP"]:
+ self.medium[ptr] = ["III_JMP",param-1]
+ elif op in ["II_POP_REG1"]:
+ reg1 = stack_var.pop()
+ self.medium[ptr] = ["III_SCOPE",["switch",reg1]]
+
+ elif op in ["II_PUSH_THIS"]:
+ stack_this += [param]
+ elif op in ["II_PUSH_THIS_FROM_STACK"]:
+ stack_this += [stack_var[-1]]
+ elif op in ["II_POP_THIS"]:
+ stack_this.pop()
+
+ elif op in ["II_CREATE_OBJECT"]:
+ stack_var += ["<<<OBJECT>>>"]
+ elif op in ["II_PUSH_INT","II_PUSH_BOOL","II_PUSH_FLOAT"]:
+ stack_var += [repr(param)]
+ elif op in ["II_PUSH_NULL"]:
+ stack_var += ["null"]
+ elif op in ["II_PUSH_STRING"]:
+ stack_var += ['"'+param+'"']
+ elif op in ["II_PUSH_VAR","II_PUSH_VAR_REF"]:
+ stack_var += [param]
+ elif op in ["II_NOT"]:
+ stack_var += ["!("+stack_var.pop()+")"]
+ elif op in self.binops:
+ y = stack_var.pop()
+ x = stack_var.pop()
+ stack_var += ["("+x+" "+self.binops[op]+" "+y+")"]
+ elif op in ["II_PUSH_BY_EXP"]:
+ key = stack_var.pop()
+ stack_var += [self.access(stack_var.pop(),key)]
+ elif op in ["II_CALL","II_EXTERNAL_CALL"]:
+ cnt = int(stack_var.pop())
+ stack_var += [param+"("+", ".join([stack_var.pop() for i in range(cnt)])+u")"]
+ elif op in ["II_CALL_BY_EXP"]:
+ y = stack_var.pop()
+ x = stack_var.pop()
+ cnt = int(stack_var.pop())
+ stack_var += [self.access(x,y)+"("+", ".join([stack_var.pop() for i in range(cnt)])+u")"]
+ elif op in ["II_PUSH_REG1"]:
+ stack_var += ["<<<REG1>>>"]
+ else:
+ print("create_medium: ",op, param, stack_var, stack_this)
+
+ def process_medium(self):
+ self.high = deepcopy(self.medium)
+ self.process_medium_pop_object()
+ self.process_medium_def_pop()
+ self.process_medium_correct_stack()
+ self.process_medium_simple_lines()
+ self.process_medium_nop_jumps()
+ self.process_medium_if_false()
+ self.process_medium_if_to_while()
+ self.process_medium_scope_ends()
+ self.process_medium_switch_end()
+ self.process_medium_switch_case()
+ self.process_medium_switch_goto()
+ self.process_medium_nop_scope_ends()
+ self.process_medium_switch_default()
+ self.process_medium_switch_break()
+ self.process_medium_while_break()
+ self.process_medium_if_else()
+ return
+
+ def process_medium_def_pop(self):
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if idx>0:
+ pptr,(pop,pparam) = items[idx-1]
+ if op == "III_POP" and pop == "III_DEF" and pparam[1] == param[0]:
+ mode = pparam[0]
+ del self.high[pptr]
+ self.high[ptr] = ["III_DEF_POP",(mode,param[0],param[1])]
+
+ def process_medium_pop_object(self):
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if idx>0:
+ pptr,(pop,pparam) = items[idx-1]
+ if op == "III_POP" and pop == "III_POP" and pparam[0] == None and "<<<OBJECT>>>" in param[1]:
+ value = param[1].replace("<<<OBJECT>>>","new "+pparam[1],1)
+ if not "<<<OBJECT>>>" in value:
+ del self.high[pptr]
+ self.high[ptr] = ["III_POP",(param[0],value)]
+
+ def process_medium_correct_stack(self):
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if idx>0:
+ pptr,(pop,pparam) = items[idx-1]
+ if op == "III_CORRECT_STACK" and pop == "III_SCOPE":
+ for i in range(param):
+ if items[idx+i+1][1][0] != "III_DEF_POP":
+ break
+ if items[idx+i+1][1][1][0] != "var":
+ break
+ if items[idx+i+1][1][1][2] != "<<<PARAM%d>>>"%i:
+ break
+ else:
+ params = [items[idx+i+1][1][1][1] for i in range(param)]
+ fcall = pparam[1] + "(" + ", ".join(params) + ")"
+ self.high[pptr] = ["III_SCOPE",[pparam[0],fcall]]
+ for i in range(param+1):
+ del self.high[items[idx+i][0]]
+
+ def process_medium_simple_lines(self):
+ items = sorted(self.high.items())
+ for ptr,(op,param) in items:
+ if op == "III_DEF":
+ self.high[ptr] = ["III_LINE","%s %s;"%param]
+ elif op == "III_DEF_POP":
+ self.high[ptr] = ["III_LINE","%s %s = %s;"%param]
+ elif op == "III_POP":
+ if param[0] == None:
+ self.high[ptr] = ["III_LINE","%s;"%param[1]]
+ else:
+ self.high[ptr] = ["III_LINE","%s = %s;"%param]
+ elif op == "III_RET":
+ if param == None:
+ self.high[ptr] = ["III_LINE","return;"]
+ else:
+ self.high[ptr] = ["III_LINE","return %s;"%param]
+
+ def process_medium_nop_jumps(self):
+ for ptr,(op,param) in sorted(self.high.items()):
+ if op == "III_JMP_FALSE" and not param[1] in self.high:
+ self.high[param[1]] = ["III_NOP",None]
+ if op == "III_JMP" and not param in self.high:
+ self.high[param] = ["III_NOP",None]
+
+ def process_medium_scope_ends(self):
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if idx>0:
+ pptr,(pop,pparam) = items[idx-1]
+ if op == "III_SCOPE" and pop == "III_JMP":
+ if not(pparam) in self.high or self.high[pparam][0] == "III_NOP":
+ self.high[ptr][1] = param + [pparam]
+ self.high[pparam] = ["III_SCOPE_END",1]
+ del self.high[pptr]
+ elif self.high[pparam][0] == "III_SCOPE_END":
+ self.high[ptr][1] = param + [pparam]
+ self.high[pparam][1] += 1
+ del self.high[pptr]
+ else:
+ print("process_medium_scope_ends: ",self.high[pparam])
+
+ def process_medium_if_false(self):
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if op == "III_JMP_FALSE":
+ target = param[1]
+ if not(target) in self.high or self.high[target][0] == "III_NOP":
+ self.high[ptr] = ["III_SCOPE",["if",param[0],target]]
+ self.high[target] = ["III_SCOPE_END",1]
+ elif self.high[target][0] == "III_SCOPE_END":
+ self.high[ptr] = ["III_SCOPE",["if",param[0],target]]
+ self.high[target][1] += 1
+ else:
+ print("process_medium_if_false: ",self.high[param[1]])
+
+ def process_medium_if_to_while(self):
+ items = sorted(self.high.items())
+ keys = sorted(self.high.keys())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if idx>0:
+ pptr,(pop,pparam) = items[idx-1]
+ if op == "III_SCOPE_END" and pop == "III_JMP":
+ if pparam < ptr and self.high[pparam][0] in ["III_NOP","III_SCOPE_END"]:
+ nxtptr = keys[keys.index(pparam)+1]
+ nxtop,nxtparam = self.high[nxtptr]
+ if nxtop == "III_SCOPE" and nxtparam[0] == "if":
+ nxtparam[0] = "while"
+ nxtparam[2] -= 1
+ self.high[pptr] = ["III_SCOPE_END",1]
+ if param == 1:
+ self.high[ptr] = ["III_NOP",None]
+ else:
+ self.high[ptr] = ["III_SCOPE_END",param-1]
+
+ def count_stack(self,start,stop):
+ delta = 0
+ for i in range(start,stop+1):
+ if i in self.high and self.high[i][0] == "III_SCOPE":
+ delta += 1
+ if i in self.high and self.high[i][0] == "III_SCOPE_END":
+ delta -= self.high[i][1]
+ return delta
+
+ def process_medium_switch_end(self):
+ scope_stack = ["<<<ERROR>>>"]
+ items = sorted(self.high.items())
+ last_switch = None
+ for idx,(ptr,(op,param)) in enumerate(items):
+
+ if op == "III_SCOPE":
+ scope_stack += [param]
+# print(ptr,"push:",param)
+# print(ptr,"stack:",scope_stack)
+ if param[0] == "switch":
+ last_switch = ptr
+ if idx>1 and last_switch:
+ pptr,(pop,pparam) = items[idx-1]
+ ppptr,(ppop,ppparam) = items[idx-2]
+ if op == "III_SCOPE_END" and pop == "III_JMP" and ppop != "III_JMP" and pparam == ptr and scope_stack[-1][0] == "switch":
+ if len(self.high[last_switch][1]) == 2:
+ del self.high[pptr]
+ self.high[ptr][1] += 1
+ self.high[last_switch][1] += [ptr]
+# print("process_medium_switch_end: rule #1:", scope_stack[-1])
+ elif self.high[last_switch][1][2] == ptr:
+ pass
+ else:
+ print(self.high[last_switch][1],ptr)
+ elif op == "III_NOP" and pop == "III_JMP" and ppop != "III_JMP" and pparam == ptr and scope_stack[-1][0] == "switch":
+ if len(self.high[last_switch][1]) == 2:
+ del self.high[pptr]
+ self.high[ptr] = ["III_SCOPE_END",1]
+ self.high[last_switch][1] += [ptr]
+# print("process_medium_switch_end: rule #2:", scope_stack[-1])
+ elif self.high[last_switch][1][2] == ptr:
+ pass
+ else:
+ print(self.high[last_switch][1],ptr)
+ elif op == "III_SCOPE_END" and pop == "III_JMP" and ppop == "III_JMP":
+ if len(self.high[last_switch][1]) == 2:
+ if self.high[ppparam][0] == "III_NOP":
+ self.high[ppparam] = ["III_SCOPE_END",1]
+ elif self.high[ppparam][0] == "III_SCOPE_END":
+ self.high[ppparam][1] += 1
+ self.high[last_switch][1] += [ppparam]
+# print("process_medium_switch_end: rule #3:", scope_stack[-1])
+ elif self.high[last_switch][1][2] == ppparam:
+ pass
+ else:
+ print(self.high[last_switch][1],ppparam)
+ if (len(self.high[last_switch][1])>3):
+ print(self.high[last_switch])
+ if self.high[ptr][0] == "III_SCOPE_END":
+ for i in range(self.high[ptr][1]):
+ parent = scope_stack.pop()
+# print(ptr,"pop:",parent)
+# print(ptr,"stack:",scope_stack)
+ if len(scope_stack) > 1:
+ print("process_medium_switch_end: stack is not empty on exit: ",scope_stack)
+ if len(scope_stack) == 0:
+ print("process_medium_switch_end: stack is exhausted on exit")
+
+ def process_medium_switch_case(self):
+ scope_stack = ["<<<ERROR>>>"]
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if op == "III_SCOPE_END":
+ for i in range(param):
+ scope_stack.pop()
+ elif op == "III_SCOPE":
+ if param[0] == "if" and param[1].endswith(" == <<<REG1>>>)"):
+ if scope_stack and scope_stack[-1][0] in ["switch"]:
+ param[0] = "case"
+ param[1] = param[1][1:-len(" == <<<REG1>>>)")]
+ else:
+ print("process_medium_switch_case: ", param, scope_stack)
+ scope_stack += [param]
+ if len(scope_stack) > 1:
+ print("process_medium_switch_case: stack is not empty on exit: ",scope_stack)
+ if len(scope_stack) == 0:
+ print("process_medium_switch_case: stack is exhausted on exit")
+
+ def process_medium_switch_goto(self):
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if idx>2:
+ pptr,(pop,pparam) = items[idx-1]
+ ppptr,(ppop,ppparam) = items[idx-2]
+ pppptr,(pppop,pppparam) = items[idx-3]
+ if op == "III_NOP" and pop == "III_SCOPE" and ppop == "III_SCOPE_END" and pppop == "III_JMP":
+ if pparam[0] == "case" and ppparam == 1 and pppparam == ptr:
+ del self.high[pppptr]
+
+ def process_medium_switch_default(self):
+ scope_stack = ["<<<ERROR>>>"]
+ keys = sorted(self.high.keys())
+ for idx,ptr in enumerate(keys):
+ op,param = self.high[ptr]
+ if op == "III_SCOPE":
+ scope_stack += [param]
+ elif op == "III_SCOPE_END":
+ for i in range(param):
+ parent = scope_stack.pop()
+ if idx>1:
+ pptr,(pop,pparam) = keys[idx-1],self.high[keys[idx-1]]
+ if op == "III_SCOPE_END" and pop == "III_JMP":
+ if pparam == ptr and parent[0] == "case" and scope_stack[-1][0] == "switch":
+ target = scope_stack[-1][2]
+ del self.high[pptr]
+ if self.high[target][0] == "III_NOP" and self.high[ptr+1][0] == "III_NOP":
+ self.high[ptr+1] = ["III_SCOPE",["default",None,pparam,parent]]
+ self.high[target] = ["III_SCOPE_END",1]
+ elif self.high[target][0] == "III_SCOPE_END" and self.high[ptr+1][0] == "III_NOP":
+ self.high[target][1] += 1
+ self.high[ptr+1] = ["III_SCOPE",["default",None,pparam,parent]]
+ else:
+ print("process_medium_if_else: ",self.high[target])
+ if len(scope_stack) > 1:
+ print("process_medium_if_else: stack is not empty on exit: ",scope_stack)
+ if len(scope_stack) == 0:
+ print("process_medium_if_else: stack is exhausted on exit")
+
+ def process_medium_switch_break(self):
+ scope_stack = ["<<<ERROR>>>"]
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if op == "III_SCOPE_END":
+ for i in range(param):
+ scope_stack.pop()
+ elif op == "III_SCOPE":
+ scope_stack += [param]
+ elif op == "III_JMP" and len(scope_stack) > 1:
+ if scope_stack[-1][0] in ["case","default"] and scope_stack[-2][0] in ["switch"]:
+ if len(scope_stack[-2])>2 and param == scope_stack[-2][2]:
+ self.high[ptr] = ["III_LINE","break;"]
+ if len(scope_stack) > 1:
+ print("process_medium_switch_break: stack is not empty on exit: ",scope_stack)
+ if len(scope_stack) == 0:
+ print("process_medium_switch_break: stack is exhausted on exit")
+
+ def process_medium_while_break(self):
+ scope_stack = ["<<<ERROR>>>"]
+ items = sorted(self.high.items())
+ for idx,(ptr,(op,param)) in enumerate(items):
+ if op == "III_SCOPE_END":
+ for i in range(param):
+ scope_stack.pop()
+ elif op == "III_SCOPE":
+ scope_stack += [param]
+ elif op == "III_JMP" and len(scope_stack) > 1:
+ if scope_stack[-1][0] in ["if"] and scope_stack[-2][0] in ["while"]:
+ if param > scope_stack[-2][2]:
+ self.high[ptr] = ["III_LINE","break;"]
+ if scope_stack[-1][0] in ["if"] and scope_stack[-2][0] in ["if"] and scope_stack[-3][0] in ["while"]:
+ if param > scope_stack[-3][2]:
+ self.high[ptr] = ["III_LINE","break;"]
+ #TODO: rewrite into something nice
+ if len(scope_stack) > 1:
+ print("process_medium_switch_break: stack is not empty on exit: ",scope_stack)
+ if len(scope_stack) == 0:
+ print("process_medium_switch_break: stack is exhausted on exit")
+
+ def process_medium_nop_scope_ends(self):
+ for ptr,(op,param) in sorted(self.high.items()):
+ if op == "III_SCOPE_END" and not (ptr+1) in self.high:
+ self.high[ptr+1] = ["III_NOP",None]
+
+ def process_medium_if_else(self):
+ scope_stack = ["<<<ERROR>>>"]
+ keys = sorted(self.high.keys())
+ for idx,ptr in enumerate(keys):
+ op,param = self.high[ptr]
+ if op == "III_SCOPE":
+ scope_stack += [param]
+ elif op == "III_SCOPE_END":
+ for i in range(param):
+ parent = scope_stack.pop()
+ if idx>1:
+ pptr,(pop,pparam) = keys[idx-1],self.high[keys[idx-1]]
+ if op == "III_SCOPE_END" and pop == "III_JMP" and parent[0] == "if":
+ if pparam > ptr:
+ if self.high[pparam][0] == "III_NOP" and self.high[ptr+1][0] == "III_NOP":
+ self.high[pparam] = ["III_SCOPE_END",1]
+ self.high[ptr+1] = ["III_SCOPE",["else",None,pparam,parent]]
+ del self.high[pptr]
+ elif self.high[pparam][0] == "III_SCOPE_END" and self.high[ptr+1][0] == "III_NOP":
+ self.high[pparam][1] += 1
+ self.high[ptr+1] = ["III_SCOPE",["else",None,pparam,parent]]
+ del self.high[pptr]
+ else:
+ print("process_medium_if_else: ",self.high[pparam])
+ elif pparam == ptr:
+ # huh?
+ del self.high[pptr]
+ if len(scope_stack) > 1:
+ print("process_medium_if_else: stack is not empty on exit: ",scope_stack)
+ if len(scope_stack) == 0:
+ print("process_medium_if_else: stack is exhausted on exit")
+
+ def process_final(self):
+ self.final_text = []
+ for (dllname, fname, ftype, fret, fargs) in self.externals:
+ txt = 'external "%s" %s %s %s(%s);' % (dllname, ftype, fret, fname, ', '.join(fargs))
+ self.final_text += [txt.replace(" "," ")+"\n"]
+ scope_stack = []
+ for ptr,(op,param) in sorted(self.high.items()):
+ fprefix = ""
+ prefix = fprefix + len(scope_stack)*"\t"
+ if op == "III_SCOPE":
+ if param[0] == "case":
+ self.final_text += [prefix+"case %s:\n"%param[1]]
+ elif param[0] == "default":
+ self.final_text += [prefix+"default:\n"]
+ elif param[0] == "event":
+ self.final_text += [prefix+"on \"%s\" {\n"%param[1]]
+ elif param[0] in ["method","function"]:
+ self.final_text += [prefix+"%s %s {\n"%(param[0],param[1])]
+ elif param[0] in ["if","while","switch"]:
+ self.final_text += [prefix+"%s(%s) {\n"%(param[0],param[1])]
+ elif param[0] == "else":
+ self.final_text += [prefix+"else {\n"]
+ else:
+ self.final_text += [prefix+"//TODO %s\n"%repr(param)]
+ scope_stack += [param]
+ elif op == "III_SCOPE_END":
+ for i in range(param):
+ parent = scope_stack.pop()
+ if parent[0] not in ["case","default"]:
+ prefix = fprefix + len(scope_stack)*"\t"
+ self.final_text += [prefix+"}\n"]
+ elif op == "III_LINE":
+ self.final_text += [prefix+param+"\n"]
+ elif op == "III_JMP":
+ self.final_text += [prefix+"//TODO: unprocessed goto\n"]
+ elif op == "III_RET_EOF":
+ if scope_stack:
+ self.final_text += [prefix+"//TODO: non-empty stack on EOF: %s"%scope_stack+"\n"]
+ for i in inc:
+ ll = len(inc[i])
+ if len(self.final_text) >= ll:
+ if all([self.final_text[c-ll] == inc[i][c] for c in range(ll)]):
+ print("include " + i)
+ self.final_text = self.final_text[:-ll] + ['#include "include\\%s"\n' % i]
+ self.final_text = "".join(self.final_text)
+
+ def dump_header(self, fn):
+ with open(fn,"w") as out:
+ for t in self.table_names:
+ for i,j in self.tables[t].items():
+ out.write("%s %s: %u\n"%(t,j,i))
+ for (dllname, fname, ftype, fret, fargs) in self.externals:
+ txt = 'external "%s" %s %s %s(%s);' % (dllname, ftype, fret, fname, ', '.join(fargs))
+ out.write(txt.replace(" "," ") + "\n")
+
+ def dump_disasm(self, fn):
+ with open(fn,"w") as out:
+ for ptr,item in sorted(self.disasm.items()):
+ out.write("%d: %s %s\n"%(ptr,item[0],item[1]))
+
+ def dump_medium(self, fn):
+ with open(fn,"w") as out:
+ for ptr,item in sorted(self.medium.items()):
+ out.write("%d: %s %s\n"%(ptr,item[0],item[1]))
+
+ def dump_high(self, fn):
+ with open(fn,"w") as out:
+ for ptr,item in sorted(self.high.items()):
+ out.write("%d: %s %s\n"%(ptr,item[0],item[1]))
+
+ def dump_final(self, fn):
+ with open(fn,"w") as out:
+ out.write(self.final_text)
+
+print(fn)
+with open(fn,"rb") as f:
+ wmd = WinterMuteDecompiler(f.read())
+ broken_flag = ""
+
+ try:
+ wmd.read_header()
+ except Exception as ex:
+ traceback.print_exc()
+ broken_flag = ".broken"
+ if LB_WRITE_HEADERS or broken_flag:
+ wmd.dump_header(fn.replace(".script",".wmeheader"+broken_flag))
+
+
+ try:
+ wmd.read_asm()
+ except Exception as ex:
+ traceback.print_exc()
+ broken_flag = ".broken"
+ wmd.dump_header(fn.replace(".script",".wmeheader"+broken_flag))
+ if LB_WRITE_DISASM or broken_flag:
+ wmd.dump_disasm(fn.replace(".script",".wmeasm"+broken_flag))
+
+
+ try:
+ wmd.create_medium()
+ except Exception as ex:
+ traceback.print_exc()
+ broken_flag = ".broken"
+ wmd.dump_header(fn.replace(".script",".wmeheader"+broken_flag))
+ wmd.dump_disasm(fn.replace(".script",".wmeasm"+broken_flag))
+ if LB_WRITE_MEDIUM or broken_flag:
+ wmd.dump_medium(fn.replace(".script",".wmemedium"+broken_flag))
+
+
+ try:
+ wmd.process_medium()
+ except Exception as ex:
+ traceback.print_exc()
+ broken_flag = ".broken"
+ wmd.dump_header(fn.replace(".script",".wmeheader"+broken_flag))
+ wmd.dump_disasm(fn.replace(".script",".wmeasm"+broken_flag))
+ wmd.dump_medium(fn.replace(".script",".wmemedium"+broken_flag))
+ if LB_WRITE_HIGHLVL or broken_flag:
+ wmd.dump_high(fn.replace(".script",".wmehigh"+broken_flag))
+
+ if LB_WRITE_RESULT or broken_flag:
+ wmd.process_final()
+ wmd.dump_final(fn.replace(".script",".wmescript"+broken_flag))
More information about the Scummvm-git-logs
mailing list