2 Aug 2017

Conversions on Irem M92 hardware (Major Title 2 to Mystic Riders)

Irem M92 hardware is composed of two boards:
- Big CPU board: identical amongst all games (except few manufacturing differences with no impact on gameplay or functionnalities, e.g. 6264 RAMs installed where 6116 are big enough).
- Smaller ROM board: different for each game and with an encrypted sound CPU code to prevent conversions.
Thus a romswap will give you a fully working game graphically but with bugged or totally absent sound.

Few other hackers have done conversions on the Irem M92 hardware, the most popular being Major Title 2 converted to R-Type Leo. Unfortunately their work isn't available anywhere online anymore...
So I had a go by myself and choose to convert a game which hasn't been converted before: Mystic Riders. (I must admit I quite like this game).
The original game I used is Major Title 2 again (probably the easiest to find and cheapest M92 game) but the principles can be applied to convert any M92 game to any other M92 game.

1) Equivalent opcodes

I've developped a simple tool to help me for the conversions.
First it creates a list of equivalent opcodes between the two games accodring to MAME tables (found in the file irem_cpu.cpp located in \src\mame\machine):

Major Title 2 table:
// majtitl2
const uint8_t majtitl2_decryption_table[256] = {
 0x87,xxxx,0x78,0xaa,xxxx,xxxx,xxxx,0x2c, 0x32,0x0a,0x0f,xxxx,0x5e,xxxx,0xc6,0x8a, /* 00 */
 0x33,xxxx,xxxx,xxxx,xxxx,0xea,xxxx,0x72, xxxx,xxxx,xxxx,xxxx,xxxx,xxxx,0x24,0x55, /* 10 */
 xxxx,xxxx,xxxx,0x89,0xfb,xxxx,0x59,0x02, xxxx,xxxx,0x5d,xxxx,xxxx,xxxx,0x36,xxxx, /* 20 */
 xxxx,0x06,0x79,xxxx,xxxx,0x1e,0x07,xxxx, xxxx,xxxx,0x83,xxxx,xxxx,xxxx,xxxx,xxxx, /* 30 */
 0x9d,xxxx,xxxx,0x74,xxxx,xxxx,xxxx,0x0c, 0x58,xxxx,xxxx,xxxx,xxxx,xxxx,xxxx,xxxx, /* 40 */
 0x3c,xxxx,0x03,xxxx,xxxx,0xfa,0x43,xxxx, 0xbf,xxxx,xxxx,0x75,xxxx,0x88,xxxx,0x80, /* 50 */
 xxxx,0xa3,xxxx,0xfe,xxxx,xxxx,xxxx,xxxx, xxxx,xxxx,xxxx,xxxx,0x3a,xxxx,xxxx,xxxx, /* 60 */
 0x2b,xxxx,xxxx,xxxx,xxxx,0xe9,0x5f,xxxx, 0x46,xxxx,0x41,xxxx,0x18,0xb8,xxxx,xxxx, /* 70 */
 0xb4,0x5a,0xb1,xxxx,xxxx,0x50,0xe8,0x20, xxxx,0xb2,xxxx,xxxx,xxxx,xxxx,xxxx,0x51, /* 80 */
 xxxx,xxxx,xxxx,0x56,xxxx,xxxx,xxxx,xxxx, xxxx,0xcf,xxxx,xxxx,xxxx,0xc3,xxxx,xxxx, /* 90 */
 xxxx,xxxx,xxxx,xxxx,0x0b,xxxx,xxxx,0xb5, 0x57,xxxx,xxxx,0xc7,0x3b,xxxx,xxxx,xxxx, /* A0 */
 xxxx,xxxx,xxxx,xxxx,0xb6,xxxx,0xeb,xxxx, 0x38,xxxx,0xa0,0x08,xxxx,0x86,0xb0,xxxx, /* B0 */
 0x42,0x1f,0x73,xxxx,0xf6,xxxx,xxxx,xxxx, 0x53,xxxx,0x52,xxxx,0x04,0xbd,xxxx,xxxx, /* C0 */
 0x26,0xff,0x2e,xxxx,0x81,xxxx,0x47,xxxx, xxxx,xxxx,xxxx,0xd0,0x22,xxxx,xxxx,0xb9, /* D0 */
 0x23,xxxx,0xf3,xxxx,xxxx,xxxx,xxxx,xxxx, xxxx,0xd2,0x8b,0xba,xxxx,xxxx,xxxx,0x5b, /* E0 */
 xxxx,xxxx,0x9c,xxxx,xxxx,xxxx,xxxx,0xfc, 0xbc,0xa2,0x2a,xxxx,xxxx,0x8e,0xbb,xxxx, /* F0 */
// 0x7c (0x18) opcode is right but arguments could be swapped
// 0x70 (0x2b) not sure, could be 0x1b
Mystic Riders table:
/* note: mysticrib sound is identical revision to bbmanw sound code */
// mysticri
const uint8_t mysticri_decryption_table[256] = {
 xxxx,0x57,xxxx,xxxx,xxxx,xxxx,xxxx,xxxx, 0xbf,0x43,xxxx,xxxx,0xb3,xxxx,0xfc,xxxx, /* 00 */
 xxxx,xxxx,xxxx,xxxx,xxxx,0x52,0xa3,0x26, xxxx,0xc7,xxxx,0x0f,xxxx,0x0c,xxxx,xxxx, /* 10 */
 xxxx,xxxx,0xff,xxxx,xxxx,0x02,xxxx,xxxx, 0x2e,xxxx,0x5f,xxxx,xxxx,xxxx,0x73,0x50, /* 20 */
 0xb2,0x3a,xxxx,xxxx,0xbb,xxxx,xxxx,xxxx, xxxx,xxxx,xxxx,xxxx,xxxx,xxxx,xxxx,xxxx, /* 30 */
 xxxx,xxxx,0x8e,0x3c,0x42,xxxx,xxxx,0xb9, xxxx,xxxx,0x2a,xxxx,0x47,0xa0,0x2b,0x03, /* 40 */
 0xb5,0x1f,xxxx,0xaa,xxxx,0xfb,xxxx,xxxx, xxxx,xxxx,xxxx,xxxx,0x38,xxxx,xxxx,xxxx, /* 50 */
 0x2c,xxxx,xxxx,0xc6,xxxx,xxxx,0xb1,xxxx, xxxx,xxxx,xxxx,xxxx,xxxx,xxxx,0xa2,xxxx, /* 60 */
 0xe9,0xe8,xxxx,xxxx,0x86,xxxx,0x8b,xxxx, xxxx,xxxx,xxxx,xxxx,0x5b,0x72,xxxx,xxxx, /* 70 */
 xxxx,xxxx,0x5d,0x0a,xxxx,xxxx,0x89,xxxx, 0xb0,0x88,xxxx,0xb7,xxxx,0x87,0x75,0xbd, /* 80 */
 xxxx,0x51,xxxx,xxxx,xxxx,xxxx,xxxx,0xbe, xxxx,xxxx,xxxx,0x5a,0x58,xxxx,xxxx,0x56, /* 90 */
 xxxx,0x8a,xxxx,0x55,xxxx,xxxx,xxxx,0xb4, 0x08,xxxx,0xf6,xxxx,xxxx,0x9d,xxxx,0xbc, /* A0 */
 0x0b,0x00,xxxx,0x5e,xxxx,xxxx,xxxx,0x22, 0x36,0x4b,0x1e,xxxx,0xb6,0xba,0x23,xxxx, /* B0 */
   /*r*/                                    /*r*/
 0x20,xxxx,xxxx,xxxx,0x59,0x53,xxxx,0x04, 0x81,xxxx,xxxx,0xf3,xxxx,xxxx,0x3b,0x06, /* C0 */
 0xe2,0x79,0x83,0x9c,xxxx,0x18,0x80,xxxx, 0xc3,xxxx,xxxx,xxxx,0x32,xxxx,0xcf,xxxx, /* D0 */
 0xeb,xxxx,xxxx,0x33,xxxx,0xfa,xxxx,xxxx, 0xd2,xxxx,0x24,xxxx,0x74,0x41,0xb8,xxxx, /* E0 */
 0x34,xxxx,0xd0,0x07,0xf8,xxxx,xxxx,xxxx, xxxx,0x46,xxxx,0xea,0xfe,0x78,xxxx,xxxx, /* F0 */
 /*r*/               /*r*/
// 0xd5 (0x18) opcode is right but arguments could be swapped
// 0x4e (0x2b) not sure, could be 0x1b
// 0x8b (0xb3) needed by mysticrib
Table of equivalence:

First column are encrypted values used by Mystic Riders, second column are unecrypted values, third column are crypted values for Major Title 2.

2) Separating code from data

Then, using MAME debugger, I played the game and used the trace function for the sound CPU (trace filename).
This gave me a huge file in which I removed duplicated lines (obviously the sound CPU executes many times the same lines of code).

3) Converting opcodes

First I read encrypted opcodes from the sound ROMs (once interleaved in one file). The address to read from is given by the MAME trace file. Most opcodes are 1 byte long but few are 2 byte long (I used the Nec V20-V30 documentation to be sure).

Then, using the list created in 1), I wrote the equivalent encrypted opcodes used by Major Title 2 to the sound CPU ROMs.

4) Tests (annoying issue spotted)

In order to force MAME to use the decryption table of Major Title 2 instead of Mystic Riders I've modified the file named m92.cpp located in \src\mame\drivers.
I've edited this section:

static MACHINE_CONFIG_DERIVED( mysticri, m92 )


static MACHINE_CONFIG_DERIVED( mysticri, m92 )

After recompiling I played the game in MAME until after beating the octopuss at the end of round 4 music stopped.
I discovered Mystic Riders uses a 0x00 instruction (add memory with register to memory) only once in its entire sound program @ 0x00E6D.
Unfortunately the MAME table for Major Title 2 doens't contain the equivalent opcode (Major Title probably doesn't use this instruction and/or MAME table is incomplete). I could have tried on the real hardware every possibility to find the corresponding encrypted opcode but:
- I don't own any M92 game [EDIT]I now own a MT2 board
- There might be no equivalent as Major Title 2 doesn't need it
- It would have required a lot of time (ROMs burning, playing the game to the problematic point, etc.)
I decided to modify slightly the code to use known opcodes instead.

As said before, the problematic instruction lies @ 0x00E6D in the sound CPU ROMs:

00E6D: add     [bp+0Ch],al (00 46 0C)

As I will need more room to fit my modified code I replaced this line by a jump to a subroutine I will create:

00E6D: call    9000h  (86 90 81)

There's a big area filled with 0xFF starting @ 0x08D70. I choosed to put my new subroutine @ 0x09000 so it's easy to find and identify.
The subroutine itself is very simple:

09000: add     al,[bp+0Ch] (27 46 0C)
09003: mov     [bp+0Ch],al (5D 46 0C)
09006: ret   (9D)

Instead of adding memory with register to memory I add memory with register to register.
This could have been a problem if the value stored in the register was reused later but this wasn't the case (value is overwritten in the next loop).
Then I move the content of the register to the expected memory location.
I played the game (in MAME as I don't own any M92 game [EDIT]and on real hardware as I now own a MT2 board) again to the end and found no other problem.

[EDIT 29/08/2017]
Tested and reported fully working on real hardware by the member of a French forum. Many thanks to him for his feedback.

Also available:
- Gunforce
- Blade Masters
- Lethal Thunder
- Undercover Cops
- Undercover Cops Alpha Renewal
- Mystic Riders/Gun Hohki
- Major Title 2/The Irem Skins Game
- Hook
- R-Type Leo
- In The Hunt
- Ninja Baseball Batman/Yakyuu Kakutou League-Man
- Perfects Soldiers/Sky Soldiers
- Dream Soccer 94
- Gunforce 2/Geostorm


  1. Thanks, this is great! On the original board the Nec CPU can also run in decrypted mode by changing 2 jumpers. This might make any feature conversions even easier and independent of the specific Nec CPU. I have a Geostorm example if you'd like to experiment.

    1. Hi, yes Caius from JAMMARCADE confirmed you can run unencrypted sound code by moving 2 jumpers near the sound CPU from position S to N. This will be covered in the future. Also as you said it makes conversion very easy as you put aside the problem of incomplete decryption tables and you can use any game as donor.
      I've decrypted almost all M92/M107 games including Gunforce II/Geostorm.

  2. oh I would be very interested in the decrypted games :)

    Thanks for the great work again!