Switch branches/tags
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
..
Failed to load latest commit information.
README.md
labels.json
monster.json

README.md

Etripator Walkthrough

Disassembling Monster Puroresu/Monster Pro Wrestling

Initial setup

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

IRQ vectors extraction

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

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

irq_1

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.

$e4b5 (unknown2)

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.

$e0ad

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.

$a0b6

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.

$e0cc (unknown1)

	.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.

$e4ca

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 

$fbc2

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.

$e138 (unknown0)

	.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.

More to come later...