2021-02-18 10:29:15 +00:00
/**
* * Supermodel
* * A Sega Model 3 Arcade Emulator .
* * Copyright 2011 - 2021 Bart Trzynadlowski , Nik Henson , Ian Curtis ,
* * Harry Tuttle , and Spindizzi
* *
* * This file is part of Supermodel .
* *
* * Supermodel is free software : you can redistribute it and / or modify it under
* * the terms of the GNU General Public License as published by the Free
* * Software Foundation , either version 3 of the License , or ( at your option )
* * any later version .
* *
* * Supermodel is distributed in the hope that it will be useful , but WITHOUT
* * ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* * FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* * more details .
* *
* * You should have received a copy of the GNU General Public License along
* * with Supermodel . If not , see < http : //www.gnu.org/licenses/>.
* */
/*
* DriveBoard . cpp
*
* Implementation of the CDriveBoard class : drive board ( force feedback )
* emulation .
*
* NOTE : Simulation does not yet work . Drive board ROMs are required .
*
* State Management :
* - - - - - - - - - - - - - - - - -
* - IsAttached : This indicates whether the drive board is present for the game
* and should be emulated , if possible . It is determined by whether a ROM was
* passed to the initializer . Entirely simulated implementations of the drive
* board should still take a ROM , even if it contains no data . The attached
* should be set only * once * at initialization and preferably by the base
* CDriveBoard class . Do not change this flag during run - time !
* - Disabled : This state indicates emulation should not proceed . Force
* feedback must be completely halted . This flag is used only to disable the
* board due to run - time errors and must * not * be re - enabled . It must not be
* used to " temporarily " disable the board . Only the Reset ( ) method may
* enable emulation and then only if the board is attached . A valid reason
* for disabling the board during run - time is e . g . , if a loaded save state is
* incompatible ( wrong format or because it was saved while the board was
* disabled , rendering its state data invalid ) .
* - IsDisabled : This method is used internally only and should be used to test
* whether emulation should occur . It is the combination of attachment and
* enabled state .
* - Disable : Use this to disable the board . Drive board implementations should
* override this to send stop commands to force feedback motors and then call
* CDriveBoard : : Disable ( ) to update the disabled flag .
*/
# include "Supermodel.h"
# include <cstdio>
# include <cmath>
# include <algorithm>
# define ROM_SIZE 0x9000
# define RAM_SIZE 0x2000 // Z80 RAM
2021-02-19 11:03:48 +00:00
static_assert ( sizeof ( bool ) = = 1 , " bools must be copied to uint8_t " ) ; // Save state code relies on this -- we must fix this so that bools are copied to uint8_t explicitly
2021-02-18 10:29:15 +00:00
void CDriveBoard : : SaveState ( CBlockFile * SaveState )
{
SaveState - > NewBlock ( " DriveBoard.2 " , __FILE__ ) ;
// Check board is attached and enabled
bool enabled = ! IsDisabled ( ) ;
SaveState - > Write ( & enabled , sizeof ( enabled ) ) ;
if ( enabled )
{
// Check if simulated
SaveState - > Write ( & m_simulated , sizeof ( m_simulated ) ) ;
if ( m_simulated )
{
// TODO - save board simulation state
}
else
{
// Save RAM state
SaveState - > Write ( m_ram , RAM_SIZE ) ;
// Save interrupt and input/output state
SaveState - > Write ( & m_initialized , sizeof ( m_initialized ) ) ;
SaveState - > Write ( & m_allowInterrupts , sizeof ( m_allowInterrupts ) ) ;
SaveState - > Write ( & m_dataSent , sizeof ( m_dataSent ) ) ;
SaveState - > Write ( & m_dataReceived , sizeof ( m_dataReceived ) ) ;
// Save CPU state
m_z80 . SaveState ( SaveState , " DriveBoard Z80 " ) ;
}
}
}
void CDriveBoard : : LoadState ( CBlockFile * SaveState )
{
if ( SaveState - > FindBlock ( " DriveBoard.2 " ) ! = OKAY )
{
ErrorLog ( " Unable to load base drive board state. Save state file is corrupt. " ) ;
Disable ( ) ;
return ;
}
// Check that board was enabled in saved state
bool isEnabled = ! IsDisabled ( ) ;
bool wasEnabled = false ;
bool wasSimulated = false ;
SaveState - > Read ( & wasEnabled , sizeof ( wasEnabled ) ) ;
if ( wasEnabled )
{
// Simulated?
SaveState - > Read ( & wasSimulated , sizeof ( wasSimulated ) ) ;
if ( wasSimulated )
{
// Derived classes may have simulation state (e.g., SkiBoard)
}
else
{
// Load RAM state
SaveState - > Read ( m_ram , RAM_SIZE ) ;
// Load interrupt and input/output state
SaveState - > Read ( & m_initialized , sizeof ( m_initialized ) ) ;
SaveState - > Read ( & m_allowInterrupts , sizeof ( m_allowInterrupts ) ) ;
SaveState - > Read ( & m_dataSent , sizeof ( m_dataSent ) ) ;
SaveState - > Read ( & m_dataReceived , sizeof ( m_dataReceived ) ) ;
// Load CPU state
// TODO: we should have a way to check whether this succeeds... make CZ80::LoadState() return a bool
m_z80 . LoadState ( SaveState , " DriveBoard Z80 " ) ;
}
}
// If the board was not in the same activity and simulation state when the
// save file was generated, we cannot safely resume and must disable it
if ( wasEnabled ! = isEnabled | | wasSimulated ! = m_simulated )
{
Disable ( ) ;
ErrorLog ( " Halting drive board emulation due to mismatch in active and restored states. " ) ;
}
}
void CDriveBoard : : LoadLegacyState ( const LegacyDriveBoardState & state , CBlockFile * SaveState )
{
2021-02-19 11:03:48 +00:00
static_assert ( RAM_SIZE = = sizeof ( state . ram ) , " Ram sizes must match " ) ;
2021-02-18 10:29:15 +00:00
memcpy ( m_ram , state . ram , RAM_SIZE ) ;
m_initialized = state . initialized ;
m_allowInterrupts = state . allowInterrupts ;
m_dataSent = state . dataSent ;
m_dataReceived = state . dataReceived ;
m_z80 . LoadState ( SaveState , " DriveBoard Z80 " ) ;
}
Game : : DriveBoardType CDriveBoard : : GetType ( void )
{
return Game : : DRIVE_BOARD_NONE ;
}
bool CDriveBoard : : IsAttached ( void )
{
return m_attached ;
}
bool CDriveBoard : : IsSimulated ( void )
{
return m_simulated ;
}
bool CDriveBoard : : IsDisabled ( void )
{
bool enabled = ! m_disabled ;
return ! ( m_attached & & enabled ) ;
}
void CDriveBoard : : Disable ( void )
{
m_disabled = true ;
}
void CDriveBoard : : GetDIPSwitches ( UINT8 & dip1 , UINT8 & dip2 )
{
dip1 = m_dip1 ;
dip2 = m_dip2 ;
}
void CDriveBoard : : GetDIPSwitches ( UINT8 & dip1 )
{
dip1 = m_dip1 ;
}
void CDriveBoard : : SetDIPSwitches ( UINT8 dip1 , UINT8 dip2 )
{
m_dip1 = dip1 ;
m_dip2 = dip2 ;
}
void CDriveBoard : : SetDIPSwitches ( UINT8 dip1 )
{
m_dip1 = dip1 ;
}
unsigned CDriveBoard : : GetForceFeedbackStrength ( )
{
return ( ( ~ ( m_dip1 > > 2 ) ) & 7 ) + 1 ;
}
void CDriveBoard : : SetForceFeedbackStrength ( unsigned strength )
{
m_dip1 = ( m_dip1 & 0xE3 ) | ( ( ( ~ ( strength - 1 ) ) & 7 ) < < 2 ) ;
}
CZ80 * CDriveBoard : : GetZ80 ( void )
{
return & m_z80 ;
}
void CDriveBoard : : AttachInputs ( CInputs * inputs , unsigned gameInputFlags )
{
m_inputs = inputs ;
m_inputFlags = gameInputFlags ;
DebugLog ( " DriveBoard attached inputs \n " ) ;
}
void CDriveBoard : : AttachOutputs ( COutputs * outputs )
{
m_outputs = outputs ;
DebugLog ( " DriveBoard attached outputs \n " ) ;
}
UINT8 CDriveBoard : : Read ( void )
{
if ( IsDisabled ( ) )
{
return 0xFF ;
}
return m_dataReceived ;
}
void CDriveBoard : : Write ( UINT8 data )
{
m_dataSent = data ;
}
UINT8 CDriveBoard : : Read8 ( UINT32 addr )
{
// TODO - shouldn't end of ROM be 0x7FFF not 0x8FFF?
if ( addr < ROM_SIZE ) // ROM is 0x0000-0x8FFF
return m_rom [ addr ] ;
else if ( addr > = 0xE000 ) // RAM is 0xE000-0xFFFF
return m_ram [ ( addr - 0xE000 ) & 0x1FFF ] ;
else
{
//DebugLog("Unhandled Z80 read of %08X (at PC = %04X)\n", addr, m_z80.GetPC());
return 0xFF ;
}
}
void CDriveBoard : : Write8 ( UINT32 addr , UINT8 data )
{
if ( addr > = 0xE000 ) // RAM is 0xE000-0xFFFF
m_ram [ ( addr - 0xE000 ) & 0x1FFF ] = data ;
# ifdef DEBUG
else
DebugLog ( " Unhandled Z80 write to %08X (at PC = %04X) \n " , addr , m_z80 . GetPC ( ) ) ;
# endif
}
UINT8 CDriveBoard : : IORead8 ( UINT32 portNum )
{
return 0xff ;
}
void CDriveBoard : : IOWrite8 ( UINT32 portNum , UINT8 data )
{
}
void CDriveBoard : : RunFrame ( void )
{
if ( IsDisabled ( ) )
{
return ;
}
// Assuming Z80 runs @ 4.0MHz and NMI triggers @ 60.0KHz for WheelBoard and JoystickBoard
// Assuming Z80 runs @ 8.0MHz and INT triggers @ 60.0KHz for BillBoard
// TODO - find out if Z80 frequency is correct and exact frequency of NMI interrupts (just guesswork at the moment!)
int cycles = ( int ) ( m_z80Clock * 1000000 / 60 ) ;
int loopCycles = 10000 ;
while ( cycles > 0 )
{
if ( m_allowInterrupts )
{
if ( m_z80NMI )
m_z80 . TriggerNMI ( ) ;
else
m_z80 . SetINT ( true ) ;
}
cycles - = m_z80 . Run ( std : : min < int > ( loopCycles , cycles ) ) ;
}
}
bool CDriveBoard : : Init ( const UINT8 * romPtr )
{
// This constructor must present a valid ROM
if ( ! romPtr )
{
return ErrorLog ( " Internal error: no drive board ROM supplied. " ) ;
}
// Assign ROM (note that the ROM data has not yet been loaded)
m_rom = romPtr ;
// Allocate memory for RAM
m_ram = new ( std : : nothrow ) UINT8 [ RAM_SIZE ] ;
if ( NULL = = m_ram )
{
float ramSizeMB = ( float ) RAM_SIZE / ( float ) 0x100000 ;
return ErrorLog ( " Insufficient memory for drive board (needs %1.1f MB) . " , ramSizeMB) ;
}
memset ( m_ram , 0 , RAM_SIZE ) ;
// Initialize Z80
m_z80 . Init ( this , NULL ) ;
// We are attached
m_attached = true ;
return OKAY ;
}
// Dummy drive board (not attached)
bool CDriveBoard : : Init ( void )
{
// Use an empty dummy ROM otherwise so debugger doesn't crash if it
// tries to read our memory or if we accidentally run the Z80 (which
// should never happen in a detached board state).
if ( ! m_dummyROM )
{
uint8_t * rom = new ( std : : nothrow ) uint8_t [ ROM_SIZE ] ;
if ( NULL = = rom )
{
return ErrorLog ( " Insufficient memory for drive board. " ) ;
}
memset ( rom , 0xFF , ROM_SIZE ) ;
m_dummyROM = rom ;
}
bool result = Init ( m_dummyROM ) ;
m_attached = false ; // force detached
return result ;
}
void CDriveBoard : : Reset ( )
{
m_initialized = false ;
m_initState = 0 ;
m_readMode = 0 ;
m_boardMode = 0 ;
m_wheelCenter = 0x80 ;
m_allowInterrupts = false ;
m_dataSent = 0 ;
m_dataReceived = 0 ;
m_z80 . Reset ( ) ; // always reset to provide a valid Z80 state
// Configure options (cannot be done in Init() because command line settings weren't yet parsed)
SetForceFeedbackStrength ( m_config [ " ForceFeedbackStrength " ] . ValueAsDefault < unsigned > ( 5 ) ) ;
// Enable only if attached -- **this is the only place this flag should ever be cleared**
if ( m_attached )
m_disabled = false ;
}
CDriveBoard : : CDriveBoard ( const Util : : Config : : Node & config )
: m_config ( config ) ,
m_attached ( false ) ,
m_disabled ( true ) , // begin in disabled state -- can be enabled only 1) if attached and 2) by successful reset
m_simulated ( false ) ,
m_initialized ( false ) ,
m_allowInterrupts ( false ) ,
m_dataSent ( 0 ) ,
m_dataReceived ( 0 ) ,
m_dip1 ( 0x00 ) ,
m_dip2 ( 0x00 ) ,
m_initState ( 0 ) ,
m_statusFlags ( 0 ) ,
m_boardMode ( 0 ) ,
m_readMode ( 0 ) ,
m_wheelCenter ( 0 ) ,
m_cockpitCenter ( 0 ) ,
m_echoVal ( 0 ) ,
m_rom ( NULL ) ,
m_ram ( NULL ) ,
m_dummyROM ( NULL ) ,
m_z80Clock ( 4.0 ) ,
m_z80NMI ( true ) ,
m_inputs ( NULL ) ,
m_inputFlags ( 0 ) ,
m_outputs ( NULL )
{
DebugLog ( " Built Drive Board \n " ) ;
}
CDriveBoard : : ~ CDriveBoard ( void )
{
if ( m_ram ! = NULL )
{
delete [ ] m_ram ;
m_ram = NULL ;
}
if ( m_dummyROM ! = NULL )
{
delete [ ] m_dummyROM ;
m_dummyROM = NULL ;
}
m_rom = NULL ;
m_inputs = NULL ;
m_outputs = NULL ;
DebugLog ( " Destroyed Drive Board \n " ) ;
}