x76f041/x76f100/zs01: Implement new operations and security features (#9137)

* x76f100: Implement security features

* x76f041: Implement security features

* zs01: Implement security features

* ksys573: Update security flash data

* k573mcal: Add master calendar for initializing security cassettes

* zs01: Update comment about unknown serial
This commit is contained in:
987123879113 2022-02-08 09:13:24 +09:00 committed by GitHub
parent e450cc7953
commit 99404ddd2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1006 additions and 286 deletions

View File

@ -2545,6 +2545,8 @@ files {
MAME_DIR .. "src/mame/machine/k573fpga.h",
MAME_DIR .. "src/mame/machine/k573kara.cpp",
MAME_DIR .. "src/mame/machine/k573kara.h",
MAME_DIR .. "src/mame/machine/k573mcal.cpp",
MAME_DIR .. "src/mame/machine/k573mcal.h",
MAME_DIR .. "src/mame/machine/k573mcr.cpp",
MAME_DIR .. "src/mame/machine/k573mcr.h",
MAME_DIR .. "src/mame/machine/k573msu.cpp",

View File

@ -48,13 +48,15 @@ x76f041_device::x76f041_device( const machine_config &mconfig, const char *tag,
m_bit( 0 ),
m_byte( 0 ),
m_command( 0 ),
m_address( 0 )
m_address( 0 ),
m_is_password_accepted ( false )
{
}
void x76f041_device::device_start()
{
memset( m_write_buffer, 0, sizeof( m_write_buffer ) );
std::fill( std::begin( m_write_buffer ), std::end( m_write_buffer ), 0 );
std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 );
save_item( NAME( m_cs ) );
save_item( NAME( m_rst ) );
@ -67,6 +69,7 @@ void x76f041_device::device_start()
save_item( NAME( m_byte ) );
save_item( NAME( m_command ) );
save_item( NAME( m_address ) );
save_item( NAME( m_is_password_accepted ) );
save_item( NAME( m_write_buffer ) );
save_item( NAME( m_response_to_reset ) );
save_item( NAME( m_write_password ) );
@ -74,6 +77,26 @@ void x76f041_device::device_start()
save_item( NAME( m_configuration_password ) );
save_item( NAME( m_configuration_registers ) );
save_item( NAME( m_data ) );
save_item( NAME( m_password_temp ) );
}
void x76f041_device::device_reset()
{
std::fill( std::begin( m_write_buffer ), std::end( m_write_buffer ), 0 );
std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 );
m_cs = 0;
m_rst = 0;
m_scl = 0;
m_sdaw = 0;
m_sdar = 0;
m_state = STATE_STOP;
m_shift = 0;
m_bit = 0;
m_byte = 0;
m_command = 0;
m_address = 0;
m_is_password_accepted = false;
}
WRITE_LINE_MEMBER( x76f041_device::write_cs )
@ -128,6 +151,15 @@ uint8_t *x76f041_device::password()
case COMMAND_READ:
return m_read_password;
case COMMAND_CONFIGURATION:
if( m_address == CONFIGURATION_PROGRAM_WRITE_PASSWORD )
return m_write_password;
if( m_address == CONFIGURATION_PROGRAM_READ_PASSWORD )
return m_read_password;
return m_configuration_password;
default:
return m_configuration_password;
}
@ -135,6 +167,9 @@ uint8_t *x76f041_device::password()
void x76f041_device::password_ok()
{
if( m_configuration_registers[ CONFIG_CR ] & CR_RETRY_COUNTER_RESET_BIT )
m_configuration_registers[ CONFIG_RC ] = 0;
switch( m_command & 0xe0 )
{
case COMMAND_WRITE:
@ -144,7 +179,7 @@ void x76f041_device::password_ok()
m_state = STATE_READ_DATA;
break;
case COMMAND_WRITE_USE_CONFIGURATION_PASSWORD:
m_state = STATE_WRITE_DATA;
m_state = STATE_CONFIGURATION_WRITE_DATA;
break;
case COMMAND_READ_USE_CONFIGURATION_PASSWORD:
m_state = STATE_READ_DATA;
@ -153,14 +188,22 @@ void x76f041_device::password_ok()
switch( m_address )
{
case CONFIGURATION_PROGRAM_WRITE_PASSWORD:
m_state = STATE_PROGRAM_WRITE_PASSWORD;
m_byte = 0;
break;
case CONFIGURATION_PROGRAM_READ_PASSWORD:
m_state = STATE_PROGRAM_READ_PASSWORD;
m_byte = 0;
break;
case CONFIGURATION_PROGRAM_CONFIGURATION_PASSWORD:
m_state = STATE_PROGRAM_CONFIGURATION_PASSWORD;
m_byte = 0;
break;
case CONFIGURATION_RESET_WRITE_PASSWORD:
m_state = STATE_RESET_WRITE_PASSWORD;
break;
case CONFIGURATION_RESET_READ_PASSWORD:
m_state = STATE_RESET_READ_PASSWORD;
break;
case CONFIGURATION_PROGRAM_CONFIGURATION_REGISTERS:
m_state = STATE_WRITE_CONFIGURATION_REGISTERS;
@ -171,8 +214,10 @@ void x76f041_device::password_ok()
m_byte = 0;
break;
case CONFIGURATION_MASS_PROGRAM:
m_state = STATE_MASS_PROGRAM;
break;
case CONFIGURATION_MASS_ERASE:
m_state = STATE_MASS_ERASE;
break;
default:
break;
@ -182,21 +227,56 @@ void x76f041_device::password_ok()
void x76f041_device::load_address()
{
/* todo: handle other bcr bits */
int bcr;
m_address = m_shift;
verboselog( 1, "-> address: %02x\n", m_address );
if( ( m_command & 1 ) == 0 )
if( ( m_configuration_registers[ CONFIG_CR ] & CR_RETRY_COUNTER_ENABLE_BIT ) != 0 &&
m_configuration_registers[ CONFIG_RR ] == m_configuration_registers[ CONFIG_RC ] &&
( m_configuration_registers[ CONFIG_CR ] & CR_UNAUTHORIZED_ACCESS_BITS ) == 0x80 )
{
bcr = m_configuration_registers[ CONFIG_BCR1 ];
// No commands are allowed
verboselog( 1, "unauthorized access rejected\n" );
m_state = STATE_STOP;
m_sdar = 1;
m_byte = 0;
return;
}
else
if( ( m_command & 0xe0 ) == COMMAND_CONFIGURATION )
{
bcr = m_configuration_registers[ CONFIG_BCR2 ];
// Configuration commands can be used regardless of array control register bits
if( m_address == CONFIGURATION_RESET_WRITE_PASSWORD ||
m_address == CONFIGURATION_RESET_READ_PASSWORD ||
m_address == CONFIGURATION_MASS_PROGRAM ||
m_address == CONFIGURATION_MASS_ERASE )
{
verboselog( 1, "password not required\n" );
password_ok();
}
else
{
verboselog( 1, "send password\n" );
m_state = STATE_LOAD_PASSWORD;
m_byte = 0;
}
return;
}
if( ( m_configuration_registers[ CONFIG_CR ] & CR_RETRY_COUNTER_ENABLE_BIT ) != 0 &&
m_configuration_registers[ CONFIG_RR ] == m_configuration_registers[ CONFIG_RC ] &&
( m_configuration_registers[ CONFIG_CR ] & CR_UNAUTHORIZED_ACCESS_BITS ) != 0x80 )
{
// Only configuration commands are allowed
verboselog( 1, "unauthorized access rejected\n" );
m_state = STATE_STOP;
m_sdar = 1;
m_byte = 0;
return;
}
int bcr = m_configuration_registers[ ( m_command & 1 ) ? CONFIG_BCR2 : CONFIG_BCR1 ];
if( ( m_address & 0x80 ) != 0 )
{
bcr >>= 4;
@ -208,7 +288,8 @@ void x76f041_device::load_address()
/* todo: find out when this is really checked. */
verboselog( 1, "command not allowed\n" );
m_state = STATE_STOP;
m_sdar = 0;
m_sdar = 1;
m_byte = 0;
}
else if( ( ( m_command & 0xe0 ) == COMMAND_WRITE && ( bcr & BCR_X ) == 0 ) ||
( ( m_command & 0xe0 ) == COMMAND_READ && ( bcr & BCR_Y ) == 0 ) )
@ -272,7 +353,15 @@ WRITE_LINE_MEMBER( x76f041_device::write_scl )
case STATE_LOAD_PASSWORD:
case STATE_VERIFY_PASSWORD:
case STATE_WRITE_DATA:
case STATE_CONFIGURATION_WRITE_DATA:
case STATE_WRITE_CONFIGURATION_REGISTERS:
case STATE_PROGRAM_WRITE_PASSWORD:
case STATE_PROGRAM_READ_PASSWORD:
case STATE_PROGRAM_CONFIGURATION_PASSWORD:
case STATE_RESET_WRITE_PASSWORD:
case STATE_RESET_READ_PASSWORD:
case STATE_MASS_PROGRAM:
case STATE_MASS_ERASE:
if( m_scl == 0 && state != 0 )
{
if( m_bit < 8 )
@ -311,6 +400,16 @@ WRITE_LINE_MEMBER( x76f041_device::write_scl )
if( m_byte == sizeof( m_write_buffer ) )
{
m_state = STATE_VERIFY_PASSWORD;
// Perform the password acceptance check before verify password because
// password verify ack is spammed and will quickly overflow the
// retry counter.
m_is_password_accepted = memcmp( password(), m_write_buffer, sizeof( m_write_buffer ) ) == 0;
if( !m_is_password_accepted )
{
if( m_configuration_registers[ CONFIG_CR ] & CR_RETRY_COUNTER_ENABLE_BIT )
m_configuration_registers[ CONFIG_RC ]++;
}
}
break;
@ -321,7 +420,7 @@ WRITE_LINE_MEMBER( x76f041_device::write_scl )
if( m_shift == 0xc0 )
{
/* todo: this should take 10ms before it returns ok. */
if( memcmp( password(), m_write_buffer, sizeof( m_write_buffer ) ) == 0 )
if( m_is_password_accepted )
{
password_ok();
}
@ -338,6 +437,36 @@ WRITE_LINE_MEMBER( x76f041_device::write_scl )
if( m_byte == sizeof( m_write_buffer ) )
{
int bcr = m_configuration_registers[ ( m_command & 1 ) ? CONFIG_BCR2 : CONFIG_BCR1 ];
if( ( m_address & 0x80 ) != 0 )
{
bcr >>= 4;
}
if( ( bcr & ( BCR_Z | BCR_T ) ) == BCR_T )
{
// Bits in the data can only be set, not cleared, when in program only mode
bool is_unauthorized_write = false;
for( m_byte = 0; m_byte < sizeof( m_write_buffer ); m_byte++ )
{
int offset = data_offset();
if( m_write_buffer[ m_byte ] < m_data[ offset ] )
{
verboselog( 1, "tried to unset bits while in program only mode\n" );
is_unauthorized_write = true;
break;
}
}
if( is_unauthorized_write )
{
m_sdar = 1;
m_byte = 0;
break;
}
}
for( m_byte = 0; m_byte < sizeof( m_write_buffer ); m_byte++ )
{
int offset = data_offset();
@ -350,6 +479,15 @@ WRITE_LINE_MEMBER( x76f041_device::write_scl )
}
break;
case STATE_CONFIGURATION_WRITE_DATA:
// Unlike normal writes, configuration writes aren't required to be exactly 8 bytes
// TODO: Store data in a temporary buffer until the proper end of the command before writing
verboselog( 2, "-> data: %02x\n", m_shift );
m_data[ data_offset() ] = m_shift;
m_byte++;
break;
case STATE_WRITE_CONFIGURATION_REGISTERS:
verboselog( 1, "-> configuration register[ %d ]: %02x\n", m_byte, m_shift );
/* todo: write after all bytes received? */
@ -360,6 +498,90 @@ WRITE_LINE_MEMBER( x76f041_device::write_scl )
m_byte = 0;
}
break;
case STATE_PROGRAM_WRITE_PASSWORD:
verboselog( 1, "-> program write password[ %d ]: %02x\n", m_byte, m_shift );
m_password_temp[ m_byte++ ] = m_shift;
if( m_byte == sizeof( m_password_temp ) )
{
// Read in the password twice and if the two copies match then write it to the password field
if( memcmp( &m_password_temp[ 0 ], &m_password_temp[ 8 ], sizeof( m_write_password ) ) == 0 )
{
std::copy_n( std::begin( m_password_temp ), sizeof( m_write_password ), std::begin ( m_write_password ) );
}
else {
m_sdar = 1;
}
std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 );
m_byte = 0;
}
break;
case STATE_PROGRAM_READ_PASSWORD:
verboselog( 1, "-> program read password[ %d ]: %02x\n", m_byte, m_shift );
m_password_temp[ m_byte++ ] = m_shift;
if( m_byte == sizeof( m_password_temp ) )
{
if( memcmp( &m_password_temp[ 0 ], &m_password_temp[ 8 ], sizeof( m_read_password ) ) == 0 )
{
std::copy_n( std::begin( m_password_temp ), sizeof( m_read_password ), std::begin ( m_read_password ) );
}
else {
m_sdar = 1;
}
std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 );
m_byte = 0;
}
break;
case STATE_PROGRAM_CONFIGURATION_PASSWORD:
verboselog( 1, "-> program configuration password[ %d ]: %02x\n", m_byte, m_shift );
m_password_temp[ m_byte++ ] = m_shift;
if( m_byte == sizeof( m_password_temp ) )
{
if( memcmp( &m_password_temp[ 0 ], &m_password_temp[ 8 ], sizeof( m_configuration_password ) ) == 0 )
{
std::copy_n( std::begin( m_password_temp ), sizeof( m_configuration_password ), std::begin ( m_configuration_password ) );
}
else {
m_sdar = 1;
}
std::fill( std::begin( m_password_temp ), std::end( m_password_temp ), 0 );
m_byte = 0;
}
break;
case STATE_RESET_WRITE_PASSWORD:
verboselog( 1, "-> reset write password\n" );
std::fill( std::begin( m_write_password ), std::end( m_write_password ), 0 );
break;
case STATE_RESET_READ_PASSWORD:
verboselog( 1, "-> reset read password\n" );
std::fill( std::begin( m_read_password ), std::end( m_read_password ), 0 );
break;
case STATE_MASS_PROGRAM:
case STATE_MASS_ERASE:
{
const uint8_t fill = m_state == STATE_MASS_ERASE ? 0xff : 0;
verboselog( 1, "-> mass erase %02x\n", fill );
std::fill( std::begin( m_data ), std::end( m_data ), fill );
std::fill( std::begin( m_configuration_password ), std::end( m_configuration_password ), fill );
std::fill( std::begin( m_configuration_registers ), std::end( m_configuration_registers ), fill );
std::fill( std::begin( m_write_password ), std::end( m_write_password ), fill );
std::fill( std::begin( m_read_password ), std::end( m_read_password ), fill );
break;
}
}
m_bit = 0;

View File

@ -29,6 +29,7 @@ public:
protected:
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
// device_nvram_interface overrides
virtual void nvram_default() override;
@ -44,11 +45,24 @@ private:
enum configuration_register_t
{
CONFIG_BCR1 = 0,
CONFIG_BCR2 = 1,
CONFIG_CR = 2,
CONFIG_RR = 3,
CONFIG_RC = 4
// If set to 1, retry counter is incremented when an invalid password is provided
CR_RETRY_COUNTER_ENABLE_BIT = 0x04,
// If set to 1, retry counter will be reset when a correct password is provided
CR_RETRY_COUNTER_RESET_BIT = 0x08,
// 10 = If retry counter is enabled, deny all commands when retry register equals retry counter
// 00, 01, 11 = If retry counter is enabled, allow only configuration commands when retry register equals retry counter
CR_UNAUTHORIZED_ACCESS_BITS = 0xc0,
};
enum configuration_registers_t
{
CONFIG_BCR1 = 0, // Array Control Register
CONFIG_BCR2 = 1, // Array Control Register 2
CONFIG_CR = 2, // Configuration Register
CONFIG_RR = 3, // Retry Register
CONFIG_RC = 4 // Reset Counter
};
enum bcr_t
@ -91,8 +105,18 @@ private:
STATE_VERIFY_PASSWORD,
STATE_READ_DATA,
STATE_WRITE_DATA,
STATE_CONFIGURATION_WRITE_DATA,
STATE_READ_CONFIGURATION_REGISTERS,
STATE_WRITE_CONFIGURATION_REGISTERS
STATE_WRITE_CONFIGURATION_REGISTERS,
STATE_PROGRAM_WRITE_PASSWORD,
STATE_PROGRAM_READ_PASSWORD,
STATE_PROGRAM_CONFIGURATION_PASSWORD,
STATE_RESET_WRITE_PASSWORD,
STATE_RESET_READ_PASSWORD,
STATE_MASS_PROGRAM,
STATE_MASS_ERASE
};
optional_memory_region m_region;
@ -109,6 +133,7 @@ private:
int m_byte;
int m_command;
int m_address;
bool m_is_password_accepted;
uint8_t m_write_buffer[ 8 ];
uint8_t m_response_to_reset[ 4 ];
uint8_t m_write_password[ 8 ];
@ -116,6 +141,7 @@ private:
uint8_t m_configuration_password[ 8 ];
uint8_t m_configuration_registers[ 8 ];
uint8_t m_data[ 512 ];
uint8_t m_password_temp[ 16 ];
};

View File

@ -45,13 +45,15 @@ x76f100_device::x76f100_device( const machine_config &mconfig, const char *tag,
m_shift( 0 ),
m_bit( 0 ),
m_byte( 0 ),
m_command( 0 )
m_command( 0 ),
m_password_retry_counter( 0 ),
m_is_password_accepted ( false )
{
}
void x76f100_device::device_start()
{
memset( m_write_buffer, 0, sizeof( m_write_buffer ) );
std::fill( std::begin( m_write_buffer ), std::end( m_write_buffer ), 0 );
save_item( NAME( m_cs ) );
save_item( NAME( m_rst ) );
@ -63,6 +65,8 @@ void x76f100_device::device_start()
save_item( NAME( m_bit ) );
save_item( NAME( m_byte ) );
save_item( NAME( m_command ) );
save_item( NAME( m_password_retry_counter ) );
save_item( NAME( m_is_password_accepted ) );
save_item( NAME( m_write_buffer ) );
save_item( NAME( m_response_to_reset ) );
save_item( NAME( m_write_password ) );
@ -70,6 +74,24 @@ void x76f100_device::device_start()
save_item( NAME( m_data ) );
}
void x76f100_device::device_reset()
{
std::fill( std::begin( m_write_buffer ), std::end( m_write_buffer ), 0 );
m_cs = 0;
m_rst = 0;
m_scl = 0;
m_sdaw = 0;
m_sdar = 0;
m_state = STATE_STOP;
m_shift = 0;
m_bit = 0;
m_byte = 0;
m_command = 0;
m_password_retry_counter = 0;
m_is_password_accepted = false;
}
WRITE_LINE_MEMBER( x76f100_device::write_cs )
{
if( m_cs != state )
@ -124,11 +146,13 @@ uint8_t *x76f100_device::password()
void x76f100_device::password_ok()
{
if( ( m_command & 0xe1 ) == COMMAND_READ )
m_password_retry_counter = 0;
if( ( m_command & 0x81 ) == COMMAND_READ )
{
m_state = STATE_READ_DATA;
}
else if( ( m_command & 0xe1 ) == COMMAND_WRITE )
else if( ( m_command & 0x81 ) == COMMAND_WRITE )
{
m_state = STATE_WRITE_DATA;
}
@ -141,8 +165,15 @@ void x76f100_device::password_ok()
int x76f100_device::data_offset()
{
int block_offset = ( m_command >> 1 ) & 0x0f;
int offset = ( block_offset * sizeof( m_write_buffer ) ) + m_byte;
return ( block_offset * sizeof( m_write_buffer ) ) + m_byte;
// Technically there are 4 bits assigned to sector values but since the data array is only 112 bytes,
// it will try reading out of bounds when the sector is 14 (= starts at 112) or 15 (= starts at 120).
// TODO: Verify what happens on real hardware when reading/writing sectors 14 and 15
if( offset >= sizeof ( m_data ) )
return -1;
return offset;
}
WRITE_LINE_MEMBER( x76f100_device::write_scl )
@ -223,6 +254,25 @@ WRITE_LINE_MEMBER( x76f100_device::write_scl )
if( m_byte == sizeof( m_write_buffer ) )
{
m_state = STATE_VERIFY_PASSWORD;
// Perform the password acceptance check before verify password because
// password verify ack is spammed and will quickly overflow the
// retry counter. This becomes an issue with System 573 games that use the
// X76F100 as an install cartridge. The boot process first tries to use the
// game cartridge password and if not accepted will try the install cartridge
// password and then enter installation mode if accepted.
m_is_password_accepted = memcmp( password(), m_write_buffer, sizeof( m_write_buffer ) ) == 0;
if( !m_is_password_accepted )
{
m_password_retry_counter++;
if( m_password_retry_counter >= 8 )
{
std::fill( std::begin( m_read_password ), std::end( m_read_password ), 0 );
std::fill( std::begin( m_write_password ), std::end( m_write_password ), 0 );
std::fill( std::begin( m_data ), std::end( m_data ), 0 );
m_password_retry_counter = 0;
}
}
}
break;
@ -233,7 +283,7 @@ WRITE_LINE_MEMBER( x76f100_device::write_scl )
if( m_shift == COMMAND_ACK_PASSWORD )
{
/* todo: this should take 10ms before it returns ok. */
if( memcmp( password(), m_write_buffer, sizeof( m_write_buffer ) ) == 0 )
if( m_is_password_accepted )
{
password_ok();
}
@ -250,11 +300,31 @@ WRITE_LINE_MEMBER( x76f100_device::write_scl )
if( m_byte == sizeof( m_write_buffer ) )
{
for( m_byte = 0; m_byte < sizeof( m_write_buffer ); m_byte++ )
if( m_command == COMMAND_CHANGE_WRITE_PASSWORD )
{
int offset = data_offset();
verboselog( 1, "-> data[ %03x ]: %02x\n", offset, m_write_buffer[ m_byte ] );
m_data[ offset ] = m_write_buffer[ m_byte ];
std::copy( std::begin( m_write_buffer ), std::end( m_write_buffer ), std::begin( m_write_password ) );
}
else if( m_command == COMMAND_CHANGE_READ_PASSWORD )
{
std::copy( std::begin( m_write_buffer ), std::end( m_write_buffer ), std::begin( m_read_password ) );
}
else
{
for( m_byte = 0; m_byte < sizeof( m_write_buffer ); m_byte++ )
{
int offset = data_offset();
if( offset != -1 )
{
verboselog( 1, "-> data[ %03x ]: %02x\n", offset, m_write_buffer[ m_byte ] );
m_data[ offset ] = m_write_buffer[ m_byte ];
}
else
{
verboselog( 1, "-> attempted to write %02x out of bounds\n", m_write_buffer[m_byte] );
break;
}
}
}
m_byte = 0;
@ -283,8 +353,18 @@ WRITE_LINE_MEMBER( x76f100_device::write_scl )
{
case STATE_READ_DATA:
offset = data_offset();
m_shift = m_data[ offset ];
verboselog( 1, "<- data[ %02x ]: %02x\n", offset, m_shift );
if( offset != -1 )
{
m_shift = m_data[ offset ];
verboselog( 1, "<- data[ %02x ]: %02x\n", offset, m_shift );
}
else
{
m_shift = 0;
verboselog( 1, "<- attempted to read out of bounds\n" );
}
break;
}
}

View File

@ -28,6 +28,7 @@ public:
protected:
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
// device_nvram_interface overrides
virtual void nvram_default() override;
@ -74,6 +75,8 @@ private:
int m_bit;
int m_byte;
int m_command;
int m_password_retry_counter;
bool m_is_password_accepted;
uint8_t m_write_buffer[ 8 ];
uint8_t m_response_to_reset[ 4 ];
uint8_t m_write_password[ 8 ];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,227 @@
// license:BSD-3-Clause
// copyright-holders:windyfairy
/*
* Konami 573 Master Calendar
*
* This device was made for development/factory use only.
* It will override the game when connected and always boot into the master calendar-specific code.
* Every game has a master calendar-specific boot sequence but a few that don't have code to initialize security cartridges.
*
* The only games that this does not work on that have a security cart are ddr2mc2, ddr2ml, and ddr2mla (all variants of 885jaa02).
* Those games will boot into a screen that shows the game code, clock, and date with nothing else.
* For these games it's possible to set Sys573 DIPSW 1 with the master calendar connected and it will do a checksum of the game's data
* and attempt to write it to "c:/tmp/chksum.dat" on the host debugger PC but will crash in MAME.
*
* DIPSW 2 and 3 on the System 573 directly are also used to specify the "spec" of the game.
* For example, setting DIPSW allows you to switch between GN and GE specs in earlier games.
*
* Some games require you to hold service/F2 (and set Sys573 DIPSW 3?) during boot to initialize the installation cartridge.
*
*/
#include "emu.h"
#include "k573mcal.h"
#include "machine/timehelp.h"
k573mcal_device::k573mcal_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
jvs_device(mconfig, KONAMI_573_MASTER_CALENDAR, tag, owner, clock),
m_in1(*this, "IN1"),
seconds(0),
mainId(0),
subId(0)
{
}
void k573mcal_device::device_start()
{
jvs_device::device_start();
}
void k573mcal_device::device_reset()
{
seconds = 0;
// Randomly picked values
// Is rendered in-game as x41-6379 where the x is generated based on the value of the main ID
mainId = 41; // valid range 0 - 99
subId = 6379; // valid range 0 - 9999
jvs_device::device_reset();
}
const char *k573mcal_device::device_id()
{
return "KONAMI CO.,LTD.;Master Calendar;Ver1.0;";
}
uint8_t k573mcal_device::command_format_version()
{
return 0x11;
}
uint8_t k573mcal_device::jvs_standard_version()
{
return 0x20;
}
uint8_t k573mcal_device::comm_method_version()
{
return 0x10;
}
int k573mcal_device::handle_message(const uint8_t* send_buffer, uint32_t send_size, uint8_t*& recv_buffer)
{
switch (send_buffer[0]) {
case 0xf0:
// msg: f0 d9
device_reset();
break;
case 0x70: {
// msg: 70
// Writes to RTC chip
system_time systime;
machine().base_datetime(systime);
uint8_t resp[] = {
0x01, // status, must be 1
uint8_t(systime.local_time.year % 100),
uint8_t(systime.local_time.month + 1),
systime.local_time.mday,
systime.local_time.weekday,
systime.local_time.hour,
systime.local_time.minute,
seconds // Can't be the same value twice in a row
};
seconds = (seconds + 1) % 60;
memcpy(recv_buffer, resp, sizeof(resp));
recv_buffer += sizeof(resp);
return 1;
}
case 0x71: {
// msg: 71 ff ff 01
uint8_t resp[] = {
0x01, // status, must be 1
uint8_t(m_in1->read() & 0x0f), // Area specification
};
memcpy(recv_buffer, resp, sizeof(resp));
recv_buffer += sizeof(resp);
return 4;
}
case 0x7c: {
// msg: 7c 7f 00 04
const uint16_t val = (send_buffer[1] << 8) | send_buffer[2];
if (val == 0x7f00) {
// Return main ID
uint8_t resp[] = {
0x01, // status, must be 1
uint8_t((mainId >> 24) & 0xff), uint8_t((mainId >> 16) & 0xff), uint8_t((mainId >> 8) & 0xff), uint8_t(mainId & 0xff),
};
memcpy(recv_buffer, resp, sizeof(resp));
recv_buffer += sizeof(resp);
}
else if (val == 0x8000) {
// Return sub ID
uint8_t resp[] = {
0x01, // status, must be 1
'<', 'I', 'N', 'I', 'T', ' ', 'C', 'O', 'M', 'P', 'L', 'E', 'T', 'E', '!', '>',
uint8_t((subId >> 24) & 0xff), uint8_t((subId >> 16) & 0xff), uint8_t((subId >> 8) & 0xff), uint8_t(subId & 0xff),
uint8_t((~subId >> 24) & 0xff), uint8_t((~subId >> 16) & 0xff), uint8_t((~subId >> 8) & 0xff), uint8_t(~subId & 0xff),
};
memcpy(recv_buffer, resp, sizeof(resp));
recv_buffer += sizeof(resp);
}
return 4;
}
case 0x7d: {
// msg: 7d 80 10 08 00 00 00 01 ff ff ff fe
const uint16_t val = (send_buffer[1] << 8) | send_buffer[2];
if (val == 0x8010) {
// Set next sub ID
subId = (send_buffer[4] << 24) | (send_buffer[5] << 16) | (send_buffer[6] << 8) | send_buffer[7];
uint8_t resp[] = {
0x01, // status, must be 1
};
memcpy(recv_buffer, resp, sizeof(resp));
recv_buffer += sizeof(resp);
}
return 12;
}
case 0x7e: {
// This builds some buffer that creates data like this: @2B0001:020304050607:BC9A78563412:000000000000B5
// 2B0001 is ???
// 020304050607 is the machine SID
// BC9A78563412 is the machine XID
// 000000000000B5 is ???
// msg: 7e xx
uint8_t resp[] = {
// 0x01 - Breaks loop, sends next byte
// 0x04 - Resends byte
0x01,
};
memcpy(recv_buffer, resp, sizeof(resp));
recv_buffer += sizeof(resp);
return 2;
}
case 0x7f:
// TODO: Where is this used?
// The command existed in the command list for a few games (starting at Drummania?) but was never referenced and then disappeared around DDR 3rd mix.
break;
}
// Command not recognized, pass it off to the base message handler
return jvs_device::handle_message(send_buffer, send_size, recv_buffer);
}
INPUT_PORTS_START( k573mcal )
PORT_START("IN1")
// Default the area to 3 because it's unused and will force you to actively select the region to initialize.
// For all but the earliest games it will show a message saying "this game only supports regions x, y, z".
// This is also a good way to discover new bootable variants that exist on the disc but were previously unknown.
PORT_DIPNAME(0x0f, 0x03, "Area")
PORT_DIPSETTING(0x00, "JA")
PORT_DIPSETTING(0x01, "UA")
PORT_DIPSETTING(0x02, "EA")
PORT_DIPSETTING(0x03, "3") // Unused
PORT_DIPSETTING(0x04, "AA")
PORT_DIPSETTING(0x05, "KA")
PORT_DIPSETTING(0x06, "JY/AY")
PORT_DIPSETTING(0x07, "JR")
PORT_DIPSETTING(0x08, "JB")
PORT_DIPSETTING(0x09, "UB")
PORT_DIPSETTING(0x0a, "EB")
PORT_DIPSETTING(0x0b, "11") // Unused
PORT_DIPSETTING(0x0c, "AB")
PORT_DIPSETTING(0x0d, "KB")
PORT_DIPSETTING(0x0e, "JZ/AZ")
PORT_DIPSETTING(0x0f, "JS")
INPUT_PORTS_END
ioport_constructor k573mcal_device::device_input_ports() const
{
return INPUT_PORTS_NAME(k573mcal);
}
DEFINE_DEVICE_TYPE(KONAMI_573_MASTER_CALENDAR, k573mcal_device, "k573mcal", "Konami 573 Master Calendar")

View File

@ -0,0 +1,52 @@
// license:BSD-3-Clause
// copyright-holders:windyfairy
/*
* Konami 573 Master Calendar
*
*/
#ifndef MAME_MACHINE_K573_MCAL_H
#define MAME_MACHINE_K573_MCAL_H
#pragma once
#include "machine/jvsdev.h"
#include "machine/timer.h"
class k573mcal_device : public jvs_device
{
public:
template <typename T>
k573mcal_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&jvs_host_tag)
: k573mcal_device(mconfig, tag, owner, clock)
{
host.set_tag(std::forward<T>(jvs_host_tag));
}
k573mcal_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
virtual ioport_constructor device_input_ports() const override;
protected:
template <uint8_t First> void set_port_tags() { }
virtual void device_start() override;
virtual void device_reset() override;
// JVS device overrides
virtual const char *device_id() override;
virtual uint8_t command_format_version() override;
virtual uint8_t jvs_standard_version() override;
virtual uint8_t comm_method_version() override;
virtual int handle_message(const uint8_t *send_buffer, uint32_t send_size, uint8_t *&recv_buffer) override;
private:
required_ioport m_in1;
uint8_t seconds;
uint32_t mainId;
uint32_t subId;
};
DECLARE_DEVICE_TYPE(KONAMI_573_MASTER_CALENDAR, k573mcal_device)
#endif // MAME_MACHINE_K573_MCAL_H

View File

@ -7,6 +7,8 @@
*
* This is a high level emulation of the PIC used in some of the System 573 security cartridges.
*
* Referred to internally in game code as "NS2K001".
*
*/
#include "emu.h"
@ -43,7 +45,8 @@ zs01_device::zs01_device( const machine_config &mconfig, const char *tag, device
m_state( STATE_STOP ),
m_shift( 0 ),
m_bit( 0 ),
m_byte( 0 )
m_byte( 0 ),
m_previous_byte( 0 )
{
}
@ -65,6 +68,7 @@ void zs01_device::device_start()
save_item( NAME( m_shift ) );
save_item( NAME( m_bit ) );
save_item( NAME( m_byte ) );
save_item( NAME( m_previous_byte ) );
save_item( NAME( m_write_buffer ) );
save_item( NAME( m_read_buffer ) );
save_item( NAME( m_response_key ) );
@ -72,6 +76,25 @@ void zs01_device::device_start()
save_item( NAME( m_command_key ) );
save_item( NAME( m_data_key ) );
save_item( NAME( m_data ) );
save_item( NAME( m_configuration_registers ) );
}
void zs01_device::device_reset()
{
memset( m_write_buffer, 0, sizeof( m_write_buffer ) );
memset( m_read_buffer, 0, sizeof( m_read_buffer ) );
memset( m_response_key, 0, sizeof( m_response_key ) );
m_cs = 0;
m_rst = 0;
m_scl = 0;
m_sdaw = 0;
m_sdar = 0;
m_state = STATE_STOP;
m_shift = 0;
m_bit = 0;
m_byte = 0;
m_previous_byte = 0;
}
WRITE_LINE_MEMBER( zs01_device::write_rst )
@ -309,9 +332,7 @@ uint16_t zs01_device::calc_crc( uint8_t *buffer, uint32_t length )
int zs01_device::data_offset()
{
int block = ( ( m_write_buffer[ 0 ] & 2 ) << 7 ) | m_write_buffer[ 1 ];
return block * SIZE_DATA_BUFFER;
return m_write_buffer[ 1 ] * SIZE_DATA_BUFFER;
}
WRITE_LINE_MEMBER( zs01_device::write_scl )
@ -386,50 +407,94 @@ WRITE_LINE_MEMBER( zs01_device::write_scl )
{
decrypt( m_write_buffer, m_write_buffer, sizeof( m_write_buffer ), m_command_key, 0xff );
// TODO: What is bit 1 of m_write_buffer[0]?
// Bit 2 seems to be set when the sector is >= 4 and the sector is not 0xfc
if( ( m_write_buffer[ 0 ] & 4 ) != 0 )
{
decrypt2( &m_write_buffer[ 2 ], &m_write_buffer[ 2 ], SIZE_DATA_BUFFER, m_data_key, 0x00 );
decrypt2( &m_write_buffer[ 2 ], &m_write_buffer[ 2 ], SIZE_DATA_BUFFER, m_data_key, m_previous_byte );
}
uint16_t crc = calc_crc( m_write_buffer, 10 );
uint16_t msg_crc = ( ( m_write_buffer[ 10 ] << 8 ) | m_write_buffer[ 11 ] );
if( crc == ( ( m_write_buffer[ 10 ] << 8 ) | m_write_buffer[ 11 ] ) )
verboselog( 1, "-> command: %02x (%s)\n", m_write_buffer[ 0 ], ( m_write_buffer[ 0 ] & 1 ) ? "READ" : "WRITE" );
verboselog( 1, "-> address: %04x (%02x)\n", data_offset(), m_write_buffer[ 1 ] );
verboselog( 1, "-> data: %02x%02x%02x%02x%02x%02x%02x%02x\n",
m_write_buffer[ 2 ], m_write_buffer[ 3 ], m_write_buffer[ 4 ], m_write_buffer[ 5 ],
m_write_buffer[ 6 ], m_write_buffer[ 7 ], m_write_buffer[ 8 ], m_write_buffer[ 9 ] );
verboselog( 1, "-> crc: %04x vs %04x %s\n", crc, msg_crc, crc == msg_crc ? "" : "(BAD)");
if( crc == msg_crc )
{
verboselog( 1, "-> command: %02x\n", m_write_buffer[ 0 ] );
verboselog( 1, "-> address: %02x\n", m_write_buffer[ 1 ] );
verboselog( 1, "-> data: %02x%02x%02x%02x%02x%02x%02x%02x\n",
m_write_buffer[ 2 ], m_write_buffer[ 3 ], m_write_buffer[ 4 ], m_write_buffer[ 5 ],
m_write_buffer[ 6 ], m_write_buffer[ 7 ], m_write_buffer[ 8 ], m_write_buffer[ 9 ] );
verboselog( 1, "-> crc: %02x%02x\n", m_write_buffer[ 10 ], m_write_buffer[ 11 ] );
m_configuration_registers[ CONFIG_RC ] = 0; // Reset password fail counter
switch( m_write_buffer[ 0 ] & 1 )
{
case COMMAND_WRITE:
memcpy( &m_data[ data_offset() ], &m_write_buffer[ 2 ], SIZE_DATA_BUFFER );
/* todo: find out what should be returned. */
memset( &m_read_buffer[ 0 ], 0, sizeof( m_write_buffer ) );
m_read_buffer[ 0 ] = STATUS_OK;
if ( m_write_buffer[ 1 ] == 0xfd )
{
// Erase
std::fill( std::begin( m_data ), std::end( m_data ), 0 );
std::fill( std::begin( m_data_key ), std::end( m_data_key ), 0 );
}
else if ( m_write_buffer[ 1 ] == 0xfe )
{
// Configuration register
memcpy( m_configuration_registers, &m_write_buffer[ 2 ], SIZE_DATA_BUFFER );
}
else if ( m_write_buffer[ 1 ] == 0xff )
{
// Set password
memcpy( m_data_key, &m_write_buffer[ 2 ], SIZE_DATA_BUFFER );
}
else if ( data_offset() < sizeof ( m_data ) )
{
memcpy( &m_data[ data_offset() ], &m_write_buffer[ 2 ], SIZE_DATA_BUFFER );
}
else
{
verboselog( 1, "-> unknown write offset: %04x (%02x)\n", data_offset(), m_write_buffer[ 1 ] );
}
break;
case COMMAND_READ:
/* todo: find out what should be returned. */
memset( &m_read_buffer[ 0 ], 0, 2 );
m_read_buffer[ 0 ] = STATUS_OK;
switch( m_write_buffer[ 1 ] )
if ( m_write_buffer[ 1 ] == 0xfc )
{
case 0xfd:
// TODO: Unknown serial
// The serial is verified by the same algorithm as the one read from 0x7e8 (DS2401 serial), but the serial is different.
for (int i = 0; i < SIZE_DATA_BUFFER; i++)
{
/* TODO: use read/write to talk to the ds2401, which will require a timer. */
for( int i = 0; i < SIZE_DATA_BUFFER; i++ )
{
m_read_buffer[ 2 + i ] = m_ds2401->direct_read( SIZE_DATA_BUFFER - i - 1 );
}
m_read_buffer[2 + i] = m_ds2401->direct_read(SIZE_DATA_BUFFER - i - 1);
}
break;
default:
}
else if ( m_write_buffer[ 1 ] == 0xfd )
{
// DS2401 serial
/* TODO: use read/write to talk to the ds2401, which will require a timer. */
for( int i = 0; i < SIZE_DATA_BUFFER; i++ )
{
m_read_buffer[ 2 + i ] = m_ds2401->direct_read( SIZE_DATA_BUFFER - i - 1 );
}
}
else if ( m_write_buffer[ 1 ] == 0xfe )
{
// Configuration register
memcpy( &m_read_buffer[ 2 ], m_configuration_registers, SIZE_DATA_BUFFER );
}
else if ( data_offset() < sizeof ( m_data ) )
{
memcpy( &m_read_buffer[ 2 ], &m_data[ data_offset() ], SIZE_DATA_BUFFER );
break;
}
else
{
verboselog( 1, "-> unknown read offset: %04x (%02x)\n", data_offset(), m_write_buffer[ 1 ] );
}
memcpy( m_response_key, &m_write_buffer[ 2 ], sizeof( m_response_key ) );
@ -439,18 +504,25 @@ WRITE_LINE_MEMBER( zs01_device::write_scl )
else
{
verboselog( 0, "bad crc\n" );
m_read_buffer[ 0 ] = STATUS_ERROR;
/* todo: find out what should be returned. */
memset( &m_read_buffer[ 0 ], 0xff, 2 );
m_configuration_registers[ CONFIG_RC ]++;
if ( m_configuration_registers[ CONFIG_RC ] >= m_configuration_registers[ CONFIG_RR ] )
{
// Too many bad reads, erase data
std::fill( std::begin( m_data ), std::end( m_data ), 0 );
std::fill( std::begin( m_data_key ), std::end( m_data_key ), 0 );
}
}
verboselog( 1, "<- status: %02x%02x\n",
m_read_buffer[ 0 ], m_read_buffer[ 1 ] );
verboselog( 1, "<- status: %02x\n", m_read_buffer[ 0 ] );
verboselog( 1, "<- data: %02x%02x%02x%02x%02x%02x%02x%02x\n",
m_read_buffer[ 2 ], m_read_buffer[ 3 ], m_read_buffer[ 4 ], m_read_buffer[ 5 ],
m_read_buffer[ 6 ], m_read_buffer[ 7 ], m_read_buffer[ 8 ], m_read_buffer[ 9 ] );
m_previous_byte = m_read_buffer[ 1 ];
crc = calc_crc( m_read_buffer, 10 );
m_read_buffer[ 10 ] = crc >> 8;
m_read_buffer[ 11 ] = crc & 255;
@ -574,12 +646,25 @@ READ_LINE_MEMBER( zs01_device::read_sda )
void zs01_device::nvram_default()
{
memset( m_response_to_reset, 0, sizeof( m_response_to_reset ) );
memset( m_command_key, 0, sizeof( m_command_key ) );
m_response_to_reset[ 0 ] = 0x5a;
m_response_to_reset[ 1 ] = 0x53;
m_response_to_reset[ 2 ] = 0x00;
m_response_to_reset[ 3 ] = 0x01;
m_command_key[ 0 ] = 0xed;
m_command_key[ 1 ] = 0x68;
m_command_key[ 2 ] = 0x50;
m_command_key[ 3 ] = 0x4b;
m_command_key[ 4 ] = 0xc6;
m_command_key[ 5 ] = 0x44;
m_command_key[ 6 ] = 0x48;
m_command_key[ 7 ] = 0x3e;
memset( m_data_key, 0, sizeof( m_data_key ) );
memset( m_configuration_registers, 0, sizeof( m_configuration_registers ) );
memset( m_data, 0, sizeof( m_data ) );
int expected_bytes = sizeof( m_response_to_reset ) + sizeof( m_command_key ) + sizeof( m_data_key ) + sizeof( m_data );
int expected_bytes = sizeof( m_response_to_reset ) + sizeof( m_command_key ) + sizeof( m_data_key ) + sizeof( m_configuration_registers ) + sizeof( m_data );
if (!m_region.found())
{
@ -596,6 +681,7 @@ void zs01_device::nvram_default()
memcpy( m_response_to_reset, region, sizeof( m_response_to_reset ) ); region += sizeof( m_response_to_reset );
memcpy( m_command_key, region, sizeof( m_command_key ) ); region += sizeof( m_command_key );
memcpy( m_data_key, region, sizeof( m_data_key ) ); region += sizeof( m_data_key );
memcpy( m_configuration_registers, region, sizeof( m_configuration_registers ) ); region += sizeof( m_configuration_registers );
memcpy( m_data, region, sizeof( m_data ) ); region += sizeof( m_data );
}
}
@ -605,6 +691,7 @@ void zs01_device::nvram_read( emu_file &file )
file.read( m_response_to_reset, sizeof( m_response_to_reset ) );
file.read( m_command_key, sizeof( m_command_key ) );
file.read( m_data_key, sizeof( m_data_key ) );
file.read( m_configuration_registers, sizeof( m_configuration_registers ) );
file.read( m_data, sizeof( m_data ) );
}
@ -613,5 +700,6 @@ void zs01_device::nvram_write( emu_file &file )
file.write( m_response_to_reset, sizeof( m_response_to_reset ) );
file.write( m_command_key, sizeof( m_command_key ) );
file.write( m_data_key, sizeof( m_data_key ) );
file.write( m_configuration_registers, sizeof( m_configuration_registers ) );
file.write( m_data, sizeof( m_data ) );
}

View File

@ -38,6 +38,7 @@ public:
protected:
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
// device_nvram_interface overrides
virtual void nvram_default() override;
@ -72,6 +73,18 @@ private:
STATE_READ_DATA
};
enum status_t
{
STATUS_OK,
STATUS_ERROR = 2,
};
enum configuration_registers_t
{
CONFIG_RR = 4, // Retry Register
CONFIG_RC = 5 // Reset Counter
};
// internal state
optional_device<ds2401_device> m_ds2401;
optional_memory_region m_region;
@ -85,13 +98,15 @@ private:
int m_shift;
int m_bit;
int m_byte;
int m_previous_byte;
uint8_t m_write_buffer[ 12 ];
uint8_t m_read_buffer[ 12 ];
uint8_t m_response_key[ 8 ];
uint8_t m_response_to_reset[ 4 ];
uint8_t m_command_key[ 8 ];
uint8_t m_data_key[ 8 ];
uint8_t m_data[ 4096 ];
uint8_t m_configuration_registers[ 8 ];
uint8_t m_data[ 112 ];
};