Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / ASM

The Main Architecture of MAME.NET

4.96/5 (52 votes)
19 Jun 2024CPOL13 min read 127.4K   4.5K  
C# arcade emulator, ROM hacking
This article looks at: How Main.NET loads ROMs, hacking ROMs with the M68000 debugger and Z80 debugger, and using a game example of how to do that.

Image 1

MAME.NET user interface

Introduction

MAME (Multiple Arcade Machine Emulator) is a free and open source emulator designed to recreate the hardware of arcade game system in software on modern personal computers and other platforms. MAME.NET is a C# based arcade emulator, and it maintains the same architecture of MAME. By using C# and the powerful integrated development environment -- Microsoft Visual Studio, there is no macro and you can debug the supported arcade game anywhere. There are some classic boards supported by now: M72, M92, Taito, Taito B, Capcom, CPS-1, CPS-1(Qsound), CPS2, Data East, Tehkan, Konami 68000, Neo Geo, Namco System 1, IGS011, PGM(PolyGame Master).

MAME.NET runs at the following steps: load the ROMs, initialize the machine, soft reset the machine, and loop cpuexec_timeslice operation. The cpuexec_timeslice operation means sequentially execute every CPU for a time slice, and execute timer callbacks. Timer callbacks contain: video update, soft reset, CPU interrupt, sound update, watchdog reset and other interrupts. By these steps, MAME.NET emulates the arcade board successfully. MAME.NET has more functions: save and load state, record and replay input, cheat, cheat search, IPS (patch main ROM), board debugger, CPU debugger.

Load the ROMs

As an emulator, MAME.NET loads ROMs first.

There are two CPUs working in Irem M72 board: a NEC V30 CPU and a Zilog Z80 CPU. There are 20-bits address and 16-bits data in V30. There are 16-bits address and 8-bits data in Z80. The V30 CPU runs at 8MHz. The program loads the file maincpu.rom as V30 ROM. The Z80 CPU runs at 3579545 Hz. The program loads the file soundcpu.rom as Z80 ROM. There are two sound chips: Yamaha YM2151, DAC chip.

There are two CPUs working in Irem M92 board: a NEC V33 CPU and a NEC V30 CPU. There are 20-bits address and 16-bits data in both CPUs. The V33 CPU runs at 9MHz. The program loads the file maincpu.rom as V33 ROM. The V30 CPU runs at 7159090 Hz. The program loads the file soundcpu.rom as V30 ROM. There are two sound chips: Yamaha YM2151, Irem GA20.

For Taito board, there are various CPU and chip settings.

For Taito B board, the M68000 CPU runs at 12MHz (16Mhz for some games). The program loads the file maincpu.rom as M68000 ROM. The file gfx1.rom contains tile data to display on screen. The Z80 CPU runs at 4MHz. The program loads the file audiocpu.rom as Z80 ROM. There are two sound chips: General Instrument AY8910, Yamaha YM2610. The program loads the file ymsnd.rom and the optional file ymsnddeltat.rom as the Yamaha YM2610 ROM.

For Capcom board, there are various CPU and chip settings.

There are 2 CPUs working in CPS-1, CPS-1(Qsound), CPS2, Neo Geo, PGM boards: a Motorola M68000 CPU and a Zilog Z80 CPU. There are 24-bits address and 16-bits data in M68000. There are 16-bits address and 8-bits data in Z80.

For CPS-1 board, the M68000 CPU runs at 10MHz (12MHz for some games). The program loads the file maincpu.rom as M68000 ROM. Size of maincpu.rom should be no greater than 0x400000 bytes. The file gfx.rom contains tile data to display on screen. The Z80 CPU runs at 3579545 Hz. The program loads the file audiocpu.rom as Z80 ROM. Size of audiocpu.rom should be no greater than 0x18000 bytes. There are two sound chips: Yamaha YM2151, Oki MSM6295. The program loads the file oki.rom as the Oki MSM6295 ROM.

For CPS-1(QSound) board, the M68000 CPU runs at 12MHz. The program loads the file maincpu.rom as M68000 ROM. Size of maincpu.rom should be no greater than 0x400000 bytes. The file gfx.rom contains tile data to display on screen. The Z80 CPU runs at 8MHz with Kabuki encrypted code. The program loads the file audiocpu.rom as Z80 ROM for ReadMemory only, the file audiocpuop.rom as the Z80 ROM for ReadOp only. Size of audiocpu.rom should not be greater than 0x50000 bytes. There is a Q-Sound chip. The program loads the file qsound.rom as the Q-Sound chip ROM.

For CPS2 board, the M68000 CPU runs at 11.8MHz. The original M68000 ROM is encrypted. The program loads the file maincpu.rom as M68000 ROM for memory code, the file maincpuop.rom as M68000 ROM for opcode. The file gfx.rom contains tile data to display on screen. The Z80 CPU runs at 8MHz. The program loads the file audiocpu.rom as Z80 ROM. Size of audiocpu.rom should not be greater than 0x50000 bytes. There is a Q-Sound chip. The program loads the file qsound.rom as the Q-Sound chip ROM.

For Data East board, the main M6502 CPU runs at 2MHz and the sound M6502 CPU runs at 1.5MHz. The file gfx1.rom, gfx2.rom contain tile data to display on screen. There are four sound chips: General Instrument AY8910, Yamaha YM2203, Yamaha YM3812, Oki MSM5205.

For Tehkan board, there are various CPU and chip settings.

For Konami 68000 board, there are various CPU and chip settings. There are Konami graphics chips: K052109, K051960, K053245, K054000, K053936 and K053251. There are relevant sound chips: Yamaha YM2151, Konami K007232, Konami K053260, Konami K054539, NEC UPD7759.

For Neo Geo board, the M68000 CPU runs at 12MHz. The program loads the file maincpu.rom as M68000 ROM. The file sprites.rom contains tile data to display on screen. The file fixed.rom is a graphic related ROM. The Z80 CPU runs at 4MHz. The program loads the file audiocpu.rom as Z80 ROM. There are two sound chips: General Instrument AY8910, Yamaha YM2610. The program loads the file ymsnd.rom and the optional file ymsnddeltat.rom as the Yamaha YM2610 ROM.

For PGM board, the M68000 CPU runs at 20MHz. The program loads the file maincpu.rom as M68000 ROM. The files sprcol.rom, sprmask.rom, tiles.rom contain tile data to display on screen. The program dynamically writes the Z80 ROM. There is an ICS2115 sound chip.

There are four CPUs working in Namco System 1 board: three Motorola M6809 CPUs and one Hitachi HD63701 CPU. There are 16-bits address and 8-bits data in the four CPUs.

For Namco System 1, the four CPUs run at 1536000Hz. The Namco Custom 117 MMU provides a 23 bits virtual address for the first and second M6809 CPUs. The program loads the file user1.rom as the two CPU ROM. The files gfx1.rom, gfx2.rom, gfx3.rom contain tile data to display on screen. The program loads the file audiocpu.rom as the third M6809 CPU ROM. The program loads the file voice.rom as the HD63701 CPU ROM. There are three sound chips: Yamaha YM2151, Namco 8-voice stereo chip, DAC chip.

For IGS011 board, the M68000 CPU runs at 7333333Hz. The program loads the file maincpu.rom as M68000 ROM. The file gfx1.rom contains tile data to display on screen. There are two sound chips: Oki MSM6295 and Yamaha YM3812. The program loads the file oki.rom as the Oki MSM6295 ROM.

The ROM format of MAME.NET is the simplest. You can open and disassemble maincpu.rom and audiocpu.rom (only not opcode encrypted) with IDA Pro directly. There is no combination of multiple ROMs, no graphic effects decoding, no various decoding, and no byte swap. So the IPS file (.cht extension, the same as cheat file) is easy to understand. ROM hackers can focus on the real address-value pair and neglect ROM encoding and decoding. You can also disassemble CPU ROMs with the M68000 debugger and Z80 debugger functions in MAME.NET.

Common Usage

Build environment: I only test on Windows 10, Microsoft Visual Studio 2008

Operating environment: Microsoft .NET Framework 3.5 or higher

Hotkey: F3 -- soft reset, F7 – load state, Shift+F7 – save state, F8 – replay input, Shift+F8 – record input (start and stop), 0-9 and A-Z after state related hotkey – handle certain files, F10 – toggle global throttle, P – pause and continue, Shift+P - skip a frame

Control Key

  • 1 -- P1 start
  • 2 -- P2 start
  • 5 -- P1 coin
  • 6 -- P2 coin
  • R -- Service 1
  • T -- Service
  • W -- P1 up
  • S -- P1 down
  • A -- P1 left
  • D -- P1 right
  • J -- P1 button1
  • K -- P1 button 2
  • L -- P1 button 3
  • U -- P1 button 4
  • I -- P1 button 5
  • O -- P1 button 6
  • Up -- P2 up
  • Down -- P2 down
  • Left -- P2 left
  • Right -- P2 right
  • NumPad1 -- P2 button 1
  • NumPad2 -- P2 button 2
  • NumPad3 -- P2 button 3
  • NumPad4 -- P2 button 4
  • NumPad5 -- P2 button 5
  • NumPad6 -- P2 button 6

Mouse supported games: Operation Wolf.

When the ROMs of a game are loaded, the emulator is auto paused. You can apply IPS and dip-switch now and press P to continue. You can get the proper dip-switch value from running MAME.

Occasionally, GDI+ error occurs and a red cross is shown. You can click File-Reset picturebox to handle the error.

You can make the MAME.NET cheat file refer to cheat file of MAME or other emulators. You should make the ^1 operation to the cheat address for some emulators (for example: Winkawaks).

There are two files for record input. The .sta file records the initial state and the .inp file records the input key.

You can combine multiple .inp files to one .inp file. For example, there are 5 record input in the directory "inp\pcktgal\" (0.sta to 4.sta, 0.inp to 4.inp). Remember do not input any control key between two record input. Copy the file "0.sta" to "q.sta" as the initial state. Sequentially concatenate the files "0.inp", "1.inp"… "4.inp" to a file "q.inp". So the "q" record input is the combination of "0", "1"… "4" record input. You can record your best input by the combination of multiple segments easily. Youtube URL: https://www.youtube.com/watch?v=5ve9w-1K1j0

Download pcktgal.mp4

ROM Hacking

You can hack the ROMs effectively with the M68000 debugger and Z80 debugger functions of MAME.NET. For a beginner, M68000 ROM hacking contains the following steps: determine the key mainram address and value, debug M68000 CPU "backward" until get the certain mainrom address and value, patch the object mainrom. Now I show one example.

1. Warriors of Fate, Kassar Sits Continuously

Image 2

Kassar sits continuously

Record 1.inp to research, 2.inp to show result.

There is prepare knowledge for game wof following. Every sprite has a base address in mainram, and the following 0xE0 bytes describe the sprite status. The offsets 0x28,0x29,0x2A,0x2B describe the subprogram of the sprite. The offsets 0x82,0x83 describe the blood of the sprite. For the 1.inp scene, the base address of Kassar is 0xBE1C, and Basl is 0xCF9C. After examining the change of mainram[0xBE1C+0x2B], I find that if I disable the writing 0x08 to mainram[0xBE1C+0x2B] (maintain the origin value 0x06) when the caught enemy is live, Kassar sits continuously.

Image 3

Modify source

To hack the rom, I find the object totalExecutedCycles of M68000 is 0x262318F0 and dissemble the code nearby.

ASM
26231866 0016B8: 6706                  beq      $16C0
26231870 0016C0: 0C69 0000 0002        cmpi.w   $0, ($2,A1)
26231880 0016C6: 6612                  bne      $16DA
2623188A 0016DA: 9169 0082             sub.w    D0, ($82,A1)
2623189A 0016DE: 4A29 00C7             tst.b    ($C7,A1)
262318A6 0016E2: 6B1C                  bmi      $1700
262318B0 001700: 4E75                  rts      
262318C0 01B706: 317C 038E 0048        move.w   $38E, ($48,A0)
262318D0 01B70C: 317C FFC8 004A        move.w   $FFC8, ($4A,A0)
262318E0 01B712: 3B7C 0016 FF9E        move.w   $16, ($FF9E,A5)

262318F0 01B718: 117C 0008 002B        move.b   $8, ($2B,A0)
26231900 01B71E: 4EF8 5F7A             jmp      ($5F7A)

2623190A 005F7A: 4A2D 0190             tst.b    ($190,A5)

Here A0=FFFFBE1C(base address of Kassar), A1=FFFFCF9C(base address of kaught enemy). Now I'll insert new program in the offset 0x01B718 of maincpu.rom.

ASM
0x1B718: jmp address1
address1: tst.b ($82,A1)        //examine the blood of caught enemy
bmi address2
jmp      ($5F7A)                //if blood is non-negative, disable the writing 
                                //of mainram[0xCF9C+0x2B] and jump directly

address2: move.b   $8, ($2B,A0) //if blood is negative, write mainram[0xCF9C+0x2B] 
                                //to 0x08 as normal
jmp      ($5F7A)

There is a big block 0x00 data in maincpu.rom from 0xE6840 to 0xE6E40. So I assign address1 to 0xE6880 and address2 to 0xE6890.

ASM
01B718:	4EF9 000E 6880        jmp ($E6880)
0E6880: 4A29 0082             tst.b ($82,A1)
0E6884: 6B0A                  bmi ($E6890)
0E6886:	4EF9 0000 5F7A        jmp      ($5F7A)

0E6890: 117C 0008 002B        move.b   $8, ($2B,A0)
0E6896: 4EF9 0000 5F7A        jmp      ($5F7A)

So I get the following cheat code:

[Kassar sits continuously]
ON=1B718,4E;1B719,F9;1B71A,00;1B71B,0E;1B71C,68;1B71D,80;E6880,4A;
E6881,29;E6882,00;E6883,82;E6884,6B;E6885,0A;E6886,4E;E6887,F9;
E6888,00;E6889,00;E688A,5F;E688B,7A;E6890,11;E6891,7C;E6892,00;
E6893,08;E6894,00;E6895,2B;E6896,4E;E6897,F9;E6898,00;E6899,00;E689A,5F;E689B,7A

The hack effect of 2.inp:

2. Samurai Shodown II, Select the Hidden Characters

Referring to samsho2.xml cheat file, I get the following code:

XML
<cheat desc="Select Character PL1"> <comment>Free player selection.
 Activate between rounds or just after selection.</comment>
              <parameter>
                       <item value="00">Off</item>
                       <item value="01">Haohmaru</item>
                       <item value="02">Nakoruru</item>
                       <item value="03">Hanzo</item>
                       <item value="04">Galford</item>
                       <item value="05">Wan-Fu</item>
                       <item value="06">Ukyo</item>
                       <item value="07">Kyoshiro</item>
                       <item value="08">Gen-An</item>
                       <item value="09">Earthquake</item>
                       <item value="10">Jubei</item>
                       <item value="11">Charlotte</item>
                       <item value="12">Genjuro</item>
                       <item value="13">Cham Cham</item>
                       <item value="14">Sieger</item>
                       <item value="15">Nicotine</item>
                       <item value="16">Mizuki</item>
                       <item value="17">Kuroko</item>
              </parameter>
              <script state="run">
                       <action condition="(param==01)">main.pb@100D0B=00</action>
                       <action condition="(param==02)">main.pb@100D0B=01</action>
                       <action condition="(param==03)">main.pb@100D0B=02</action>
                       <action condition="(param==04)">main.pb@100D0B=03</action>
                       <action condition="(param==05)">main.pb@100D0B=04</action>
                       <action condition="(param==06)">main.pb@100D0B=05</action>
                       <action condition="(param==07)">main.pb@100D0B=06</action>
                       <action condition="(param==08)">main.pb@100D0B=07</action>
                       <action condition="(param==09)">main.pb@100D0B=08</action>
                       <action condition="(param==10)">main.pb@100D0B=09</action>
                       <action condition="(param==11)">main.pb@100D0B=0B</action>
                       <action condition="(param==12)">main.pb@100D0B=0C</action>
                       <action condition="(param==13)">main.pb@100D0B=0D</action>
                       <action condition="(param==14)">main.pb@100D0B=0E</action>
                       <action condition="(param==15)">main.pb@100D0B=0F</action>
                       <action condition="(param==16)">main.pb@100D0B=10</action>
                       <action condition="(param==17)">main.pb@100D0B=11</action>
              </script>
    </cheat>

I abbreviate Haohmaru as c00, Nakoruru as c01… The characters are arranged in selection scene as follows:

Image 4

Selection scene
character id:
03 0e 06 00 0c 02 0b 04
 07 01 09 05 0d 08 0f

There are two hidden characters: c10 and c11. I'll hack the ROM to show them.

Replay 1 record input. Find when the M68000 code writes the mainram[0xD0B] to 0x06, and then debug backward until you find out the object mainrom position.

ASM
6E3D35F 01474A: 1030 0000             move.b   (A0,D0.w), D0 D0=00100004->00100006 
                                                          A0=000142CC mainrom[0x142D0]=0x06
6E3D36D 01474E: 0240 00FF             andi.w   $FF, D0
6E3D375 014752: 1540 000B             move.b   D0, ($B,A2) D0=00100006 A2=00100D00 
                                                           mainram[0xD0B]=0x06

I examine the maincpu.rom file at the offset 0x142CC, and find the following code:

Image 5

Maincpu ROM

The marked code is about character id. The base address of character id is 0x000142CC.

I will extend the selection scene to:

character id:
 03 0e 06 00 0c 02 0b 04
11 07 01 09 05 0d 08 0f 10

I find the following code to read the address 0x000142CC.

ASM
657F8E0 0141E4: 49F9 0001 42CC        lea      $142CC.l, A4
657F8EC 0141EA: 7A00                  moveq    $0, D5
657F8F0 0141EC: 7C0E                  moveq    $E, D6

Replace data in maincpu ROM from 0x000142CC to 0x000142CB. Replace $E to $10 because two characters will be added.

I debug backward from the PPC 01474A:

ASM
6590B24 01471A: 720F                  moveq    $F, D1
6590B28 01471C: 0240 000F             andi.w   $F, D0 D0=00105200->00100000
6590B30 014720: 41F9 0001 478A        lea      $1478A.l, A0
6590B3C 014726: 1030 0000             move.b   (A0,D0.w), D0 D0=00100000 A0=0001478A
6590B4A 01472A: 1400                  move.b   D0, D2 D2=53540000
6590B4E 01472C: 4880                  ext.w    D0 
6590B52 01472E: D051                  add.w    (A1), D0 D0=00100000->00100006 
                                                     A1=00100C00 mainram[0xC00]=0x0006
6590B5A 014730: 6B00 0004             bmi      $14736
6590B66 014734: 4441                  neg.w    D1 D1=0000000F->0000FFF1
6590B6A 014736: 0C40 000E             cmpi.w   $E, D0 D0=00100006
6590B72 01473A: 6300 0004             bls      $14740
6590B7C 014740: 3280                  move.w   D0, (A1) A1=00100C00 mainram[0xC00]=0006
6590B84 014742: 41F9 0001 42CC        lea      $142CC.l, A0
6590B90 014748: 3011                  move.w   (A1), D0 D0=00100006
6590B98 01474A: 1030 0000             move.b   (A0,D0.w), D0 D0=00100006->00100000 
                                                          A0=000142CC mainrom[0x142D2]=0x00
6590BA6 01474E: 0240 00FF             andi.w   $FF, D0
6590BAE 014752: 1540 000B             move.b   D0, ($B,A2) D0=00100000 A2=00100D00 
                                               mainram[0xD0B]=0x00

Replace data $F to $11, $E to $10 because two characters will be added.

So I get the following cheat code:

[17 select 1]
ON=1387D,CB;141E9,CB;141ED,10;142CB,11;142DB,10;1471B,11;14739,10;14747,CB

After applying the above cheat code, I get the following selection scene:

Image 6

Selection scene

The function is right. The remaining thing is to add the head images for c10 and c11.

Using the neogeo debug function, I find the first character(c03) image head code:

063 0C80 B102 CB06 0FFF

Image 7

Neogeo debugger
sprite_number=0x063, neogeo_videoram[0x8463]=0x0C80, neogeo_videoram[0x8263]=0xB102, 
neogeo_videoram[0x18C0]=0xCB06, neogeo_videoram[0x8063]=0x0FFF

By replaying 1 record input several times, I debug backward until I find out how the mainrom draws the head image of c03.

ASM
657FFE0 014206: 7000                  moveq    $0, D0 D0=0000FFFF->00000000
657FFE4 014208: 101C                  move.b   (A4)+, D0 D0=00000000->00000003 
                                                      A4=000142CC mainrom[0x142CC]=0x03
657FFEC 01420A: E748                  lsl.w    3, D0 D0=00000003->00000018 A4=000142CD
657FFF8 01420C: 337C 0013 0064        move.w   $13, ($64,A1) D0=00000018 
                                                             A1=00105DE0 mainram[0x5E44]=0x0013
6580008 014212: 3373 0000 0066        move.w   (A3,D0.w), ($66,A1) D0=00000018 
                                      A1=00105DE0 A3=0001423C 
                                      mainram[0x5E46]=mainrom[0x14254]=0x0070
658001E 014218: 3373 0002 004E        move.w   (A3,D0.w,$2), ($4E,A1) 
                                                mainram[0x5E2E]=mainrom[0x14256]=0x0029
6580034 01421E: 3373 0004 0050        move.w   (A3,D0.w,$4), ($50,A1) 
                                                mainram[0x5E30]=mainrom[0x14258]=0x00AE
…
6587C92 00344E: 3D6E 004E 005C        move.w   ($4E,A6), ($5C,A6) A6=00105DE0 
                                                mainram[0x5E3C]=mainram[0x5E2E]=0x0029
6587CA6 003454: 3D6E 0050 005E        move.w   ($50,A6), ($5E,A6) A6=00105DE0 
                                                mainram[0x5E3E]=mainram[0x5E30]=0x00AE
…
6587CFC 003470: 202E 0064             move.l   ($64,A6), 
                                                D0 D0=mainram[0x5E44]=0x00130070 A6=00105DE0
…
6587D98 0036AC: 302E 0064             move.w   ($64,A6), D0 D0=00130070->00130013 
                                                A6=00105DE0 mainram[0x5E44]=0x0013
6587DA4 0036B0: D040                  add.w    D0, D0 D0=00130013->00130026
6587DA8 0036B2: D040                  add.w    D0, D0 D0=00130026->0013004C
6587DAC 0036B4: 43F9 0022 0280        lea      $220280.l, A1
6587DB8 0036BA: 2271 0000             movea.l  (A1,D0.w), A1 D0=0013004C 
                                               A1=mainrom[0x1202CC]=002AC948
6587DCA 0036BE: 302E 0066             move.w   ($66,A6), D0 A6=00105DE0 mainram[0x5E46]=0x0070
6587DD6 0036C2: D040                  add.w    D0, D0 D0=00130070->001300E0
6587DDA 0036C4: D040                  add.w    D0, D0 D0=001300E0->001301C0
6587DDE 0036C6: 2D71 0000 006C        move.l   (A1,D0.w), ($6C,A6) D0=001301C0 
                                                A1=002AC948 A6=00105DE0 
                                                mainram[0x5E4C]=mainrom[0x1ACB08]=0x002AE40A
…
6587E0C 0034A0: 206E 006C             movea.l  ($6C,A6), A0 A0=mainram[0x5E4C]=0x002AE40A 
                                                A6=00105DE0
…
6587E36 0034D6: 1C18                  move.b   (A0)+, D6 A0=002AE40A->002AE40B
…
6587E4C 0034DC: 1A18                  move.b   (A0)+, D5 A0=002AE40B->002AE40C
…
6587E64 0034E6: 3D58 0074             move.w   (A0)+, ($74,A6) A0=002AE40C->002AE40E
…
6587EAE 0036CE: 5448                  addq.w   2, A0 A0=0002AE40E->002AE410
…
6588130 0035F6: 3030 3000             move.w   (A0,D3.w), D0 D0=00101040->00103958 
                                               D3=20200000 A0=002AE410 mainrom[0x1AE410]=0x3958
658813E 0035FA: 0280 0000 7FFF        andi.l   $7FFF, D0  D0=0010395A->0000395A
658814C 003600: 3740 0018             move.w   D0, ($18,A3) D0=00003958 A3=00101040 
                                                    mainram[0x1058]=0x3958
…
65881E6 00362C: 376E 005C 0010        move.w   ($5C,A6), ($10,A3) A3=00101040 
                                                A6=00105DE0 
                                                mainram[0x1050]=mainram[0x5E3C]=0x0029
65881FA 003632: 376E 005E 0012        move.w   ($5E,A6), ($12,A3) A3=00101040 
                                                A6=00105DE0 mainram[0x105E]=
                                                mainram[0x5E3E]=0x00AE
…
659545E 003318: 382E 0018             move.w   ($18,A6), D4 D4=FFFF0003->FFFF3958 
                                                         A6=00101040 mainram[0x1058]=0x3958
659546A 00331C: 4EB9 0000 4120        jsr      $4120.l
659547E 004120: 0284 0000 FFFF        andi.l   $FFFF, D4 D4=FFFF3958->00003958
659548C 004126: D884                  add.l    D4, D4 D4=00003958->000072B0
6595494 004128: D884                  add.l    D4, D4 D4=000072B0->0000E560
659549C 00412A: 43F9 0007 2000        lea      $72000.l, A1 A1=00072000
65954A8 004130: 2AB1 4800             move.l   (A1,D4.l), (A5) D4=0000E560 
                                      A5=00108000 mainram[0x8000]=mainrom[0x80560]=0x410BB296
65954C2 004134: 3015                  move.w   (A5), D0 D0=00100000->0010410B 
                                                mainram[0x8000]=0x410BB296
65954CA 004136: 0255 0007             andi.w   $7, (A5) D0=0010410B 
                                      A5=00108000 mainram[0x8000]=0x410BB296->0003B296
65954DA 00413A: 2255                  movea.l  (A5), A1 A1=mainram[0x8000]=0x0003B296 
                                                A5=00108000
65954E6 00413C: 4295                  clr.l    (A5) mainram[0x8000]=0x00000000
65954FA 00413E: D3C9                  adda.l   A1, A1 A1=0003B296->0007652C
6595502 004140: D3FC 0008 2004        adda.l   $82004, A1 A1=0007652C->000F8530
…
65980F6 003D82: D259                  add.w    (A1)+, D1 D1=00000000->0000ED10 
                                                A1=000F8530->000F8532 mainrom[0xF8530]=0xED10
65980FE 003D84: B541                  eor.w    D2, D1 D1=0000ED10 D2=00400000
6598102 003D86: C26D 8B7E             and.w    ($8B7E,A5), D1 D1=0000ED10 
                                                A5=00108000 mainram[B7E]=0xFFFF
659810E 003D8A: 3419                  move.w   (A1)+, D2 D2=00400000->0040CB06 
                                                A1=000F8532 mainrom[0xF8532]=0xCB06
6598116 003D8C: 1A3B 50E4             move.b   (PC,D5.w,-$1C), D5 D5=00000001->00000070 
                                                mainrom[0x3D73]=0x70
6598124 003D90: 3943 FFFE             move.w   D3, ($FFFE,A4) D3=000018C0
6598130 003D94: 4EFB 5002             jmp      (PC,D5.w,$2)
659813E 003E08: 3942 0000             move.w   D2, ($0,A4) D2=0040CB06 
                                               neogeo_videoram[0x18C0]=0xCB06 
                                               code_2=Neogeo.neogeo_videoram[sprite_number<<6]
…
6598262 003394: 302E 0012             move.w   ($12,A6), D0 D0=00000000->000000AE 
                                                         A6=00101040 mainram[0x1052]=0x00AE
659826E 003398: 9041                  sub.w    D1, D0 D0=000000AE->0000009E D1=00000010
6598272 00339A: 4440                  neg.w    D0 D0=0000009E->0000FF62
6598276 00339C: EF48                  lsl.w    7, D0 D0=0000FF62->0000B100
659828A 00339E: 806E 0008             or.w     ($8,A6), D0 D0=0000B100->0000B102 
                                                        A6=00101040 mainram[0x1048]=0x0002
6598296 0033A2: 36C0                  move.w   D0, (A3)+ D0=0000B102 A3=001007C4 
                                               mainram[0x7C4]=0xB102
…
6598320 0033E8: 302E 0010             move.w   ($10,A6), D0 A6=00101040 
                                               D0=mainram[0x1050]=0x0029
659832C 0033EC: 9041                  sub.w    D1, D0 D0=00000029->00000019 D1=00000010
6598330 0033EE: EF48                  lsl.w    7, D0 D0=00000019->00000C80
6598344 0033F0: 3880                  move.w   D0, (A4) D0=00000C80 A4=003C0002 neogeo_videoram
                                      [0x8463]=0x0C80 x_2=Neogeo.neogeo_videoram[0x8400|
                                      sprite_number]
…
65A72B6 0031FE: 3898                  move.w   (A0)+, (A4) A0=001007C4 A4=003C0002 
                                      neogeo_videoram[0x8263]=mainram[0x7C4]=0xB102 
                                      y_control=Neogeo.neogeo_videoram[0x8200|sprite_number]

I find out that the following marked code determines the coordinate and sprite code of c00-c11 head image:

Image 8

Maincpu ROM

The original coordinates of the 15 characters:

x coordinate:
29 4B 6D 8F B1 D3 F5 117
 39 5B 7D 9F C1 E3 105
y coordinate:
AE AA A7 A5 A5 A7 AA AE
 D0 CC C9 C8 C9 CC D0

I'll extend the coordinates to 17 characters:

x coordinate:
 29 4B 6D 8F B1 D3 F5 117
17 39 5B 7D 9F C1 E3 105 127
y coordinate:
 AE AA A7 A5 A5 A7 AA AE
D5 D0 CC C9 C8 C9 CC D0 D5

So I get the following cheat code:

[17 select 2]
ON=1387D,CB;141E9,CB;141ED,10;142BE,01;142BF,27;142C1,D5;142C7,17;142C9,
D5;142CB,11;142DB,10;1471B,11;14739,10;14747,CB

After applying the above cheat code, I get the following selection scene:

Image 9

Selection scene

By Neogeo debugging, I find the following sprite_gfx offset of the 17 characters:

character id mainrom sprite code sprite_gfx offset
07 6F 1CB0200
03 70 1CB0600
00 71 1CB0A00
05 72 1CB0E00
0B 73 1CB1200
08 74 1CB1600
01 75 1CB1A00
04 76 1CB1E00
09 77 1CB2200
02 78 1CB2600
06 79 1CB2A00
0C 7A 1CB2E00
0E 7B 1CB3200
0F 7C 1CB3600
0D 7D 1CB3A00
11 7E 1CDC600
10 ? 1F5A700

Now I'll calculate the mainrom sprite code of c10 from the known sprite_gfx offset.

The known calculation of c03:

0x03*8=0x18
0x1423C+0x18=0x14254
mainrom[0x14254]=0x0070
0x70*4=0x1C0
0x2AC948+0x1C0=0x2ACB08
mainrom[0x1ACB08]=0x002AE40A
0x1AE40A+0x06=0x1AE410
mainrom[0x1AE410]=0x3958
0x3958*4=0xE560
0x72000+0xE560=0x80560
mainrom[0x80560]=0x410BB296
0x410BB296&0x7FFFF=0x3B296
0x3B296*2=0x7652C
0x7652C+0x82004=0xF8530
0xF8530+2=0xF8532
mainrom[0xF8532]=0xCB06
CB06->1CB0600

The calculation of c10:

1F5A700->F5A7
The reasonable mainrom address is 0xA62B6.
mainrom[0xA62B6]=0xF5A7
0xA62B6-2=0xA62B4
0xA62B4-0x82004=0x242B0
0x242B0/2=0x12158
0x12158+(0x410BB296-0x3B296)=0x41092158
The mainrom address is 0x77198.
mainrom[0x77198]=0x41092158
0x77198-0x72000=0x5198
0x5198/4=0x1466
The reasonable mainrom address is 0x1B21D8
mainrom[0x1B21D8]=0x1466
0x1B21D8-6=0x1B21D2
The mainrom address is 0x1AD530.
mainrom[0x1AD530]=0x002B21D2
0x2AD530-0x2AC948=0xBE8
0xBE8/4=0x2FA
0x10*8=0x80
0x1423C+0x80=0x142BC

I'll change the mainrom[0x142BC] to 0x02FA. So I get the following cheat code:

[17 select]
ON=1387D,CB;141E9,CB;141ED,10;142BC,02;142BD,FA;142BE,01;142BF,27;142C1,
D5;142C7,17;142C9,D5;142CB,11;142DB,10;1471B,11;14739,10;14747,CB

The hack ROM is done as the title image.

Conclusion

I've finished MAME.NET. It figures out how the arcade games work. You can make any other game supported by referring to MAME source code. By the translation from C to C#, there is little unsafe code and the code is much more readable. I've preserved the main architecture of MAME. Programmers can refer to the MAME.NET source code and emulate more arcade game systems by C#.

History

  • 1st February, 2019: Finished MAME.NET, supports 792 games (build 20190201)
  • 4th June, 2019: Added M72 and M92 boards, supports 826 games (build 20190604)
  • 19th May, 2020: Added Taito B board, supports 830 games (build 20200519)
  • 3rd December, 2020: Added Konami 68000 board, supports 882 games (build 20201203)
  • 16th June, 2021: Added Capcom board, supports 888 games (build 20210616)
  • 5th August, 2021: Supports 890 games (build 20210805)
  • 27th February, 2022: Added rom hacking: Warriors of Fate, Kassar Sits Continuously
  • 8th February, 2023: Added Taido board, supports 912 games (build 20230208)
  • 24th April, 2023: Added mouse support, supports 918 games (build 20230424)
  • 14th August, 2023: Added 11 Capcom games, supports 929 games (build 20230814)
  • 19th February, 2024: Added 5 Tehkan games, supports 934 games (build 20240219)
  • 20th June, 2024, Added 6 Data East games, support 940 games (build 20240620)

References

  1. MAME-Multiple Arcade Machine Emulator - https://github.com/mamedev
  2. MSDN - https://msdn.microsoft.com
  3. BizHawk M68000 and Z80 code - https://github.com/TASEmulators/BizHawk/tree/master/src/BizHawk.Emulation.Cores/CPUs
  4. VCMAME detail by Bryan McPhail - https://www.codeproject.com/Articles/4923/VCMAME-Multiple-Arcade-Machine-Emulator-for-Visual
  5. MAME and MAMEUI Visual C Project Files - http://www.mikesarcade.com/arcade/vcmame.html
  6. CPS1.NET - https://www.codeproject.com/Articles/998595/CPS1-NET-A-Csharp-Based-CPS1-MAME-Emulator

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)