/** ** 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 . **/ /* * SkiBoard.cpp * * Implementation of the CSkiBoard class: rumble ski pad emulation * emulation. * * NOTE: Simulation does not yet work. Drive board ROMs are required. */ #include "Supermodel.h" #include #include #include Game::DriveBoardType CSkiBoard::GetType(void) { return Game::DRIVE_BOARD_SKI; } unsigned CSkiBoard::GetForceFeedbackStrength() { return 0; } void CSkiBoard::SetForceFeedbackStrength(unsigned strength) { } void CSkiBoard::LoadState(CBlockFile *SaveState) { CDriveBoard::LoadState(SaveState); if (!IsDisabled()) SendVibrate(0); } void CSkiBoard::Disable(void) { SendVibrate(0); CDriveBoard::Disable(); } bool CSkiBoard::Init(const UINT8 *romPtr) { bool result = CDriveBoard::Init(romPtr); m_simulated = true; return result; } void CSkiBoard::Reset(void) { CDriveBoard::Reset(); m_lastVibrate = 0; if (!m_config["ForceFeedback"].ValueAsDefault(false)) Disable(); // Stop any effects that may still be playing if (!IsDisabled()) SendVibrate(0); } UINT8 CSkiBoard::Read(void) { if (IsDisabled()) { return 0xFF; } // TODO - simulate initialization sequence even when emulating to get rid of long pause at boot up (drive board can // carry on booting whilst game starts) return SimulateRead(); } void CSkiBoard::Write(UINT8 data) { if (IsDisabled()) { return; } //if (data >= 0x01 && data <= 0x0F || // data >= 0x20 && data <= 0x2F || // data >= 0x30 && data <= 0x3F || // data >= 0x40 && data <= 0x4F || // data >= 0x70 && data <= 0x7F) // DebugLog("DriveBoard.Write(%02X)\n", data); if (m_simulated) SimulateWrite(data); } UINT8 CSkiBoard::SimulateRead(void) { if (m_initialized) { switch (m_readMode) { // Note about Driveboard error : // The value returned by case 0x0 is the error value displayed when error occurs. // In service menu->output test, you will see "foot ctrl clutch" occilating free/lock due to // the incrementation of m_statusFlags (000x0000 : x=1=lock x=0=free) case 0x0: return m_statusFlags++; // Status flags - Increment every time to bypass driveboard error case 0x1: return m_dip1; // DIP switch 1 value case 0x4: return 0xFF; // Foot sensor Left=0xf0 Right=0x0f Both=0xff default: return 0xFF; } } else { switch (m_initState / 5) { case 0: return 0xCF; // Initiate start case 1: return 0xCE; case 2: return 0xCD; case 3: return 0xCC; // Centering wheel default: m_initialized = true; return 0x80; } } } void CSkiBoard::SimulateWrite(UINT8 cmd) { // Following are commands for Scud Race. Daytona 2 has a compatible command set while Sega Rally 2 is completely different // TODO - finish for Scud Race and Daytona 2 // TODO - implement for Sega Rally 2 UINT8 type = cmd>>4; UINT8 val = cmd&0xF; // Ski Champ vibration pad switch (type) { case 0x00: // nothing to do ? break; case 0x01: // full stop ? if (val == 0) SendVibrate(0); break; case 0x04: SendVibrate(val*0x11); break; case 0x08: if (val == 0x08) { // test driveboard passed ? } break; case 0x0A: // only when test in service menu switch (val) { case 3: // clutch break; case 5: // motor test SendVibrate(val * 0x11); break; case 6: // end motor test SendVibrate(0); break; } break; case 0x0C: // game state or reset driveboard ? switch (val) { case 1: // in game break; case 2: // game ready break; case 3: // test mode break; } break; case 0x0D: // 0xD0-DF Set read mode m_readMode = val & 0x7; break; default: //DebugLog("Skipad unknown command %02X , val= %02X\n",type,val); break; } } void CSkiBoard::RunFrame(void) { if (m_simulated) { if (!m_initialized) m_initState++; } } UINT8 CSkiBoard::Read8(UINT32 addr) { return 0xFF; } void CSkiBoard::Write8(UINT32 addr, UINT8 data) { } void CSkiBoard::SendVibrate(UINT8 val) { if (val == m_lastVibrate) return; /* if (val == 0) DebugLog(">> Stop Vibrate\n"); else DebugLog(">> Vibrate %02X\n", val); */ ForceFeedbackCmd ffCmd; ffCmd.id = FFVibrate; ffCmd.force = (float)val / 255.0f; m_inputs->skiX->SendForceFeedbackCmd(ffCmd); m_lastVibrate = val; } CSkiBoard::CSkiBoard(const Util::Config::Node& config) : CDriveBoard(config), m_lastVibrate(0) { m_attached = false; m_simulated = false; m_initialized = false; m_dip1 = 0xCF; DebugLog("Built Drive Board (ski pad)\n"); } CSkiBoard::~CSkiBoard(void) { }