flycast/core/hw/naomi/m4cartridge.cpp

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);
}