1
0
mirror of synced 2025-01-31 12:13:49 +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
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
@ -122,6 +134,14 @@ def name_ref(param: Any) -> str:
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):
# A break from a loop (forces execution to the next line after the loop).
def __repr__(self) -> str:
@ -202,6 +222,23 @@ class PlayMovieStatement(Statement):
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):
# Call a method on an object.
def __init__(self, name: Union[str, StringConstant], params: List[Any]) -> None:
@ -273,22 +310,36 @@ class NewObject(Expression):
class SetMemberStatement(Statement):
# 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.name = name
self.valueref = valueref
def __repr__(self) -> str:
ref = object_ref(self.objectref)
name = name_ref(self.name)
val = value_ref(self.valueref)
return f"{ref}.{name} = {val}"
try:
ref = object_ref(self.objectref)
name = name_ref(self.name)
val = value_ref(self.valueref)
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]:
ref = object_ref(self.objectref)
name = name_ref(self.name)
val = value_ref(self.valueref)
return [f"{prefix}{ref}.{name} = {val};"]
try:
ref = object_ref(self.objectref)
name = name_ref(self.name)
val = value_ref(self.valueref)
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):
@ -665,7 +716,7 @@ class Variable(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.member = member
@ -673,9 +724,15 @@ class Member(Expression):
return self.render()
def render(self, nested: bool = False) -> str:
member = name_ref(self.member)
ref = object_ref(self.objectref)
return f"{ref}.{member}"
try:
member = name_ref(self.member)
ref = object_ref(self.objectref)
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:
@ -1337,7 +1394,35 @@ class ByteCodeDecompiler(VerboseOutput):
break
if len(chunks_by_id[cur_id].next_chunks) == 1:
# This is just a goto/chunk, move on to the next one.
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.
cur_id = chunks_by_id[cur_id].next_chunks[0]
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
@ -1546,6 +1631,21 @@ class ByteCodeDecompiler(VerboseOutput):
chunk.actions[i] = make_if_expr(action, False)
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 action.opcode == AP2Action.STOP:
chunk.actions[i] = StopMovieStatement()
@ -1630,9 +1730,7 @@ class ByteCodeDecompiler(VerboseOutput):
if action.opcode == AP2Action.SET_MEMBER:
set_value = stack.pop()
member_name = stack.pop()
if not isinstance(member_name, (str, StringConstant)):
self.vprint(chunk.actions)
self.vprint(stack)
if not isinstance(member_name, (str, Expression)):
raise Exception("Logic error!")
object_reference = stack.pop()
@ -1650,7 +1748,7 @@ class ByteCodeDecompiler(VerboseOutput):
if action.opcode == AP2Action.GET_MEMBER:
member_name = stack.pop()
if not isinstance(member_name, (str, StringConstant)):
if not isinstance(member_name, (str, Expression)):
raise Exception("Logic error!")
object_reference = stack.pop()
stack.append(Member(object_reference, member_name))
@ -1673,10 +1771,30 @@ class ByteCodeDecompiler(VerboseOutput):
chunk.actions[i] = NopStatement()
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):
# We already handled this
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):
# A partially-converted if from loop detection. Let's hoist it out properly.
chunk.actions[i] = IfStatement(
@ -1706,6 +1824,7 @@ class ByteCodeDecompiler(VerboseOutput):
if isinstance(action, MultiAction):
for new_action in action.actions:
new_actions.append(new_action)
continue
new_actions.append(action)
return new_actions
@ -1718,6 +1837,9 @@ class ByteCodeDecompiler(VerboseOutput):
# Grab the chunk to operate on.
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):
# Evaluate the loop
self.vprint(f"Evaluating graph in Loop {chunk.id}")
@ -1766,6 +1888,7 @@ class ByteCodeDecompiler(VerboseOutput):
for statement in new_statements:
if not isinstance(statement, Statement):
# We didn't convert a statement properly.
self.vprint(statement)
raise Exception("Logic error!")
statements.append(statement)