/** ** 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 . **/ /* * 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 #include #include #define ROM_SIZE 0x9000 #define RAM_SIZE 0x2000 // Z80 RAM 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 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) { static_assert(RAM_SIZE == sizeof(state.ram),"Ram sizes must match"); 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(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(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"); }