Skip to content

Latest commit

 

History

History

monster_puroresu

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

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