1
0
mirror of synced 2025-02-07 15:01:21 +01:00

Handle if-goto pattern that I previously neglected.

This commit is contained in:
Jennifer Taylor 2021-04-24 18:09:44 +00:00
parent 6221d0273b
commit f1aea996c4

View File

@ -1,7 +1,19 @@
import os import os
from typing import Any, Dict, List, Sequence, Tuple, Set, Union, Optional, cast from typing import Any, Dict, List, Sequence, Tuple, Set, Union, Optional, cast
from .types import AP2Action, JumpAction, IfAction, PushAction, Expression, Register, GenericObject, StringConstant, StoreRegisterAction, DefineFunction2Action from .types import (
AP2Action,
JumpAction,
IfAction,
PushAction,
AddNumVariableAction,
Expression,
Register,
GenericObject,
StringConstant,
StoreRegisterAction,
DefineFunction2Action,
)
from .util import VerboseOutput from .util import VerboseOutput
@ -122,6 +134,14 @@ def name_ref(param: Any) -> str:
ArbitraryOpcode = Union[AP2Action, ConvertedAction] ArbitraryOpcode = Union[AP2Action, ConvertedAction]
class DefineLabelStatement(Statement):
def __init__(self, location: int) -> None:
self.location = location
def render(self, prefix: str) -> List[str]:
return [f"label_{self.location}:"]
class BreakStatement(Statement): class BreakStatement(Statement):
# A break from a loop (forces execution to the next line after the loop). # A break from a loop (forces execution to the next line after the loop).
def __repr__(self) -> str: def __repr__(self) -> str:
@ -202,6 +222,23 @@ class PlayMovieStatement(Statement):
return [f"{prefix}builtin_StartPlaying();"] return [f"{prefix}builtin_StartPlaying();"]
class ArithmeticExpression(Expression):
def __init__(self, left: Any, op: str, right: Any) -> None:
self.left = left
self.op = op
self.right = right
def __repr__(self) -> str:
left = value_ref(self.left, parens=True)
right = value_ref(self.right, parens=True)
return f"{left} {self.op} {right}"
def render(self, nested: bool = False) -> str:
left = value_ref(self.left, parens=True)
right = value_ref(self.right, parens=True)
return f"{left} {self.op} {right}"
class FunctionCall(Expression): class FunctionCall(Expression):
# Call a method on an object. # Call a method on an object.
def __init__(self, name: Union[str, StringConstant], params: List[Any]) -> None: def __init__(self, name: Union[str, StringConstant], params: List[Any]) -> None:
@ -273,22 +310,36 @@ class NewObject(Expression):
class SetMemberStatement(Statement): class SetMemberStatement(Statement):
# Call a method on an object. # Call a method on an object.
def __init__(self, objectref: Any, name: Union[str, StringConstant], valueref: Any) -> None: def __init__(self, objectref: Any, name: Union[str, Expression], valueref: Any) -> None:
self.objectref = objectref self.objectref = objectref
self.name = name self.name = name
self.valueref = valueref self.valueref = valueref
def __repr__(self) -> str: def __repr__(self) -> str:
try:
ref = object_ref(self.objectref) ref = object_ref(self.objectref)
name = name_ref(self.name) name = name_ref(self.name)
val = value_ref(self.valueref) val = value_ref(self.valueref)
return f"{ref}.{name} = {val}" return f"{ref}.{name} = {val}"
except Exception:
# This is not a simple string object reference.
ref = object_ref(self.objectref)
name = value_ref(self.name)
val = value_ref(self.valueref)
return f"{ref}[{name}] = {val}"
def render(self, prefix: str) -> List[str]: def render(self, prefix: str) -> List[str]:
try:
ref = object_ref(self.objectref) ref = object_ref(self.objectref)
name = name_ref(self.name) name = name_ref(self.name)
val = value_ref(self.valueref) val = value_ref(self.valueref)
return [f"{prefix}{ref}.{name} = {val};"] return [f"{prefix}{ref}.{name} = {val};"]
except Exception:
# This is not a simple string object reference.
ref = object_ref(self.objectref)
name = value_ref(self.name)
val = value_ref(self.valueref)
return [f"{prefix}{ref}[{name}] = {val};"]
class DeleteVariableStatement(Statement): class DeleteVariableStatement(Statement):
@ -665,7 +716,7 @@ class Variable(Expression):
class Member(Expression): class Member(Expression):
def __init__(self, objectref: Any, member: Union[str, StringConstant]) -> None: def __init__(self, objectref: Any, member: Union[str, Expression]) -> None:
self.objectref = objectref self.objectref = objectref
self.member = member self.member = member
@ -673,9 +724,15 @@ class Member(Expression):
return self.render() return self.render()
def render(self, nested: bool = False) -> str: def render(self, nested: bool = False) -> str:
try:
member = name_ref(self.member) member = name_ref(self.member)
ref = object_ref(self.objectref) ref = object_ref(self.objectref)
return f"{ref}.{member}" return f"{ref}.{member}"
except Exception:
# This is not a simple string object reference.
member = value_ref(self.member)
ref = object_ref(self.objectref)
return f"{ref}[{member}]"
class BitVector: class BitVector:
@ -1337,10 +1394,38 @@ class ByteCodeDecompiler(VerboseOutput):
break break
if len(chunks_by_id[cur_id].next_chunks) == 1: if len(chunks_by_id[cur_id].next_chunks) == 1:
if not isinstance(cur_chunk, ByteCodeChunk):
# This is an already-handled loop or if, don't bother checking for
# if-goto patterns.
cur_id = chunks_by_id[cur_id].next_chunks[0]
continue
last_action = cur_chunk.actions[-1]
if not isinstance(last_action, IfAction):
# This is just a goto/chunk, move on to the next one. # This is just a goto/chunk, move on to the next one.
cur_id = chunks_by_id[cur_id].next_chunks[0] cur_id = chunks_by_id[cur_id].next_chunks[0]
continue continue
# This is an if with a goto in the true clause. Verify that and
# then convert it.
jump_offset = offset_map[last_action.jump_if_true_offset]
if jump_offset == chunks_by_id[cur_id].next_chunks[0]:
# We have an if that goes to the next chunk on true, so we've
# lost the false path. This is a problem.
raise Exception("Logic error!")
# Conver this to an if-goto statement, much like we do in loops.
cur_chunk.actions[-1] = IntermediateIf(
last_action,
[GotoStatement(jump_offset)],
[],
negate=False,
)
self.vprint("Converted if-goto pattern in chuk ID {cur_id} to intermediate if")
cur_id = chunks_by_id[cur_id].next_chunks[0]
continue
if not isinstance(cur_chunk, ByteCodeChunk): if not isinstance(cur_chunk, ByteCodeChunk):
# We should only be looking at bytecode chunks at this point, all other # We should only be looking at bytecode chunks at this point, all other
# types should have a single next chunk. # types should have a single next chunk.
@ -1546,6 +1631,21 @@ class ByteCodeDecompiler(VerboseOutput):
chunk.actions[i] = make_if_expr(action, False) chunk.actions[i] = make_if_expr(action, False)
continue continue
if isinstance(action, AddNumVariableAction):
variable_name = stack.pop()
if not isinstance(variable_name, (str, StringConstant)):
raise Exception("Logic error!")
chunk.actions[i] = SetVariableStatement(
variable_name,
ArithmeticExpression(
Variable(variable_name),
"+" if action.amount_to_add >= 0 else '-',
abs(action.amount_to_add),
)
)
continue
if isinstance(action, AP2Action): if isinstance(action, AP2Action):
if action.opcode == AP2Action.STOP: if action.opcode == AP2Action.STOP:
chunk.actions[i] = StopMovieStatement() chunk.actions[i] = StopMovieStatement()
@ -1630,9 +1730,7 @@ class ByteCodeDecompiler(VerboseOutput):
if action.opcode == AP2Action.SET_MEMBER: if action.opcode == AP2Action.SET_MEMBER:
set_value = stack.pop() set_value = stack.pop()
member_name = stack.pop() member_name = stack.pop()
if not isinstance(member_name, (str, StringConstant)): if not isinstance(member_name, (str, Expression)):
self.vprint(chunk.actions)
self.vprint(stack)
raise Exception("Logic error!") raise Exception("Logic error!")
object_reference = stack.pop() object_reference = stack.pop()
@ -1650,7 +1748,7 @@ class ByteCodeDecompiler(VerboseOutput):
if action.opcode == AP2Action.GET_MEMBER: if action.opcode == AP2Action.GET_MEMBER:
member_name = stack.pop() member_name = stack.pop()
if not isinstance(member_name, (str, StringConstant)): if not isinstance(member_name, (str, Expression)):
raise Exception("Logic error!") raise Exception("Logic error!")
object_reference = stack.pop() object_reference = stack.pop()
stack.append(Member(object_reference, member_name)) stack.append(Member(object_reference, member_name))
@ -1673,10 +1771,30 @@ class ByteCodeDecompiler(VerboseOutput):
chunk.actions[i] = NopStatement() chunk.actions[i] = NopStatement()
continue continue
if action.opcode == AP2Action.ADD2:
expr1 = stack.pop()
expr2 = stack.pop()
stack.append(ArithmeticExpression(expr1, "+", expr2))
chunk.actions[i] = NopStatement()
continue
if isinstance(action, NullReturnStatement): if isinstance(action, NullReturnStatement):
# We already handled this # We already handled this
continue continue
if isinstance(action, ContinueStatement):
# We already handled this
continue
if isinstance(action, BreakStatement):
# We already handled this
continue
if isinstance(action, GotoStatement):
# We already handled this
continue
if isinstance(action, IntermediateIf): if isinstance(action, IntermediateIf):
# A partially-converted if from loop detection. Let's hoist it out properly. # A partially-converted if from loop detection. Let's hoist it out properly.
chunk.actions[i] = IfStatement( chunk.actions[i] = IfStatement(
@ -1706,6 +1824,7 @@ class ByteCodeDecompiler(VerboseOutput):
if isinstance(action, MultiAction): if isinstance(action, MultiAction):
for new_action in action.actions: for new_action in action.actions:
new_actions.append(new_action) new_actions.append(new_action)
continue
new_actions.append(action) new_actions.append(action)
return new_actions return new_actions
@ -1718,6 +1837,9 @@ class ByteCodeDecompiler(VerboseOutput):
# Grab the chunk to operate on. # Grab the chunk to operate on.
chunk = chunks_by_id[start_id] chunk = chunks_by_id[start_id]
# Make sure when we collapse chunks, we don't lose labels.
statements.append(DefineLabelStatement(start_id))
if isinstance(chunk, Loop): if isinstance(chunk, Loop):
# Evaluate the loop # Evaluate the loop
self.vprint(f"Evaluating graph in Loop {chunk.id}") self.vprint(f"Evaluating graph in Loop {chunk.id}")
@ -1766,6 +1888,7 @@ class ByteCodeDecompiler(VerboseOutput):
for statement in new_statements: for statement in new_statements:
if not isinstance(statement, Statement): if not isinstance(statement, Statement):
# We didn't convert a statement properly. # We didn't convert a statement properly.
self.vprint(statement)
raise Exception("Logic error!") raise Exception("Logic error!")
statements.append(statement) statements.append(statement)