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---POpening 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 > rOnce 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 > rThis 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
Post a Comment