Logo Pending


SCI decompilation and Weird Loops

They’re not really that weird on the face of it, but that depends on who’s looking.

The decompiler in SCI Companion is a work of art. You can tell because I didn’t write it. (I only fixed a thing or two.) But there are some things that it can’t figure out, and when that happens the function or method body is replaced with a raw asm block. For example, the copy protection in Laura Bow 2 – The Dagger of Amon Ra has loop in it that SCI Companion can’t hack.

It’s a bit much to take in but the important bits are as follows: this code block (rm18::init) has four discrete segments. The first isn’t shown here and sets up a few simple things. The second (code_0087) is a regular loop, where temp0 counts up from zero to eleven. When it hits twelve, the loop is broken and we go to section three, code_00ad. Section three is a weird loop. If you look at the check at the top we see this:

pushi #size
pushi 0
lofsa tempList
send 4
bnt code_0116

Which basically means that when (tempList size?) returns zero/is false, we skip to section four. At the bottom of the section, right before the label for code_0116, there’s the command that makes section three a loop; jmp code_00ad.

So that means that section three keeps repeating until tempList is out of items. Section two put a bunch of values in it, and section three then takes items out at random and puts them into goodList, effectively randomizing the order. The items, incidentally, are the tiles depicting the various Egyptian gods that the copy protection is all about, clones of egyptProp given increasing cel values. Section three positions them as they’re added to goodList. It’s a good routine, Brent.

The problem that trips up SCI Companion and makes it spit out the stuff in those two pictures is that it doesn’t recognize the second loop for what it is. Counting from one value to another by a given increment? Easy. Iterating over a collection? It can figure those out. But picking items from a bag until it’s empty? That’s not on the menu.

To make this decompile, then, we first need to break the loop by commenting out that last jmp command. A single ; suffices. Compile the script resource, then go back and re-decompile it. A conditional loop, of course, consists of a check and a jump. We removed the jump so now it’s just the check:

(method (init &tmp i theTile theX theY)
  (LoadMany rsVIEW 18) ; load the tiles
  (super init:)
  (gGame handsOn:)
  (gIconBar disable: 0 1 3 4 5 6 7)
  (goodList add:)
  (tempList add:)
  (= theX -32)
  (= theY 46)
  (= i 0)
 
  ; Instantiate twelve tiles, with increasing cel numbers.
  (while (< i 12)
    (tempList add: ((egyptProp new:) cel: i yourself:))
    (++ i)
  )
 
  ; This should be "(while (tempList size?)" but we removed the jump, remember?
  (if (tempList size?)
    ; Pick a tile number.
    (= i (Random 0 (- (tempList size?) 1)))
    ; Get the i-th tile.
    (= theTile (tempList at: i))
 
    ; Set up the tile's position on the grid and add it to goodList.
    (goodList add:
      (theTile
         x: (= theX (+ theX 48))
         y: theY
         yourself:
      )
    )
    ; Once we're halfway through, CRLF to the next row.
    (if (== (goodList size?) 6)
      (= theX -32)
      (= theY 111)
    )
 
    ; Actually remove the tile from tempList so we won't pick it again.
    (tempList delete: theTile)
  )
 
  ; Section four
  (gGame handsOff:)
  (self setScript: sInitEm)
)

The cool part is that once we replace that if with a while and compile the script, the result is effectively the same as the original. Only some of the specific opcode choices are different. For example, the original uses the two-byte pushi 1 throughout (also 0 and 2), but SCI Companion’s script compiler prefers to use the one-byte push1 there. The same values are pushed regardless.

[ , , , ] Leave a Comment

On SCI Windows – Part 6 – Laura Bow, Frontier Pharmacist

It’s a double feature this time because these last two games’ windows are very similar.

These windows have decorations depending on your location or progress. Here are the view resources, both of them #994…

…and here is the code for the lb2Window first:

(instance lb2Win of SysWindow
  (properties
    ; This time, we set the custom bit up here.
    type 128
  )
 
  (method (open &tmp oldPort loop)
    ; Decide which decoration to show depending on our location.
    ; (These have been cut down a bit and the doubles were already
    ;  there. I don't know what's up with that.)
    (cond 
      ((OneOf gRoomNumber 280 210 330 240 260 300) (= loop 0))
      ((OneOf gRoomNumber 210 220 230 260 270 280) (= loop 1))
      ((OneOf gRoomNumber 100 105 110 120 140 150) (= loop 2))
      ((OneOf gRoomNumber 460 660 700 710 715 720) (= loop 4))
      ((OneOf gRoomNumber 335 340 350 355 360 370) (= loop 3))
      (else (= loop 4))
    )
 
    ; Make room for the edges, including the decorations.
    (= lsLeft (- left (/ (CelWide 994 loop 0) 2)))
    ; Yes, LB2 windows adjust for titles, despite the custom bit.
    (= lsTop (- top (if title 19 else 10)))
    (= lsRight (+ right (/ (CelWide 994 loop 0) 2)))
    ; Be at *least* as tall as the decoration.
    (= lsBottom (Max (+ bottom 3) (+ lsTop (CelHigh 994 loop 0) 3)))
 
    ; Always top priority
    (= priority 15)
    (super open:)
 
    ; Now we draw, on the whole screen.
    (= oldPort (GetPort))
    (SetPort 0)
 
    ; Draw our fill...
    (Graph grFILL_BOX top left bottom right 3 gBack 15)
    ; ...border...
    (Graph grDRAW_LINE (- top 1) (- left 1) (- top 1) right gFore 15)
    (Graph grDRAW_LINE (- top 1) (- left 1) bottom (- left 1) gFore 15)
    (Graph grDRAW_LINE bottom (- left 1) bottom right gFore 15)
    (Graph grDRAW_LINE (- top 1) right bottom right gFore 15)
    ; Show what we have wrought...
    (Graph grUPDATE_BOX top left bottom right 1)
    ; ...but also dirty the part where the decorations will go.
    (Graph grUPDATE_BOX lsTop lsLeft (+ lsTop (CelHigh 994 loop 0)) (+ lsLeft (CelWide 994 loop 0)) 1)
    (Graph grUPDATE_BOX lsTop (- lsRight (CelWide 994 loop 0))  (+ lsTop (CelHigh 994 loop 0)) lsRight 1)
    ; Why? I have no idea.
 
    ; Now draw the decorations!
    (DrawCel 994 loop 0 (+ lsLeft 1) (+ lsTop 1) -1)
    (DrawCel 994 loop 1 (- (- lsRight (CelWide 994 loop 0)) 1) (+ lsTop 1) -1)
 
    (SetPort oldPort)
  )
)

Don’t let the thicker right edge fool you — half that line is part of the decoration!

As I said, the code for fpWin is remarkably similar… yet different? Most of it’s just in how the thicker edges are drawn, but a significant bit is in the last part:

(instance fpWin of SysWindow
  (properties
    type 128
  )
 
  (method (open &tmp oldPort loop theLsTop theLsLeft theLsRight)
    ; Decide which decoration to show depending on the act.
    (switch gAct
      (1 (= loop 0))
      (2 (if (Bset 1) (= loop 2) else (= loop 1)))
      (3 (= loop 3))
      (4 (= loop 4))
      (5 (= loop 4))
    )
 
    ; Make room for the edges, including the decorations.
    (= lsLeft (- (- left 3) 15))
    (= lsTop (- (- top 3) (if title 25 else 15)))
    (= lsRight (+ right 3 15))
    (= lsBottom (Max (+ bottom 3) (+ lsTop (CelHigh 994 loop 0) 3)))
 
    ; Always top priority
    (= priority 15)
    (super open:)
 
    ; Now we draw, on the whole screen.
    (= oldPort (GetPort))
    (SetPort 0)
 
    ; Draw our fill...
    (Graph grFILL_BOX top left bottom right 3 gBack 15)
    (if title (= top (- top 10)))
 
    ; Inner box, middling dark...
    (Graph grDRAW_LINE (- top 1) (- left 1) (- top 1) right 17 15)
    (Graph grDRAW_LINE (- top 1) (- left 1) bottom (- left 1) 17 15)
    (Graph grDRAW_LINE bottom (- left 1) bottom right 17 15)
    (Graph grDRAW_LINE (- top 1) right bottom right 17 15)
    ; Middle box, lighter...
    (Graph grDRAW_LINE (- top 2) (- left 2) (- top 2) (+ right 1) 19 15)
    (Graph grDRAW_LINE (- top 2) (- left 2) (+ bottom 1) (- left 2) 19 15)
    (Graph grDRAW_LINE (+ bottom 1) (- left 2) (+ bottom 1) (+ right 1) 19 15)
    (Graph grDRAW_LINE (- top 2) (+ right 1) (+ bottom 1) (+ right 1) 19 15)
    ; And finally the outer, darkest box.
    (Graph grDRAW_LINE (- top 3) (- left 3) (- top 3) (+ right 2) 16 15)
    (Graph grDRAW_LINE (- top 3) (- left 3) (+ bottom 2) (- left 3) 16 15)
    (Graph grDRAW_LINE (+ bottom 2) (- left 3) (+ bottom 2) (+ right 2) 16 15)
    (Graph grDRAW_LINE (- top 3) (+ right 2) (+ bottom 2) (+ right 2) 16 15)
 
    ; Show what we have wrought.
    (Graph grUPDATE_BOX (- top 3) (- left 3) (+ bottom 3) (+ right 3) 1)
 
    ; Unlike LB2, FPFP does this part the hard way.
    (switch gAct
      (1
        (= theLsLeft (+ lsLeft 2))
        (= theLsRight (- (- lsRight 15) 14))
        (= theLsTop lsTop)
      )
      (2
        (if (Bset 1)
          (= theLsLeft lsLeft)
          (= theLsRight (- (- lsRight 15) 13))
          (= theLsTop (+ lsTop 2))
        else
          (= theLsLeft (+ lsLeft 8))
          (= theLsRight (- (- lsRight 15) 14))
          (= theLsTop (+ lsTop 4))
        )
      )
      (3
        (= theLsLeft (+ lsLeft 6))
        (= theLsRight (- (- lsRight 15) 40))
        (= theLsTop (+ lsTop 11))
      )
      (4
        (= theLsLeft (+ lsLeft 7))
        (= theLsRight (- (- lsRight 15) 14))
        (= theLsTop (+ lsTop 8))
      )
      (5
        (= theLsLeft (+ lsLeft 7))
        (= theLsRight (- (- lsRight 15) 14))
        (= theLsTop (+ lsTop 8))
      )
    )
 
    ; Now draw the decorations!
    (DrawCel 994 loop 0 theLsLeft theLsTop -1)
    (DrawCel 994 loop 1 theLsRight theLsTop -1)
 
    (SetPort oldPort)
  )
)

I don’t know why either. Maybe something silly about the decorations?

[ , , , ] Leave a Comment

On SCI Windows – Part NaN – The Colonel’s Bequest

I was going to cover Laura Bow – The Colonel’s Bequest but it turns out its custom window is almost exactly the same as the one in KQ5 and it’d be faster to just go over the differences.

  1. No drop shadow.
  2. No color properties.
  3. The corners are in two loops of two cels each.
  4. No memorizing the adjusted coordinates — the dispose method does the adjustments itself.

That’s literally all there is to it.

[ , , ] 2 Comments on On SCI Windows – Part NaN – The Colonel’s Bequest