334 lines
7.8 KiB
C++
334 lines
7.8 KiB
C++
/*
|
|
* m4cartridge.cpp
|
|
*
|
|
* Created on: Nov 5, 2018
|
|
* Author: flyinghead
|
|
* Plagiarized from mame (mame/src/mame/machine/naomim4.cpp)
|
|
* // license:BSD-3-Clause
|
|
* // copyright-holders:Olivier Galibert,Andreas Naive
|
|
*/
|
|
|
|
#include "m4cartridge.h"
|
|
#include "serialize.h"
|
|
|
|
|
|
// Decoder for M4-type NAOMI cart encryption
|
|
|
|
// In hardware, the decryption is managed by the XC3S50 Xilinx Spartan FPGA (IC2)
|
|
// and the annexed PIC16C621A PIC MCU (IC3).
|
|
// - The FPGA control the clock line of the security PIC.
|
|
// - The protocol between the FPGA and the MCU is nibble-based, though it hasn't been RE for now.
|
|
// - The decryption algorithm is clearly nibble-based too.
|
|
|
|
// The decryption algorithm itself implements a stream cipher built on top of a 16-bits block cipher.
|
|
// The underlying block-cipher is a SP-network of 2 rounds (both identical in structure). In every
|
|
// round, the substitution phase is done using 4 fixed 4-to-4 sboxes acting on every nibble. The permutation
|
|
// phase is indeed a nibble-based linear combination.
|
|
// With that block cipher, a stream cipher is constructed by feeding the output result of the 1st round
|
|
// of a certain 16-bits block as a whitening value for the next block. The cart dependent data used by
|
|
// the algorithm is a 32-bits key stored in the PIC16C621A. The hardware auto-reset the feed value
|
|
// to the cart-based IV every 16 blocks (32 bytes); that reset is not address-based, but index-based.
|
|
|
|
const u8 M4Cartridge::k_sboxes[4][16] = {
|
|
{9,8,2,11,1,14,5,15,12,6,0,3,7,13,10,4},
|
|
{2,10,0,15,14,1,11,3,7,12,13,8,4,9,5,6},
|
|
{4,11,3,8,7,2,15,13,1,5,14,9,6,12,0,10},
|
|
{1,13,8,2,0,5,6,14,4,11,15,10,12,3,7,9}
|
|
};
|
|
|
|
// from S29GL512N datasheet
|
|
static u8 cfidata[] = {
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x51,0x00,0x52,0x00,0x59,0x00,0x02,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x07,0x00,
|
|
0x07,0x00,0x0a,0x00,0x00,0x00,0x01,0x00,0x05,0x00,0x04,0x00,0x00,0x00,0x1a,0x00,0x02,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x01,0x00,0xff,0x00,0x01,0x00,0x00,0x00,
|
|
0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x50,0x00,0x52,0x00,0x49,0x00,0x31,0x00,0x33,0x00,0x10,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0xb5,0x00,0xc5,0x00,0x04,0x00,
|
|
0x01,0x00
|
|
};
|
|
|
|
void M4Cartridge::device_start()
|
|
{
|
|
if (m4id == 0)
|
|
{
|
|
INFO_LOG(NAOMI, "Warning: M4 ID not provided\n");
|
|
m4id = 0x5504;
|
|
}
|
|
|
|
if (m_key_data != nullptr)
|
|
{
|
|
subkey1 = (m_key_data[0x5e2] << 8) | m_key_data[0x5e0];
|
|
subkey2 = (m_key_data[0x5e6] << 8) | m_key_data[0x5e4];
|
|
}
|
|
else
|
|
{
|
|
WARN_LOG(NAOMI, "Missing M4 key");
|
|
}
|
|
|
|
enc_init();
|
|
}
|
|
|
|
void M4Cartridge::enc_init()
|
|
{
|
|
for (int round_input = 0; round_input < 0x10000; round_input++)
|
|
{
|
|
u8 input_nibble[4];
|
|
u8 output_nibble[4];
|
|
|
|
for (int nibble_idx = 0; nibble_idx < 4; ++nibble_idx) {
|
|
input_nibble[nibble_idx] = (round_input >> (nibble_idx*4)) & 0xf;
|
|
output_nibble[nibble_idx] = 0;
|
|
}
|
|
|
|
u8 aux_nibble = input_nibble[3];
|
|
for (int nibble_idx = 0; nibble_idx < 4; ++nibble_idx) { // 4 s-boxes per round
|
|
aux_nibble ^= k_sboxes[nibble_idx][input_nibble[nibble_idx]];
|
|
for (int i = 0; i < 4; ++i) // diffusion of the bits
|
|
output_nibble[(nibble_idx - i) & 3] |= aux_nibble & (1 << i);
|
|
}
|
|
|
|
u16 result = 0;
|
|
for (int nibble_idx = 0; nibble_idx < 4; ++nibble_idx)
|
|
result |= (output_nibble[nibble_idx] << (4 * nibble_idx));
|
|
|
|
one_round[round_input] = result;
|
|
}
|
|
}
|
|
|
|
void M4Cartridge::device_reset()
|
|
{
|
|
rom_cur_address = 0;
|
|
buffer_actual_size = 0;
|
|
encryption = false;
|
|
cfi_mode = false;
|
|
counter = 0;
|
|
iv = 0;
|
|
xfer_ready = false;
|
|
}
|
|
|
|
void M4Cartridge::DmaOffsetChanged(u32 dma_offset)
|
|
{
|
|
xfer_ready = false;
|
|
}
|
|
|
|
void M4Cartridge::PioOffsetChanged(u32 pio_offset)
|
|
{
|
|
encryption = pio_offset & 0x40000000;
|
|
xfer_ready = false;
|
|
}
|
|
|
|
|
|
bool M4Cartridge::Read(u32 offset, u32 size, void *dst) {
|
|
if (cfi_mode)
|
|
{
|
|
u32 fpr_num = m4id & 0x7f;
|
|
|
|
if (((offset >> 26) & 0x07) < fpr_num) {
|
|
*(u16 *)dst = *(u16 *)&cfidata[offset & 0xffff];
|
|
return true;
|
|
}
|
|
}
|
|
if (!xfer_ready)
|
|
{
|
|
rom_cur_address = RomPioOffset & 0x1ffffffe;
|
|
if (encryption)
|
|
{
|
|
//printf("M4 CRYPT m4id %x skey1 %x skey2 %x RomPioOffset %08x\n", m4id, subkey1, subkey2, RomPioOffset);
|
|
enc_reset();
|
|
enc_fill();
|
|
}
|
|
xfer_ready = true;
|
|
}
|
|
if (encryption)
|
|
{
|
|
switch (size)
|
|
{
|
|
case 2:
|
|
*(u16 *)dst = *(u16 *)buffer;
|
|
break;
|
|
case 4:
|
|
*(u32 *)dst = *(u32 *)buffer;
|
|
break;
|
|
}
|
|
if (RomPioAutoIncrement)
|
|
AdvancePtr(size);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return NaomiCartridge::Read(offset & 0x1ffffffe, size, dst);
|
|
}
|
|
|
|
void *M4Cartridge::GetDmaPtr(u32 &size)
|
|
{
|
|
static u8 retzero[2] = { 0, 0 };
|
|
|
|
if (cfi_mode) {
|
|
u32 fpr_num = m4id & 0x7f;
|
|
|
|
if (((rom_cur_address >> 26) & 0x07) < fpr_num) {
|
|
size = std::min(size, 2u);
|
|
return &cfidata[rom_cur_address & 0xffff];
|
|
}
|
|
}
|
|
|
|
if (!xfer_ready)
|
|
{
|
|
rom_cur_address = DmaOffset & 0x1ffffffe;
|
|
if (encryption)
|
|
{
|
|
//printf("M4 CRYPT m4id %x skey1 %x skey2 %x DmaOffset %08x\n", m4id, subkey1, subkey2, DmaOffset);
|
|
enc_reset();
|
|
enc_fill();
|
|
}
|
|
xfer_ready = true;
|
|
}
|
|
if (encryption)
|
|
{
|
|
size = std::min(size, (u32)sizeof(buffer));
|
|
return buffer;
|
|
|
|
}
|
|
else
|
|
{
|
|
if ((DmaOffset & 0x1ffffffe) < RomSize)
|
|
{
|
|
size = std::min(size, RomSize - (DmaOffset & 0x1ffffffe));
|
|
return RomPtr + (DmaOffset & 0x1ffffffe);
|
|
}
|
|
else
|
|
{
|
|
size = 2;
|
|
return retzero;
|
|
}
|
|
}
|
|
}
|
|
|
|
void M4Cartridge::AdvancePtr(u32 size)
|
|
{
|
|
if (encryption)
|
|
{
|
|
if (size < buffer_actual_size)
|
|
{
|
|
memmove(buffer, buffer + size, buffer_actual_size - size);
|
|
buffer_actual_size -= size;
|
|
}
|
|
else
|
|
buffer_actual_size = 0;
|
|
enc_fill();
|
|
}
|
|
else
|
|
rom_cur_address += size;
|
|
}
|
|
|
|
void M4Cartridge::enc_reset()
|
|
{
|
|
buffer_actual_size = 0;
|
|
iv = 0;
|
|
counter = 0;
|
|
}
|
|
|
|
u16 M4Cartridge::decrypt_one_round(u16 word, u16 subkey)
|
|
{
|
|
return one_round[word ^ subkey] ^ subkey ;
|
|
}
|
|
|
|
u16 M4Cartridge::decrypt(u16 enc)
|
|
{
|
|
u16 dec = iv;
|
|
iv = decrypt_one_round(enc ^ iv, subkey1);
|
|
dec ^= decrypt_one_round(iv, subkey2);
|
|
counter++;
|
|
if (counter == 16) {
|
|
counter = 0;
|
|
iv = 0;
|
|
}
|
|
|
|
return dec;
|
|
}
|
|
|
|
void M4Cartridge::enc_fill()
|
|
{
|
|
const u8 *base = RomPtr + rom_cur_address;
|
|
while (buffer_actual_size < sizeof(buffer))
|
|
{
|
|
u16 dec = decrypt(base[0] | (base[1] << 8));
|
|
|
|
buffer[buffer_actual_size++] = dec;
|
|
buffer[buffer_actual_size++] = dec >> 8;
|
|
|
|
base += 2;
|
|
rom_cur_address += 2;
|
|
}
|
|
// printf("Decrypted M4 data:\n");
|
|
// for (int i = 0; i < buffer_actual_size; i++)
|
|
// {
|
|
// printf("%c ", buffer[i]);
|
|
// if ((i + 1) % 16 == 0)
|
|
// printf("\n");
|
|
// }
|
|
// printf("\n");
|
|
|
|
|
|
}
|
|
|
|
bool M4Cartridge::Write(u32 offset, u32 size, u32 data)
|
|
{
|
|
if (((offset&0xffff) == 0x00aa) && (data == 0x0098))
|
|
cfi_mode = true;
|
|
if (((offset&0xffff) == 0x0000) && (data == 0x00f0))
|
|
cfi_mode = false;
|
|
return true; // ?
|
|
}
|
|
|
|
M4Cartridge::~M4Cartridge()
|
|
{
|
|
free(m_key_data);
|
|
}
|
|
|
|
bool M4Cartridge::GetBootId(RomBootID *bootId)
|
|
{
|
|
if (RomSize < sizeof(RomBootID))
|
|
return false;
|
|
RomBootID *pBootId = (RomBootID *)RomPtr;
|
|
if (memcmp(pBootId->boardName, "NAOMI", 5)
|
|
&& memcmp(pBootId->boardName, "Naomi2", 6)
|
|
&& memcmp(pBootId->boardName, "SystemSP", 8))
|
|
{
|
|
rom_cur_address = 0;
|
|
enc_reset();
|
|
enc_fill();
|
|
pBootId = (RomBootID *)buffer;
|
|
}
|
|
memcpy(bootId, pBootId, sizeof(RomBootID));
|
|
|
|
return true;
|
|
}
|
|
|
|
void M4Cartridge::Serialize(Serializer& ser) const
|
|
{
|
|
ser << buffer;
|
|
ser << rom_cur_address;
|
|
ser << buffer_actual_size;
|
|
ser << iv;
|
|
ser << counter;
|
|
ser << encryption;
|
|
ser << cfi_mode;
|
|
ser << xfer_ready;
|
|
|
|
NaomiCartridge::Serialize(ser);
|
|
}
|
|
|
|
void M4Cartridge::Deserialize(Deserializer& deser)
|
|
{
|
|
deser >> buffer;
|
|
deser >> rom_cur_address;
|
|
deser >> buffer_actual_size;
|
|
deser >> iv;
|
|
deser >> counter;
|
|
deser >> encryption;
|
|
deser >> cfi_mode;
|
|
deser >> xfer_ready;
|
|
|
|
NaomiCartridge::Deserialize(deser);
|
|
}
|