We will start with nothing but a good old terminal, the etripator binary and the ROM (I don't want to know how you got it) of Monster Puroresu. So basically your working directory may look like this:
➜ ll
total 596
drwxr-xr-x 2 blockos blockos 4096 Oct 10 14:00 ./
drwxr-xr-x 7 blockos blockos 4096 Oct 10 13:59 ../
-rwxr-xr-x 1 blockos blockos 74187 Oct 6 19:33 etripator*
-rw-r--r-- 1 blockos blockos 524288 Oct 10 14:00 monster_puroresu.pce
As we are starting from scratch, the first thing to do is to a perform an automatic IRQ vectors extraction. This is done by calling etripator with -i (or --irq-detect).
➜ ./etripator -i monster_puroresu.pce
[Info] (...) irq_2 found at fff0
[Info] (...) irq_1 found at e058
[Info] (...) irq_timer found at fff0
[Info] (...) irq_nmi found at fff0
[Info] (...) irq_reset found at e000
[Info] (...) irq_1:
[Info] (...) e05f short jump to e068 (00)
[Info] (...) e065 short jump to e07d (00)
[Info] (...) irq_2:
[Info] (...) irq_nmi:
[Info] (...) irq_reset:
[Info] (...) e02f short jump to e029 (00)
[Info] (...) e040 short jump to e033 (00)
[Info] (...) e046 long jump to e138 (00)
[Info] (...) e049 long jump to e0cc (00)
[Info] (...) e051 long jump to e4b5 (00)
[Info] (...) e055 long jump to e055 (00)
[Info] (...) e05f short jump to e068 (00)
[Info] (...) e065 short jump to e07d (00)
[Info] (...) irq_timer:
etripator first outputs the logical and physical address of the 5 PC Engine IRQ vectors. A code section is automatically created for IRQ vectors. Each section will be output in a different file. Namely:
- irq_1.asm
- irq_2.asm
- irq_timer.asm
- irq_nmi.asm
- irq_reset.asm
Then comes a summary of the disassembly of each section. Each time a branch or jump instruction, the address where the jump occurs and the destination is output.
From this output we see that irq_2, irq_timer and irq_nmi all points to the same address. The last 2 elements of the jump list of irq_reset matches the ones from irq_1. This means that both interrupts overlaps. It usually happens when there is no return instruction. There could be an infinite loop before irq_1 or some jumps. When automatic IRQ vectors extraction or no size is specified in the configuration file, the disassembly of a code section stops when RTS or RTI instruction is found.
A new configuration file will be created in order to overcome those issues.
{
"irq_reset": {
"filename": "startup.asm",
"type": "code",
"bank": "0",
"org": "e000",
"size": "58",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
},
"irq_dummy": {
"filename": "startup.asm",
"type": "code",
"bank": "0",
"org": "fff0",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
},
"irq_vectors": {
"filename": "startup.asm",
"type": "inc_data",
"bank": "0",
"org": "fff6",
"size": "a",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
}
}
Before running etripator with the new configuration, we should remove the files generated by the IRQ vectors extraction in order to keep a somehow clean workspace.
➜ rm *.asm
➜ ./etripator monster.json monster_puroresu.pce
[Info] (...) irq_reset:
[Info] (...) e02f short jump to e029 (00)
[Info] (...) e040 short jump to e033 (00)
[Info] (...) e046 long jump to e138 (00)
[Info] (...) e049 long jump to e0cc (00)
[Info] (...) e051 long jump to e4b5 (00)
[Info] (...) e055 long jump to e055 (00)
[Info] (...) e05f short jump to e068 (00)
[Info] (...) e065 short jump to e07d (00)
irq_dummy:
Let's take a look at startup.asm.
.code
.bank 0
.org $e000
irq_reset:
nop
sei
csh
cld
lda #$ff
tam #$00
lda #$f8
tam #$01
lda #$04
tam #$02
lda #$05
tam #$03
lda #$01
tam #$04
lda #$02
tam #$05
lda #$03
tam #$06
lda #$00
tam #$07
ldx #$ff
txs
clx
cla
le029_00:
sta <$00, X
sta $2100, X
inx
bne le029_00
ldy #$07
le033_00:
dey
sty $0800
stz $0804
stz $0805
stz $0807
bne le033_00
ldx #$c4
ldy #$be
jsr le138_00
jsr le0cc_00
lda #$05
sta $1402
jsr le4b5_00
cli
le055_00:
jmp le055_00
irq_1:
lda $0000
sta <$10
and #$20
bne le068_00
lda <$10
and #$04
bne le07d_00
rti
.code
.bank 0
.org $fff0
irq_dummy:
rti
.data
.bank 0
.org $fff6
irq_vectors:
.db $f0,$ff,$58,$e0,$f0,$ff,$f0,$ff
.db $00,$e0
Everything seems OK. Except that irq_1 and irq_reset are fused together.
irq_reset is the first piece of code executed when you power up your console.
nop
sei
The NOP instruction does nothing. You can see it as a 2 cycles wait. SEI disables interruptions.
csh
cld
The CPU is switched high speed (CSH) and the decimal flag is cleared (CLD).
lda #$ff
tam #$00
lda #$f8
Maps the I/O page to mpr #0 and the RAM page to mpr #1.
lda #$04
tam #$02
lda #$05
tam #$03
lda #$01
tam #$04
lda #$02
tam #$05
lda #$03
tam #$06
lda #$00
tam #$07
Maps some ROM banks to mpr #2 to #7.
ldx #$ff
txs
Reset stack pointer. The stack index is decremented each time a byte is pushed onto the stack.
clx
cla
le029_00:
sta <$00, X
sta $2100, X
inx
bne le029_00
Clears zero-page and stack memory. le029_00 can be renamed to .clear_zp_sp. This is done by creating what is called a label definition file. Here we will call it labels.json.
".clear_zp_sp": { "logical": "e029", "page": "00" }
Back on irq_reset.
ldy #$07
le033_00:
dey
sty $0800
stz $0804
stz $0805
stz $0807
bne le033_00
$0800 is the PSG channel select register. $0804 is the PSG channel control register, $0805 channel balance and $0807 noise control register. Basically this loops will mute every PSG channels. A new entry may be added to the label definition file. For example, le033_00 will be renamed .mute_channels.
".mute_channels": { "logical": "e033", "page": "00" }
We are nearly at the end of irq_reset.
ldx #$c4
ldy #$be
jsr le138_00
jsr le0cc_00
lda #$05
sta $1402
jsr le4b5_00
cli
le055_00:
jmp le055_00
lda $0000
sta <$10
and #$20
bne le068_00
lda <$10
and #$04
bne le07d_00
rti
There is an infinite loop at le055_00. Remember that irq_1 starts at $e058 which is just after JMP le055_00. This means that we can calculte the size of the irq_reset section, which is 88 (#$58 in hexadecimal). The section for le138_00, le0cc_00 and le4b5_00 routines can also be added.
{
"irq_reset": {
"filename": "startup.asm",
"type": "code",
"bank": "0",
"org": "e000",
"size": "58",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
},
"irq_1": {
"filename": "startup.asm",
"type": "code",
"bank": "0",
"org": "e058",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
},
"unknown0": {
"filename": "startup.asm",
"type": "code",
"bank": "0",
"org": "e138",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
},
"unknown1": {
"filename": "startup.asm",
"type": "code",
"bank": "0",
"org": "e0cc",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
},
"unknown2": {
"filename": "startup.asm",
"type": "code",
"bank": "0",
"org": "e4b5",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
}
}
The label definition files is updated as follow.
{
"clear_zp_sp": { "logical": "e029", "page": "00" },
".mute_channels": { "logical": "e033", "page": "00" },
".loop": { "logical": "e055", "page": "00" },
}
Run etripator with the -l (or --labels) options.
➜ ./etripator -l labels.json monster.json monster_puroresu.pce
[Info] (...) irq_reset:
[Info] (...) e02f short jump to e029 (00)
[Info] (...) e040 short jump to e033 (00)
[Info] (...) e046 long jump to e138 (00)
[Info] (...) e049 long jump to e0cc (00)
[Info] (...) e051 long jump to e4b5 (00)
[Info] (...) e055 long jump to e055 (00)
[Info] (...) irq_1:
[Info] (...) e05f short jump to e068 (00)
[Info] (...) e065 short jump to e07d (00)
[Info] (...) unknown0:
[Info] (...) e142 short jump to e146 (00)
[Info] (...) e159 short jump to e15d (00)
[Info] (...) unknown1:
[Info] (...) unknown2:
[Info] (...) irq_dummy:
Opens startup.asm. Labels in irq_reset may have been replaced by the ones defined in the label definition file.
.code
.bank 0
.org $e000
irq_reset:
nop
sei
csh
cld
lda #$ff
tam #$00
lda #$f8
tam #$01
lda #$04
tam #$02
lda #$05
tam #$03
lda #$01
tam #$04
lda #$02
tam #$05
lda #$03
tam #$06
lda #$00
tam #$07
ldx #$ff
txs
clx
cla
clear_zp_sp:
sta <$00, X
sta $2100, X
inx
bne clear_zp_sp
ldy #$07
.mute_channels:
dey
sty $0800
stz $0804
stz $0805
stz $0807
bne .mute_channels
ldx #$c4
ldy #$be
jsr unknown0
jsr unknown1
lda #$05
sta $1402
jsr unknown2
cli
.loop:
jmp .loop
The current disassembly of irq_1 is :
.code
.bank 0
.org $e058
irq_1:
lda $0000
sta <$10
and #$20
bne le068_01
lda <$10
and #$04
bne le07d_01
rti
The automatic disassembly stops at the RTI instruction. Nevertheless, there are 2 conditional branches. One to $e068 and another one to $e07d. Let's extend irq_1 section to #$30.
"irq_1": {
"filename": "startup.asm",
"type": "code",
"bank": "0",
"org": "e058",
"size": "30",
"mpr": ["ff", "f8", 0, 0, 0, 0, 0, 0 ]
}
Unfortunately that is too low.
irq_1:
lda $0000
sta <$10
and #$20
bne le068_01
lda <$10
and #$04
bne le07d_01
rti
le068_01:
jsr unknown2
jsr le0ad_01
jsr la0b6_01
jsr unknown1
jsr le4ca_01
jsr lfbc2_01
inc <$08
rti
le07d_01:
pha
lda #$07
sta $0000
lda #$00
sta $0002
Expand it to #$60. We see that
le07d_01:
pha
lda #$07
sta $0000
lda #$00
sta $0002
lda #$01
sta $0003
lda #$08
sta $0000
lda #$80
sta $0002
lda #$01
sta $0003
lda #$05
sta $0000
lda #$8c
sta $0002
lda <$41
sta $0003
pla
rti
le0ad_01:
lda #$07
sta $0000
lda <$04
sta $0002
lda <$05
An RTI is caught but the size of the section is a little too big. Hopefully, the $e0ad subroutine starts just after. The section size is then #$e0ad-#$e058 which is equals to #$55.
irq_1:
lda $0000
sta <$10
and #$20
bne le068_01
lda <$10
and #$04
bne le07d_01
rti
le068_01:
jsr unknown2
jsr le0ad_01
jsr la0b6_01
jsr unknown1
jsr le4ca_01
jsr lfbc2_01
inc <$08
rti
le07d_01:
pha
lda #$07
sta $0000
lda #$00
sta $0002
lda #$01
sta $0003
lda #$08
sta $0000
lda #$80
sta $0002
lda #$01
sta $0003
lda #$05
sta $0000
lda #$8c
sta $0002
lda <$41
sta $0003
pla
rti
irq_1 starts by comparing if the 6th bit of the VDC status register. This bit is set when a vertical blank interrupt occurs. le068_01 can be renamed .vblank. Next, the 3th bit is tested which is set when the raster compare (of horizontal blank) interrupt occurs. Hence le07d_01 can be renamed .hblank.
.vblank successively jumps to 6 subroutines. 2 of them were already encountered (they are named unknown1 and unknown2 for the moment). 3 subroutines are located in the first bank (le0ad_01, le4ca_01 and lfbc2_01). In order to indentify the ROM bank where la0b6_b1 is, the value of the MPR #5 (#$a0b6 >> 13 = 5). The ROM bank #0 subroutines will be examined first in order to see if the mpr #5 is explicitely mapped. If that is not the case, the value set in the irq_reset will be used (#$02).
We will look at those subroutines in order.
unknown2:
lda #$05
sta $0
lda <$40
sta $0002
lda <$41
sta $0003
rts
This routine simply the VDC Control register using the values stored at $2040 and $2041. It can safely be named set_vdc_ctrl.
le0ad_01:
lda #$07
sta $0000
lda <$04
sta $0002
lda <$05
sta $0003
lda #$08
sta $0000
lda <$06
sta $0002
lda <$07
sta $0003
rts
$e0ad sets VDC scroll registers using $2004, $2005 for X, and $2006, $2007 for Y. It's safe to name it update_scroll.
In order to find where this routine, we must find the value of the mpr #5. From the irq_reset code, we have:
lda #$02
tam #$05
In order not to bloat startup.asm, the routine from bank #2 will be disassembled in a separate file (bank2.asm).
"unknown8": {
"filename": "bank2.asm",
"type": "code",
"bank": "2",
"org": "a0b6",
"mpr": ["ff", "f8", 0, 0, 0, 2, 0, 0 ]
}
Automatic extraction does not work very well here as the routine jumps to a location stored in a table. In fact there are 2 tables. One starting at $a0dd and the other at $a1dd.
.code
.bank 2
.org $a0b6
unknown8:
lda <$09
cmp #$80
bcs la0cb_10
asl A
tax
lda $a0dd, X
sta <$28
lda $a0de, X
sta <$29
jmp [$2028]
la0cb_10:
sec
sbc #$80
asl A
tax
lda $a1dd, X
sta <$28
lda $a1de, X
sta <$29
jmp [$2028]
We will keep it as is for the moment.
.code
.bank 0
.org $e0cc
unknown1:
lda #$00
sta $0000
lda #$00
sta $0002
lda #$7f
sta $0003
lda #$02
sta $0000
tia $2200, $0002, $0200
lda #$13
sta $10
lda #$00
sta $0002
lda #$7f
sta $0003
rts
512 bytes from RAM at $2200 are copied to the VDC RAM at $7f00. Then the VRAM-SATB DMA is started. It's safe to rename unknown1 update_satb.
The almighty joypad read routine, with multi-tap support and the run+select reboot combo.
.code
.bank 0
.org $e4ca
read_joy:
cly
lda #$01
sta $1000
lda #$03
sta $1000
.next_joypad:
lda #$01
sta $1000
pha
pla
nop
lda $2030, y
sta $2035, y
lda $1000
asl a
asl a
asl a
asl a
sta $2030, y
stz $1000
pha
pla
nop
lda $1000
and #$0f
ora $2030, y
eor #$ff
sta $2030, y
eor $2035, y
and $2030, y
sta $203a, y
iny
cpy #$05
bcc .next_joypad
lda <$3a
cmp #$04
bne .read_joy_end
lda <$30
cmp #$0c
bne .read_joy_end
jmp soft_reset
.read_joy_end:
rts
This routine can be split in 4 parts. First, mpr 2, 3 and 4 are mapped to hucard pages $07, $08 and $09. Then the same routine $fcac is called repeatedly with different values of X ($00, $02, $04, $06, $08, $0a). The same is done with $fd36 ($00, $10). Finally, the mpr 4 is set to hucard page 01.
.code
.bank 0
.org $fbc2
unknown5:
lda #$07
tam #$02
lda #$08
tam #$03
lda #$09
tam #$04
ldx #$00
jsr $fcac
ldx #$02
jsr $fcac
ldx #$04
jsr $fcac
ldx #$06
jsr $fcac
ldx #$08
jsr $fcac
ldx #$0a
jsr $fcac
ldx #$00
jsr $fd36
ldx #$10
jsr $fd36
lda #$01
tam #$04
rts
A quick look at $fcac shows that $fbc2 is in fact the sound fx/music routine and $fcac the PSG channel update. Let's switch back to irq_reset. We will get back to it afterwards. The only remaining unknown routine there is $e138.
.code
.bank 0
.org $e138
unknown0:
stx <$20
sty <$21
ldy #$00
lda [$20], y
inc <$20
bne le146_02
inc <$21
le146_02:
asl a
tax
lda $e170, x
sta <$22
lda $e171, x
sta <$23
jmp [$2022]
lda [$20], y
inc <$20
bne le15d_02
inc <$21
le15d_02:
rts
Once again the automatic routine extraction went too far. The code right after JMP [$2022] is never reached.