diff --git a/bemani/format/afp/decompile.py b/bemani/format/afp/decompile.py index 63e3d2c..dfbfa6f 100644 --- a/bemani/format/afp/decompile.py +++ b/bemani/format/afp/decompile.py @@ -27,22 +27,26 @@ from .util import VerboseOutput class ByteCode: # A list of bytecodes to execute. - def __init__(self, actions: Sequence[AP2Action], end_offset: int) -> None: + def __init__(self, name: Optional[str], actions: Sequence[AP2Action], end_offset: int) -> None: + self.name = name self.actions = list(actions) self.start_offset = self.actions[0].offset if actions else None self.end_offset = end_offset - def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: - if kwargs.get('decompile_bytecode', False): - decompiler = ByteCodeDecompiler(self) - decompiler.decompile(verbose=True) - code = decompiler.as_string(prefix=" ", verbose=True) + def decompile(self, verbose: bool = False) -> str: + decompiler = ByteCodeDecompiler(self) + decompiler.decompile(verbose=verbose) + code = decompiler.as_string(prefix=" " if self.name else "", verbose=verbose) + if self.name: opar = '{' cpar = '}' - code = f"main(){os.linesep}{opar}{os.linesep}{code}{os.linesep}{cpar}" + code = f"{self.name}(){os.linesep}{opar}{os.linesep}{code}{os.linesep}{cpar}" + return code + def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: + if kwargs.get('decompile_bytecode', False): return { - 'code': code, + 'code': self.decompile(verbose=kwargs.get('verbose', False)), } else: return { @@ -141,35 +145,35 @@ class MultiAction(ConvertedAction): class Statement(ConvertedAction): # This is just a type class for finished statements. - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: raise NotImplementedError(f"{self.__class__.__name__} does not implement render()!") -def object_ref(obj: Any, parent_prefix: str) -> str: +def object_ref(obj: Any, parent_prefix: str, verbose: bool=False) -> str: if isinstance(obj, (GenericObject, Variable, TempVariable, Member, MethodCall, FunctionCall, Register)): - return obj.render(parent_prefix, nested=True) + return obj.render(parent_prefix, verbose=verbose, nested=True) else: raise Exception(f"Unsupported objectref {obj} ({type(obj)})") -def value_ref(param: Any, parent_prefix: str, parens: bool = False) -> str: +def value_ref(param: Any, parent_prefix: str, verbose: bool=False, parens: bool = False) -> str: if isinstance(param, StringConstant): # Treat this as a string constant. - return repr(param.render(parent_prefix)) + return repr(param.render(parent_prefix, verbose=verbose)) elif isinstance(param, Expression): - return param.render(parent_prefix, nested=parens) + return param.render(parent_prefix, verbose=verbose, nested=parens) elif isinstance(param, (str, int, float)): return repr(param) else: raise Exception(f"Unsupported valueref {param} ({type(param)})") -def name_ref(param: Any, parent_prefix: str) -> str: +def name_ref(param: Any, parent_prefix: str, verbose: bool=False) -> str: # Reference a name, so strings should not be quoted. if isinstance(param, str): return param elif isinstance(param, StringConstant): - return param.render(parent_prefix) + return param.render(parent_prefix, verbose=verbose) else: raise Exception(f"Unsupported nameref {param} ({type(param)})") @@ -184,7 +188,7 @@ class DefineLabelStatement(Statement): def __repr__(self) -> str: return f"label_{self.location}:" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"label_{self.location}:"] @@ -193,7 +197,7 @@ class BreakStatement(Statement): def __repr__(self) -> str: return "break" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"{prefix}break;"] @@ -202,7 +206,7 @@ class ContinueStatement(Statement): def __repr__(self) -> str: return "continue" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"{prefix}continue;"] @@ -217,7 +221,7 @@ class GotoStatement(Statement): def __repr__(self) -> str: return f"goto label_{self.location}" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"{prefix}goto label_{self.location};"] @@ -227,7 +231,7 @@ class NullReturnStatement(Statement): def __repr__(self) -> str: return "return" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"{prefix}return;"] @@ -241,8 +245,8 @@ class ReturnStatement(Statement): ret = value_ref(self.ret, "") return f"return {ret}" - def render(self, prefix: str) -> List[str]: - ret = value_ref(self.ret, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + ret = value_ref(self.ret, prefix, verbose=verbose) return [f"{prefix}return {ret};"] @@ -257,8 +261,8 @@ class ThrowStatement(Statement): exc = value_ref(self.exc, "") return f"throw {exc}" - def render(self, prefix: str) -> List[str]: - exc = value_ref(self.exc, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + exc = value_ref(self.exc, prefix, verbose=verbose) return [f"{prefix}throw {exc};"] @@ -267,7 +271,7 @@ class NopStatement(Statement): def __repr__(self) -> str: return "nop" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: # We should never render this! raise Exception("Logic error, a NopStatement should never make it to the render stage!") @@ -280,8 +284,8 @@ class ExpressionStatement(Statement): def __repr__(self) -> str: return f"{self.expr.render('')}" - def render(self, prefix: str) -> List[str]: - return [f"{prefix}{self.expr.render(prefix)};"] + def render(self, prefix: str, verbose: bool = False) -> List[str]: + return [f"{prefix}{self.expr.render(prefix, verbose=verbose)};"] class StopMovieStatement(Statement): @@ -289,7 +293,7 @@ class StopMovieStatement(Statement): def __repr__(self) -> str: return "builtin_StopPlaying()" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"{prefix}builtin_StopPlaying();"] @@ -298,7 +302,7 @@ class PlayMovieStatement(Statement): def __repr__(self) -> str: return "builtin_StartPlaying()" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"{prefix}builtin_StartPlaying();"] @@ -307,7 +311,7 @@ class NextFrameStatement(Statement): def __repr__(self) -> str: return "builtin_GotoNextFrame()" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"{prefix}builtin_GotoNextFrame();"] @@ -316,7 +320,7 @@ class PreviousFrameStatement(Statement): def __repr__(self) -> str: return "builtin_GotoPreviousFrame()" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: return [f"{prefix}builtin_GotoPreviousFrame();"] @@ -329,8 +333,8 @@ class DebugTraceStatement(Statement): trace = value_ref(self.trace, "") return f"builtin_DebugTrace({trace})" - def render(self, prefix: str) -> List[str]: - trace = value_ref(self.trace, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + trace = value_ref(self.trace, prefix, verbose=verbose) return [f"{prefix}builtin_DebugTrace({trace});"] @@ -343,8 +347,8 @@ class GotoFrameStatement(Statement): frame = value_ref(self.frame, "") return f"builtin_GotoFrame({frame})" - def render(self, prefix: str) -> List[str]: - frame = value_ref(self.frame, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + frame = value_ref(self.frame, prefix, verbose=verbose) return [f"{prefix}builtin_GotoFrame({frame});"] @@ -361,10 +365,10 @@ class CloneSpriteStatement(Statement): depth = value_ref(self.depth, "") return f"builtin_CloneSprite({obj}, {name}, {depth})" - def render(self, prefix: str) -> List[str]: - obj = object_ref(self.obj_to_clone, prefix) - name = value_ref(self.name, prefix) - depth = value_ref(self.depth, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + obj = object_ref(self.obj_to_clone, prefix, verbose=verbose) + name = value_ref(self.name, prefix, verbose=verbose) + depth = value_ref(self.depth, prefix, verbose=verbose) return [f"{prefix}builtin_CloneSprite({obj}, {name}, {depth});"] @@ -377,8 +381,8 @@ class RemoveSpriteStatement(Statement): obj = object_ref(self.obj_to_remove, "") return f"builtin_RemoveSprite({obj})" - def render(self, prefix: str) -> List[str]: - obj = object_ref(self.obj_to_remove, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + obj = object_ref(self.obj_to_remove, prefix, verbose=verbose) return [f"{prefix}builtin_RemoveSprite({obj});"] @@ -394,9 +398,9 @@ class GetURL2Statement(Statement): target = value_ref(self.target, "") return f"builtin_GetURL2({self.action}, {url}, {target})" - def render(self, prefix: str) -> List[str]: - url = value_ref(self.url, "") - target = value_ref(self.target, "") + def render(self, prefix: str, verbose: bool = False) -> List[str]: + url = value_ref(self.url, prefix, verbose=verbose) + target = value_ref(self.target, prefix, verbose=verbose) return [f"{prefix}builtin_GetURL2({self.action}, {url}, {target});"] @@ -407,7 +411,7 @@ class MaybeStackEntry(Expression): def __repr__(self) -> str: return f"MaybeStackEntry({self.parent_stack_id})" - def render(self, parent_prefix: str, nested: bool = False) -> str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: raise Exception("Logic error, a MaybeStackEntry should never make it to the render stage!") @@ -422,9 +426,9 @@ class ArithmeticExpression(Expression): right = value_ref(self.right, "", parens=True) return f"{left} {self.op} {right}" - def render(self, parent_prefix: str, nested: bool = False) -> str: - left = value_ref(self.left, parent_prefix, parens=True) - right = value_ref(self.right, parent_prefix, parens=True) + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: + left = value_ref(self.left, parent_prefix, parens=True, verbose=verbose) + right = value_ref(self.right, parent_prefix, parens=True, verbose=verbose) if nested and self.op == '-': return f"({left} {self.op} {right})" @@ -440,8 +444,8 @@ class NotExpression(Expression): obj = value_ref(self.obj, "", parens=True) return f"not {obj}" - def render(self, parent_prefix: str, nested: bool = False) -> str: - obj = value_ref(self.obj, parent_prefix, parens=True) + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: + obj = value_ref(self.obj, parent_prefix, parens=True, verbose=verbose) return f"not {obj}" @@ -453,8 +457,8 @@ class Array(Expression): def __repr__(self) -> str: return self.render("") - def render(self, parent_prefix: str, nested: bool = False) -> str: - params = [value_ref(param, parent_prefix) for param in self.params] + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: + params = [value_ref(param, parent_prefix, verbose=verbose) for param in self.params] return f"[{', '.join(params)}]" @@ -466,8 +470,8 @@ class Object(Expression): def __repr__(self) -> str: return self.render("") - def render(self, parent_prefix: str, nested: bool = False) -> str: - params = [f"{value_ref(key, parent_prefix)}: {value_ref(val, parent_prefix)}" for (key, val) in self.params.items()] + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: + params = [f"{value_ref(key, parent_prefix, verbose=verbose)}: {value_ref(val, parent_prefix, verbose=verbose)}" for (key, val) in self.params.items()] lpar = "{" rpar = "}" @@ -483,9 +487,9 @@ class FunctionCall(Expression): def __repr__(self) -> str: return self.render("") - def render(self, parent_prefix: str, nested: bool = False) -> str: - name = name_ref(self.name, parent_prefix) - params = [value_ref(param, parent_prefix) for param in self.params] + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: + name = name_ref(self.name, parent_prefix, verbose=verbose) + params = [value_ref(param, parent_prefix, verbose=verbose) for param in self.params] return f"{name}({', '.join(params)})" @@ -505,16 +509,16 @@ class MethodCall(Expression): def __repr__(self) -> str: return self.render("") - def render(self, parent_prefix: str, nested: bool = False) -> str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: try: - obj = object_ref(self.objectref, parent_prefix) - name = name_ref(self.name, parent_prefix) - params = [value_ref(param, parent_prefix) for param in self.params] + obj = object_ref(self.objectref, parent_prefix, verbose=verbose) + name = name_ref(self.name, parent_prefix, verbose=verbose) + params = [value_ref(param, parent_prefix, verbose=verbose) for param in self.params] return f"{obj}.{name}({', '.join(params)})" except Exception: - obj = object_ref(self.objectref, parent_prefix) - name = value_ref(self.name, parent_prefix) - params = [value_ref(param, parent_prefix) for param in self.params] + obj = object_ref(self.objectref, parent_prefix, verbose=verbose) + name = value_ref(self.name, parent_prefix, verbose=verbose) + params = [value_ref(param, parent_prefix, verbose=verbose) for param in self.params] return f"{obj}[{name}]({', '.join(params)})" @@ -527,12 +531,12 @@ class NewFunction(Expression): def __repr__(self) -> str: return self.render("") - def render(self, parent_prefix: str, nested: bool = False) -> str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: # This feels somewhat like a hack, but the bytecode inside the function definition # *is* independent of the bytecode in this function, except for the shared string table. decompiler = ByteCodeDecompiler(self.body) - decompiler.decompile(verbose=True) - code = decompiler.as_string(prefix=parent_prefix + " ") + decompiler.decompile(verbose=verbose) + code = decompiler.as_string(prefix=parent_prefix + " ", verbose=verbose) opar = '{' cpar = '}' @@ -552,9 +556,9 @@ class NewObject(Expression): def __repr__(self) -> str: return self.render('') - def render(self, parent_prefix: str, nested: bool = False) -> str: - objname = name_ref(self.objname, parent_prefix) - params = [value_ref(param, parent_prefix) for param in self.params] + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: + objname = name_ref(self.objname, parent_prefix, verbose=verbose) + params = [value_ref(param, parent_prefix, verbose=verbose) for param in self.params] val = f"new {objname}({', '.join(params)})" if nested: return f"({val})" @@ -593,17 +597,17 @@ class SetMemberStatement(Statement): val = value_ref(self.valueref, "") return f"{ref}[{name}] = {val}" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: try: - ref = object_ref(self.objectref, prefix) - name = name_ref(self.name, prefix) - val = value_ref(self.valueref, prefix) + ref = object_ref(self.objectref, prefix, verbose=verbose) + name = name_ref(self.name, prefix, verbose=verbose) + val = value_ref(self.valueref, prefix, verbose=verbose) return [f"{prefix}{ref}.{name} = {val};"] except Exception: # This is not a simple string object reference. - ref = object_ref(self.objectref, prefix) - name = value_ref(self.name, prefix) - val = value_ref(self.valueref, prefix) + ref = object_ref(self.objectref, prefix, verbose=verbose) + name = value_ref(self.name, prefix, verbose=verbose) + val = value_ref(self.valueref, prefix, verbose=verbose) return [f"{prefix}{ref}[{name}] = {val};"] @@ -616,8 +620,8 @@ class DeleteVariableStatement(Statement): name = name_ref(self.name, "") return f"del {name}" - def render(self, prefix: str) -> List[str]: - name = name_ref(self.name, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + name = name_ref(self.name, prefix, verbose=verbose) return [f"{prefix}del {name};"] @@ -638,15 +642,15 @@ class DeleteMemberStatement(Statement): name = value_ref(self.name, "") return f"del {ref}[{name}]" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: try: - ref = object_ref(self.objectref, prefix) - name = name_ref(self.name, prefix) + ref = object_ref(self.objectref, prefix, verbose=verbose) + name = name_ref(self.name, prefix, verbose=verbose) return [f"{prefix}del {ref}.{name};"] except Exception: # This is not a simple string object reference. - ref = object_ref(self.objectref, prefix) - name = value_ref(self.name, prefix) + ref = object_ref(self.objectref, prefix, verbose=verbose) + name = value_ref(self.name, prefix, verbose=verbose) return [f"{prefix}del {ref}[{name}];"] @@ -663,9 +667,9 @@ class StoreRegisterStatement(Statement): val = value_ref(self.valueref, "") return f"{self.register.render('')} = {val}" - def render(self, prefix: str) -> List[str]: - val = value_ref(self.valueref, prefix) - return [f"{prefix}{self.register.render(prefix)} = {val};"] + def render(self, prefix: str, verbose: bool = False) -> List[str]: + val = value_ref(self.valueref, prefix, verbose=verbose) + return [f"{prefix}{self.register.render(prefix, verbose=verbose)} = {val};"] class SetVariableStatement(Statement): @@ -682,9 +686,9 @@ class SetVariableStatement(Statement): val = value_ref(self.valueref, "") return f"{name} = {val}" - def render(self, prefix: str) -> List[str]: - name = name_ref(self.name, prefix) - val = value_ref(self.valueref, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + name = name_ref(self.name, prefix, verbose=verbose) + val = value_ref(self.valueref, prefix, verbose=verbose) return [f"{prefix}{name} = {val};"] @@ -702,9 +706,9 @@ class SetLocalStatement(Statement): val = value_ref(self.valueref, "") return f"local {name} = {val}" - def render(self, prefix: str) -> List[str]: - name = name_ref(self.name, prefix) - val = value_ref(self.valueref, prefix) + def render(self, prefix: str, verbose: bool = False) -> List[str]: + name = name_ref(self.name, prefix, verbose=verbose) + val = value_ref(self.valueref, prefix, verbose=verbose) return [f"{prefix}local {name} = {val};"] @@ -856,14 +860,14 @@ class IfStatement(Statement): "}" ]) - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: true_entries: List[str] = [] for statement in self.true_statements: - true_entries.extend(statement.render(prefix=prefix + " ")) + true_entries.extend(statement.render(prefix=prefix + " ", verbose=verbose)) false_entries: List[str] = [] for statement in self.false_statements: - false_entries.extend(statement.render(prefix=prefix + " ")) + false_entries.extend(statement.render(prefix=prefix + " ", verbose=verbose)) if false_entries: return [ @@ -900,10 +904,10 @@ class DoWhileStatement(Statement): "} while (True)" ]) - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: entries: List[str] = [] for statement in self.body: - entries.extend(statement.render(prefix=prefix + " ")) + entries.extend(statement.render(prefix=prefix + " ", verbose=verbose)) return [ f"{prefix}do", @@ -942,13 +946,13 @@ class ForStatement(DoWhileStatement): "}" ]) - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: entries: List[str] = [] for statement in self.body: - entries.extend(statement.render(prefix=prefix + " ")) + entries.extend(statement.render(prefix=prefix + " ", verbose=verbose)) - inc_init = value_ref(self.inc_init, "") - inc_assign = value_ref(self.inc_assign, "") + inc_init = value_ref(self.inc_init, prefix, verbose=verbose) + inc_assign = value_ref(self.inc_assign, prefix, verbose=verbose) if self.local: local = "local " else: @@ -979,10 +983,10 @@ class WhileStatement(DoWhileStatement): "}" ]) - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: entries: List[str] = [] for statement in self.body: - entries.extend(statement.render(prefix=prefix + " ")) + entries.extend(statement.render(prefix=prefix + " ", verbose=verbose)) return [ f"{prefix}while ({self.cond}) {{", @@ -1130,8 +1134,8 @@ class Variable(Expression): def __repr__(self) -> str: return f"Variable({name_ref(self.name, '')})" - def render(self, parent_prefix: str, nested: bool = False) -> str: - return name_ref(self.name, parent_prefix) + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: + return name_ref(self.name, parent_prefix, verbose=verbose) class TempVariable(Expression): @@ -1143,7 +1147,7 @@ class TempVariable(Expression): def __repr__(self) -> str: return f"TempVariable({self.name})" - def render(self, parent_prefix: str, nested: bool = False) -> str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: return self.name @@ -1154,7 +1158,7 @@ class InsertionLocation(Statement): def __repr__(self) -> str: return f"" - def render(self, prefix: str) -> List[str]: + def render(self, prefix: str, verbose: bool = False) -> List[str]: raise Exception("Logic error, an InsertionLocation should never make it to the render stage!") @@ -1168,15 +1172,15 @@ class Member(Expression): def __repr__(self) -> str: return self.render("") - def render(self, parent_prefix: str, nested: bool = False) -> str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: try: - member = name_ref(self.member, parent_prefix) - ref = object_ref(self.objectref, parent_prefix) + member = name_ref(self.member, parent_prefix, verbose=verbose) + ref = object_ref(self.objectref, parent_prefix, verbose=verbose) return f"{ref}.{member}" except Exception: # This is not a simple string object reference. - member = value_ref(self.member, parent_prefix) - ref = object_ref(self.objectref, parent_prefix) + member = value_ref(self.member, parent_prefix, verbose=verbose) + ref = object_ref(self.objectref, parent_prefix, verbose=verbose) return f"{ref}[{member}]" @@ -1240,7 +1244,7 @@ class BitVector: class ByteCodeDecompiler(VerboseOutput): - def __init__(self, bytecode: ByteCode, optimize: bool = False) -> None: + def __init__(self, bytecode: ByteCode, optimize: bool = True) -> None: super().__init__() self.bytecode = bytecode @@ -3922,7 +3926,7 @@ class ByteCodeDecompiler(VerboseOutput): output: List[str] = [] for statement in statements: - output.extend(statement.render(prefix)) + output.extend(statement.render(prefix, verbose=self.verbose)) return os.linesep.join(output) diff --git a/bemani/format/afp/swf.py b/bemani/format/afp/swf.py index 69bb532..1a18e0b 100644 --- a/bemani/format/afp/swf.py +++ b/bemani/format/afp/swf.py @@ -464,7 +464,7 @@ class SWF(TrackedCoverage, VerboseOutput): 'references': self.references, } - def __parse_bytecode(self, datachunk: bytes, string_offsets: List[int] = [], prefix: str = "") -> ByteCode: + def __parse_bytecode(self, bytecode_name: Optional[str], datachunk: bytes, string_offsets: List[int] = [], prefix: str = "") -> ByteCode: # First, we need to check if this is a SWF-style bytecode or an AP2 bytecode. ap2_sentinel = struct.unpack("'}, ByteCode Offset: {hex(bytecode_offset)}, ByteCode Length: {hex(bytecode_count)}") - function = self.__parse_bytecode(datachunk[offset_ptr:(offset_ptr + bytecode_count)], string_offsets=string_offsets, prefix=prefix + " ") + # No name for this chunk, it will only ever be decompiled and printed in the context of another + # chunk. + function = self.__parse_bytecode(None, datachunk[offset_ptr:(offset_ptr + bytecode_count)], string_offsets=string_offsets, prefix=prefix + " ") self.vprint(f"{prefix} END_{action_name}") @@ -930,9 +932,9 @@ class SWF(TrackedCoverage, VerboseOutput): else: raise Exception(f"Can't advance, no handler for opcode {opcode} ({hex(opcode)})!") - return ByteCode(actions, offset_ptr) + return ByteCode(bytecode_name, actions, offset_ptr) - def __parse_tag(self, ap2_version: int, afp_version: int, ap2data: bytes, tagid: int, size: int, dataoffset: int, prefix: str = "") -> Tag: + def __parse_tag(self, ap2_version: int, afp_version: int, ap2data: bytes, tagid: int, size: int, dataoffset: int, tag_frame: int, prefix: str = "") -> Tag: if tagid == AP2Tag.AP2_SHAPE: if size != 4: raise Exception(f"Invalid shape size {size}") @@ -959,7 +961,7 @@ class SWF(TrackedCoverage, VerboseOutput): self.add_coverage(dataoffset + 4, 4) self.vprint(f"{prefix} Tag ID: {sprite_id}") - tags, frames, references = self.__parse_tags(ap2_version, afp_version, ap2data, subtags_offset, prefix=" " + prefix) + tags, frames, references = self.__parse_tags(ap2_version, afp_version, ap2data, subtags_offset, tag_frame, prefix=" " + prefix) return AP2DefineSpriteTag(sprite_id, tags, frames, references) elif tagid == AP2Tag.AP2_DEFINE_FONT: @@ -995,7 +997,7 @@ class SWF(TrackedCoverage, VerboseOutput): return AP2DefineFontTag(font_id, fontname, xml_prefix, heights, text_indexes) elif tagid == AP2Tag.AP2_DO_ACTION: datachunk = ap2data[dataoffset:(dataoffset + size)] - bytecode = self.__parse_bytecode(datachunk, prefix=prefix) + bytecode = self.__parse_bytecode(f"on_enter_frame_{tag_frame}", datachunk, prefix=prefix) self.add_coverage(dataoffset, size) return AP2DoActionTag(bytecode) @@ -1219,7 +1221,7 @@ class SWF(TrackedCoverage, VerboseOutput): bytecode_length = beginning_to_end[bytecode_offset] - bytecode_offset self.vprint(f"{prefix} Flags: {hex(evt_flags)} ({', '.join(events)}), KeyCode: {hex(keycode)}, ByteCode Offset: {hex(dataoffset + bytecode_offset)}, Length: {bytecode_length}") - bytecode = self.__parse_bytecode(datachunk[bytecode_offset:(bytecode_offset + bytecode_length)], prefix=prefix + " ") + bytecode = self.__parse_bytecode(f"on_tag_{object_id}_event", datachunk[bytecode_offset:(bytecode_offset + bytecode_length)], prefix=prefix + " ") self.add_coverage(dataoffset + bytecode_offset, bytecode_length) bytecodes[evt_flags] = [*bytecodes.get(evt_flags, []), bytecode] @@ -1760,7 +1762,7 @@ class SWF(TrackedCoverage, VerboseOutput): self.vprint(f"Unknown tag {hex(tagid)} with data {ap2data[dataoffset:(dataoffset + size)]!r}") raise Exception(f"Unimplemented tag {hex(tagid)}!") - def __parse_tags(self, ap2_version: int, afp_version: int, ap2data: bytes, tags_base_offset: int, prefix: str = "") -> Tuple[List[Tag], List[Frame], Dict[int, str]]: + def __parse_tags(self, ap2_version: int, afp_version: int, ap2data: bytes, tags_base_offset: int, sprite_frame: int, prefix: str = "") -> Tuple[List[Tag], List[Frame], Dict[int, str]]: name_reference_flags, name_reference_count, frame_count, tags_count, name_reference_offset, frame_offset, tags_offset = struct.unpack( "> 20) & 0xFFF + frames.append(Frame(start_tag_offset, num_tags_to_play)) + + self.vprint(f"{prefix} Frame Start Tag: {start_tag_offset}, Count: {num_tags_to_play}") + for j in range(num_tags_to_play): + if start_tag_offset + j in tag_to_frame: + raise Exception("Logic error!") + tag_to_frame[start_tag_offset + j] = i + frame_offset += 4 + + # Now, parse regular tags. tags: List[Tag] = [] self.vprint(f"{prefix}Number of Tags: {tags_count}") for i in range(tags_count): @@ -1786,23 +1807,9 @@ class SWF(TrackedCoverage, VerboseOutput): raise Exception(f"Invalid tag size {size} ({hex(size)})") self.vprint(f"{prefix} Tag: {hex(tagid)} ({AP2Tag.tag_to_name(tagid)}), Size: {hex(size)}, Offset: {hex(tags_offset + 4)}") - tags.append(self.__parse_tag(ap2_version, afp_version, ap2data, tagid, size, tags_offset + 4, prefix=prefix)) + tags.append(self.__parse_tag(ap2_version, afp_version, ap2data, tagid, size, tags_offset + 4, tag_to_frame[i] + sprite_frame, prefix=prefix)) tags_offset += ((size + 3) & 0xFFFFFFFC) + 4 # Skip past tag header and data, rounding to the nearest 4 bytes. - # Now, parse frames. - frames: List[Frame] = [] - self.vprint(f"{prefix}Number of Frames: {frame_count}") - for i in range(frame_count): - frame_info = struct.unpack("> 20) & 0xFFF - frames.append(Frame(start_tag_offset, num_tags_to_play)) - - self.vprint(f"{prefix} Frame Start Tag: {start_tag_offset}, Count: {num_tags_to_play}") - frame_offset += 4 - # Finally, parse place object name references. self.vprint(f"{prefix}Number of Object Name References: {name_reference_count}, Flags: {hex(name_reference_flags)}") references: Dict[int, str] = {} @@ -2007,7 +2014,7 @@ class SWF(TrackedCoverage, VerboseOutput): # Tag sections tags_offset = struct.unpack(" str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: raise NotImplementedError(f"{self.__class__.__name__} does not implement render()!") @@ -630,7 +630,7 @@ class GenericObject(Expression): def __repr__(self) -> str: return self.name - def render(self, parent_prefix: str, nested: bool = False) -> str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: return self.name @@ -650,7 +650,7 @@ class Register(Expression): def __repr__(self) -> str: return f"Register({self.no})" - def render(self, parent_prefix: str, nested: bool = False) -> str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: return f"registers[{self.no}]" @@ -2600,7 +2600,7 @@ class StringConstant(Expression): else: return f"StringConstant({hex(self.const)}: {StringConstant.property_to_name(self.const)})" - def render(self, parent_prefix: str, nested: bool = False) -> str: + def render(self, parent_prefix: str, verbose: bool = False, nested: bool = False) -> str: if self.alias: return self.alias else: diff --git a/bemani/tests/test_afp_decompile.py b/bemani/tests/test_afp_decompile.py index 5e7a6d7..4b09daf 100644 --- a/bemani/tests/test_afp_decompile.py +++ b/bemani/tests/test_afp_decompile.py @@ -115,6 +115,7 @@ class TestAFPControlGraph(ExtendedTestCase): def __make_bytecode(self, actions: Sequence[AP2Action]) -> ByteCode: return ByteCode( + None, actions, actions[-1].offset + 1, ) @@ -527,6 +528,7 @@ class TestAFPDecompile(ExtendedTestCase): def __make_bytecode(self, actions: Sequence[AP2Action]) -> ByteCode: return ByteCode( + None, actions, actions[-1].offset + 1, ) diff --git a/bemani/utils/afputils.py b/bemani/utils/afputils.py index db88e99..0f5c860 100644 --- a/bemani/utils/afputils.py +++ b/bemani/utils/afputils.py @@ -439,7 +439,7 @@ def main() -> int: afpfile = TXP2File(bfp.read(), verbose=args.verbose) # Now, print it - print(json.dumps(afpfile.as_dict(decompile_bytecode=args.decompile_bytecode), sort_keys=True, indent=4)) + print(json.dumps(afpfile.as_dict(decompile_bytecode=args.decompile_bytecode, verbose=args.verbose), sort_keys=True, indent=4)) if args.action == "parseafp": # First, load the AFP and BSI files @@ -449,7 +449,7 @@ def main() -> int: # Now, print it swf.parse(verbose=args.verbose) - print(json.dumps(swf.as_dict(decompile_bytecode=args.decompile_bytecode), sort_keys=True, indent=4)) + print(json.dumps(swf.as_dict(decompile_bytecode=args.decompile_bytecode, verbose=args.verbose), sort_keys=True, indent=4)) if args.action == "parsegeo": # First, load the AFP and BSI files