From 691a6ddc9706019d638283a63e8b0541b3015743 Mon Sep 17 00:00:00 2001 From: NeoDement Date: Sat, 23 May 2026 12:13:40 +0100 Subject: [PATCH 1/3] Save and restore decompiler stack state at jump targets Switch dispatch in the v6 decompiler was leaking stack state between cases, corrupting later comparisons. Save the stack at each jump target and restore it when we get there. --- src/nutcracker/sputm/windex_v6.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/nutcracker/sputm/windex_v6.py b/src/nutcracker/sputm/windex_v6.py index 10ebe10..a274328 100644 --- a/src/nutcracker/sputm/windex_v6.py +++ b/src/nutcracker/sputm/windex_v6.py @@ -5799,6 +5799,10 @@ def decompile_script(elem, game, verbose=False, transform=True): asts = deque() res = None + # Stack state tracking for proper control flow handling + # Maps jump target addresses to saved stack states + saved_stacks = {} + # # clear local variables: # for key in g_vars: # NOTE: dict key is tuple, we iterates on keys only # _, var = key @@ -5840,6 +5844,7 @@ def decompile_script(elem, game, verbose=False, transform=True): yield f'\tverb {entries[off + 8]} {{' indent = 2 * '\t' stack.clear() + saved_stacks.clear() if verbose: yield ' '.join( [ @@ -5850,6 +5855,11 @@ def decompile_script(elem, game, verbose=False, transform=True): ) if isinstance(res, ConditionalJump) or isinstance(res, UnconditionalJump): srefs.add(off) + + # Restore saved stack state at jump targets + if off in saved_stacks: + stack = deque(saved_stacks[off]) + stack_backup = list(stack) try: res = ops.get(stat.name, defop)(stat, stack, game) @@ -5870,6 +5880,12 @@ def decompile_script(elem, game, verbose=False, transform=True): # # '\t\t\t\t', # # defop(stat, stack, bytecode), # ) + + # Save stack state for jump targets + if isinstance(res, (ConditionalJump, UnconditionalJump)): + target = res.ref.abs + if target not in saved_stacks: + saved_stacks[target] = list(stack) yield from print_locals(indent) l_vars.clear() yield from print_asts( From 0513eef1f1f2b3c99505cc4bca8a884452dab726 Mon Sep 17 00:00:00 2001 From: NeoDement Date: Wed, 3 Jun 2026 01:07:22 +0100 Subject: [PATCH 2/3] added 'cursor image room ' fix --- src/nutcracker/sputm/windex_v6.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/nutcracker/sputm/windex_v6.py b/src/nutcracker/sputm/windex_v6.py index a274328..d59a35a 100644 --- a/src/nutcracker/sputm/windex_v6.py +++ b/src/nutcracker/sputm/windex_v6.py @@ -2406,9 +2406,10 @@ def o6_cursorCommand(op, stack, game): if game.he_version >= 70 or game.version >= 7: # TODO: Figure out object? return f'cursor {stack.pop()}' - # TODO: another pop for non HE or HE < 70 games - image = stack.pop() - return f'cursor {stack.pop()} image {image}' + # Non-HE / HE<70 (v6): setCursorFromImg(obj, room). ScummVM popRoomAndObj pops + # room first, then obj; emit obj first so this reads 'cursor image room '. + room = stack.pop() + return f'cursor image {stack.pop()} room {room}' elif cmd.num == 0x9A: ypos = stack.pop() xpos = stack.pop() From 4c871ea22535a9daddd7b88ec50cfc4c79856ff7 Mon Sep 17 00:00:00 2001 From: NeoDement Date: Wed, 3 Jun 2026 18:31:40 +0100 Subject: [PATCH 3/3] self-parenthesize so each formula is one token this makes cases like this more readable: do-animation 9 61 / 10 + 9 -> do-animation 9 (61 / 10 + 9) --- src/nutcracker/sputm/windex_v6.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/nutcracker/sputm/windex_v6.py b/src/nutcracker/sputm/windex_v6.py index d59a35a..dce489c 100644 --- a/src/nutcracker/sputm/windex_v6.py +++ b/src/nutcracker/sputm/windex_v6.py @@ -174,6 +174,8 @@ def __repr__(self): 'or': 11, } +ARITH_OPS = frozenset('+-*/%') # self-parenthesize so each formula is one token + class BinExpr: def __init__(self, op, left, right): @@ -204,6 +206,7 @@ def __repr__(self): or isinstance(left, Negate) or ( isinstance(left, BinExpr) + and left.op not in ARITH_OPS and left.pre >= self.pre and left.op != self.op ) @@ -215,11 +218,14 @@ def __repr__(self): or isinstance(right, Negate) or ( isinstance(right, BinExpr) + and right.op not in ARITH_OPS and right.pre >= self.pre and right.op != self.op ) ): right = f'({right})' + if self.op in ARITH_OPS: + return f'({left} {self.op} {right})' return f'{left} {self.op} {right}'