nmk/nmk214.cpp: Added NMK214 graphics unscrambling device. (#12039)

The current implementation is less than ideal due to inflexibility of device_gfx_interface.

nmk/nmk16.cpp: Hooked up NMK214 device for sabotenb.
This commit is contained in:
Sergio G 2024-03-08 18:18:33 +01:00 committed by GitHub
parent 34e362e9d5
commit 3b02100587
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 415 additions and 20 deletions

View File

@ -208,6 +208,7 @@ Reference of music tempo:
#include "sound/ymopn.h"
#include "sound/ymopl.h"
#include "multibyte.h"
#include "screen.h"
#include "speaker.h"
@ -4358,14 +4359,43 @@ u8 tdragon_prot_state::mcu_port6_r()
void tdragon_prot_state::machine_start()
{
nmk16_state::machine_start();
save_item(NAME(m_bus_status));
}
void tdragon_prot_state::machine_reset()
{
nmk16_state::machine_reset();
m_bus_status = 0x04;
}
void tdragon_prot_214_state::device_post_load()
{
tdragon_prot_state::device_post_load();
if (m_gfx_unscramble_enabled && !m_gfx_decoded)
{
// Loaded a state saved after graphics were decoded before decoding graphics
// Force graphics unscrambling now
decode_nmk214();
m_gfxdecode->gfx(1)->mark_all_dirty(); // background tiles
m_gfxdecode->gfx(2)->mark_all_dirty(); // sprites
m_bg_tilemap[0]->mark_all_dirty();
m_gfx_decoded = true;
}
}
void tdragon_prot_214_state::machine_start()
{
tdragon_prot_state::machine_start();
save_item(NAME(m_init_data_nmk214));
save_item(NAME(m_init_clock_nmk214));
save_item(NAME(m_gfx_unscramble_enabled));
}
void tdragon_prot_state::mcu_side_shared_w(offs_t offset, u8 data)
{
LOG("%s: mcu_side_shared_w offset %08x (data %02x)\n", machine().describe_context(), offset, data);
@ -4899,32 +4929,74 @@ void nmk16_state::bjtwin(machine_config &config)
nmk112.set_rom1_tag("oki2");
}
// the 215 writes minimal data here, probably only enables the decryption logic, rather than supplying the decryption table
void tdragon_prot_state::mcu_port3_to_214_w(u8 data)
// The 215 writes minimal data here to select an unscrambling configuration hardwired inside the NMK214 chip.
void tdragon_prot_214_state::mcu_port3_to_214_w(u8 data)
{
// startup only
logerror("%s: mcu_port3_to_214_w (data %02x)\n", machine().describe_context(), data);
LOG("%s: mcu_port3_to_214_w (data %02x)\n", machine().describe_context(), data);
// Startup only. Bit 2 on Port 3 of MCU (NMK215) acts as strobe/clock (rising edge) for storing the byte previously set on the Port 7
// of MCU (NMK215) into the internal registers of both NMK214 devices
if (m_init_clock_nmk214 == 0 && BIT(data, 2) == 1)
{
// Value is sent to both devices at the same time. Internally, each one evaluates if the value should be stored or not based on
// bit 3 of the incoming value matches to the operation mode configured on each device:
m_nmk214[0]->set_init_config(m_init_data_nmk214);
m_nmk214[1]->set_init_config(m_init_data_nmk214);
// Force decode gfx after setting both nmk214 init config, just for testing purposes. Real devices perform the decoding on the fly
// for each byte/word fetch from GFX ROMs
if (!m_gfx_unscramble_enabled && m_nmk214[0]->is_device_initialized() && m_nmk214[1]->is_device_initialized())
{
m_gfx_unscramble_enabled = true;
if (!m_gfx_decoded)
{
decode_nmk214();
m_gfxdecode->gfx(1)->mark_all_dirty(); // background tiles
m_gfxdecode->gfx(2)->mark_all_dirty(); // sprites
m_bg_tilemap[0]->mark_all_dirty();
m_gfx_decoded = true;
}
}
}
m_init_clock_nmk214 = BIT(data, 2);
}
void tdragon_prot_state::mcu_port7_to_214_w(u8 data)
void tdragon_prot_214_state::mcu_port7_to_214_w(u8 data)
{
// startup only
logerror("%s: mcu_port7_to_214_w (data %02x)\n", machine().describe_context(), data);
LOG("%s: mcu_port7_to_214_w (data %02x)\n", machine().describe_context(), data);
// Startup only. Value written here is kept as outputs on Port 7 of MCU (NMK215) in order to be read by the NMK214 devices when the
// clock signal is sent (Bit 2 of Port 3)
m_init_data_nmk214 = data;
}
void tdragon_prot_state::saboten_prot(machine_config &config)
// Bitswaps that represent how the address bus of the NMK214s (13 bits) are hooked up related to the address bus of the ROMs.
// Every element represents the bit number in the ROM address bus that should be taken for each NMK214 address bus position, starting by the LSB
static const std::array<u8, 13> nmk214_sprites_address_bitswap = {0, 1, 2, 3, 10, 12, 13, 14, 15, 16, 17, 18, 19};
static const std::array<u8, 13> nmk214_bg_address_bitswap = {0, 1, 2, 3, 11, 13, 14, 15, 16, 17, 18, 19, 20};
void tdragon_prot_214_state::saboten_prot(machine_config &config)
{
bjtwin(config);
TMP90840(config, m_protcpu, 4000000); // Toshiba TMP90840 marked as NMK-215, with 8Kbyte internal ROM, 256bytes internal RAM
m_protcpu->set_addrmap(AS_PROGRAM, &tdragon_prot_state::tdragon_prot_map);
m_protcpu->port_write<6>().set(FUNC(tdragon_prot_state::mcu_port6_w));
m_protcpu->port_read<5>().set(FUNC(tdragon_prot_state::mcu_port5_r));
m_protcpu->port_read<6>().set(FUNC(tdragon_prot_state::mcu_port6_r));
m_protcpu->set_addrmap(AS_PROGRAM, &tdragon_prot_214_state::tdragon_prot_map);
m_protcpu->port_write<6>().set(FUNC(tdragon_prot_214_state::mcu_port6_w));
m_protcpu->port_read<5>().set(FUNC(tdragon_prot_214_state::mcu_port5_r));
m_protcpu->port_read<6>().set(FUNC(tdragon_prot_214_state::mcu_port6_r));
// the 215 has these hooked up, going to the 214
m_protcpu->port_write<3>().set(FUNC(tdragon_prot_state::mcu_port3_to_214_w));
m_protcpu->port_write<7>().set(FUNC(tdragon_prot_state::mcu_port7_to_214_w));
m_protcpu->port_write<3>().set(FUNC(tdragon_prot_214_state::mcu_port3_to_214_w));
m_protcpu->port_write<7>().set(FUNC(tdragon_prot_214_state::mcu_port7_to_214_w));
NMK214(config, m_nmk214[0], 0); // Descrambling device for sprite GFX data
m_nmk214[0]->set_mode(0);
m_nmk214[0]->set_input_address_bitswap(nmk214_sprites_address_bitswap);
NMK214(config, m_nmk214[1], 0); // Descrambling device for BG GFX data
m_nmk214[1]->set_mode(1);
m_nmk214[1]->set_input_address_bitswap(nmk214_bg_address_bitswap);
config.set_maximum_quantum(attotime::from_hz(6000));
}
@ -5118,6 +5190,32 @@ void nmk16_state::decode_gfx()
}
}
void tdragon_prot_214_state::decode_nmk214()
{
u8 *rom;
int len;
// background tiles
rom = memregion("bgtile")->base();
len = memregion("bgtile")->bytes();
for (int A = 0; A < len; A++)
{
rom[A] = m_nmk214[1]->decode_byte(A, rom[A]);
}
// sprites
rom = memregion("sprites")->base();
len = memregion("sprites")->bytes();
for (int A = 0; A < (len - 1); A += 2)
{
// sprite ROM is 16-bit big Endian
u16 word = get_u16be(&rom[A]);
// A is a byte address, divide by 2 to give word address for NMK214
put_u16be(&rom[A], m_nmk214[0]->decode_word(A/2, word));
}
}
void nmk16_state::decode_tdragonb()
{
/* Descrambling Info Again Taken from Raine, Huge Thanks to Antiriad and the Raine Team for
@ -8981,8 +9079,8 @@ GAME( 1994, raphero, arcadian, raphero, raphero, nmk16_state, init_
GAME( 1994, rapheroa, arcadian, raphero, raphero, nmk16_state, init_banked_audiocpu, ROT270, "NMK (Media Trading license)", "Rapid Hero (Media Trading)", 0 ) // ^^ - note that all ROM sets have Media Trading(aka Media Shoji) in the tile graphics, but this is the only set that shows it on the titlescreen
// both sets of both these games show a date of 9th Mar 1992 in the test mode, they look like different revisions so I doubt this is accurate
GAME( 1992, sabotenb, 0, saboten_prot, sabotenb, tdragon_prot_state, init_nmk, ROT0, "NMK / Tecmo", "Saboten Bombers (set 1)", MACHINE_NO_COCKTAIL )
GAME( 1992, sabotenba, sabotenb, saboten_prot, sabotenb, tdragon_prot_state, init_nmk, ROT0, "NMK / Tecmo", "Saboten Bombers (set 2)", MACHINE_NO_COCKTAIL )
GAME( 1992, sabotenb, 0, saboten_prot, sabotenb, tdragon_prot_214_state, empty_init,ROT0, "NMK / Tecmo", "Saboten Bombers (set 1)", MACHINE_NO_COCKTAIL )
GAME( 1992, sabotenba, sabotenb, saboten_prot, sabotenb, tdragon_prot_214_state, empty_init,ROT0, "NMK / Tecmo", "Saboten Bombers (set 2)", MACHINE_NO_COCKTAIL )
GAME( 1992, cactus, sabotenb, bjtwin, sabotenb, nmk16_state, init_nmk, ROT0, "bootleg", "Cactus (bootleg of Saboten Bombers)", MACHINE_NO_COCKTAIL ) // PCB marked 'Cactus', no title screen
GAME( 1993, bjtwin, 0, bjtwin, bjtwin, nmk16_state, init_bjtwin, ROT270, "NMK", "Bombjack Twin (set 1)", MACHINE_NO_COCKTAIL )

View File

@ -7,6 +7,7 @@
#pragma once
#include "nmk004.h"
#include "nmk214.h"
#include "nmk16spr.h"
#include "seibusound.h"
@ -241,14 +242,13 @@ public:
tdragon_prot_state(const machine_config &mconfig, device_type type, const char *tag) :
nmk16_state(mconfig, type, tag),
m_protcpu(*this, "protcpu")
{}
{
}
void tdragon_prot(machine_config &config);
void hachamf_prot(machine_config &config);
void saboten_prot(machine_config &config);
protected:
virtual void machine_start() override;
virtual void machine_reset() override;
@ -263,10 +263,40 @@ protected:
u8 mcu_port6_r();
u8 mcu_port7_r(); // NMK-113 uses this
u8 m_bus_status;
};
class tdragon_prot_214_state : public tdragon_prot_state
{
public:
tdragon_prot_214_state(const machine_config &mconfig, device_type type, const char *tag) :
tdragon_prot_state(mconfig, type, tag),
m_nmk214(*this, "nmk214_%u", 0U),
m_init_data_nmk214(0),
m_init_clock_nmk214(0),
m_gfx_unscramble_enabled(false),
m_gfx_decoded(false)
{
}
void saboten_prot(machine_config &config);
protected:
virtual void device_post_load() override;
virtual void machine_start() override;
private:
void decode_nmk214();
void mcu_port3_to_214_w(u8 data);
void mcu_port7_to_214_w(u8 data);
u8 m_bus_status;
required_device_array<nmk214_device, 2> m_nmk214;
u8 m_init_data_nmk214;
u8 m_init_clock_nmk214;
bool m_gfx_unscramble_enabled;
bool m_gfx_decoded; // excluded from save states
};
class afega_state : public nmk16_state

213
src/mame/nmk/nmk214.cpp Normal file
View File

@ -0,0 +1,213 @@
// license:BSD-3-Clause
// copyright-holders:Sergio Galiano
/*
NMK214 GFX Descrambler emulation
This device is used for descrambling the GFX data on some game PCBs from NMK (nmk16).
It works in tandem with NMK215, that's a Toshiba MCU which sends initialization data to NMK214 in order to do the
descrambling process.
Every game PCB using it has two NMK214 chips, one for sprites and another for background tiles.
It can work in two different modes: word and byte:
For sprites it always works in word mode; for backgrounds it always works in byte mode.
There are 8 hard-wired internal configurations. The data received from NMK215 selects one of those them at startup.
That init data is stored in the device when bit 3 matches with the operation mode wired directly on the PCB.
The descrambling process is essentially a dynamic bitswap of the incoming word/byte data, doing a different bitswap
based on the address of the data to be descrambled.
The input address bus on the device is used to determine which bitswap do for each word/byte, and it's usually
hooked differently for sprites and background tiles, so an 'input_address_bitswap' is included to get the effective
address the device will use.
*/
#include "emu.h"
#include "nmk214.h"
namespace {
const std::array<u8, 13> default_input_address_bitswap = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// Static byte values for each of the 8 different hardwired configurations.
// One bit will be taken from each byte in the selected config (row), and those
// 3 bits will be used to select the output bitswap down below:
const u8 init_configs[8][3] =
{
{0xaa, 0xcc, 0xf0}, // Predefined config 0
{0x55, 0x39, 0x1e}, // Predefined config 1
{0xc5, 0x69, 0x5c}, // Predefined config 2
{0x35, 0x5c, 0xc5}, // Predefined config 3
{0x78, 0x1d, 0x2e}, // Predefined config 4
{0x55, 0x33, 0x0f}, // Predefined config 5
{0xa5, 0xb8, 0x36}, // Predefined config 6
{0x8b, 0x69, 0x2e} // Predefined config 7
};
// 3 values for each configuration to determine which lines from input address
// bus are used to select a bit from hardwired config byte values above:
const u8 selection_address_bits[8][3] =
{
{0x8, 0x9, 0xa}, // A8, A9 and A10 for predefined config 0
{0x6, 0x8, 0xb}, // A6, A8 and A11 for predefined config 1
{0x3, 0x9, 0xc}, // A3, A9 and A12 for predefined config 2
{0x3, 0x7, 0xa}, // A3, A7 and A10 for predefined config 3
{0x2, 0x5, 0xb}, // A2, A5 and A11 for predefined config 4
{0x1, 0x4, 0xa}, // A1, A4 and A10 for predefined config 5
{0x2, 0x4, 0xa}, // A2, A4 and A10 for predefined config 6
{0x0, 0x4, 0xc} // A0, A4 and A12 for predefined config 7
};
// Output word data bitswaps hardwired inside the chip.
// Each value in the same column represents which bit from input data word is
// used for current bit position in the output word.
const u8 output_word_bitswaps[8][16] =
{
// D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 D14 D15
{0x2, 0x3, 0x7, 0x8, 0xc, 0x4, 0xb, 0x9, 0x1, 0xf, 0xa, 0x5, 0xe, 0x6, 0xd, 0x0}, // word bitswap 0
{0x0, 0x3, 0x8, 0x7, 0xa, 0xc, 0x4, 0x1, 0xf, 0x9, 0x6, 0xd, 0xe, 0xb, 0x5, 0x2}, // word bitswap 1
{0x9, 0x8, 0x2, 0x3, 0x6, 0x5, 0xd, 0xf, 0x7, 0x0, 0xc, 0xb, 0xa, 0x4, 0xe, 0x1}, // word bitswap 2
{0x0, 0x3, 0x9, 0xf, 0xd, 0xc, 0xb, 0x1, 0x2, 0x7, 0xe, 0x6, 0x4, 0xa, 0x5, 0x8}, // word bitswap 3
{0x1, 0x3, 0xf, 0x7, 0xd, 0xa, 0xe, 0x9, 0x0, 0x8, 0xc, 0x4, 0x6, 0x5, 0xb, 0x2}, // word bitswap 4
{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}, // word bitswap 5
{0x0, 0xf, 0x3, 0x2, 0xe, 0x4, 0x6, 0x7, 0x8, 0x9, 0x5, 0xd, 0xc, 0xb, 0xa, 0x1}, // word bitswap 6
{0xf, 0x2, 0x3, 0x1, 0xb, 0xe, 0xd, 0x8, 0x7, 0x0, 0x4, 0xc, 0x6, 0xa, 0x5, 0x9} // word bitswap 7
};
// Output byte data bitswaps hardwired inside the chip.
// Values on the table are calculated from the above word-bitswaps based on the
// following input data lines correlation (that's how the data lines are hooked
// up in real hardware):
/*
BYTE mode | WORD mode
----------|----------
D0 | D13
D1 | D10
D2 | D4
D3 | D12
D4 | D6
D5 | D14
D6 | D11
D7 | D5
*/
const u8 output_byte_bitswaps[8][8] =
{
// D0 D1 D2 D3 D4 D5 D6 D7
{0x4, 0x1, 0x3, 0x5, 0x6, 0x0, 0x7, 0x2}, // byte bitswap 0
{0x6, 0x4, 0x1, 0x5, 0x2, 0x7, 0x0, 0x3}, // byte bitswap 1
{0x2, 0x3, 0x4, 0x1, 0x0, 0x5, 0x6, 0x7}, // byte bitswap 2
{0x1, 0x5, 0x0, 0x2, 0x6, 0x7, 0x4, 0x3}, // byte bitswap 3
{0x7, 0x3, 0x0, 0x4, 0x5, 0x6, 0x2, 0x1}, // byte bitswap 4
{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, // byte bitswap 5
{0x6, 0x7, 0x5, 0x3, 0x4, 0x1, 0x0, 0x2}, // byte bitswap 6
{0x1, 0x2, 0x6, 0x4, 0x0, 0x7, 0x3, 0x5} // byte bitswap 7
};
} // anonymous namespace
DEFINE_DEVICE_TYPE(NMK214, nmk214_device, "nmk214", "NMK214 Graphics Descrambler")
nmk214_device::nmk214_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, NMK214, tag, owner, clock)
, m_mode(0)
, m_input_address_bitswap(default_input_address_bitswap)
, m_init_config(0)
, m_device_initialized(false)
{
}
u8 nmk214_device::get_bitswap_select_value(u32 addr) const noexcept
{
// as the address lines could be hooked up in different order/positions on
// the game PCB, reorder it and get the effective address to use:
const u32 effective_address = bitswap<13>(addr,
m_input_address_bitswap[12],
m_input_address_bitswap[11],
m_input_address_bitswap[10],
m_input_address_bitswap[9],
m_input_address_bitswap[8],
m_input_address_bitswap[7],
m_input_address_bitswap[6],
m_input_address_bitswap[5],
m_input_address_bitswap[4],
m_input_address_bitswap[3],
m_input_address_bitswap[2],
m_input_address_bitswap[1],
m_input_address_bitswap[0]);
// get selection address using only 3 bits of the effective address, based
// on the initial config selected while device initialization:
const u8 selection_address = bitswap<3>(effective_address,
selection_address_bits[m_init_config][2],
selection_address_bits[m_init_config][1],
selection_address_bits[m_init_config][0]);
// get the bitswap selection value, using the previously computed selection
// address and the selected internal config values:
return (BIT(init_configs[m_init_config][0], selection_address) << 0)
| (BIT(init_configs[m_init_config][1], selection_address) << 1)
| (BIT(init_configs[m_init_config][2], selection_address) << 2);
}
u16 nmk214_device::decode_word(u32 addr, u16 data) const noexcept
{
// compute the select value to choose which bitswap apply to the input word,
// based on the address where the data is located
const u8 bitswap_select = get_bitswap_select_value(addr);
return bitswap<16>(data,
output_word_bitswaps[bitswap_select][15],
output_word_bitswaps[bitswap_select][14],
output_word_bitswaps[bitswap_select][13],
output_word_bitswaps[bitswap_select][12],
output_word_bitswaps[bitswap_select][11],
output_word_bitswaps[bitswap_select][10],
output_word_bitswaps[bitswap_select][9],
output_word_bitswaps[bitswap_select][8],
output_word_bitswaps[bitswap_select][7],
output_word_bitswaps[bitswap_select][6],
output_word_bitswaps[bitswap_select][5],
output_word_bitswaps[bitswap_select][4],
output_word_bitswaps[bitswap_select][3],
output_word_bitswaps[bitswap_select][2],
output_word_bitswaps[bitswap_select][1],
output_word_bitswaps[bitswap_select][0]);
}
u8 nmk214_device::decode_byte(u32 addr, u8 data) const noexcept
{
// compute the select value to choose which bitswap apply to the input byte,
// based on the address where the data is located
u8 bitswap_select = get_bitswap_select_value(addr);
return bitswap<8>(data,
output_byte_bitswaps[bitswap_select][7],
output_byte_bitswaps[bitswap_select][6],
output_byte_bitswaps[bitswap_select][5],
output_byte_bitswaps[bitswap_select][4],
output_byte_bitswaps[bitswap_select][3],
output_byte_bitswaps[bitswap_select][2],
output_byte_bitswaps[bitswap_select][1],
output_byte_bitswaps[bitswap_select][0]);
}
void nmk214_device::set_init_config(u8 init_config) noexcept
{
// store config data only if Bit 3 matches with the operation mode of the device
if (BIT(init_config, 3) == m_mode)
{
m_init_config = init_config & 0x7;
m_device_initialized = true;
}
}
void nmk214_device::device_start()
{
save_item(NAME(m_init_config));
save_item(NAME(m_device_initialized));
}
void nmk214_device::device_reset()
{
m_init_config = 0;
m_device_initialized = false;
}

54
src/mame/nmk/nmk214.h Normal file
View File

@ -0,0 +1,54 @@
// license:BSD-3-Clause
// copyright-holders:Sergio Galiano
#ifndef MAME_NMK_NMK214_H
#define MAME_NMK_NMK214_H
#pragma once
#include <array>
class nmk214_device : public device_t
{
public:
nmk214_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
void set_mode(const u8 mode) { m_mode = mode; }
void set_input_address_bitswap(const std::array<u8, 13> &input_address_bitswap) { m_input_address_bitswap = input_address_bitswap; }
void set_init_config(u8 init_config) noexcept;
bool is_device_initialized() const noexcept { return m_device_initialized; }
u16 decode_word(u32 addr, u16 data) const noexcept;
u8 decode_byte(u32 addr, u8 data) const noexcept;
protected:
virtual void device_start() override;
virtual void device_reset() override;
private:
// Operation mode - in practice, only LSB is used.
// Allowed values: 0 or 1.
// This is hard-wired on the PCB, with opposite values for the two devices.
u8 m_mode;
// Input address lines bitswap.
// Represents how the 13 NMK214 address lines are wired to the graphics ROM address bus.
std::array<u8, 13> m_input_address_bitswap;
// Selects between eight internal configurations.
// Bits 0 to 2 select the configuration.
// Bit 3 must be set to match the operation mode for the configuration to take effect.
u8 m_init_config;
// Indicates that the device has been configured and can perform descrambling.
bool m_device_initialized;
// Gets the bitswap index for a given data address.
u8 get_bitswap_select_value(u32 addr) const noexcept;
};
DECLARE_DEVICE_TYPE(NMK214, nmk214_device)
#endif // MAME_NMK_NMK214_H