Skip to content

Commit

Permalink
Add utility ir2_to_gta3
Browse files Browse the repository at this point in the history
  • Loading branch information
thelink2012 committed Nov 6, 2016
1 parent f40ae7e commit f4fd61d
Show file tree
Hide file tree
Showing 8 changed files with 431 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
*.pyc
config/**.ir2
utils/**.ir2
utils/**.sc
utils/**.scm
utils/test

#################
## C++
Expand Down
7 changes: 5 additions & 2 deletions src/compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ struct CompilerContext
compile_statements(node);
break;
case NodeType::NOT:
program.error(node, "NOT disallowed outside of a conditional statement");
if(!program.opt.relax_conditions)
program.error(node, "NOT disallowed outside of a conditional statement");
compile_statement(node.child(0), !not_flag);
break;
case NodeType::Command:
Expand All @@ -271,7 +272,9 @@ struct CompilerContext
case NodeType::GreaterEqual:
case NodeType::Lesser:
case NodeType::LesserEqual:
program.error(node, "expression disallowed outside of a conditional statement");
if(!program.opt.relax_conditions)
program.error(node, "expression disallowed outside of a conditional statement");
compile_condition(node, not_flag);
break;
case NodeType::Equal:
case NodeType::Cast:
Expand Down
7 changes: 4 additions & 3 deletions src/disassembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ void Disassembler::explore(size_t offset)
{
if(this->type == Type::RecursiveTraversal)
{
program.warning(nocontext, "a branch command jumps into the local offset {:X} which is outside the bytecode", offset);
program.warning(nocontext, "a branch command jumps into the local offset 0x{:X} which is outside the bytecode", offset);
program.note(nocontext, "use --verbose to find which block this offset belongs to");
}
return;
Expand Down Expand Up @@ -557,8 +557,9 @@ optional<DecompiledScmHeader> DecompiledScmHeader::from_bytecode(const void* byt
models.reserve(num_models);
for(size_t i = 0; i < num_models; ++i)
{
auto model_name = bf.fetch_chars(seg2_offset + 8 + 4 + 24 + (24 * i), 24).value();
models.emplace_back(std::move(model_name));
char buffer[32];
bf.fetch_chars(seg2_offset + 8 + 4 + 24 + (24 * i), 24, buffer).value();
models.emplace_back(buffer);
}

auto main_size = bf.fetch_u32(seg3_offset + 8 + 0).value();
Expand Down
10 changes: 9 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,15 @@ bool decompile(const void* bytecode, size_t bytecode_size,
}
else if(lang == Options::Lang::IR2)
{
auto base_offset = 0;
{
std::string temp_string;
for(size_t i = 0; i < header.models.size(); ++i)
{
temp_string = header.models[i];
std::transform(temp_string.begin(), temp_string.end(), temp_string.begin(), ::toupper); // TODO FIXME toupper is bad
callback(fmt::format("#DEFINE_MODEL {} -{}", temp_string, i+1));
}
}

auto main_ir2 = DecompilerIR2(program.commands, main_segment_asm.get_data(), 0, main_segment.size, "MAIN", true);
main_ir2.decompile(callback);
Expand Down
1 change: 1 addition & 0 deletions src/program.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct Options
bool fsyntax_only = false;
bool emit_ir2 = false;
bool linear_sweep = false;
bool relax_conditions = true;

// 8 bit stuff
HeaderVersion header = HeaderVersion::None;
Expand Down
4 changes: 2 additions & 2 deletions utils/discover_entity_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def main(ir2file, xmlfile):

scopes = ir2.discover_scopes()
current_scope = None
first_scope = scopes[0].start if len(scopes) > 0 else None
first_scope = scopes[0] if len(scopes) > 0 else None

commands = {cmd.name: cmd for cmd in config.commands}
cmds_set = set(config.get_alternator("SET"))
Expand All @@ -101,7 +101,7 @@ def main(ir2file, xmlfile):
for off, data in ir2:

if current_scope == None:
if first_scope != None and off >= first_scope:
if first_scope != None and off >= first_scope.start:
current_scope = Scope.from_offset(off, scopes)
assert current_scope != None
elif not current_scope.owns_offset(off):
Expand Down
124 changes: 113 additions & 11 deletions utils/gta3sc/bytecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

TABLE_SCOPE_SPAWNERS = {
# Name ArgId
"GOSUB_FILE": 1,
"START_NEW_SCRIPT": 0,
"LAUNCH_MISSION": 0,
"CALL": 3,
Expand All @@ -68,10 +69,11 @@

class Bytecode:

def __init__(self, main_block, mission_blocks=[], streamed_blocks=[]):
def __init__(self, main_block, mission_blocks=[], streamed_blocks=[], models=[]):
self.main_block = main_block
self.mission_blocks = mission_blocks
self.streamed_blocks = streamed_blocks
self.models = models

self.label_table = {}
for off, data in self:
Expand Down Expand Up @@ -114,9 +116,15 @@ def get(self, offset):
return the_block[offset.index]
return None

def get_model(self, i):
return self.models[i] if i < len(self.models) else None

def offset_from_label(self, name):
return self.label_table.get(name)

def offset_from_mission(self, i):
return Offset(BYTECODE_OFFSET_MISSION, i, 0)

def offset_from_streamed(self, i):
return Offset(BYTECODE_OFFSET_STREAMED, i, 0)

Expand Down Expand Up @@ -144,6 +152,12 @@ def discover_scopes(self): # -> sorted [Scope, ...]

return result

def discover_global_vars(self, commands=None): # -> sorted [VarInfo, ...]
return _discover_vars(iter(self), False, commands=commands)

def discover_local_vars(self, scope, commands=None): # -> sorted [VarInfo, ...]
return _discover_vars(iter(scope.iter_data(self)), True, commands=commands)

def discover_global_arrays(self): # -> { offset: end_offset, ... }
return _discover_arrays(iter(self), False)

Expand All @@ -155,6 +169,13 @@ def discover_local_arrays(self, scope): # -> { offset: end_offset, ... }

ScopeBase = namedtuple('ScopeBase', ['start', 'end'])

class VarInfo:
def __init__(self, start_offset, end_offset, typestring, arraysize):
self.start_offset = start_offset
self.end_offset = end_offset
self.type = typestring # may be None, meaning unknown type
self.size = arraysize # None means not array

class Scope(ScopeBase):
# assert self.start until self.end doesn't change blocks/script types

Expand Down Expand Up @@ -315,6 +336,9 @@ def __init__(self, strtype, value):
def is_string(self):
return True

def is_buffer128(self):
return self.type == DATATYPE_BUFFER128

def __str__(self):
# TODO unescape
if self.type == DATATYPE_TEXTLABEL8:
Expand Down Expand Up @@ -342,12 +366,14 @@ def is_global(self):
def is_local(self):
return self.type in DATATYPES_LOCALVARS

def size_in_bytes(self):
def get_datatype(self): # returns the GLOBALVAR version of the datatype
if self.is_local():
datatype = DATATYPES_GLOBALVARS[DATATYPES_LOCALVARS.index(self.type)]
return DATATYPES_GLOBALVARS[DATATYPES_LOCALVARS.index(self.type)]
else:
datatype = self.type
return self.type

def size_in_bytes(self):
datatype = self.get_datatype();
if datatype == DATATYPE_GLOBALVAR_NUMBER:
return 4
elif datatype == DATATYPE_GLOBALVAR_TEXTLABEL:
Expand Down Expand Up @@ -381,6 +407,12 @@ def is_var(self):
def is_array(self):
return True

def is_global(self):
return self.base.is_global()

def is_local(self):
return self.base.is_local()

def __str__(self):
etc = _char_from_elemtype(self.elem_type)
return "%s(%s,%d%c)" % (self.base, self.index, self.size, etc)
Expand Down Expand Up @@ -494,25 +526,29 @@ def arg_from_token(token):
main_block = []
mission_blocks = []
streamed_blocks = []
models = []

current_block = main_block

for line in lines:
line = line.rstrip('\r\n')
assert len(line) > 0 and not line[0].isspace() and not line[-1].isspace()
if line[0] == '#':
if line.startswith("#MISSION_BLOCK_START"):
assert len(mission_blocks) == int(line.split()[1])
tokens = line.split()
if tokens[0] == "#MISSION_BLOCK_START":
assert len(mission_blocks) == int(tokens[1])
current_block = []
elif line.startswith("#STREAMED_BLOCK_START"):
assert len(streamed_blocks) == int(line.split()[1])
elif tokens[0] == "#STREAMED_BLOCK_START":
assert len(streamed_blocks) == int(tokens[1])
current_block = []
elif line.startswith("#MISSION_BLOCK_END"):
elif tokens[0] == "#MISSION_BLOCK_END":
mission_blocks.append(current_block)
current_block = None
elif line.startswith("#STREAMED_BLOCK_END"):
elif tokens[0] == "#STREAMED_BLOCK_END":
streamed_blocks.append(current_block)
current_block = None
elif tokens[0] == "#DEFINE_MODEL":
models.append(tokens[1])
elif line[-1] == ':':
label = Label(line[:-1])
current_block.append(label)
Expand All @@ -527,7 +563,7 @@ def arg_from_token(token):
else:
current_block.append(Command(not_flag, cmdname, cmdargs))

return Bytecode(main_block, mission_blocks, streamed_blocks)
return Bytecode(main_block, mission_blocks, streamed_blocks, models)


def _char_from_vartype(vartype):
Expand Down Expand Up @@ -562,6 +598,72 @@ def _discover_arrays(bytecode_iter, is_local): # -> { offset: arraysize_in_bytes
arrays[arg.base.offset] = arg.size * arg.base.size_in_bytes()
return arrays


def _discover_vars(bytecode_iter, is_local, commands=None): # -> sorted [VarInfo, ...]

# probably optimizable, but this is Python mate!

vardict = dict()

if is_local:
check_var_kind = lambda x: x.is_local()
else:
check_var_kind = lambda x: x.is_global()

for off, data in filter(lambda (o, d): d.is_command(), bytecode_iter):
cmdinfo = commands.get(data.name, None) if commands else None
for i, arg in enumerate(data.args):
if arg.is_var() and check_var_kind(arg):

if arg.is_array():
offset_start = arg.base.offset
offset_end = offset_start + (arg.size * arg.base.size_in_bytes())
array_size = arg.size
datatype = arg.base.get_datatype()
else:
offset_start = arg.offset
offset_end = offset_start + arg.size_in_bytes()
array_size = None
datatype = arg.get_datatype()

vartype = None
if datatype == DATATYPE_GLOBALVAR_NUMBER:
arginfo = cmdinfo.get_arg(i) if cmdinfo else None
if arginfo is None:
pass
elif arginfo.type == "INT":
vartype = "INT"
elif arginfo.type == "FLOAT":
vartype = "FLOAT"
elif arginfo.type == "PARAM":
vartype = None
else:
assert False
elif datatype == DATATYPE_GLOBALVAR_TEXTLABEL:
vartype = "TEXT_LABEL"
elif datatype == DATATYPE_GLOBALVAR_TEXTLABEL16:
vartype = "TEXT_LABEL16"

var = vardict.get(offset_start)
if var != None:
assert var.type == vartype or var.type == None or vartype == None
assert var.size == array_size
if vartype != None:
var.type = vartype
else:
vardict[offset_start] = VarInfo(offset_start, offset_end, vartype, array_size)

result = list()

varlist = sorted(vardict.itervalues(), key=lambda k: k.start_offset)
for v in varlist:
if len(result) > 0:
if v.start_offset < result[-1].end_offset:
continue
result.append(v)

return result

if __name__ == "__main__":
import sys
ir2 = read_ir2(sys.argv[1])
Expand Down
Loading

0 comments on commit f4fd61d

Please sign in to comment.