Restructure a lot of types to fit an expression/statement model and making a lot of stuff make more sense.
This commit is contained in:
parent
19c6de1fcc
commit
f2761b90b0
@ -2,7 +2,7 @@ from .geo import Shape, DrawParams
|
||||
from .swf import SWF, NamedTagReference
|
||||
from .container import TXP2File, PMAN, Texture, TextureRegion, Unknown1, Unknown2
|
||||
from .render import AFPRenderer
|
||||
from .types import Matrix, Color, Point, Rectangle, AP2Tag, AP2Action, AP2Object, AP2Pointer, AP2Property
|
||||
from .types import Matrix, Color, Point, Rectangle, AP2Tag, AP2Action, AP2Object, AP2Pointer
|
||||
|
||||
|
||||
__all__ = [
|
||||
@ -25,5 +25,4 @@ __all__ = [
|
||||
'AP2Action',
|
||||
'AP2Object',
|
||||
'AP2Pointer',
|
||||
'AP2Property',
|
||||
]
|
||||
|
@ -1,7 +1,7 @@
|
||||
import os
|
||||
from typing import Any, Dict, List, Sequence, Tuple, Set, Union, Optional, cast
|
||||
|
||||
from .types import AP2Action, JumpAction, IfAction, PushAction, GenericObject, DefineFunction2Action
|
||||
from .types import AP2Action, JumpAction, IfAction, PushAction, Expression, Register, GenericObject, StringConstant, StoreRegisterAction, DefineFunction2Action
|
||||
from .util import VerboseOutput
|
||||
|
||||
|
||||
@ -74,39 +74,66 @@ class ControlFlow:
|
||||
|
||||
class ConvertedAction:
|
||||
# An action that has been analyzed and converted to an intermediate representation.
|
||||
semi = False
|
||||
|
||||
|
||||
class MultiStatementAction(ConvertedAction):
|
||||
# An action that allows us to expand the number of lines we have to work with, for
|
||||
# opcodes that perform more than one statement's worth of actions.
|
||||
def __init__(self, actions: Sequence[ConvertedAction]) -> None:
|
||||
self.actions = actions
|
||||
|
||||
def __repr__(self) -> str:
|
||||
# We should never emit one of these in printing.
|
||||
return f"MultiStatementAction({self.actions})"
|
||||
|
||||
|
||||
class Statement(ConvertedAction):
|
||||
# This is just a type class for finished statements.
|
||||
semi = True
|
||||
|
||||
def _object_ref(self, obj: Any) -> str:
|
||||
if isinstance(obj, (GenericObject, Variable, Member)):
|
||||
return repr(obj)
|
||||
else:
|
||||
raise Exception(f"Unsupported objectref {obj}")
|
||||
|
||||
def _value_ref(self, param: Any) -> str:
|
||||
if isinstance(param, (GenericObject, Variable, Member, CallFunctionStatement, CallMethodStatement)):
|
||||
return repr(param)
|
||||
elif isinstance(param, (str, int, float)):
|
||||
return repr(param)
|
||||
else:
|
||||
raise Exception(f"Unsupported valueref {param} ({type(param)})")
|
||||
def object_ref(obj: Any) -> str:
|
||||
if isinstance(obj, (GenericObject, Variable, Member)):
|
||||
return repr(obj)
|
||||
else:
|
||||
raise Exception(f"Unsupported objectref {obj} ({type(obj)})")
|
||||
|
||||
|
||||
def value_ref(param: Any) -> str:
|
||||
if isinstance(param, (Expression)):
|
||||
return repr(param)
|
||||
elif isinstance(param, (str, int, float)):
|
||||
return repr(param)
|
||||
else:
|
||||
raise Exception(f"Unsupported valueref {param} ({type(param)})")
|
||||
|
||||
|
||||
def name_ref(param: Any) -> str:
|
||||
if isinstance(param, str):
|
||||
return param
|
||||
elif isinstance(param, StringConstant):
|
||||
return repr(param)
|
||||
else:
|
||||
raise Exception(f"Unsupported nameref {param} ({type(param)})")
|
||||
|
||||
|
||||
ArbitraryOpcode = Union[AP2Action, ConvertedAction]
|
||||
|
||||
|
||||
class BreakStatement(ConvertedAction):
|
||||
class BreakStatement(Statement):
|
||||
# A break from a loop (forces execution to the next line after the loop).
|
||||
def __repr__(self) -> str:
|
||||
return "break"
|
||||
|
||||
|
||||
class ContinueStatement(ConvertedAction):
|
||||
class ContinueStatement(Statement):
|
||||
# A continue in a loop (forces execution to the top of the loop).
|
||||
def __repr__(self) -> str:
|
||||
return "continue"
|
||||
|
||||
|
||||
class GotoStatement(ConvertedAction):
|
||||
class GotoStatement(Statement):
|
||||
# A goto, including the ID of the chunk we want to jump to.
|
||||
def __init__(self, location: int) -> None:
|
||||
self.location = location
|
||||
@ -115,63 +142,154 @@ class GotoStatement(ConvertedAction):
|
||||
return f"goto label_{self.location}"
|
||||
|
||||
|
||||
class NullReturnStatement(ConvertedAction):
|
||||
class NullReturnStatement(Statement):
|
||||
# A statement which directs the control flow to the end of the code, but
|
||||
# does not pop the stack to return
|
||||
def __repr__(self) -> str:
|
||||
return "return"
|
||||
|
||||
|
||||
class NopStatement(ConvertedAction):
|
||||
class NopStatement(Statement):
|
||||
# A literal no-op. We will get rid of these in an optimizing pass.
|
||||
def __repr__(self) -> str:
|
||||
return "nop"
|
||||
|
||||
|
||||
class StopMovieStatement(ConvertedAction):
|
||||
class ExpressionStatement(Statement):
|
||||
# A statement which is an expression that discards its return.
|
||||
def __init__(self, expr: Expression) -> None:
|
||||
self.expr = expr
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.expr}"
|
||||
|
||||
|
||||
class StopMovieStatement(Statement):
|
||||
# Stop the movie, this is an actionscript-specific opcode.
|
||||
def __repr__(self) -> str:
|
||||
return "builtin_StopPlaying()"
|
||||
|
||||
|
||||
class CallFunctionStatement(ConvertedAction):
|
||||
class PlayMovieStatement(Statement):
|
||||
# Play the movie, this is an actionscript-specific opcode.
|
||||
def __repr__(self) -> str:
|
||||
return "builtin_StartPlaying()"
|
||||
|
||||
|
||||
class FunctionCall(Expression):
|
||||
# Call a method on an object.
|
||||
def __init__(self, name: str, params: List[Any]) -> None:
|
||||
def __init__(self, name: Union[str, StringConstant], params: List[Any]) -> None:
|
||||
self.name = name
|
||||
self.params = params
|
||||
|
||||
def __repr__(self) -> str:
|
||||
params = [self._value_ref(param) for param in self.params]
|
||||
return f"{self.name}({', '.join(params)})"
|
||||
name = name_ref(self.name)
|
||||
params = [value_ref(param) for param in self.params]
|
||||
return f"{name}({', '.join(params)})"
|
||||
|
||||
|
||||
class CallMethodStatement(ConvertedAction):
|
||||
class MethodCall(Expression):
|
||||
# Call a method on an object.
|
||||
def __init__(self, objectref: Any, name: str, params: List[Any]) -> None:
|
||||
def __init__(self, objectref: Any, name: Union[str, StringConstant], params: List[Any]) -> None:
|
||||
self.objectref = objectref
|
||||
self.name = name
|
||||
self.params = params
|
||||
|
||||
def __repr__(self) -> str:
|
||||
obj = self._object_ref(self.objectref)
|
||||
params = [self._value_ref(param) for param in self.params]
|
||||
return f"{obj}.{self.name}({', '.join(params)})"
|
||||
obj = object_ref(self.objectref)
|
||||
name = name_ref(self.name)
|
||||
params = [value_ref(param) for param in self.params]
|
||||
return f"{obj}.{name}({', '.join(params)})"
|
||||
|
||||
|
||||
class SetMemberStatement(ConvertedAction):
|
||||
class NewFunction(Expression):
|
||||
# Create a new function.
|
||||
def __init__(self, funcname: Optional[str], flags: int, body: ByteCode) -> None:
|
||||
self.funcname = funcname
|
||||
self.flags = flags
|
||||
self.body = body
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"new function({repr(self.funcname) or '<anonymous function>'}, {hex(self.flags)}, 'TODO: ByteCode')"
|
||||
|
||||
|
||||
class NewObject(Expression):
|
||||
# Create a new object of type.
|
||||
def __init__(self, objname: Union[str, StringConstant], params: List[Any]) -> None:
|
||||
self.objname = objname
|
||||
self.params = params
|
||||
|
||||
def __repr__(self) -> str:
|
||||
objname = name_ref(self.objname)
|
||||
params = [value_ref(param) for param in self.params]
|
||||
return f"new {objname}({', '.join(params)})"
|
||||
|
||||
|
||||
class SetMemberStatement(Statement):
|
||||
# Call a method on an object.
|
||||
def __init__(self, objectref: Any, name: str, valueref: Any) -> None:
|
||||
def __init__(self, objectref: Any, name: Union[str, StringConstant], valueref: Any) -> None:
|
||||
self.objectref = objectref
|
||||
self.name = name
|
||||
self.valueref = valueref
|
||||
|
||||
def __repr__(self) -> str:
|
||||
ref = self._object_ref(self.objectref)
|
||||
val = self._value_ref(self.valueref)
|
||||
return f"{ref}.{self.name} = {val}"
|
||||
ref = object_ref(self.objectref)
|
||||
name = name_ref(self.name)
|
||||
val = value_ref(self.valueref)
|
||||
return f"{ref}.{name} = {val}"
|
||||
|
||||
|
||||
class IsUndefinedIfStatement(ConvertedAction):
|
||||
class DeleteVariableStatement(Statement):
|
||||
# Call a method on an object.
|
||||
def __init__(self, name: Union[str, StringConstant]) -> None:
|
||||
self.name = name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
name = name_ref(self.name)
|
||||
return f"del {name}"
|
||||
|
||||
|
||||
class StoreRegisterStatement(Statement):
|
||||
# Set a variable to a value.
|
||||
def __init__(self, register: Register, valueref: Any) -> None:
|
||||
self.register = register
|
||||
self.valueref = valueref
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val = value_ref(self.valueref)
|
||||
return f"{self.register} = {val}"
|
||||
|
||||
|
||||
class SetVariableStatement(Statement):
|
||||
# Set a variable to a value.
|
||||
def __init__(self, name: Union[str, StringConstant], valueref: Any) -> None:
|
||||
self.name = name
|
||||
self.valueref = valueref
|
||||
|
||||
def __repr__(self) -> str:
|
||||
name = name_ref(self.name)
|
||||
val = value_ref(self.valueref)
|
||||
return f"{name} = {val}"
|
||||
|
||||
|
||||
class SetLocalStatement(Statement):
|
||||
# Define a local variable with a value.
|
||||
def __init__(self, name: Union[str, StringConstant], valueref: Any) -> None:
|
||||
self.name = name
|
||||
self.valueref = valueref
|
||||
|
||||
def __repr__(self) -> str:
|
||||
name = name_ref(self.name)
|
||||
val = value_ref(self.valueref)
|
||||
return f"local {name} = {val}"
|
||||
|
||||
|
||||
class IfExpr(ConvertedAction):
|
||||
# This is just for typing.
|
||||
pass
|
||||
|
||||
|
||||
class IsUndefinedIf(IfExpr):
|
||||
# No semicolon on this.
|
||||
semi = False
|
||||
|
||||
@ -180,30 +298,140 @@ class IsUndefinedIfStatement(ConvertedAction):
|
||||
self.negate = negate
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val = self._value_ref(self.conditional)
|
||||
val = value_ref(self.conditional)
|
||||
if self.negate:
|
||||
return f"if ({val} !== UNDEFINED)"
|
||||
return f"if ({val} is not UNDEFINED)"
|
||||
else:
|
||||
return f"if ({val} === UNDEFINED)"
|
||||
return f"if ({val} is UNDEFINED)"
|
||||
|
||||
|
||||
class IntermediateIfStatement(ConvertedAction):
|
||||
def __init__(self, parent_action: IfAction, true_actions: Sequence[ArbitraryOpcode], false_actions: Sequence[ArbitraryOpcode], negate: bool) -> None:
|
||||
class IsBooleanIf(IfExpr):
|
||||
# No semicolon on this.
|
||||
semi = False
|
||||
|
||||
def __init__(self, conditional: Any, negate: bool) -> None:
|
||||
self.conditional = conditional
|
||||
self.negate = negate
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val = value_ref(self.conditional)
|
||||
if self.negate:
|
||||
return f"if ({val} is False)"
|
||||
else:
|
||||
return f"if ({val} is True)"
|
||||
|
||||
|
||||
class IsEqualIf(IfExpr):
|
||||
# No semicolon on this.
|
||||
semi = False
|
||||
|
||||
def __init__(self, conditional1: Any, conditional2: Any, negate: bool) -> None:
|
||||
self.conditional1 = conditional1
|
||||
self.conditional2 = conditional2
|
||||
self.negate = negate
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val1 = value_ref(self.conditional1)
|
||||
val2 = value_ref(self.conditional2)
|
||||
return f"if ({val1} {'!=' if self.negate else '=='} {val2})"
|
||||
|
||||
|
||||
class IsStrictEqualIf(IfExpr):
|
||||
# No semicolon on this.
|
||||
semi = False
|
||||
|
||||
def __init__(self, conditional1: Any, conditional2: Any, negate: bool) -> None:
|
||||
self.conditional1 = conditional1
|
||||
self.conditional2 = conditional2
|
||||
self.negate = negate
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val1 = value_ref(self.conditional1)
|
||||
val2 = value_ref(self.conditional2)
|
||||
return f"if ({val1} {'!==' if self.negate else '==='} {val2})"
|
||||
|
||||
|
||||
class MagnitudeIf(IfExpr):
|
||||
# No semicolon on this.
|
||||
semi = False
|
||||
|
||||
def __init__(self, conditional1: Any, conditional2: Any, negate: bool) -> None:
|
||||
self.conditional1 = conditional1
|
||||
self.conditional2 = conditional2
|
||||
self.negate = negate
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val1 = value_ref(self.conditional1)
|
||||
val2 = value_ref(self.conditional2)
|
||||
return f"if ({val1} {'<' if self.negate else '>'} {val2})"
|
||||
|
||||
|
||||
class MagnitudeEqualIf(IfExpr):
|
||||
# No semicolon on this.
|
||||
semi = False
|
||||
|
||||
def __init__(self, conditional1: Any, conditional2: Any, negate: bool) -> None:
|
||||
self.conditional1 = conditional1
|
||||
self.conditional2 = conditional2
|
||||
self.negate = negate
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val1 = value_ref(self.conditional1)
|
||||
val2 = value_ref(self.conditional2)
|
||||
return f"if ({val1} {'<=' if self.negate else '>='} {val2})"
|
||||
|
||||
|
||||
class IfStatement(Statement):
|
||||
# If statements aren't semicoloned.
|
||||
semi = False
|
||||
|
||||
def __init__(self, cond: IfExpr, true_statements: Sequence[Statement], false_statements: Sequence[Statement]) -> None:
|
||||
self.cond = cond
|
||||
self.true_statements = true_statements
|
||||
self.false_statements = false_statements
|
||||
|
||||
def __repr__(self) -> str:
|
||||
true_entries: List[str] = []
|
||||
for statement in self.true_statements:
|
||||
true_entries.extend([f" {s}" for s in (str(statement) + (';' if statement.semi else '')).split(os.linesep)])
|
||||
|
||||
false_entries: List[str] = []
|
||||
for statement in self.false_statements:
|
||||
false_entries.extend([f" {s}" for s in (str(statement) + (';' if statement.semi else '')).split(os.linesep)])
|
||||
|
||||
if false_entries:
|
||||
return os.linesep.join([
|
||||
f"{self.cond} {{",
|
||||
os.linesep.join(true_entries),
|
||||
"} else {",
|
||||
os.linesep.join(false_entries),
|
||||
"}"
|
||||
])
|
||||
else:
|
||||
return os.linesep.join([
|
||||
f"{self.cond} {{",
|
||||
os.linesep.join(true_entries),
|
||||
"}"
|
||||
])
|
||||
|
||||
|
||||
class IntermediateIf(ConvertedAction):
|
||||
def __init__(self, parent_action: IfAction, true_statements: Sequence[Statement], false_statements: Sequence[Statement], negate: bool) -> None:
|
||||
self.parent_action = parent_action
|
||||
self.true_actions = list(true_actions)
|
||||
self.false_actions = list(false_actions)
|
||||
self.true_statements = list(true_statements)
|
||||
self.false_statements = list(false_statements)
|
||||
self.negate = negate
|
||||
|
||||
def __repr__(self) -> str:
|
||||
true_entries: List[str] = []
|
||||
for action in self.true_actions:
|
||||
for action in self.true_statements:
|
||||
true_entries.extend([f" {s}" for s in str(action).split(os.linesep)])
|
||||
|
||||
false_entries: List[str] = []
|
||||
for action in self.false_actions:
|
||||
for action in self.false_statements:
|
||||
false_entries.extend([f" {s}" for s in str(action).split(os.linesep)])
|
||||
|
||||
if self.false_actions:
|
||||
if self.false_statements:
|
||||
return os.linesep.join([
|
||||
f"if <{'!' if self.negate else ''}{self.parent_action}> {{",
|
||||
os.linesep.join(true_entries),
|
||||
@ -320,22 +548,23 @@ class IfBody:
|
||||
)
|
||||
|
||||
|
||||
class Variable(ConvertedAction):
|
||||
def __init__(self, name: str) -> None:
|
||||
class Variable(Expression):
|
||||
def __init__(self, name: Union[str, StringConstant]) -> None:
|
||||
self.name = name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.name
|
||||
return name_ref(self.name)
|
||||
|
||||
|
||||
class Member(ConvertedAction):
|
||||
def __init__(self, objectref: Any, member: str) -> None:
|
||||
class Member(Expression):
|
||||
def __init__(self, objectref: Any, member: Union[str, StringConstant]) -> None:
|
||||
self.objectref = objectref
|
||||
self.member = member
|
||||
|
||||
def __repr__(self) -> str:
|
||||
ref = self._object_ref(self.objectref)
|
||||
return f"{ref}.{self.member}"
|
||||
member = name_ref(self.member)
|
||||
ref = object_ref(self.objectref)
|
||||
return f"{ref}.{member}"
|
||||
|
||||
|
||||
class BitVector:
|
||||
@ -724,7 +953,7 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
false_jump_point = false_jump_points[0]
|
||||
|
||||
# Calculate true and false jump points, see if they are break/continue/goto.
|
||||
true_action: Optional[ConvertedAction] = None
|
||||
true_action: Optional[Statement] = None
|
||||
if true_jump_point == break_point:
|
||||
self.vprint("Converting jump if true to loop break into break statement.")
|
||||
true_action = BreakStatement()
|
||||
@ -742,7 +971,7 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
true_action = GotoStatement(true_jump_point)
|
||||
chunk.next_chunks = [n for n in chunk.next_chunks if n != true_jump_point]
|
||||
|
||||
false_action: Optional[ConvertedAction] = None
|
||||
false_action: Optional[Statement] = None
|
||||
if false_jump_point == break_point:
|
||||
self.vprint("Converting jump if false to loop break into break statement.")
|
||||
false_action = BreakStatement()
|
||||
@ -771,7 +1000,7 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
# This is an internal-only if statement, we don't care.
|
||||
continue
|
||||
|
||||
chunk.actions[-1] = IntermediateIfStatement(
|
||||
chunk.actions[-1] = IntermediateIf(
|
||||
cast(IfAction, last_action),
|
||||
[true_action],
|
||||
[false_action] if false_action else [],
|
||||
@ -1133,9 +1362,35 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
# Return the tree, stripped of all dead code (most likely just the return sentinel).
|
||||
return new_chunks
|
||||
|
||||
def __eval_stack(self, chunk: ByteCodeChunk) -> None:
|
||||
def __eval_stack(self, chunk: ByteCodeChunk, offset_map: Dict[int, int]) -> None:
|
||||
stack: List[Any] = []
|
||||
|
||||
def make_if_expr(action: IfAction, negate: bool) -> IfExpr:
|
||||
if action.comparison in ["IS DEFINED", "IS NOT UNDEFINED"]:
|
||||
conditional = stack.pop()
|
||||
return IsUndefinedIf(conditional, negate=negate != (action.comparison == "IS DEFINED"))
|
||||
if action.comparison in ["IS TRUE", "IS FALSE"]:
|
||||
conditional = stack.pop()
|
||||
return IsBooleanIf(conditional, negate=negate != (action.comparison == "IS FALSE"))
|
||||
if action.comparison in ["==", "!="]:
|
||||
conditional2 = stack.pop()
|
||||
conditional1 = stack.pop()
|
||||
return IsEqualIf(conditional1, conditional2, negate=negate != (action.comparison == "!="))
|
||||
if action.comparison in ["STRICT ==", "STRICT !="]:
|
||||
conditional2 = stack.pop()
|
||||
conditional1 = stack.pop()
|
||||
return IsStrictEqualIf(conditional1, conditional2, negate=negate != (action.comparison == "STRICT !="))
|
||||
if action.comparison in ["<", ">"]:
|
||||
conditional2 = stack.pop()
|
||||
conditional1 = stack.pop()
|
||||
return MagnitudeIf(conditional1, conditional2, negate=negate != (action.comparison == "<"))
|
||||
if action.comparison in ["<=", ">="]:
|
||||
conditional2 = stack.pop()
|
||||
conditional1 = stack.pop()
|
||||
return MagnitudeEqualIf(conditional1, conditional2, negate=negate != (action.comparison == "<="))
|
||||
|
||||
raise Exception("TODO: {action}")
|
||||
|
||||
for i in range(len(chunk.actions)):
|
||||
action = chunk.actions[i]
|
||||
|
||||
@ -1146,33 +1401,73 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
chunk.actions[i] = NopStatement()
|
||||
continue
|
||||
|
||||
if isinstance(action, DefineFunction2Action):
|
||||
# TODO: We need to recursively decompile this function and add its contents here.
|
||||
stack.append(NewFunction(action.name, action.flags, action.body))
|
||||
|
||||
chunk.actions[i] = NopStatement()
|
||||
continue
|
||||
|
||||
if isinstance(action, StoreRegisterAction):
|
||||
# This one's fun, because a store register can generate zero or more statements.
|
||||
# So we need to expand the stack. But we can't mid-iteration without a lot of
|
||||
# shenanigans, so we instead invent a new type of ConvertedAction that can contain
|
||||
# multiple statements.
|
||||
set_value = stack.pop()
|
||||
if action.preserve_stack:
|
||||
stack.append(set_value)
|
||||
|
||||
store_actions: List[StoreRegisterStatement] = []
|
||||
|
||||
for reg in action.registers:
|
||||
store_actions.append(StoreRegisterStatement(reg, set_value))
|
||||
|
||||
chunk.actions[i] = MultiStatementAction(store_actions)
|
||||
continue
|
||||
|
||||
if isinstance(action, JumpAction):
|
||||
# This could possibly be a jump to the very next line, but we will wait for the
|
||||
# optimization pass to figure that out.
|
||||
chunk.actions[i] = GotoStatement(offset_map[action.jump_offset])
|
||||
continue
|
||||
|
||||
if isinstance(action, IfAction):
|
||||
if action.comparison in ["IS DEFINED", "IS NOT UNDEFINED"]:
|
||||
conditional = stack.pop()
|
||||
chunk.actions[i] = IsUndefinedIfStatement(conditional, negate=(action.comparison == "IS DEFINED"))
|
||||
continue
|
||||
chunk.actions[i] = make_if_expr(action, False)
|
||||
continue
|
||||
|
||||
if isinstance(action, AP2Action):
|
||||
if action.opcode == AP2Action.STOP:
|
||||
chunk.actions[i] = StopMovieStatement()
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.PLAY:
|
||||
chunk.actions[i] = PlayMovieStatement()
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.END:
|
||||
chunk.actions[i] = NullReturnStatement()
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.GET_VARIABLE:
|
||||
variable_name = stack.pop()
|
||||
if not isinstance(variable_name, str):
|
||||
if not isinstance(variable_name, (str, StringConstant)):
|
||||
raise Exception("Logic error!")
|
||||
stack.append(Variable(variable_name))
|
||||
|
||||
chunk.actions[i] = NopStatement()
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.DELETE2:
|
||||
variable_name = stack.pop()
|
||||
if not isinstance(variable_name, (str, StringConstant)):
|
||||
raise Exception("Logic error!")
|
||||
|
||||
chunk.actions[i] = DeleteVariableStatement(variable_name)
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.CALL_METHOD:
|
||||
method_name = stack.pop()
|
||||
if not isinstance(method_name, str):
|
||||
if not isinstance(method_name, (str, StringConstant)):
|
||||
raise Exception("Logic error!")
|
||||
object_reference = stack.pop()
|
||||
num_params = stack.pop()
|
||||
@ -1181,14 +1476,14 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
params = []
|
||||
for _ in range(num_params):
|
||||
params.append(stack.pop())
|
||||
stack.append(CallMethodStatement(object_reference, method_name, params))
|
||||
stack.append(MethodCall(object_reference, method_name, params))
|
||||
|
||||
chunk.actions[i] = NopStatement()
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.CALL_FUNCTION:
|
||||
function_name = stack.pop()
|
||||
if not isinstance(function_name, str):
|
||||
if not isinstance(function_name, (str, StringConstant)):
|
||||
raise Exception("Logic error!")
|
||||
num_params = stack.pop()
|
||||
if not isinstance(num_params, int):
|
||||
@ -1196,7 +1491,7 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
params = []
|
||||
for _ in range(num_params):
|
||||
params.append(stack.pop())
|
||||
stack.append(CallFunctionStatement(function_name, params))
|
||||
stack.append(FunctionCall(function_name, params))
|
||||
|
||||
chunk.actions[i] = NopStatement()
|
||||
continue
|
||||
@ -1205,26 +1500,46 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
# This is a discard. Let's see if its discarding a function or method
|
||||
# call. If so, that means the return doesn't matter.
|
||||
discard = stack.pop()
|
||||
if isinstance(discard, CallMethodStatement):
|
||||
if isinstance(discard, MethodCall):
|
||||
# It is! Let's act on the statement.
|
||||
chunk.actions[i] = discard
|
||||
chunk.actions[i] = ExpressionStatement(discard)
|
||||
else:
|
||||
chunk.actions[i] = NopStatement()
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.SET_VARIABLE:
|
||||
set_value = stack.pop()
|
||||
local_name = stack.pop()
|
||||
if not isinstance(local_name, (str, StringConstant)):
|
||||
raise Exception("Logic error!")
|
||||
|
||||
chunk.actions[i] = SetVariableStatement(local_name, set_value)
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.SET_MEMBER:
|
||||
set_value = stack.pop()
|
||||
member_name = stack.pop()
|
||||
if not isinstance(member_name, str):
|
||||
if not isinstance(member_name, (str, StringConstant)):
|
||||
self.vprint(chunk.actions)
|
||||
self.vprint(stack)
|
||||
raise Exception("Logic error!")
|
||||
object_reference = stack.pop()
|
||||
|
||||
chunk.actions[i] = SetMemberStatement(object_reference, member_name, set_value)
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.DEFINE_LOCAL:
|
||||
set_value = stack.pop()
|
||||
local_name = stack.pop()
|
||||
if not isinstance(local_name, (str, StringConstant)):
|
||||
raise Exception("Logic error!")
|
||||
|
||||
chunk.actions[i] = SetLocalStatement(local_name, set_value)
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.GET_MEMBER:
|
||||
member_name = stack.pop()
|
||||
if not isinstance(member_name, str):
|
||||
if not isinstance(member_name, (str, StringConstant)):
|
||||
raise Exception("Logic error!")
|
||||
object_reference = stack.pop()
|
||||
stack.append(Member(object_reference, member_name))
|
||||
@ -1232,8 +1547,32 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
chunk.actions[i] = NopStatement()
|
||||
continue
|
||||
|
||||
if action.opcode == AP2Action.NEW_OBJECT:
|
||||
object_name = stack.pop()
|
||||
if not isinstance(object_name, (str, StringConstant)):
|
||||
raise Exception("Logic error!")
|
||||
num_params = stack.pop()
|
||||
if not isinstance(num_params, int):
|
||||
raise Exception("Logic error!")
|
||||
params = []
|
||||
for _ in range(num_params):
|
||||
params.append(stack.pop())
|
||||
stack.append(NewObject(object_name, params))
|
||||
|
||||
chunk.actions[i] = NopStatement()
|
||||
continue
|
||||
|
||||
if isinstance(action, NullReturnStatement):
|
||||
# We alreadyf handled this
|
||||
# We already handled this
|
||||
continue
|
||||
|
||||
if isinstance(action, IntermediateIf):
|
||||
# A partially-converted if from loop detection. Let's hoist it out properly.
|
||||
chunk.actions[i] = IfStatement(
|
||||
make_if_expr(action.parent_action, action.negate),
|
||||
action.true_statements,
|
||||
action.false_statements,
|
||||
)
|
||||
continue
|
||||
|
||||
self.vprint(chunk.actions)
|
||||
@ -1250,11 +1589,14 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
if new_actions and isinstance(new_actions[-1], NullReturnStatement):
|
||||
# Filter out redundant return statements.
|
||||
continue
|
||||
if isinstance(action, MultiStatementAction):
|
||||
for new_action in action.actions:
|
||||
new_actions.append(new_action)
|
||||
|
||||
new_actions.append(action)
|
||||
chunk.actions = new_actions
|
||||
|
||||
def __eval_chunks(self, start_id: int, chunks: Sequence[ArbitraryCodeChunk]) -> None:
|
||||
def __eval_chunks(self, start_id: int, chunks: Sequence[ArbitraryCodeChunk], offset_map: Dict[int, int]) -> None:
|
||||
chunks_by_id: Dict[int, ArbitraryCodeChunk] = {chunk.id: chunk for chunk in chunks}
|
||||
|
||||
while True:
|
||||
@ -1264,19 +1606,19 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
if isinstance(chunk, Loop):
|
||||
# Evaluate the loop
|
||||
self.vprint(f"Evaluating graph in Loop {chunk.id}")
|
||||
self.__eval_chunks(chunk.id, chunk.chunks)
|
||||
self.__eval_chunks(chunk.id, chunk.chunks, offset_map)
|
||||
elif isinstance(chunk, IfBody):
|
||||
# Evaluate the if body
|
||||
if chunk.true_chunks:
|
||||
self.vprint(f"Evaluating graph of IfBody {chunk.id} true case")
|
||||
true_start = self.__get_entry_block(chunk.true_chunks)
|
||||
self.__eval_chunks(true_start, chunk.true_chunks)
|
||||
self.__eval_chunks(true_start, chunk.true_chunks, offset_map)
|
||||
if chunk.false_chunks:
|
||||
self.vprint(f"Evaluating graph of IfBody {chunk.id} false case")
|
||||
false_start = self.__get_entry_block(chunk.false_chunks)
|
||||
self.__eval_chunks(false_start, chunk.false_chunks)
|
||||
self.__eval_chunks(false_start, chunk.false_chunks, offset_map)
|
||||
else:
|
||||
self.__eval_stack(chunk)
|
||||
self.__eval_stack(chunk, offset_map)
|
||||
|
||||
# Go to the next chunk
|
||||
if not chunk.next_chunks:
|
||||
@ -1358,7 +1700,7 @@ class ByteCodeDecompiler(VerboseOutput):
|
||||
chunks_loops_and_ifs = self.__check_graph(start_id, chunks_loops_and_ifs)
|
||||
|
||||
# Now, its safe to start actually evaluating the stack.
|
||||
self.__eval_chunks(start_id, chunks_loops_and_ifs)
|
||||
self.__eval_chunks(start_id, chunks_loops_and_ifs, offset_map)
|
||||
|
||||
# Finally, let's print the code!
|
||||
if self.main:
|
||||
|
@ -8,7 +8,6 @@ from .types import Matrix, Color, Point, Rectangle
|
||||
from .types import (
|
||||
AP2Action,
|
||||
AP2Tag,
|
||||
AP2Property,
|
||||
DefineFunction2Action,
|
||||
InitRegisterAction,
|
||||
StoreRegisterAction,
|
||||
@ -22,6 +21,7 @@ from .types import (
|
||||
StartDragAction,
|
||||
GotoFrame2Action,
|
||||
Register,
|
||||
StringConstant,
|
||||
NULL,
|
||||
UNDEFINED,
|
||||
THIS,
|
||||
@ -531,107 +531,107 @@ class SWF(TrackedCoverage, VerboseOutput):
|
||||
elif obj_to_create == 0x10:
|
||||
# Property constant with no alias.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x100
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} PROPERTY CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
elif obj_to_create == 0x11:
|
||||
# Property constant referencing a string table entry.
|
||||
propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)])
|
||||
propertyval += 0x100
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
referenceval = self.__get_string(string_offsets[reference])
|
||||
objects.append(StringConstant(propertyval, referenceval))
|
||||
|
||||
offset_ptr += 2
|
||||
self.vprint(f"{prefix} PROPERTY CONST NAME: {AP2Property.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
self.vprint(f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
elif obj_to_create == 0x12:
|
||||
# Same as above, but with allowance for a 16-bit constant offset.
|
||||
propertyval, reference = struct.unpack(">BH", datachunk[offset_ptr:(offset_ptr + 3)])
|
||||
propertyval += 0x100
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
referenceval = self.__get_string(string_offsets[reference])
|
||||
objects.append(StringConstant(propertyval, referenceval))
|
||||
|
||||
offset_ptr += 3
|
||||
self.vprint(f"{prefix} PROPERTY CONST NAME: {AP2Property.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
self.vprint(f"{prefix} PROPERTY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
elif obj_to_create == 0x13:
|
||||
# Class property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x300
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} CLASS CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} CLASS CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
elif obj_to_create == 0x14:
|
||||
# Class property constant with alias.
|
||||
propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)])
|
||||
propertyval += 0x300
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
referenceval = self.__get_string(string_offsets[reference])
|
||||
objects.append(StringConstant(propertyval, referenceval))
|
||||
|
||||
offset_ptr += 2
|
||||
self.vprint(f"{prefix} CLASS CONST NAME: {AP2Property.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
self.vprint(f"{prefix} CLASS CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
# One would expect 0x15 to be identical to 0x12 but for class properties instead. However, it appears
|
||||
# that this has been omitted from game binaries.
|
||||
elif obj_to_create == 0x16:
|
||||
# Func property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x400
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} FUNC CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} FUNC CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
elif obj_to_create == 0x17:
|
||||
# Func property name referencing a string table entry.
|
||||
propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)])
|
||||
propertyval += 0x400
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
referenceval = self.__get_string(string_offsets[reference])
|
||||
objects.append(StringConstant(propertyval, referenceval))
|
||||
|
||||
offset_ptr += 2
|
||||
self.vprint(f"{prefix} FUNC CONST NAME: {AP2Property.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
self.vprint(f"{prefix} FUNC CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
# Same comment with 0x15 applies here with 0x18.
|
||||
elif obj_to_create == 0x19:
|
||||
# Other property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x200
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} OTHER CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} OTHER CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
elif obj_to_create == 0x1a:
|
||||
# Other property name referencing a string table entry.
|
||||
propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)])
|
||||
propertyval += 0x200
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
referenceval = self.__get_string(string_offsets[reference])
|
||||
objects.append(StringConstant(propertyval, referenceval))
|
||||
|
||||
offset_ptr += 2
|
||||
self.vprint(f"{prefix} OTHER CONST NAME: {AP2Property.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
self.vprint(f"{prefix} OTHER CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
# Same comment with 0x15 and 0x18 applies here with 0x1b.
|
||||
elif obj_to_create == 0x1c:
|
||||
# Event property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x500
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} EVENT CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} EVENT CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
elif obj_to_create == 0x1d:
|
||||
# Event property name referencing a string table entry.
|
||||
propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)])
|
||||
propertyval += 0x500
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
referenceval = self.__get_string(string_offsets[reference])
|
||||
objects.append(StringConstant(propertyval, referenceval))
|
||||
|
||||
offset_ptr += 2
|
||||
self.vprint(f"{prefix} EVENT CONST NAME: {AP2Property.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
self.vprint(f"{prefix} EVENT CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
# Same comment with 0x15, 0x18 and 0x1b applies here with 0x1e.
|
||||
elif obj_to_create == 0x1f:
|
||||
# Key constants.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x600
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} KEY CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} KEY CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
elif obj_to_create == 0x20:
|
||||
# Key property name referencing a string table entry.
|
||||
propertyval, reference = struct.unpack(">BB", datachunk[offset_ptr:(offset_ptr + 2)])
|
||||
propertyval += 0x600
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
referenceval = self.__get_string(string_offsets[reference])
|
||||
objects.append(StringConstant(propertyval, referenceval))
|
||||
|
||||
offset_ptr += 2
|
||||
self.vprint(f"{prefix} KEY CONST NAME: {AP2Property.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
self.vprint(f"{prefix} KEY CONST NAME: {StringConstant.property_to_name(propertyval)}, ALIAS: {referenceval}")
|
||||
# Same comment with 0x15, 0x18, 0x1b and 0x1e applies here with 0x21.
|
||||
elif obj_to_create == 0x22:
|
||||
# Pointer to global object.
|
||||
@ -644,41 +644,41 @@ class SWF(TrackedCoverage, VerboseOutput):
|
||||
elif obj_to_create == 0x24:
|
||||
# Some other property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x700
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} ETC2 CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} ETC2 CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
# Possibly in newer binaries, 0x25 and 0x26 are implemented as 8-bit and 16-bit alias pointer
|
||||
# versions of 0x24.
|
||||
elif obj_to_create == 0x27:
|
||||
# Some other property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x800
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} ORGFUNC2 CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} ORGFUNC2 CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
# Possibly in newer binaries, 0x28 and 0x29 are implemented as 8-bit and 16-bit alias pointer
|
||||
# versions of 0x27.
|
||||
elif obj_to_create == 0x2a:
|
||||
# Some other property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0x900
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} ETCFUNC2 CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} ETCFUNC2 CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
# Possibly in newer binaries, 0x2b and 0x2c are implemented as 8-bit and 16-bit alias pointer
|
||||
# versions of 0x2a.
|
||||
elif obj_to_create == 0x2d:
|
||||
# Some other property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0xa00
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} EVENT2 CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} EVENT2 CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
# Possibly in newer binaries, 0x2e and 0x2f are implemented as 8-bit and 16-bit alias pointer
|
||||
# versions of 0x2d.
|
||||
elif obj_to_create == 0x30:
|
||||
# Some other property name.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0xb00
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} EVENT METHOD CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} EVENT METHOD CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
# Possibly in newer binaries, 0x31 and 0x32 are implemented as 8-bit and 16-bit alias pointer
|
||||
# versions of 0x30.
|
||||
elif obj_to_create == 0x33:
|
||||
@ -691,9 +691,9 @@ class SWF(TrackedCoverage, VerboseOutput):
|
||||
elif obj_to_create == 0x34:
|
||||
# Some other property names.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0xc00
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} GENERIC CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} GENERIC CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
# Possibly in newer binaries, 0x35 and 0x36 are implemented as 8-bit and 16-bit alias pointer
|
||||
# versions of 0x34.
|
||||
elif obj_to_create == 0x37:
|
||||
@ -706,9 +706,9 @@ class SWF(TrackedCoverage, VerboseOutput):
|
||||
elif obj_to_create == 0x38:
|
||||
# Some other property names.
|
||||
propertyval = struct.unpack(">B", datachunk[offset_ptr:(offset_ptr + 1)])[0] + 0xd00
|
||||
objects.append(AP2Property.property_to_name(propertyval))
|
||||
objects.append(StringConstant(propertyval))
|
||||
offset_ptr += 1
|
||||
self.vprint(f"{prefix} GENERIC2 CONST NAME: {AP2Property.property_to_name(propertyval)}")
|
||||
self.vprint(f"{prefix} GENERIC2 CONST NAME: {StringConstant.property_to_name(propertyval)}")
|
||||
# Possibly in newer binaries, 0x39 and 0x3a are implemented as 8-bit and 16-bit alias pointer
|
||||
# versions of 0x38.
|
||||
else:
|
||||
@ -752,7 +752,7 @@ class SWF(TrackedCoverage, VerboseOutput):
|
||||
self.vprint(f"{prefix} REGISTER NO: {register_no}")
|
||||
self.vprint(f"{prefix} END_{action_name}")
|
||||
|
||||
actions.append(StoreRegisterAction(lineno, store_registers))
|
||||
actions.append(StoreRegisterAction(lineno, store_registers, preserve_stack=True))
|
||||
elif opcode == AP2Action.STORE_REGISTER2:
|
||||
register_no = struct.unpack(">B", datachunk[(offset_ptr + 1):(offset_ptr + 2)])[0]
|
||||
offset_ptr += 2
|
||||
@ -761,7 +761,7 @@ class SWF(TrackedCoverage, VerboseOutput):
|
||||
self.vprint(f"{prefix} REGISTER NO: {register_no}")
|
||||
self.vprint(f"{prefix} END_{action_name}")
|
||||
|
||||
actions.append(StoreRegisterAction(lineno, [Register(register_no)]))
|
||||
actions.append(StoreRegisterAction(lineno, [Register(register_no)], preserve_stack=False))
|
||||
elif opcode == AP2Action.IF:
|
||||
jump_if_true_offset = struct.unpack(">h", datachunk[(offset_ptr + 1):(offset_ptr + 3)])[0]
|
||||
jump_if_true_offset += (lineno + 3)
|
||||
|
@ -4,8 +4,8 @@ from .ap2 import (
|
||||
AP2Action,
|
||||
AP2Object,
|
||||
AP2Pointer,
|
||||
AP2Property,
|
||||
DefineFunction2Action,
|
||||
Expression,
|
||||
GenericObject,
|
||||
NULL,
|
||||
UNDEFINED,
|
||||
@ -15,6 +15,7 @@ from .ap2 import (
|
||||
CLIP,
|
||||
GLOBAL,
|
||||
Register,
|
||||
StringConstant,
|
||||
PushAction,
|
||||
InitRegisterAction,
|
||||
StoreRegisterAction,
|
||||
@ -38,8 +39,8 @@ __all__ = [
|
||||
'AP2Action',
|
||||
'AP2Object',
|
||||
'AP2Pointer',
|
||||
'AP2Property',
|
||||
'DefineFunction2Action',
|
||||
'Expression',
|
||||
'GenericObject',
|
||||
'NULL',
|
||||
'UNDEFINED',
|
||||
@ -49,6 +50,7 @@ __all__ = [
|
||||
'CLIP',
|
||||
'GLOBAL',
|
||||
'Register',
|
||||
'StringConstant',
|
||||
'PushAction',
|
||||
'InitRegisterAction',
|
||||
'StoreRegisterAction',
|
||||
|
@ -614,8 +614,13 @@ class DefineFunction2Action(AP2Action):
|
||||
])
|
||||
|
||||
|
||||
class Expression:
|
||||
# This is just a type class for all things that can be expressions.
|
||||
pass
|
||||
|
||||
|
||||
# A bunch of stuff for implementing PushAction
|
||||
class GenericObject:
|
||||
class GenericObject(Expression):
|
||||
def __init__(self, name: str) -> None:
|
||||
self.__name = name
|
||||
|
||||
@ -632,306 +637,15 @@ CLIP = GenericObject('CLIP')
|
||||
GLOBAL = GenericObject('GLOBAL')
|
||||
|
||||
|
||||
class Register:
|
||||
class Register(Expression):
|
||||
def __init__(self, no: int) -> None:
|
||||
self.no = no
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Register {self.no}"
|
||||
return f"registers[{self.no}]"
|
||||
|
||||
|
||||
class PushAction(AP2Action):
|
||||
def __init__(self, offset: int, objects: List[Any]) -> None:
|
||||
super().__init__(offset, AP2Action.PUSH)
|
||||
self.objects = objects
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
# TODO: We need to do better than this when exporting objects,
|
||||
# we should preserve their type.
|
||||
'objects': [repr(o) for o in self.objects],
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
objects = [f" {repr(obj)}" for obj in self.objects]
|
||||
action_name = AP2Action.action_to_name(self.opcode)
|
||||
return os.linesep.join([
|
||||
f"{self.offset}: {action_name}",
|
||||
*objects,
|
||||
f"END_{action_name}",
|
||||
])
|
||||
|
||||
|
||||
class InitRegisterAction(AP2Action):
|
||||
def __init__(self, offset: int, registers: List[Register]) -> None:
|
||||
super().__init__(offset, AP2Action.INIT_REGISTER)
|
||||
self.registers = registers
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'registers': [r.no for r in self.registers],
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
registers = [f" {reg}" for reg in self.registers]
|
||||
action_name = AP2Action.action_to_name(self.opcode)
|
||||
return os.linesep.join([
|
||||
f"{self.offset}: {action_name}",
|
||||
*registers,
|
||||
f"END_{action_name}",
|
||||
])
|
||||
|
||||
|
||||
class StoreRegisterAction(AP2Action):
|
||||
def __init__(self, offset: int, registers: List[Register]) -> None:
|
||||
super().__init__(offset, AP2Action.STORE_REGISTER)
|
||||
self.registers = registers
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'registers': [r.no for r in self.registers],
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
registers = [f" {reg}" for reg in self.registers]
|
||||
action_name = AP2Action.action_to_name(self.opcode)
|
||||
return os.linesep.join([
|
||||
f"{self.offset}: {action_name}",
|
||||
*registers,
|
||||
f"END_{action_name}",
|
||||
])
|
||||
|
||||
|
||||
class IfAction(AP2Action):
|
||||
def __init__(self, offset: int, comparison: str, jump_if_true_offset: int) -> None:
|
||||
super().__init__(offset, AP2Action.IF)
|
||||
self.comparison = comparison
|
||||
self.jump_if_true_offset = jump_if_true_offset
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'jump_if_true_offset': self.jump_if_true_offset,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Comparison: {self.comparison}, Offset To Jump To If True: {self.jump_if_true_offset}"
|
||||
|
||||
|
||||
class JumpAction(AP2Action):
|
||||
def __init__(self, offset: int, jump_offset: int) -> None:
|
||||
super().__init__(offset, AP2Action.JUMP)
|
||||
self.jump_offset = jump_offset
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'jump_offset': self.jump_offset,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Offset To Jump To: {self.jump_offset}"
|
||||
|
||||
|
||||
class WithAction(AP2Action):
|
||||
def __init__(self, offset: int, unknown: bytes) -> None:
|
||||
super().__init__(offset, AP2Action.WITH)
|
||||
self.unknown = unknown
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
# TODO: We need to do better than this, so I guess it comes down to having
|
||||
# a better idea how WITH works.
|
||||
'unknown': str(self.unknown),
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Unknown: {self.unknown!r}"
|
||||
|
||||
|
||||
class GotoFrame2Action(AP2Action):
|
||||
def __init__(self, offset: int, additional_frames: int, stop: bool) -> None:
|
||||
super().__init__(offset, AP2Action.GOTO_FRAME2)
|
||||
self.additional_frames = additional_frames
|
||||
self.stop = stop
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'additiona_frames': self.additional_frames,
|
||||
'stop': self.stop,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Additional Frames: {self.additional_frames}, Stop On Arrival: {'yes' if self.stop else 'no'}"
|
||||
|
||||
|
||||
class AddNumVariableAction(AP2Action):
|
||||
def __init__(self, offset: int, amount_to_add: int) -> None:
|
||||
super().__init__(offset, AP2Action.ADD_NUM_VARIABLE)
|
||||
self.amount_to_add = amount_to_add
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'amount_to_add': self.amount_to_add,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Amount To Add: {self.amount_to_add}"
|
||||
|
||||
|
||||
class AddNumRegisterAction(AP2Action):
|
||||
def __init__(self, offset: int, register: Register, amount_to_add: int) -> None:
|
||||
super().__init__(offset, AP2Action.ADD_NUM_REGISTER)
|
||||
self.register = register
|
||||
self.amount_to_add = amount_to_add
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'register': self.register.no,
|
||||
'amount_to_add': self.amount_to_add,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Register: {self.register}, Amount To Add: {self.amount_to_add}"
|
||||
|
||||
|
||||
class GetURL2Action(AP2Action):
|
||||
def __init__(self, offset: int, action: int) -> None:
|
||||
super().__init__(offset, AP2Action.GET_URL2)
|
||||
self.action = action
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'action': self.action,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Action: {self.action}"
|
||||
|
||||
|
||||
class StartDragAction(AP2Action):
|
||||
def __init__(self, offset: int, constrain: Optional[bool]) -> None:
|
||||
super().__init__(offset, AP2Action.START_DRAG)
|
||||
self.constrain = constrain
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'constrain': self.constrain,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.constrain is None:
|
||||
cstr = "check stack"
|
||||
else:
|
||||
cstr = "yes" if self.constrain else "no"
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Constrain Mouse: {cstr}"
|
||||
|
||||
|
||||
class AP2Object:
|
||||
UNDEFINED = 0x0
|
||||
NAN = 0x1
|
||||
BOOLEAN = 0x2
|
||||
INTEGER = 0x3
|
||||
S64 = 0x4
|
||||
FLOAT = 0x5
|
||||
DOUBLE = 0x6
|
||||
STRING = 0x7
|
||||
POINTER = 0x8
|
||||
OBJECT = 0x9
|
||||
INFINITY = 0xa
|
||||
CONST_STRING = 0xb
|
||||
BUILT_IN_FUNCTION = 0xc
|
||||
|
||||
|
||||
class AP2Pointer:
|
||||
# The type of the object if it is an AP2Object.POINTER or AP2Object.OBJECT
|
||||
UNDEFINED = 0x0
|
||||
AFP_TEXT = 0x1
|
||||
AFP_RECT = 0x2
|
||||
AFP_SHAPE = 0x3
|
||||
DRAG = 0x4
|
||||
MATRIX = 0x5
|
||||
POINT = 0x6
|
||||
GETTER_SETTER_PROPERTY = 0x7
|
||||
FUNCTION_WITH_PROTOTYPE = 0x8
|
||||
ROW_DATA = 0x20
|
||||
|
||||
object_W = 0x50
|
||||
movieClip_W = 0x51
|
||||
sound_W = 0x52
|
||||
color_W = 0x53
|
||||
date_W = 0x54
|
||||
array_W = 0x55
|
||||
xml_W = 0x56
|
||||
xmlNode_W = 0x57
|
||||
textFormat_W = 0x58
|
||||
sharedObject_W = 0x59
|
||||
sharedObjectData_W = 0x5a
|
||||
textField_W = 0x5b
|
||||
xmlAttrib_W = 0x5c
|
||||
bitmapdata_W = 0x5d
|
||||
matrix_W = 0x5e
|
||||
point_W = 0x5f
|
||||
ColorMatrixFilter_W = 0x60
|
||||
String_W = 0x61
|
||||
Boolean_W = 0x62
|
||||
Number_W = 0x63
|
||||
function_W = 0x64
|
||||
prototype_W = 0x65
|
||||
super_W = 0x66
|
||||
transform_W = 0x68
|
||||
colorTransform_W = 0x69
|
||||
rectangle_W = 0x6a
|
||||
|
||||
# All of these can have prototypes, not sure what the "C" stands for.
|
||||
Object_C = 0x78
|
||||
MovieClip_C = 0x79
|
||||
Sound_C = 0x7a
|
||||
Color_C = 0x7b
|
||||
Date_C = 0x7c
|
||||
Array_C = 0x7d
|
||||
XML_C = 0x7e
|
||||
XMLNode_C = 0x7f
|
||||
TextFormat_C = 0x80
|
||||
TextField_C = 0x83
|
||||
BitmapData_C = 0x85
|
||||
matrix_C = 0x86
|
||||
point_C = 0x87
|
||||
String_C = 0x89
|
||||
Boolean_C = 0x8a
|
||||
Number_C = 0x8b
|
||||
Function_C = 0x8c
|
||||
aplib_C = 0x8f
|
||||
transform_C = 0x90
|
||||
colorTransform_C = 0x91
|
||||
rectangle_C = 0x92
|
||||
asdlib_C = 0x93
|
||||
XMLController_C = 0x94
|
||||
eManager_C = 0x95
|
||||
|
||||
stage_O = 0xa0
|
||||
math_O = 0xa1
|
||||
key_O = 0xa2
|
||||
mouse_O = 0xa3
|
||||
system_O = 0xa4
|
||||
sharedObject_O = 0xa5
|
||||
flash_O = 0xa6
|
||||
global_O = 0xa7
|
||||
display_P = 0xb4
|
||||
geom_P = 0xb5
|
||||
filtesr_P = 0xb6
|
||||
|
||||
|
||||
class AP2Property:
|
||||
class StringConstant(Expression):
|
||||
__PROPERTIES: List[Tuple[int, str]] = [
|
||||
# Seems to be properties on every object.
|
||||
(0x100, '_x'),
|
||||
@ -2866,3 +2580,305 @@ class AP2Property:
|
||||
if i == propid:
|
||||
return p
|
||||
return f"<UNKNOWN {hex(propid)}>"
|
||||
|
||||
def __init__(self, const: int, alias: Optional[str] = None) -> None:
|
||||
self.const = const
|
||||
self.alias = alias
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.alias:
|
||||
return self.alias
|
||||
else:
|
||||
return StringConstant.property_to_name(self.const)
|
||||
|
||||
|
||||
class PushAction(AP2Action):
|
||||
def __init__(self, offset: int, objects: List[Any]) -> None:
|
||||
super().__init__(offset, AP2Action.PUSH)
|
||||
self.objects = objects
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
# TODO: We need to do better than this when exporting objects,
|
||||
# we should preserve their type.
|
||||
'objects': [repr(o) for o in self.objects],
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
objects = [f" {repr(obj)}" for obj in self.objects]
|
||||
action_name = AP2Action.action_to_name(self.opcode)
|
||||
return os.linesep.join([
|
||||
f"{self.offset}: {action_name}",
|
||||
*objects,
|
||||
f"END_{action_name}",
|
||||
])
|
||||
|
||||
|
||||
class InitRegisterAction(AP2Action):
|
||||
def __init__(self, offset: int, registers: List[Register]) -> None:
|
||||
super().__init__(offset, AP2Action.INIT_REGISTER)
|
||||
self.registers = registers
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'registers': [r.no for r in self.registers],
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
registers = [f" {reg}" for reg in self.registers]
|
||||
action_name = AP2Action.action_to_name(self.opcode)
|
||||
return os.linesep.join([
|
||||
f"{self.offset}: {action_name}",
|
||||
*registers,
|
||||
f"END_{action_name}",
|
||||
])
|
||||
|
||||
|
||||
class StoreRegisterAction(AP2Action):
|
||||
def __init__(self, offset: int, registers: List[Register], preserve_stack: bool) -> None:
|
||||
super().__init__(offset, AP2Action.STORE_REGISTER)
|
||||
self.registers = registers
|
||||
self.preserve_stack = preserve_stack
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'registers': [r.no for r in self.registers],
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
registers = [f" {reg}" for reg in self.registers]
|
||||
action_name = AP2Action.action_to_name(self.opcode)
|
||||
return os.linesep.join([
|
||||
f"{self.offset}: {action_name}, Preserve Stack: {self.preserve_stack}",
|
||||
*registers,
|
||||
f"END_{action_name}",
|
||||
])
|
||||
|
||||
|
||||
class IfAction(AP2Action):
|
||||
def __init__(self, offset: int, comparison: str, jump_if_true_offset: int) -> None:
|
||||
super().__init__(offset, AP2Action.IF)
|
||||
self.comparison = comparison
|
||||
self.jump_if_true_offset = jump_if_true_offset
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'jump_if_true_offset': self.jump_if_true_offset,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Comparison: {self.comparison}, Offset To Jump To If True: {self.jump_if_true_offset}"
|
||||
|
||||
|
||||
class JumpAction(AP2Action):
|
||||
def __init__(self, offset: int, jump_offset: int) -> None:
|
||||
super().__init__(offset, AP2Action.JUMP)
|
||||
self.jump_offset = jump_offset
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'jump_offset': self.jump_offset,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Offset To Jump To: {self.jump_offset}"
|
||||
|
||||
|
||||
class WithAction(AP2Action):
|
||||
def __init__(self, offset: int, unknown: bytes) -> None:
|
||||
super().__init__(offset, AP2Action.WITH)
|
||||
self.unknown = unknown
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
# TODO: We need to do better than this, so I guess it comes down to having
|
||||
# a better idea how WITH works.
|
||||
'unknown': str(self.unknown),
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Unknown: {self.unknown!r}"
|
||||
|
||||
|
||||
class GotoFrame2Action(AP2Action):
|
||||
def __init__(self, offset: int, additional_frames: int, stop: bool) -> None:
|
||||
super().__init__(offset, AP2Action.GOTO_FRAME2)
|
||||
self.additional_frames = additional_frames
|
||||
self.stop = stop
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'additiona_frames': self.additional_frames,
|
||||
'stop': self.stop,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Additional Frames: {self.additional_frames}, Stop On Arrival: {'yes' if self.stop else 'no'}"
|
||||
|
||||
|
||||
class AddNumVariableAction(AP2Action):
|
||||
def __init__(self, offset: int, amount_to_add: int) -> None:
|
||||
super().__init__(offset, AP2Action.ADD_NUM_VARIABLE)
|
||||
self.amount_to_add = amount_to_add
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'amount_to_add': self.amount_to_add,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Amount To Add: {self.amount_to_add}"
|
||||
|
||||
|
||||
class AddNumRegisterAction(AP2Action):
|
||||
def __init__(self, offset: int, register: Register, amount_to_add: int) -> None:
|
||||
super().__init__(offset, AP2Action.ADD_NUM_REGISTER)
|
||||
self.register = register
|
||||
self.amount_to_add = amount_to_add
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'register': self.register.no,
|
||||
'amount_to_add': self.amount_to_add,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Register: {self.register}, Amount To Add: {self.amount_to_add}"
|
||||
|
||||
|
||||
class GetURL2Action(AP2Action):
|
||||
def __init__(self, offset: int, action: int) -> None:
|
||||
super().__init__(offset, AP2Action.GET_URL2)
|
||||
self.action = action
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'action': self.action,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Action: {self.action}"
|
||||
|
||||
|
||||
class StartDragAction(AP2Action):
|
||||
def __init__(self, offset: int, constrain: Optional[bool]) -> None:
|
||||
super().__init__(offset, AP2Action.START_DRAG)
|
||||
self.constrain = constrain
|
||||
|
||||
def as_dict(self, *args: Any, **kwargs: Any) -> Dict[str, Any]:
|
||||
return {
|
||||
**super().as_dict(*args, **kwargs),
|
||||
'constrain': self.constrain,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.constrain is None:
|
||||
cstr = "check stack"
|
||||
else:
|
||||
cstr = "yes" if self.constrain else "no"
|
||||
return f"{self.offset}: {AP2Action.action_to_name(self.opcode)}, Constrain Mouse: {cstr}"
|
||||
|
||||
|
||||
class AP2Object:
|
||||
UNDEFINED = 0x0
|
||||
NAN = 0x1
|
||||
BOOLEAN = 0x2
|
||||
INTEGER = 0x3
|
||||
S64 = 0x4
|
||||
FLOAT = 0x5
|
||||
DOUBLE = 0x6
|
||||
STRING = 0x7
|
||||
POINTER = 0x8
|
||||
OBJECT = 0x9
|
||||
INFINITY = 0xa
|
||||
CONST_STRING = 0xb
|
||||
BUILT_IN_FUNCTION = 0xc
|
||||
|
||||
|
||||
class AP2Pointer:
|
||||
# The type of the object if it is an AP2Object.POINTER or AP2Object.OBJECT
|
||||
UNDEFINED = 0x0
|
||||
AFP_TEXT = 0x1
|
||||
AFP_RECT = 0x2
|
||||
AFP_SHAPE = 0x3
|
||||
DRAG = 0x4
|
||||
MATRIX = 0x5
|
||||
POINT = 0x6
|
||||
GETTER_SETTER_PROPERTY = 0x7
|
||||
FUNCTION_WITH_PROTOTYPE = 0x8
|
||||
ROW_DATA = 0x20
|
||||
|
||||
object_W = 0x50
|
||||
movieClip_W = 0x51
|
||||
sound_W = 0x52
|
||||
color_W = 0x53
|
||||
date_W = 0x54
|
||||
array_W = 0x55
|
||||
xml_W = 0x56
|
||||
xmlNode_W = 0x57
|
||||
textFormat_W = 0x58
|
||||
sharedObject_W = 0x59
|
||||
sharedObjectData_W = 0x5a
|
||||
textField_W = 0x5b
|
||||
xmlAttrib_W = 0x5c
|
||||
bitmapdata_W = 0x5d
|
||||
matrix_W = 0x5e
|
||||
point_W = 0x5f
|
||||
ColorMatrixFilter_W = 0x60
|
||||
String_W = 0x61
|
||||
Boolean_W = 0x62
|
||||
Number_W = 0x63
|
||||
function_W = 0x64
|
||||
prototype_W = 0x65
|
||||
super_W = 0x66
|
||||
transform_W = 0x68
|
||||
colorTransform_W = 0x69
|
||||
rectangle_W = 0x6a
|
||||
|
||||
# All of these can have prototypes, not sure what the "C" stands for.
|
||||
Object_C = 0x78
|
||||
MovieClip_C = 0x79
|
||||
Sound_C = 0x7a
|
||||
Color_C = 0x7b
|
||||
Date_C = 0x7c
|
||||
Array_C = 0x7d
|
||||
XML_C = 0x7e
|
||||
XMLNode_C = 0x7f
|
||||
TextFormat_C = 0x80
|
||||
TextField_C = 0x83
|
||||
BitmapData_C = 0x85
|
||||
matrix_C = 0x86
|
||||
point_C = 0x87
|
||||
String_C = 0x89
|
||||
Boolean_C = 0x8a
|
||||
Number_C = 0x8b
|
||||
Function_C = 0x8c
|
||||
aplib_C = 0x8f
|
||||
transform_C = 0x90
|
||||
colorTransform_C = 0x91
|
||||
rectangle_C = 0x92
|
||||
asdlib_C = 0x93
|
||||
XMLController_C = 0x94
|
||||
eManager_C = 0x95
|
||||
|
||||
stage_O = 0xa0
|
||||
math_O = 0xa1
|
||||
key_O = 0xa2
|
||||
mouse_O = 0xa3
|
||||
system_O = 0xa4
|
||||
sharedObject_O = 0xa5
|
||||
flash_O = 0xa6
|
||||
global_O = 0xa7
|
||||
display_P = 0xb4
|
||||
geom_P = 0xb5
|
||||
filtesr_P = 0xb6
|
||||
|
Loading…
Reference in New Issue
Block a user