Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New NMK214 device: added implementation for NMK214 descrambling GFX device and hook it up to nmk16 driver #12039

Merged
merged 5 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 65 additions & 7 deletions src/mame/nmk/nmk16.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ Reference of music tempo:
#include "nmk16.h"

#include "nmk004.h"
#include "nmk214.h"

#include "cpu/m68000/m68000.h"
#include "cpu/pic16c5x/pic16c5x.h"
Expand Down Expand Up @@ -4359,6 +4360,8 @@ u8 tdragon_prot_state::mcu_port6_r()
void tdragon_prot_state::machine_start()
{
save_item(NAME(m_bus_status));
save_item(NAME(m_init_data_nmk214));
save_item(NAME(m_init_clock_nmk214));
}

void tdragon_prot_state::machine_reset()
Expand Down Expand Up @@ -4899,19 +4902,38 @@ 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
// the 215 writes minimal data here. Data is, in fact, used for selecting one of the internal configurations hardwired inside NMK214 device to make the decryption logic:
void tdragon_prot_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);

// TEMP: 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_nmk214[0]->is_device_initialized() && m_nmk214[1]->is_device_initialized())
decode_sabotenb();
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved
}

m_init_clock_nmk214 = BIT(data, 2);
}
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved

void tdragon_prot_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;
}

// 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 in each NMK214 address bus position, starting by the LSB
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved
static const u8 nmk214_sprites_address_bitswap[] = {0, 1, 2, 3, 10, 12, 13, 14, 15, 16, 17, 18, 19};
static const u8 nmk214_bg_address_bitswap[] = {0, 1, 2, 3, 11, 13, 14, 15, 16, 17, 18, 19, 20};

void tdragon_prot_state::saboten_prot(machine_config &config)
{
bjtwin(config);
Expand All @@ -4925,6 +4947,14 @@ void tdragon_prot_state::saboten_prot(machine_config &config)
// 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));

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));
}
Expand Down Expand Up @@ -5118,6 +5148,34 @@ void nmk16_state::decode_gfx()
}
}

void tdragon_prot_state::decode_sabotenb()
{
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; A += 2)
{
// When the sprite ROM is loaded, it's also byte-swapped, so we keep the same order here to compound the word (A: second-byte, A+1: first-byte)
u16 word = (rom[A] << 8) | rom[A+1];
// As Sprites ROM works in word mode, address value here is in terms of bytes and should be divided by 2 to get the proper base-16 address value
u16 decoded_word = m_nmk214[0]->decode_word(A/2, word);

rom[A] = decoded_word >> 8;
rom[A+1] = decoded_word & 0xff;
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved
}
}
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved

void nmk16_state::decode_tdragonb()
{
/* Descrambling Info Again Taken from Raine, Huge Thanks to Antiriad and the Raine Team for
Expand Down Expand Up @@ -8981,8 +9039,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_state, empty_init, ROT0, "NMK / Tecmo", "Saboten Bombers (set 1)", MACHINE_NO_COCKTAIL )
GAME( 1992, sabotenba, sabotenb, saboten_prot, sabotenb, tdragon_prot_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 )
Expand Down
12 changes: 11 additions & 1 deletion src/mame/nmk/nmk16.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#pragma once

#include "nmk004.h"
#include "nmk214.h"
#include "nmk16spr.h"

#include "seibusound.h"
Expand Down Expand Up @@ -240,7 +241,8 @@ class tdragon_prot_state : public nmk16_state
public:
tdragon_prot_state(const machine_config &mconfig, device_type type, const char *tag) :
nmk16_state(mconfig, type, tag),
m_protcpu(*this, "protcpu")
m_protcpu(*this, "protcpu"),
m_nmk214(*this, "nmk214_%u", 0U)
{}

void tdragon_prot(machine_config &config);
Expand All @@ -253,9 +255,12 @@ class tdragon_prot_state : public nmk16_state
virtual void machine_reset() override;

optional_device<tlcs90_device> m_protcpu;
optional_device_array<nmk214_device, 2> m_nmk214;

void tdragon_prot_map(address_map &map);

void decode_sabotenb();

void mcu_side_shared_w(offs_t offset, u8 data);
u8 mcu_side_shared_r(offs_t offset);
void mcu_port6_w(u8 data);
Expand All @@ -267,6 +272,11 @@ class tdragon_prot_state : public nmk16_state
void mcu_port7_to_214_w(u8 data);

u8 m_bus_status;

private:

u8 m_init_data_nmk214;
u8 m_init_clock_nmk214;
};

class afega_state : public nmk16_state
Expand Down
191 changes: 191 additions & 0 deletions src/mame/nmk/nmk214.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// 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 is a Toshiba MCU which sends initialization data to NMK214 to perform the descrambling process.
Every game pcb using it has two units of NMK214, one for descrambling sprite data and another one for descrambling background tiles data.
It can work in two different modes: word and byte. For sprites it always works in word mode. On the order side, for backgrounds it always works in byte mode.
There are 8 different internal configs hardwired inside the device, and the data received from NMK215 selects one of those 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 up in different way for sprites and bg tiles, so an 'input_address_bitswap' is included in the implementation in order to get the effective address the device will use internally for each case.
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved
*/


#include "emu.h"
#include "nmk214.h"


static const u8 default_input_address_bitswap[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};


DEFINE_DEVICE_TYPE(NMK214, nmk214_device, "nmk214", "NMK214 GFX 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_init_config(0)
, m_device_initialized(false)
, m_input_address_bitswap(default_input_address_bitswap)
{
}

// static byte values for each of the 8 different hardwired configs. 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:
static 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 config to determine which lines from input address bus are used to select one bit from hardwired config byte values above:
static 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 the same column represents which bit from input data word is used for current bit position in the output word.
static 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 this 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 hw):
/*
BYTE mode | WORD mode
----------|----------
D0 | D13
D1 | D10
D2 | D4
D3 | D12
D4 | D6
D5 | D14
D6 | D11
D7 | D5
*/
static 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
};

u8 nmk214_device::get_bitswap_select_value(u32 addr)
{
// 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:
u32 effective_address = bitswap(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:
u8 selection_address =
(BIT(effective_address, selection_address_bits[m_init_config][0]) << 0)
| (BIT(effective_address, selection_address_bits[m_init_config][1]) << 1)
| (BIT(effective_address, selection_address_bits[m_init_config][2]) << 2);

// 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)
{
// compute the select value to choose which bitswap apply to the input word, based on the address where the data is located
u8 bitswap_select = get_bitswap_select_value(addr);

return bitswap(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)
{
// 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(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)
{
// 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_mode));
save_item(NAME(m_init_config));
save_item(NAME(m_device_initialized));
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved
}

void nmk214_device::device_reset()
{
m_init_config = 0;
m_device_initialized = false;
}
38 changes: 38 additions & 0 deletions src/mame/nmk/nmk214.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// license:BSD-3-Clause
// copyright-holders:Sergio Galiano
#ifndef MAME_NMK_NMK214_H
#define MAME_NMK_NMK214_H

#pragma once


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(u8 mode) { m_mode = mode; }
void set_init_config(u8 init_config);
bool is_device_initialized() { return m_device_initialized; }
void set_input_address_bitswap(const u8 *input_address_bitswap) { m_input_address_bitswap = input_address_bitswap; }
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved

u16 decode_word(u32 addr, u16 data);
u8 decode_byte(u32 addr, u8 data);

protected:
virtual void device_start() override;
virtual void device_reset() override;

private:
u8 m_mode; // Operation mode. In practice, only LSB is used. Allowed values: 0 or 1. This value is hardwired for each NM214 out of 2 ones in the game board, each device having a different configuration than the other.
u8 m_init_config; // An init config out of 8 existing ones in NMK214 to be used while descrambling GFX. In practice, only lower 3 bits (From 0 to 2) are used, allowed values: 0 to 7. Bit 3 is used to match with operation mode and let the init config to be stored on the device or not
bool m_device_initialized; // Flag to mark the device already received the init configuration and it can perform descrambling from now on

const u8 *m_input_address_bitswap; // Input address lines bitswap. These represents the way the 13 address lines on NMK214 (from A0 to A12) are hooked up externally related to the GFX ROMs address bus, that could be in different order/position
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved

u8 get_bitswap_select_value(u32 addr); // Gets the a value to select the output bitswap to apply to the input data, based on the address where the data is located
sergiopolog marked this conversation as resolved.
Show resolved Hide resolved
};

DECLARE_DEVICE_TYPE(NMK214, nmk214_device)

#endif // MAME_NMK_NMK214_H