So it's been pretty quiet lately, I figured I'd give you all a quick update.
My first project was working on route16. The game has been in MAME a long time (the non-bootlegs, since 2003!) and has always had the ROM hacked to bypass the protection.
I spent a lot of time disassembling the code for the game and figuring out exactly what it needed to make it work.
This is more or less all it takes to get the game working properly.
READ8_MEMBER(route16_state::route16_prot_read)
{
m_protection_data++;
return (1 << ((m_protection_data >> 1) & 7));
}
These are the ugly patches that are now gone
DRIVER_INIT_MEMBER(route16_state,route16)
{
uint8_t *ROM = memregion("cpu1")->base();
/* TO DO : Replace these patches with simulation of the protection device */
/* patch the protection */
ROM[0x0105] = 0x00; /* jp nz,$4109 (nirvana) - NOP's in route16c */
ROM[0x0106] = 0x00;
ROM[0x0107] = 0x00;
ROM[0x072a] = 0x00; /* jp nz,$4238 (nirvana) */
ROM[0x072b] = 0x00;
ROM[0x072c] = 0x00;
DRIVER_INIT_CALL(route16c);
}
DRIVER_INIT_MEMBER(route16_state,route16c)
{
uint8_t *ROM = memregion("cpu1")->base();
/* Is this actually a bootleg? some of the protection has
been removed */
/* patch the protection */
ROM[0x00e9] = 0x3a;
ROM[0x0754] = 0xc3;
ROM[0x0755] = 0x63;
ROM[0x0756] = 0x07;
}
DRIVER_INIT_MEMBER(route16_state,route16a)
{
uint8_t *ROM = memregion("cpu1")->base();
/* TO DO : Replace these patches with simulation of the protection device */
/* patch the protection */
ROM[0x00e9] = 0x3a;
ROM[0x0105] = 0x00; /* jp nz,$4109 (nirvana) - NOP's in route16c */
ROM[0x0106] = 0x00;
ROM[0x0107] = 0x00;
ROM[0x0731] = 0x00; /* jp nz,$4238 (nirvana) */
ROM[0x0732] = 0x00;
ROM[0x0733] = 0x00;
ROM[0x0747] = 0xc3;
ROM[0x0748] = 0x56;
ROM[0x0749] = 0x07;
}
Next I looked at Metafox. This game has a special memory region that it tests and throws an error if it doesn't pass.
I was able to greatly simplify what it was actually doing (writing to RAM, reading back certain values at different addresses than they are written) to get the game to play nice.
Here's the nice, new protection sim.
READ16_MEMBER(seta_state::metafox_protection_r)
{
// very simplified protection simulation
// 21c000-21c3ff, 21d000-21d3ff, and 21e000-21e3ff are tested as 8 bit reads/writes
// the first address in each range is special and returns data written elsewhere in that range
// 21fde0-21fdff appears to be control bytes?
switch (offset)
{
case 0x0001/2:
return 0x3d;
case 0x1001/2:
return 0x76;
case 0x2001/2:
return 0x10;
}
return offset * 0x1f;
}
DRIVER_INIT_MEMBER(seta_state,metafox)
{
m_maincpu->space(AS_PROGRAM).install_read_handler(0x21c000, 0x21ffff,read16_delegate(FUNC(seta_state::metafox_protection_r),this));
}
Here are the patches that were needed before.
DRIVER_INIT_MEMBER(seta_state,metafox)
{
uint16_t *RAM = (uint16_t *) memregion("maincpu")->base();
/* This game uses the 21c000-21ffff area for protection? */
RAM[0x8ab1c/2] = 0x4e71; // patch protection test: "cp error"
RAM[0x8ab1e/2] = 0x4e71;
RAM[0x8ab20/2] = 0x4e71;
}
Next, I took a look at Maketrax. This game and its clones (Crush Roller) have had a really nasty way of making them work.
They use 1. ROM patches to bypass some of the protection checks, 2. the opcodes were being copied to a seperate area of memory and being used like the game had an encrypted ROM so that it passes rom checks. 3. hardcoded checks to bypass more. This isn't good on multiple levels for properly emulating this game.
here's the new code to emulate the protection
WRITE8_MEMBER(pacman_state::maketrax_protection_w)
{
if (data == 0) // disable protection / reset?
{
m_maketrax_counter = 0;
m_maketrax_offset = 0;
m_maketrax_disable_protection = 1;
return;
}
if (data == 1)
{
m_maketrax_disable_protection = 0;
m_maketrax_counter++;
if (m_maketrax_counter == 0x3c)
{
m_maketrax_counter = 0;
m_maketrax_offset++;
if (m_maketrax_offset == 0x1e)
m_maketrax_offset = 0;
}
}
}
READ8_MEMBER(pacman_state::maketrax_special_port2_r)
{
const uint8_t protdata[0x1e] = { // table at $ebd (odd entries)
0x00, 0xc0, 0x00, 0x40, 0xc0, 0x40, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xc0, 0x00, 0x40, 0xc0, 0x40,
0x00, 0xc0, 0x00, 0x40, 0x00, 0xc0, 0x00, 0x40, 0xc0, 0x40, 0x00, 0xc0, 0x00, 0x40
};
if (m_maketrax_disable_protection == 0)
return protdata[m_maketrax_offset];
uint8_t data = ioport("DSW1")->read() & 0x3f;
switch (offset)
{
case 0x01:
case 0x04:
data |= 0x40; break;
case 0x05:
data |= 0xc0; break;
default:
data &= 0x3f; break;
}
return data;
}
READ8_MEMBER(pacman_state::maketrax_special_port3_r)
{
const uint8_t protdata[0x1e] = { // table at $ebd (even entries)
0x1f, 0x3f, 0x2f, 0x2f, 0x0f, 0x0f, 0x0f, 0x3f, 0x0f, 0x0f, 0x1c, 0x3c, 0x2c, 0x2c, 0x0c, 0x0c,
0x0c, 0x3c, 0x0c, 0x0c, 0x11, 0x31, 0x21, 0x21, 0x01, 0x01, 0x01, 0x31, 0x01, 0x01
};
if (m_maketrax_disable_protection == 0)
return protdata[m_maketrax_offset];
switch (offset)
{
case 0x00:
return 0x1f;
case 0x09:
return 0x30;
case 0x0c:
return 0x00;
default:
return 0x20;
}
Here's the old code.
READ8_MEMBER(pacman_state::maketrax_special_port2_r)
{
int data = ioport("DSW1")->read();
int pc = m_maincpu->pcbase();
if ((pc == 0x1973) || (pc == 0x2389)) return data | 0x40;
switch (offset)
{
case 0x01:
case 0x04:
data |= 0x40; break;
case 0x05:
data |= 0xc0; break;
default:
data &= 0x3f; break;
}
return data;
}
READ8_MEMBER(pacman_state::maketrax_special_port3_r)
{
int pc = m_maincpu->pcbase();
if (pc == 0x040e) return 0x20;
if ((pc == 0x115e) || (pc == 0x3ae2)) return 0x00;
switch (offset)
{
case 0x00:
return 0x1f;
case 0x09:
return 0x30;
case 0x0c:
return 0x00;
default:
return 0x20;
}
}
void pacman_state::maketrax_rom_decode()
{
uint8_t *rom = memregion("maincpu")->base();
/* patch protection using a copy of the opcodes so ROM checksum */
/* tests will not fail */
memcpy(m_patched_opcodes,rom,0x4000);
m_patched_opcodes[0x0415] = 0xc9;
m_patched_opcodes[0x1978] = 0x18;
m_patched_opcodes[0x238e] = 0xc9;
m_patched_opcodes[0x3ae5] = 0xe6;
m_patched_opcodes[0x3ae7] = 0x00;
m_patched_opcodes[0x3ae8] = 0xc9;
m_patched_opcodes[0x3aed] = 0x86;
m_patched_opcodes[0x3aee] = 0xc0;
m_patched_opcodes[0x3aef] = 0xb0;
}
DRIVER_INIT_MEMBER(pacman_state,maketrax)
{
/* set up protection handlers */
m_maincpu->space(AS_PROGRAM).install_read_handler(0x5080, 0x50bf, read8_delegate(FUNC(pacman_state::maketrax_special_port2_r),this));
m_maincpu->space(AS_PROGRAM).install_read_handler(0x50c0, 0x50ff, read8_delegate(FUNC(pacman_state::maketrax_special_port3_r),this));
maketrax_rom_decode();
}
Robert pointed out that after this change crushbl2 and mbrush (clones of maketrax) were not working.
These bootlegs had most of the protection changed! I looked at every read the games do from the area where the protection chip usually sits and figured out what it was expecting back.
Robert took some liberties with the code to make it a little clearer
READ8_MEMBER(pacman_state::mbrush_prot_r)
{
uint8_t data = ioport("DSW1")->read() & 0x3f;
switch (offset)
{
case 0x00:
case 0x04:
case 0x07:
case 0x0e:
case 0x0f:
return 0xc0 | data;
case 0x40:
case 0x41:
case 0x47:
case 0x49:
case 0x4d:
case 0x5d:
return 0x00;
case 0x42:
case 0x43:
case 0x46:
case 0x4c:
case 0x50:
return 0x02;
case 0x7f:
return 0x10;
}
return data;
}