Pop -Star Major League Baseball


RetroChallenge kicks off today, so here we go...

The first step is to diassemble WCB's ROM image, recovering the assembler source code.  Happily Joe Zbiciak comes to the rescue again.  In the SDK-1600 Intellivision development kit he provides dis1600, an Intellivision aware disassembler.  This will convert the object code in the ROM to its assembler source code.  To use it we run:
  > dis1600 -f wcb.bin wcb.asm
  SEGMENT ofs 0000  len 2000  addr 5000  FLAGS: R---P
  SEGMENT ofs 2000  len 1000  addr D000  FLAGS: R---P
Opening the resulting wcb.asm we find the block at the $5000 at the top of the file (don't be intimidated by most of this junk, just notice the text for "PLAYERS" and "SKILL LEVEL" we saw in the last post towards the bottom of the code block at addresses $505f and $506f respectively - addresses are the first value after the semi-colon on each line):
      ORG     $5000

  .HEADER:
      BIDECLE $652B           ; 5000   Ptr: MOB graphic images
      BIDECLE $501C           ; 5002   Ptr: EXEC timer table
      BIDECLE $5036           ; 5004   Ptr: Start of game
      BIDECLE $5DAB           ; 5006   Ptr: Backgnd gfx list
      BIDECLE $5D82           ; 5008   Ptr: GRAM init sequence
      BIDECLE $530C           ; 500A   Ptr: Date/Title
      DECLE   $007E           ; 500C   Key-click / flags
      DECLE   $0000           ; 500D   Border extension
      DECLE   $0000           ; 500E   Color Stack / FGBG
      DECLE   $0004,  $0003   ; 500F   Color Stack init (0, 1)
      DECLE   $0004,  $0003   ; 5011   Color Stack init (2, 3)
      DECLE   $000B           ; 5013   Border color init

      DECLE   $180,  $190,  $1A0,  $1B0   ; 5014    0180 0190 01A0 01B0
      DECLE   $1C0,  $1D0,  $1E0,  $1F0   ; 5018    01C0 01D0 01E0 01F0

  .TIMER:
      DECLE   $71,  $1A,  $01,  $80; 501C   Timer dispatch/interval
      DECLE   $11,  $51,  $01,  $00; 5020   Timer dispatch/interval
      DECLE   $6C,  $52,  $30,  $80; 5024   Timer dispatch/interval
      DECLE   $14,  $59,  $05,  $00; 5028   Timer dispatch/interval
      DECLE   $C9,  $55,  $1E,  $00; 502C   Timer dispatch/interval
      DECLE   $99,  $5F,  $01,  $80; 5030   Timer dispatch/interval
      BIDECLE $0000           ; 5034   End of timer table

  .START:
      EIS                                 ; 5036    0002
      PSHR    R5                          ; 5037    0275
      MVII    #$0003, R0                  ; 5038    02B8 0003

  ... skip a bit ...

      JSR     R5,     X_PRINT_R5          ; 505C    0004 0118 007B
      STRING  "PLAYERS:"                  ; 505F   
      DECLE   $0000                       ; 5067    0000

      MVII    #$027A, R4                  ; 5068    02BC 027A
      MVII    #$0007, R3                  ; 506A    02BB 0007

      JSR     R5,     X_PRINT_R5          ; 506C    0004 0118 007B
      STRING  "SKILL LEVEL:"              ; 506F   
      DECLE   $0000                       ; 507B    0000

  L_507C:
      MVII    #$0001, R0                  ; 507C    02B8 0001
      MVII    #$0247, R1                  ; 507E    02B9 0247

And skipping on in the disassembly we can find the addresses that should have our mnemonics ($da9f onward):
      RLC     R0,     1                   ; DA9F    0050
      SLL     R3,     2                   ; DAA0    004F
      RLC     R0,     1                   ; DAA1    0050
      NEGR    R0                          ; DAA2    0020
      RLC     R0,     1                   ; DAA3    0050
      RLC     R1,     2                   ; DAA4    0055
      RLC     R3,     1                   ; DAA5    0053
      SLL     R0,     1                   ; DAA6    0048
      SWAP    R3,     1                   ; DAA7    0043
      SLL     R0,     2                   ; DAA8    004C
      RLC     R2,     1                   ; DAA9    0052
      NEGR    R0                          ; DAAA    0020
      RLC     R0,     2                   ; DAAB    0054
      RLC     R3,     1                   ; DAAC    0053
      RLC     R0,     2                   ; DAAD    0054
      NEGR    R0                          ; DAAE    0020

This is a bit uninspiring, because dis1600 incorrectly thinks that this data is code. Adding a hint to the dis1600 command line, and making a small tweak so that dis1600 will output printable characters like JzIntv does (it is open source after all), things look better. The command...
  > dis1600 -f -d0xda9f-0xdb86 wcb.bin wcb.asm
  SEGMENT ofs 0000  len 2000  addr 5000  FLAGS: R---P
  SEGMENT ofs 2000  len 1000  addr D000  FLAGS: R---P
...now gives the following around our area of interest. Notice the mnemonics printed at the right of each line:
      PULR    R4                              ; DA9C    02B4  [.] 
      PULR    R1                              ; DA9D    02B1  [.] 
      PULR    R7                              ; DA9E    02B7  [.] 

      DECLE   $0050,  $004F,  $0050,  $0020   ; DA9F    0050 004F 0050 0020  [POP ] 
      DECLE   $0050,  $0055,  $0053,  $0048   ; DAA3    0050 0055 0053 0048  [PUSH] 
      DECLE   $0043,  $004C,  $0052,  $0020   ; DAA7    0043 004C 0052 0020  [CLR ] 
      DECLE   $0054,  $0053,  $0054,  $0020   ; DAAB    0054 0053 0054 0020  [TST ] 

  ... skip a few ...

      DECLE   $004A,  $0044,  $0020,  $0020   ; DB77    004A 0044 0020 0020  [JD  ] 
      DECLE   $004A,  $0053,  $0052,  $0020   ; DB7B    004A 0053 0052 0020  [JSR ] 
      DECLE   $004A,  $0053,  $0052,  $0045   ; DB7F    004A 0053 0052 0045  [JSRE] 
      DECLE   $004A,  $0053,  $0052,  $0044   ; DB83    004A 0053 0052 0044  [JSRD] 

  L_DB87:
  
      PSHR    R5                              ; DB87    0275  [.] 
      MOVR    R1,     R0                      ; DB88    0088  [.] 

The commands surrounding this block of data are also interesting. Just before our block is the following command, which is an assembly language RETURN command at the end of a subroutine:
      PULR    R7                              ; DA9E    02B7  [.] 
And the command that immediately follows it is normally interpreted as BEGIN, marking the start of the next subroutine:
      PSHR    R5                              ; DB87    0275  [.] 

So, our mnemonics are data, plonked in between two subroutines. Now, programmers like to keep code and data close together if they can, typically with data following related code. It makes it easy to find things. Therefore, if we apply a bit of location proximity, let's have a look at the code immediately preceding the mnemonics - again, don't worry if the comments at the right of each line don't make sense, there is a high-level explanation after the code:
  ; ------------------------------------------------------------------------------
  ; Write a 4 character mnemonic for the "opcode" number passed in R2 to the 
  ; screen location $27d (character 5 of line 6 of the screen)
  ; Inputs:   R2 = "opcode" to be written
  ; Outputs:  None

  L_DA82:
      PSHR    R5                  ; store return address (BEGIN)
      PSHR    R1                  ; store R1
      PSHR    R4                  ; store R4
      PSHR    R0                  ; store R0
      MVII    #$0004, R0          ; load 4 to R0, this is a character counter
      SLL     R2,     2           ; multiply R2 by 4
      MOVR    R2,     R5          ; R5 = R2
      SDBD                        ; add the address of the mnemonic table to R5
      ADDI    #$DA9F, R5          ;   suggests R5 is a lookup into the table
      MVII    #$027D, R4          ; load the value $27d to R4.  This a screen
                                  ;   location if interpreted as an address.
  L_DA90:                         ;
      MVI@    R5,     R1          ; use R5 to load R1 with a mnemonic character
      SUBI    #$0020, R1          ; convert the ASCII value to a GROM character
      SLL     R1,     2           ;   by subtracting 32 and multiplying by 8
      SLL     R1,     1           ; 
      ADDI    #$0007, R1          ; mix in colour 7 (white)
      MVO@    R1,     R4          ; write the character to the screen (R4)
      DECR    R0                  ; decrement the character counter
      BNEQ    L_DA90              ; if character count != 0, goto L_DA90
                                  ;
      PULR    R0                  ; restore R0
      PULR    R4                  ; restore R4
      PULR    R1                  ; restore R1
      PULR    R7                  ; restore return address (RETURN)

This code is a small subroutine that writes an entry from the table of mnemonics to a particular location on the screen. The value in CPU register R2 at the point the routine is called defines the number of the mnemonic we want printed. The location on screen ($027D = row 6, column 5) and the colour of the text ($0007 = white) are hardcoded within the subroutine. We can see this function in operation by booting WCB in JzIntv in debug mode as before, and then running the following commands:
   0000 0000 0000 0000 0000 0000 0000 1000 --------  JSRD R5,$1026
  > r 
Once the standard title screen appears...


...if we hit F4 and then key in the following instructions, we can trigger the mnemonic printing routine above:
  Starting jzIntv...
   5F56 0000 0000 01FF 02AE 0001 02F8 1686 ---ZI-i-  SLL  R0,2
  > b 1014
  Set breakpoint at $1014
  > r
  Hit breakpoint at $1014
   0077 0148 0000 01FF 0102 1014 0300 1014 --O-I-i-  PULR R5
  > g 2 0
   0077 0148 0000 01FF 0102 1014 0300 1014 --O-I-i-  PULR R5
  > g 7 da82
   0077 0148 0000 01FF 0102 1014 0300 DA82 --O-I-i-  PSHR R5
  > n 1014
  Unset breakpoint at $1014
  > r
This little set of instructions waits for the next video frame, then sets up a call to the mnemonic print routine by setting R2 = 0 (the POP mnemonic) and forcing the program counter (R7) to the start of the routine at $da82, removes the breakpoint and starts everything running again. The result is that we are now playing POP -STAR rather than ALL-STAR baseball:


It's a small step, but it is progress. And so the process of reverse engineering starts...

Comments