1
0
mirror of synced 2024-11-27 23:50:47 +01:00

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:
Jennifer Taylor 2021-04-24 18:05:58 +00:00
parent 19c6de1fcc
commit f2761b90b0
5 changed files with 776 additions and 417 deletions

View File

@ -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',
]

View File

@ -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:

View File

@ -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)

View File

@ -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',

View File

@ -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