Huge refactor of the Driveboard:

-Separate each possible boards (wheel, joystick, skipad, billboard).
-Defined a Driveboard type in Games.xml for each games.
-Due to the refactoring, Driveboard Savestates have changed (a common base data + a specific board data are saved).
-Backwards compatibility with previous save states is maintained.
-Driveboard rom section is no longer required anymore. This disables Driveboard emulation in case the rom is not found.
-Added Billboard emulation (vf3, vs2, fvipers2, von2). 7 segments and lamps Outputs are redirected to Supermodel outputs.
-Changes project to C++ 17 standard.
This commit is contained in:
SpinDizzy 2021-02-18 10:29:15 +00:00
parent ab367774d3
commit 08d4735ee8
23 changed files with 3611 additions and 1568 deletions

View file

@ -171,6 +171,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<real3d_status_bit_set_percent_of_frame>24</real3d_status_bit_set_percent_of_frame>
<netboard>true</netboard>
<inputs>
@ -246,7 +247,7 @@
<file offset="0x800000" name="mpr-20889.ic22" crc32="0x18EEC79E" />
<file offset="0xC00000" name="mpr-20890.ic24" crc32="0xAAC96FA2" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-20985.bin" crc32="0xB139481D" />
</region>
</roms>
@ -263,6 +264,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<netboard>true</netboard>
<inputs>
<input type="common" />
@ -342,6 +344,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -399,7 +402,7 @@
<file offset="0x800000" name="mpr-21032.23" crc32="0x3D3FF407" />
</region>
<!-- Spindizzi notes : apparently it uses the same rom from scud race-->
<region name="driveboard_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-19338a.bin" crc32="0xC9FAC464" />
</region>
</roms>
@ -416,6 +419,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -448,6 +452,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -480,6 +485,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -512,6 +518,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -543,6 +550,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<drive_board>Wheel</drive_board>
<inputs>
<input type="common" />
<input type="vehicle" />
@ -607,7 +615,7 @@
<file offset="0xC00000" name="mpr-22890.25" crc32="0xB638BD7C" />
</region>
<!-- Spindizzi notes : apparently it uses the same rom from scud race-->
<region name="driveboard_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-19338a.bin" crc32="0xC9FAC464" />
</region>
</roms>
@ -623,6 +631,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<drive_board>Wheel</drive_board>
<inputs>
<input type="common" />
<input type="vehicle" />
@ -668,6 +677,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<drive_board>Wheel</drive_board>
<inputs>
<input type="common" />
<input type="vehicle" />
@ -700,6 +710,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<drive_board>Wheel</drive_board>
<inputs>
<input type="common" />
<input type="vehicle" />
@ -732,6 +743,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<drive_board>Billboard</drive_board>
<real3d_status_bit_set_percent_of_frame>24</real3d_status_bit_set_percent_of_frame>
<inputs>
<input type="common" />
@ -797,6 +809,9 @@
<file offset="0x800000" name="mpr-20577" crc32="0x3B236187" />
<file offset="0xC00000" name="mpr-20579" crc32="0x08788436" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-18022.ic2" crc32="0x0CA70F80" />
</region>
</roms>
</game>
@ -1013,6 +1028,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<drive_board>Wheel</drive_board>
<netboard>true</netboard>
<inputs>
<input type="common" />
@ -1077,7 +1093,7 @@
</region>
<!-- Spindizzi notes : original Driveboard from model2 hardware -->
<!-- Not working ATM - Commands don't correspond -->
<region name="driveboard_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-18261.bin" crc32="0x0C7FAC58" />
<!-- Driveboard program from scud - Can be a replacement from original model2 z80 program -->
<!-- I think Model3 driveboard hardware acts exactly like Model2 driveboard hardware (same irq, same memory mapping, same commands etc...) -->
@ -1340,6 +1356,7 @@
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<mpeg_board>DSB1</mpeg_board>
<drive_board>Wheel</drive_board>
<netboard>true</netboard>
<inputs>
<input type="common" />
@ -1406,7 +1423,7 @@
<file offset="0x400000" name="mpr-19605.59" crc32="0xBEC891EB" />
<file offset="0x600000" name="mpr-19606.60" crc32="0xADAD46B2" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-19338a.bin" crc32="0xC9FAC464" />
</region>
</roms>
@ -1423,6 +1440,7 @@
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<mpeg_board>DSB1</mpeg_board>
<drive_board>Wheel</drive_board>
<netboard>true</netboard>
<inputs>
<input type="common" />
@ -1456,6 +1474,7 @@
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<mpeg_board>DSB1</mpeg_board>
<drive_board>Wheel</drive_board>
<netboard>true</netboard>
<inputs>
<input type="common" />
@ -1527,6 +1546,7 @@
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<mpeg_board>DSB1</mpeg_board>
<drive_board>Wheel</drive_board>
<netboard>true</netboard>
<inputs>
<input type="common" />
@ -1555,7 +1575,7 @@
<region name="sound_samples" stride="1" chunk_size="1" byte_swap="true">
<file offset="0x400000" name="mpr-20101.24" crc32="0x66D1E31F" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-19338.bin" crc32="0xDBF88DE6" />
</region>
</roms>
@ -1572,6 +1592,7 @@
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<mpeg_board>DSB1</mpeg_board>
<drive_board>Wheel</drive_board>
<pci_bridge>MPC106</pci_bridge>
<netboard>true</netboard>
<inputs>
@ -1601,7 +1622,7 @@
<region name="sound_samples" stride="1" chunk_size="1" byte_swap="true">
<file offset="0x400000" name="mpr-20101.24" crc32="0x66D1E31F" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-19338.bin" crc32="0xDBF88DE6" />
</region>
</roms>
@ -1617,6 +1638,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<drive_board>Ski</drive_board>
<netboard>true</netboard>
<inputs>
<input type="common" />
@ -1869,6 +1891,7 @@
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<netboard>true</netboard>
<inputs>
<input type="common" />
@ -1942,7 +1965,7 @@
<file offset="0x800000" name="mpr-20639.59" crc32="0xF6603B7B" />
<file offset="0xC00000" name="mpr-20640.60" crc32="0x9EEA07B7" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-20512.bin" crc32="0xCF64350D" />
</region>
</roms>
@ -1959,6 +1982,7 @@
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<inputs>
<input type="common" />
<input type="vehicle" />
@ -2013,6 +2037,7 @@
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<inputs>
<input type="common" />
<input type="vehicle" />
@ -2065,6 +2090,7 @@
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Wheel</drive_board>
<real3d_status_bit_set_percent_of_frame>48</real3d_status_bit_set_percent_of_frame>
<inputs>
<input type="common" />
@ -2149,6 +2175,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Joystick</drive_board>
<inputs>
<input type="common" />
<input type="analog_joystick" />
@ -2227,7 +2254,7 @@
</region>
<!-- Force feedback controller prg -->
<region name="ffb_program" stride="1" chunk_size="1">
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-21119.ic8" crc32="0x65082B14" />
</region>
@ -2245,6 +2272,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Joystick</drive_board>
<inputs>
<input type="common" />
<input type="analog_joystick" />
@ -2286,6 +2314,7 @@
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<mpeg_board>DSB2</mpeg_board>
<drive_board>Joystick</drive_board>
<inputs>
<input type="common" />
<input type="analog_joystick" />
@ -2362,6 +2391,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.0</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -2423,6 +2453,9 @@
<file offset="0x000000" name="mpr-19209.22" crc32="0x3715E38C" />
<file offset="0x400000" name="mpr-19210.24" crc32="0xC03D6502" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-18022.ic2" crc32="0x0CA70F80" />
</region>
</roms>
</game>
@ -2436,6 +2469,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.0</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -2463,6 +2497,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.0</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -2490,6 +2525,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.0</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -2524,6 +2560,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<drive_board>Billboard</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -2588,6 +2625,9 @@
<file offset="0x800000" name="mpr-20664.23" crc32="0x89220782" />
<file offset="0xC00000" name="mpr-20666.25" crc32="0x3ECB2606" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-18022.ic2" crc32="0x0CA70F80" />
</region>
</roms>
</game>
@ -2601,6 +2641,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<drive_board>Billboard</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -2629,6 +2670,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<drive_board>Billboard</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -2657,6 +2699,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<drive_board>Billboard</drive_board>
<real3d_pci_id>0x16C311DB</real3d_pci_id>
<netboard>true</netboard>
<inputs>
@ -2685,6 +2728,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -2746,6 +2790,9 @@
<file offset="0x000000" name="mpr-19785.22" crc32="0xE7D190E3" />
<file offset="0x400000" name="mpr-19786.24" crc32="0xB08D889B" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-18022.ic2" crc32="0x0CA70F80" />
</region>
</roms>
</game>
@ -2759,6 +2806,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<drive_board>Billboard</drive_board>
<pci_bridge>MPC106</pci_bridge>
<inputs>
<input type="common" />
@ -2787,6 +2835,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<drive_board>Billboard</drive_board>
<pci_bridge>MPC106</pci_bridge>
<inputs>
<input type="common" />
@ -2815,6 +2864,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.0</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -2889,6 +2939,9 @@
<file offset="0x000000" name="mpr-20903.22" crc32="0xE343E131" />
<file offset="0x400000" name="mpr-20904.24" crc32="0x21A91B84" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-18022.ic2" crc32="0x0CA70F80" />
</region>
</roms>
</game>
@ -2902,6 +2955,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<drive_board>Billboard</drive_board>
<pci_bridge>MPC106</pci_bridge>
<inputs>
<input type="common" />
@ -2930,6 +2984,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -2992,6 +3047,9 @@
<file offset="0x000000" name="mpr-21513.22" crc32="0xCCA1CC00" />
<file offset="0x400000" name="mpr-21514.24" crc32="0x6CEDD292" />
</region>
<region name="driveboard_program" stride="1" chunk_size="1" required="false">
<file offset="0" name="epr-18022.ic2" crc32="0x0CA70F80" />
</region>
</roms>
</game>
@ -3005,6 +3063,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -3033,6 +3092,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>1.5</stepping>
<drive_board>Billboard</drive_board>
<pci_bridge>MPC106</pci_bridge>
<inputs>
<input type="common" />
@ -3061,6 +3121,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />
@ -3089,6 +3150,7 @@
<hardware>
<platform>Sega Model 3</platform>
<stepping>2.1</stepping>
<drive_board>Billboard</drive_board>
<inputs>
<input type="common" />
<input type="joystick1" />

View file

@ -73,7 +73,7 @@ ARCH =
OPT = -O3
WARN = -Wall
CSTD = -std=iso9899:2011
CXXSTD = -std=c++14
CXXSTD = -std=c++17
#
@ -139,7 +139,11 @@ SRC_FILES = \
Src/Model3/53C810.cpp \
Src/Model3/PCI.cpp \
Src/Model3/RTC72421.cpp \
Src/Model3/DriveBoard.cpp \
Src/Model3/DriveBoard/DriveBoard.cpp \
Src/Model3/DriveBoard/WheelBoard.cpp \
Src/Model3/DriveBoard/JoystickBoard.cpp \
Src/Model3/DriveBoard/SkiBoard.cpp \
Src/Model3/DriveBoard/BillBoard.cpp \
Src/Model3/MPC10x.cpp \
Src/Inputs/Input.cpp \
Src/Inputs/Inputs.cpp \

View file

@ -48,6 +48,16 @@ struct Game
INPUT_ALL = 0x003FFFFF
};
uint32_t inputs = 0;
enum DriveBoardType
{
DRIVE_BOARD_NONE = 0,
DRIVE_BOARD_WHEEL,
DRIVE_BOARD_JOYSTICK,
DRIVE_BOARD_SKI,
DRIVE_BOARD_BILLBOARD
};
DriveBoardType driveboard_type = DriveBoardType::DRIVE_BOARD_NONE;
};
#endif // INCLUDED_GAME_H

View file

@ -166,6 +166,7 @@ GameLoader::Region::ptr_t GameLoader::Region::Create(const GameLoader &loader, c
region->stride = region_node["stride"].ValueAs<size_t>();
region->chunk_size = region_node["chunk_size"].ValueAs<size_t>();
region->byte_swap = region_node["byte_swap"].ValueAsDefault<bool>(false);
region->required = region_node["required"].ValueAsDefault<bool>(true);
return region;
}
@ -236,6 +237,16 @@ static void PopulateGameInfo(Game *game, const Util::Config::Node &game_node)
game->inputs |= input_flags[input_type];
}
}
std::map<std::string, Game::DriveBoardType> drive_board_types
{
{ "Wheel", Game::DRIVE_BOARD_WHEEL },
{ "Joystick", Game::DRIVE_BOARD_JOYSTICK },
{ "Ski", Game::DRIVE_BOARD_SKI },
{ "Billboard", Game::DRIVE_BOARD_BILLBOARD}
};
std::string drive_board_type = game_node["hardware/drive_board"].ValueAsDefault<std::string>(std::string());
game->driveboard_type = drive_board_types[drive_board_type];
}
bool GameLoader::LoadGamesFromXML(const Util::Config::Node &xml)
@ -500,7 +511,8 @@ void GameLoader::IdentifyGamesInZipArchive(
std::map<std::string, std::set<File::ptr_t>> files_found_by_game;
// Determine which files each game requires and which files are present in
// the zip archive
// the zip archive. Files belonging to optional regions cannot be used to
// identify games.
for (auto &v1: regions_by_game)
{
const std::string &game_name = v1.first;
@ -508,6 +520,8 @@ void GameLoader::IdentifyGamesInZipArchive(
for (auto &v2: regions_by_name)
{
Region::ptr_t region = v2.second;
if (!region->required)
continue;
for (auto file: region->files)
{
// Add each file to the set of required files per game
@ -764,15 +778,31 @@ bool GameLoader::LoadROMs(ROMSet *rom_set, const std::string &game_name, const Z
{
auto &region = v.second;
uint32_t region_size = 0;
bool error_loading_region = false;
// Attempt to load the region
if (ComputeRegionSize(&region_size, region, zip))
error |= true;
error_loading_region = true;
else
{
// Load up the ROM region
auto &rom = rom_set->rom_by_region[region->region_name];
rom.data.reset(new uint8_t[region_size], std::default_delete<uint8_t[]>());
rom.size = region_size;
error |= LoadRegion(&rom, region, zip);
error_loading_region = LoadRegion(&rom, region, zip);
}
if (error_loading_region && !region->required)
{
// Failed to load the region but it wasn't required anyway, so remove it
// and proceed
rom_set->rom_by_region.erase(region->region_name);
ErrorLog("Optional ROM region '%s' in '%s' could not be loaded.", region->region_name.c_str(), game_name.c_str());
}
else
{
// Proceed normally: accumulate errors
error |= error_loading_region;
}
}

View file

@ -32,6 +32,7 @@ private:
size_t stride;
size_t chunk_size;
bool byte_swap;
bool required;
std::vector<File::ptr_t> files;
static ptr_t Create(const GameLoader &loader, const Util::Config::Node &region_node);
bool AttribsMatch(const ptr_t &other) const;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,192 @@
/**
** 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/>.
**/
/*
* BillBoard.cpp
*
* Implementation of the CBillBoard class
* emulation.
*
*/
/*
** digits format
**
** a
** -------
** | |
** f | | b
** | g |
** -------
** | |
** e | | c
** | |
** -------
** d (O) h
**
** h g f e d c b a
** msb lsb
**
** 0 switch on
** 1 switch off
**
**
** lamps
** x x x x x x P2 P1
** 0 switch on
** 1 switch off
**
**/
#include "Supermodel.h"
#include <cstdio>
#include <cmath>
#include <algorithm>
Game::DriveBoardType CBillBoard::GetType(void)
{
return Game::DRIVE_BOARD_BILLBOARD;
}
unsigned CBillBoard::GetForceFeedbackStrength()
{
return 0;
}
void CBillBoard::SetForceFeedbackStrength(unsigned strength)
{
}
void CBillBoard::SaveState(CBlockFile* SaveState)
{
CDriveBoard::SaveState(SaveState);
SaveState->NewBlock("BillBoard", __FILE__);
SaveState->Write(&m_dip1, sizeof(m_dip1));
}
void CBillBoard::LoadState(CBlockFile* SaveState)
{
CDriveBoard::LoadState(SaveState);
if (SaveState->FindBlock("BillBoard") != OKAY)
{
ErrorLog("Unable to load billboard state. Save state file is corrupt.");
return;
}
SaveState->Read(&m_dip1, sizeof(m_dip1));
}
void CBillBoard::AttachInputs(CInputs* inputs, unsigned gameInputFlags)
{
}
UINT8 CBillBoard::IORead8(UINT32 portNum)
{
switch (portNum)
{
case 0x20:
// return the dipswitch
// 0x80 : test all segments
return m_dip1;
case 0x21:
//DebugLog(" Bill R portnum=%X m_dataSent=%X\n", portNum, m_dataSent);
return m_dataSent;
case 0x26:
//DebugLog(" Bill R portnum=%X m_dataSent=%X\n", portNum, m_dataSent);
// 0xf0 or 0x0f = no more test lamp
return 0xff;
default:
DebugLog("Unhandled Z80 input on port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return 0xff;
}
}
void CBillBoard::IOWrite8(UINT32 portNum, UINT8 data)
{
switch (portNum)
{
case 0x22: // P1 Digit 1
//DebugLog("Bill W 0x22 <- %X\n", data);
if (m_outputs != NULL)
m_outputs->SetValue(OutputBill1, data);
m_dataReceived = data;
break;
case 0x23: // P1 Digit 2
//DebugLog("Bill W 0x23 <- %X\n", data);
if (m_outputs != NULL)
m_outputs->SetValue(OutputBill2, data);
m_dataReceived = data;
break;
case 0x24: // P2 Digit 1
//DebugLog("Bill W 0x24 <- %X\n", data);
if (m_outputs != NULL)
m_outputs->SetValue(OutputBill3, data);
m_dataReceived = data;
break;
case 0x25: // P2 Digit 2
//DebugLog("Bill W 0x25 <- %X\n", data);
if (m_outputs != NULL)
m_outputs->SetValue(OutputBill4, data);
m_dataReceived = data;
break;
case 0x26: // lamp P1 P2
//DebugLog("Bill W 0x26 <- %X\n", data);
if (m_outputs != NULL)
m_outputs->SetValue(OutputBill5, data);
m_dataReceived = data;
break;
case 0x28:
//DebugLog("Bill W 0x28 <- %X\n", data);
if (data == 0x03)
m_allowInterrupts = true;
break;
case 0x2e:
//DebugLog("Bill W 0x2e <- %X\n", data);
if (data == 0x00)
m_initialized = true;
return;
default:
DebugLog("Unhandled Z80 output on port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
break;
}
}
CBillBoard::CBillBoard(const Util::Config::Node& config)
: CDriveBoard(config)
{
m_dip1 = 0x0f;
m_simulated = false;
m_z80Clock = 8.0;
m_z80NMI = false;
DebugLog("Built Drive Board (billboard)\n");
}
CBillBoard::~CBillBoard(void)
{
}

View file

@ -0,0 +1,117 @@
/**
** 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/>.
**/
/*
* BillBoard.h
*
* Header for the CBillBoard class (BillBoard emulation).
*/
#ifndef INCLUDED_BILLBOARD_H
#define INCLUDED_BILLBOARD_H
#include "Util/NewConfig.h"
/*
* CBillBoard
*/
class CBillBoard : public CDriveBoard
{
public:
/*
* GetType(void):
*
* Returns:
* Drive board type.
*/
Game::DriveBoardType GetType(void);
unsigned GetForceFeedbackStrength(void);
void SetForceFeedbackStrength(unsigned strength);
/*
* SaveState(SaveState):
*
* Saves the bill board state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Restores the bill board state.
*
* Parameters:
* SaveState Block file to load save state information from.
*/
void LoadState(CBlockFile *SaveState);
void AttachInputs(CInputs* inputs, unsigned gameInputFlags);
/*
* CBillBoard(config):
* ~CBillBoard():
*
* Constructor and destructor. Memory is freed by destructor.
*
* Paramters:
* config Run-time configuration. The reference should be held because
* this changes at run-time.
*/
CBillBoard(const Util::Config::Node &config);
~CBillBoard(void);
/*
* Read8(addr):
* IORead8(portNum):
*
* Methods for reading from Z80's memory and IO space. Required by CBus.
*
* Parameters:
* addr Address in memory (0-0xFFFF).
* portNum Port address (0-255).
*
* Returns:
* A byte of data from the address or port.
*/
UINT8 IORead8(UINT32 portNum);
/*
* Write8(addr, data):
* IORead8(portNum, data):
*
* Methods for writing to Z80's memory and IO space. Required by CBus.
*
* Parameters:
* addr Address in memory (0-0xFFFF).
* portNum Port address (0-255).
* data Byte to write.
*/
void IOWrite8(UINT32 portNum, UINT8 data);
};
#endif // INCLUDED_BILLBOARD_H

View file

@ -0,0 +1,426 @@
/**
** 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
static_assert(sizeof(bool) == 1); // 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));
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");
}

View file

@ -0,0 +1,322 @@
/**
** 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.h
*
* Header for the CDriveBoard (force feedback emulation) class.
* Abstract base class defining the common interface for wheel, joystick, ski, bill board types.
*/
#ifndef INCLUDED_DRIVEBOARD_H
#define INCLUDED_DRIVEBOARD_H
#include "Util/NewConfig.h"
#include "Game.h"
/*
* CDriveBoard
*/
class CDriveBoard : public IBus
{
public:
/*
* GetType(void):
*
* Returns:
* Drive board type.
*/
virtual Game::DriveBoardType GetType(void);
/*
* IsAttached(void):
*
* Returns:
* True if the drive board is "attached" and should be emulated,
* otherwise false.
*/
virtual bool IsAttached(void);
/*
* IsSimulated(void):
*
* Returns:
* True if the drive board is being simulated rather than actually
* emulated, otherwise false.
*/
virtual bool IsSimulated(void);
/*
* GetDIPSwitches(dip1, dip2):
*
* Reads the two sets of DIP switches on the drive board.
*
* Parameters:
* dip1 Reference of variable to store DIP switch 1 to.
* dip2 DIP switch 2.
*/
virtual void GetDIPSwitches(UINT8 &dip1, UINT8 &dip2);
virtual void GetDIPSwitches(UINT8& dip1);
/*
* SetDIPSwitches(dip1, dip2):
*
* Sets the DIP switches.
*
* Parameters:
* dip1 DIP switch 1 value.
* dip2 DIP switch 2 value.
*/
virtual void SetDIPSwitches(UINT8 dip1, UINT8 dip2);
virtual void SetDIPSwitches(UINT8 dip1);
/*
* GetForceFeedbackStrength(void):
*
* Returns:
* Strength of the force feedback based on drive board DIP switches (1-8).
*/
virtual unsigned GetForceFeedbackStrength(void);
/*
* SetForceFeedbackStrength(strength):
*
* Sets the force feedback strength (modifies the DIP switch setting).
*
* Parameters:
* strength A value ranging from 1 to 8.
*/
virtual void SetForceFeedbackStrength(unsigned strength);
/*
* GetZ80(void):
*
* Returns:
* The Z80 object.
*/
virtual CZ80 *GetZ80(void);
/*
* SaveState(SaveState):
*
* Saves the drive board state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
virtual void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Restores the drive board state.
*
* Parameters:
* SaveState Block file to load save state information from.
*/
virtual void LoadState(CBlockFile *SaveState);
/*
* Init(romPtr):
*
* Initializes (and "attaches") the drive board. This should be called
* before other members.
*
* Parameters:
* romPtr Pointer to the drive board ROM (Z80 program).
*
* Returns:
* FAIL if the drive board could not be initialized (prints own error
* message), otherwise OKAY.
*/
virtual bool Init(const UINT8 *romPtr);
/*
* Init(void):
*
* Initializes a dummy drive board in a detached state. This should be called
* before other members. This initializer is provided in case a CDriveBoard
* object or pointer is needed but no drive board actually exists.
*/
bool Init(void);
/*
* AttachInputs(InputsPtr, gameInputFlags):
*
* Attaches inputs to the drive board (for access to the steering wheel
* position).
*
* Parameters:
* inputs Pointer to the input object.
* gameInputFlags The current game's input flags.
*/
virtual void AttachInputs(CInputs *inputs, unsigned gameInputFlags);
virtual void AttachOutputs(COutputs *outputs);
/*
* Reset(void):
*
* Resets the drive board.
*/
virtual void Reset(void);
/*
* Read():
*
* Reads data from the drive board.
*
* Returns:
* Data read.
*/
virtual UINT8 Read(void);
/*
* Write(data):
*
* Writes data to the drive board.
*
* Parameters:
* data Data to send.
*/
virtual void Write(UINT8 data);
/*
* RunFrame(void):
*
* Emulates a single frame's worth of time on the drive board.
*/
virtual void RunFrame(void);
/*
* CDriveBoard(config):
* ~CDriveBoard():
*
* Constructor and destructor. Memory is freed by destructor.
*
* Paramters:
* config Run-time configuration. The reference should be held because
* this changes at run-time.
*/
CDriveBoard(const Util::Config::Node& config);
virtual ~CDriveBoard(void);
/*
* Read8(addr):
* IORead8(portNum):
*
* Methods for reading from Z80's memory and IO space. Required by CBus.
*
* Parameters:
* addr Address in memory (0-0xFFFF).
* portNum Port address (0-255).
*
* Returns:
* A byte of data from the address or port.
*/
virtual UINT8 Read8(UINT32 addr);
virtual UINT8 IORead8(UINT32 portNum);
/*
* Write8(addr, data):
* IORead8(portNum, data):
*
* Methods for writing to Z80's memory and IO space. Required by CBus.
*
* Parameters:
* addr Address in memory (0-0xFFFF).
* portNum Port address (0-255).
* data Byte to write.
*/
virtual void Write8(UINT32 addr, UINT8 data);
virtual void IOWrite8(UINT32 portNum, UINT8 data);
protected:
struct LegacyDriveBoardState
{
uint8_t dip1;
uint8_t dip2;
uint8_t ram[0x2000];
uint8_t initialized;
uint8_t allowInterrupts;
uint8_t dataSent;
uint8_t dataReceived;
uint16_t adcPortRead;
uint8_t adcPortBit;
uint8_t uncenterVal1;
uint8_t uncenterVal2;
};
// Disable the drive board (without affecting attachment state). Used internally only to disable emulation.
virtual void Disable(void);
// Whether disabled and/or not attached -- used to determine whether to carry out emulation
bool IsDisabled(void);
// Attempt to load drive board data from old save states (prior to drive board refactor)
void LoadLegacyState(const LegacyDriveBoardState &state, CBlockFile *SaveState);
const Util::Config::Node& m_config;
bool m_attached; // True if drive board is attached
bool m_disabled; // True if emulation is internally disabled (e.g., by loading an incompatible save state). Can only be enabled once and if attached.
bool m_simulated; // True if drive board should be simulated rather than emulated
// Emulation state
bool m_initialized; // True if drive board has finished initialization
bool m_allowInterrupts; // True if drive board has enabled NMI interrupts
UINT8 m_dataSent; // Last command sent by main board
UINT8 m_dataReceived; // Data to send back to main board
UINT8 m_dip1; // Value of DIP switch 1
UINT8 m_dip2; // Value of DIP switch 2
// Simulation state
UINT8 m_initState;
UINT8 m_statusFlags;
UINT8 m_boardMode;
UINT8 m_readMode;
UINT8 m_wheelCenter;
UINT8 m_cockpitCenter;
UINT8 m_echoVal;
const UINT8* m_rom; // 32k ROM
UINT8* m_ram; // 8k RAM
const UINT8* m_dummyROM;
CZ80 m_z80; // Z80 CPU
float m_z80Clock; // Z80 clock frequency
bool m_z80NMI; // Non Masquable Interrupt or Interrupt
CInputs* m_inputs;
unsigned m_inputFlags;
COutputs* m_outputs;
};
#endif // INCLUDED_DRIVEBOARD_H

View file

@ -0,0 +1,731 @@
/**
** 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/>.
**/
/*
* JoyBoard.cpp
*
* Implementation of the CJoyBoard class: drive board (force feedback emulation
* for joystick) emulation.
*
* NOTE: This is largely a copy of CWheelBoard as it appears to be the same drive
* board. The joystick X axis is used rather than the wheel input. It is unknown
* how or whether the Y axis is involved at all.
*
* NOTE: Simulation does not yet work. Drive board ROMs are required.
*/
#include "Supermodel.h"
#include <cstdio>
#include <cmath>
#include <algorithm>
Game::DriveBoardType CJoyBoard::GetType(void)
{
return Game::DRIVE_BOARD_JOYSTICK;
}
void CJoyBoard::Get7SegDisplays(UINT8 &seg1Digit1, UINT8 &seg1Digit2, UINT8 &seg2Digit1, UINT8 &seg2Digit2)
{
seg1Digit1 = m_seg1Digit1;
seg1Digit2 = m_seg1Digit2;
seg2Digit1 = m_seg2Digit1;
seg2Digit2 = m_seg2Digit2;
}
void CJoyBoard::SaveState(CBlockFile *SaveState)
{
CDriveBoard::SaveState(SaveState);
SaveState->NewBlock("JoystickBoard", __FILE__);
SaveState->Write(&m_simulated, sizeof(m_simulated));
if (m_simulated)
{
// TODO - save board simulation state
}
else
{
// Save DIP switches and digit displays
SaveState->Write(&m_dip1, sizeof(m_dip1));
SaveState->Write(&m_dip2, sizeof(m_dip2));
SaveState->Write(&m_adcPortRead, sizeof(m_adcPortRead));
SaveState->Write(&m_adcPortBit, sizeof(m_adcPortBit));
SaveState->Write(&m_uncenterVal1, sizeof(m_uncenterVal1));
SaveState->Write(&m_uncenterVal2, sizeof(m_uncenterVal2));
}
}
void CJoyBoard::LoadState(CBlockFile *SaveState)
{
CDriveBoard::LoadState(SaveState);
if (SaveState->FindBlock("JoystickBoard") != OKAY)
{
ErrorLog("Unable to load joystick drive board state. Save state file is corrupt.");
Disable();
return;
}
bool wasSimulated;
SaveState->Read(&wasSimulated, sizeof(wasSimulated));
if (wasSimulated)
{
// Simulation has never existed
ErrorLog("Save state contains unexpected data. Halting drive board emulation.");
Disable();
return;
}
else
{
// Load DIP switches and digit displays
SaveState->Read(&m_dip1, sizeof(m_dip1));
SaveState->Read(&m_dip2, sizeof(m_dip2));
SaveState->Read(&m_adcPortRead, sizeof(m_adcPortRead));
SaveState->Read(&m_adcPortBit, sizeof(m_adcPortBit));
SaveState->Read(&m_uncenterVal1, sizeof(m_uncenterVal1));
SaveState->Read(&m_uncenterVal2, sizeof(m_uncenterVal2));
}
}
void CJoyBoard::Disable(void)
{
SendStopAll();
CDriveBoard::Disable();
}
void CJoyBoard::Reset(void)
{
CDriveBoard::Reset();
m_seg1Digit1 = 0xFF;
m_seg1Digit2 = 0xFF;
m_seg2Digit1 = 0xFF;
m_seg2Digit2 = 0xFF;
m_adcPortRead = 0;
m_adcPortBit = 0;
m_port42Out = 0;
m_port45Out = 0;
m_port46Out = 0;
m_prev42Out = 0;
m_prev45Out = 0;
m_prev46Out = 0;
m_uncenterVal1 = 0;
m_uncenterVal2 = 0;
m_lastConstForce = 0;
m_lastConstForceY = 0;
m_lastSelfCenter = 0;
m_lastFriction = 0;
m_lastVibrate = 0;
m_simulated = false; //TODO: make this run-time configurable when simulation mode is supported
if (!m_config["ForceFeedback"].ValueAsDefault<bool>(false))
Disable();
// Stop any effects that may still be playing
if (!IsDisabled())
SendStopAll();
}
UINT8 CJoyBoard::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)
if (m_simulated)
return SimulateRead();
else
return CDriveBoard::Read();
}
void CJoyBoard::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);
else
{
CDriveBoard::Write(data);
if (data == 0xCB)
m_initialized = false;
}
}
UINT8 CJoyBoard::SimulateRead(void)
{
if (m_initialized)
{
switch (m_readMode)
{
case 0x0: return m_statusFlags; // Status flags
case 0x1: return m_dip1; // DIP switch 1 value
case 0x2: return m_dip2; // DIP switch 2 value
case 0x3: return m_wheelCenter; // Wheel center
case 0x4: return 0x80; // Cockpit banking center
case 0x5: return m_inputs->analogJoyX->value; // Wheel position
case 0x6: return 0x80; // Cockpit banking position
case 0x7: return m_echoVal; // Init status/echo test
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 CJoyBoard::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;
switch (type)
{
case 0: // 0x00-0F Play sequence
/* TODO */
break;
case 1: // 0x10-1F Set centering strength
if (val == 0)
// Disable auto-centering
// TODO - is 0x10 for disable?
SendSelfCenter(0);
else
// Enable auto-centering (0x1 = weakest, 0xF = strongest)
SendSelfCenter(val * 0x11);
break;
case 2: // 0x20-2F Friction strength
if (val == 0)
// Disable friction
// TODO - is 0x20 for disable?
SendFriction(0);
else
// Enable friction (0x1 = weakest, 0xF = strongest)
SendFriction(val * 0x11);
break;
case 3: // 0x30-3F Uncentering (vibrate)
if (val == 0)
// Disable uncentering
SendVibrate(0);
else
// Enable uncentering (0x1 = weakest, 0xF = strongest)
SendVibrate(val * 0x11);
break;
case 4: // 0x40-4F Play power-slide sequence
/* TODO */
break;
case 5: // 0x50-5F Rotate wheel right
SendConstantForce((val + 1) * 0x5);
break;
case 6: // 0x60-6F Rotate wheel left
SendConstantForce(-(val + 1) * 0x5);
break;
case 7: // 0x70-7F Set steering parameters
/* TODO */
break;
case 8: // 0x80-8F Test Mode
switch (val & 0x7)
{
case 0: SendStopAll(); break; // 0x80 Stop motor
case 1: SendConstantForce(20); break; // 0x81 Roll wheel right
case 2: SendConstantForce(-20); break; // 0x82 Roll wheel left
case 3: /* Ignore - no clutch */ break; // 0x83 Clutch on
case 4: /* Ignore - no clutch */ break; // 0x84 Clutch off
case 5: m_wheelCenter = m_inputs->analogJoyX->value; break; // 0x85 Set wheel center position
case 6: /* Ignore */ break; // 0x86 Set cockpit banking position
case 7: /* Ignore */ break; // 0x87 Lamp on/off
}
case 0x9: // 0x90-9F ??? Don't appear to have any effect with Scud Race ROM
/* TODO */
break;
case 0xA: // 0xA0-AF ??? Don't appear to have any effect with Scud Race ROM
/* TODO */
break;
case 0xB: // 0xB0-BF Invalid command (reserved for use by PPC to send cabinet type 0xB0 or 0xB1 during initialization)
/* Ignore */
break;
case 0xC: // 0xC0-CF Set board mode (0xCB = reset board)
SendStopAll();
if (val >= 0xB)
{
// Reset board
m_initialized = false;
m_initState = 0;
}
else
m_boardMode = val;
break;
case 0xD: // 0xD0-DF Set read mode
m_readMode = val & 0x7;
break;
case 0xE: // 0xE0-EF Invalid command
/* Ignore */
break;
case 0xF: // 0xF0-FF Echo test
m_echoVal = val;
break;
}
}
void CJoyBoard::RunFrame(void)
{
if (m_simulated)
SimulateFrame();
else
CDriveBoard::RunFrame();
}
void CJoyBoard::SimulateFrame(void)
{
if (!m_initialized)
m_initState++;
// TODO - update m_statusFlags and play preset scripts according to board mode
}
UINT8 CJoyBoard::IORead8(UINT32 portNum)
{
UINT8 adcVal;
switch (portNum)
{
case 0x20: // DIP 1 value
return m_dip1;
case 0x21: // DIP 2 value
return m_dip2;
case 0x24: // ADC channel 1 - Y analog axis for joystick
case 0x25: // ADC channel 2 - steering wheel position (0x00 = full left, 0x80 = center, 0xFF = full right) and X analog axis for joystick
case 0x26: // ADC channel 3 - cockpit bank position (deluxe cabinets) (0x00 = full left, 0x80 = center, 0xFF = full right)
case 0x27: // ADC channel 4 - not connected
if (portNum == m_adcPortRead && m_adcPortBit-- > 0)
{
switch (portNum)
{
case 0x24: // Y analog axis for joystick
adcVal = ReadADCChannel1();
break;
case 0x25: // Steering wheel for twin racing cabinets - TODO - check actual range of steering, suspect it is not really 0x00-0xFF
adcVal = ReadADCChannel2();
break;
case 0x26: // Cockpit bank position for deluxe racing cabinets
adcVal = ReadADCChannel3();
break;
case 0x27: // Not connected
adcVal = ReadADCChannel4();
break;
default:
DebugLog("Unhandled Z80 input on ADC port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return 0xFF;
}
return (adcVal >> m_adcPortBit) & 0x01;
}
else
{
DebugLog("Unhandled Z80 input on ADC port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return 0xFF;
}
case 0x28: // PPC command
return m_dataSent;
case 0x2c: // Encoder error reporting (kept at 0x00 for no error)
// Bit 1 0
// 0 0 = encoder okay, no error
// 0 1 = encoder error 1 - overcurrent error
// 1 0 = encoder error 2 - overheat error
// 1 1 = encoder error 3 - encoder error, reinitializes board
return 0x00;
default:
DebugLog("Unhandled Z80 input on port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return 0xFF;
}
}
void CJoyBoard::IOWrite8(UINT32 portNum, UINT8 data)
{
switch (portNum)
{
case 0x10: // Unsure? - single byte 0x03 sent at initialization, then occasionally writes 0x07 & 0xFA to port
return;
case 0x11: // Interrupt control
if (data == 0x57)
m_allowInterrupts = true;
else if (data == 0x53) // Strictly speaking 0x53 then 0x04
m_allowInterrupts = false;
return;
case 0x1c: // Unsure? - two bytes 0xFF, 0xFF sent at initialization only
case 0x1d: // Unsure? - two bytes 0x0F, 0x17 sent at initialization only
case 0x1e: // Unsure? - same as port 28
case 0x1f: // Unsure? - same as port 31
return;
case 0x20: // Left digit of 7-segment display 1
m_seg1Digit1 = data;
return;
case 0x21: // Right digit of 7-segment display 1
m_seg1Digit2 = data;
return;
case 0x22: // Left digit of 7-segment display 2
m_seg2Digit1 = data;
return;
case 0x23: // Right digit of 7-segment display 2
m_seg2Digit2 = data;
return;
case 0x24: // ADC channel 1 control
case 0x25: // ADC channel 2 control
case 0x26: // ADC channel 3 control
case 0x27: // ADC channel 4 control
m_adcPortRead = portNum;
m_adcPortBit = 8;
return;
case 0x29: // Reply for PPC
m_dataReceived = data;
if (data == 0xCB)
m_initialized = true;
return;
case 0x2a: // Encoder motor data (x axis)
m_port42Out = data;
ProcessEncoderCmdJoystick();
return;
case 0x2d: // Clutch/lamp control (deluxe cabinets) ( or y axis)
m_port45Out = data;
ProcessEncoderCmdJoystick();
return;
case 0x2e: // Encoder motor control
m_port46Out = data;
return;
case 0xf0: // Unsure? - single byte 0xBB sent at initialization only
return;
case 0xf1: // Unsure? - single byte 0x4E sent regularly - some sort of watchdog?
return;
default:
DebugLog("Unhandled Z80 output on port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return;
}
}
void CJoyBoard::ProcessEncoderCmdJoystick(void)
{
if (m_prev42Out != m_port42Out || m_prev46Out != m_port46Out || m_prev45Out != m_port45Out)
{
switch (m_port46Out)
{
case 0xEE:
// Apply constant force
if (m_port42Out > 0x7f) // X
{
SendConstantForce(2 * (m_port42Out - 0x7f));
}
else if (m_port42Out < 0x7f)
{
SendConstantForce(2 * (m_port42Out - 0x7f));
}
else
{
SendConstantForce(0);
}
if (m_port45Out > 0x7f) // Y
{
SendConstantForceY(-2 * (m_port45Out - 0x7f));
}
else if (m_port45Out < 0x7f)
{
SendConstantForceY(-2 * (m_port45Out - 0x7f));
}
else
{
SendConstantForceY(0);
}
if (m_port42Out == 0x7f && m_port45Out == 0x81)
{
SendSelfCenter(255);
}
else SendSelfCenter(0);
break;
case 0xFF:
// Stop all effects
if (m_port42Out == 0 || m_port45Out == 0)
SendStopAll();
break;
case 0xcc: // init
//42[0B] / 45[0A]
//42[0B] / 45[0B]
//42[FF] / 45[0B]
//42[FF] / 45[FF]
break;
case 0xdd: // init
// 42[FF] / 45[00]
// 42[FF] / 45[FF]
// 42[0A] / 45[00]
// 42[0A] / 45[0A]
break;
case 0xce:
// 42[7F] / 45[08]
// 42[7F] / 45[09]
// 42[7F] / 45[0A]
// 42[7F] / 45[0B]
// 42[7F] / 45[81]
if (m_port42Out == 0x7f && m_port45Out != 0x81) // X
{
SendConstantForce(2 * m_port45Out);
}
if (m_port42Out == 0x7f && m_port45Out == 0x81)
{
SendSelfCenter(255);
}
break;
case 0xec:
// 42[09] / 45[81]
// 42[2A] / 45[81]
// 42[1B] / 45[81]
// 42[7F] / 45[81]
if (m_port45Out == 0x81 && m_port42Out != 0x7f) // Y
{
SendConstantForceY(2 * m_port42Out);
}
if (m_port42Out == 0x7f && m_port45Out == 0x81)
{
SendSelfCenter(255);
}
break;
case 0x00: // init
// 42[FF] / 45[00]
// 42[FF] / 45[FF]
break;
case 0x99: // init
// 42[B0] / 45[B0]
// 42[80] / 45[B0]
// 42[80] / 45[80]
break;
default:
//DebugLog("Unknown = 46 [%02X] / 42 [%02X] / 45 [%02X]\n", m_port46Out, m_port42Out, m_port45Out);
break;
}
m_prev42Out = m_port42Out;
m_prev46Out = m_port46Out;
m_prev45Out = m_port45Out;
}
}
void CJoyBoard::SendStopAll(void)
{
//DebugLog(">> Stop All Effects\n");
ForceFeedbackCmd ffCmd;
ffCmd.id = FFStop;
m_inputs->analogJoyX->SendForceFeedbackCmd(ffCmd);
m_inputs->analogJoyY->SendForceFeedbackCmd(ffCmd);
m_lastConstForce = 0;
m_lastSelfCenter = 0;
m_lastFriction = 0;
m_lastVibrate = 0;
m_lastConstForceY = 0;
}
void CJoyBoard::SendConstantForce(INT8 val)
{
if (val == m_lastConstForce)
return;
/*
if (val > 0)
{
DebugLog(">> Force Right %02X [%8s", val, "");
for (unsigned i = 0; i < 8; i++)
DebugLog(i == 0 || i <= (val + 1) / 16 ? ">" : " ");
DebugLog("]\n");
}
else if (val < 0)
{
DebugLog(">> Force Left %02X [", -val);
for (unsigned i = 0; i < 8; i++)
DebugLog(i == 7 || i >= (val + 128) / 16 ? "<" : " ");
DebugLog("%8s]\n", "");
}
else
DebugLog(">> Stop Force [%16s]\n", "");
*/
ForceFeedbackCmd ffCmd;
ffCmd.id = FFConstantForce;
ffCmd.force = (float)val / (val >= 0 ? 127.0f : 128.0f);
m_inputs->analogJoyX->SendForceFeedbackCmd(ffCmd);
m_lastConstForce = val;
}
void CJoyBoard::SendConstantForceY(INT8 val)
{
if (val == m_lastConstForceY)
return;
ForceFeedbackCmd ffCmd;
ffCmd.id = FFConstantForce;
ffCmd.force = (float)val / (val >= 0 ? 127.0f : 128.0f);
m_inputs->analogJoyY->SendForceFeedbackCmd(ffCmd);
m_lastConstForceY = val;
}
void CJoyBoard::SendSelfCenter(UINT8 val)
{
if (val == m_lastSelfCenter)
return;
/*
if (val == 0)
DebugLog(">> Stop Self-Center\n");
else
DebugLog(">> Self-Center %02X\n", val);
*/
ForceFeedbackCmd ffCmd;
ffCmd.id = FFSelfCenter;
ffCmd.force = (float)val / 255.0f;
m_inputs->analogJoyX->SendForceFeedbackCmd(ffCmd);
m_inputs->analogJoyY->SendForceFeedbackCmd(ffCmd);
m_lastSelfCenter = val;
}
void CJoyBoard::SendFriction(UINT8 val)
{
if (val == m_lastFriction)
return;
/*
if (val == 0)
DebugLog(">> Stop Friction\n");
else
DebugLog(">> Friction %02X\n", val);
*/
ForceFeedbackCmd ffCmd;
ffCmd.id = FFFriction;
ffCmd.force = (float)val / 255.0f;
m_inputs->analogJoyX->SendForceFeedbackCmd(ffCmd);
m_lastFriction = val;
}
void CJoyBoard::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->analogJoyX->SendForceFeedbackCmd(ffCmd);
m_lastVibrate = val;
}
uint8_t CJoyBoard::ReadADCChannel1()
{
if (m_initialized)
return (UINT8)m_inputs->analogJoyY->value;
else
return 0x80; // If not initialized, return 0x80 so that ffb centering test does not fail
}
uint8_t CJoyBoard::ReadADCChannel2()
{
if (m_initialized)
return (UINT8)m_inputs->analogJoyX->value;
else
return 0x80; // If not initialized, return 0x80 so that ffb centering test does not fail
}
uint8_t CJoyBoard::ReadADCChannel3()
{
return 0x80;
}
uint8_t CJoyBoard::ReadADCChannel4()
{
return 0x80;
}
CJoyBoard::CJoyBoard(const Util::Config::Node &config)
: CDriveBoard(config)
{
m_dip1 = 0xCF;
m_dip2 = 0xFF;
DebugLog("Built Drive Board (Joystick)\n");
}
CJoyBoard::~CJoyBoard(void)
{
}

View file

@ -1,7 +1,8 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski, Nik Henson
** Copyright 2011-2021 Bart Trzynadlowski, Nik Henson, Ian Curtis,
** Harry Tuttle, and Spindizzi
**
** This file is part of Supermodel.
**
@ -20,88 +21,30 @@
**/
/*
* DriveBoard.h
* JoyBoard.h
*
* Header for the CDriveBoard (force feedback emulation) class.
* Header for the CJoyBoard (force feedback emulation for joystick) class.
*/
#ifndef INCLUDED_DRIVEBOARD_H
#define INCLUDED_DRIVEBOARD_H
#ifndef INCLUDED_JOYBOARD_H
#define INCLUDED_JOYBOARD_H
#include "Util/NewConfig.h"
#include "Game.h"
/*
* CDriveBoard
* CJoyBoard
*/
class CDriveBoard : public IBus
class CJoyBoard : public CDriveBoard
{
public:
enum BoardType
{
Wheel=0,
Joystick,
SkiPad
};
BoardType m_boardType;
/*
* IsAttached(void):
* GetType(void):
*
* Returns:
* True if the drive board is "attached" and should be emulated,
* otherwise false.
* Drive board type.
*/
bool IsAttached(void);
/*
* IsSimulated(void):
*
* Returns:
* True if the drive board is being simulated rather than actually
* emulated, otherwise false.
*/
bool IsSimulated(void);
/*
* GetDIPSwitches(dip1, dip2):
*
* Reads the two sets of DIP switches on the drive board.
*
* Parameters:
* dip1 Reference of variable to store DIP switch 1 to.
* dip2 DIP switch 2.
*/
void GetDIPSwitches(UINT8 &dip1, UINT8 &dip2);
/*
* SetDIPSwitches(dip1, dip2):
*
* Sets the DIP switches.
*
* Parameters:
* dip1 DIP switch 1 value.
* dip2 DIP switch 2 value.
*/
void SetDIPSwitches(UINT8 dip1, UINT8 dip2);
/*
* GetSteeringStrength(void):
*
* Returns:
* Strength of the steering based on drive board DIP switches (1-8).
*/
unsigned GetSteeringStrength(void);
/*
* SetSteeringStrength(steeringStrength):
*
* Sets the steering strength (modifies the DIP switch setting).
*
* Parameters:
* steeringStrength A value ranging from 1 to 8.
*/
void SetSteeringStrength(unsigned steeringStrength);
Game::DriveBoardType GetType(void);
/*
* Get7SegDisplays(seg1Digit1, seg1Digit2, seg2Digit1, seg2Digit2):
@ -117,13 +60,6 @@ public:
*/
void Get7SegDisplays(UINT8 &seg1Digit, UINT8 &seg1Digit2, UINT8 &seg2Digit1, UINT8 &seg2Digit2);
/*
* GetZ80(void):
*
* Returns:
* The Z80 object.
*/
CZ80 *GetZ80(void);
/*
* SaveState(SaveState):
@ -145,38 +81,6 @@ public:
*/
void LoadState(CBlockFile *SaveState);
/*
* Init(romPtr):
*
* Initializes (and "attaches") the drive board. This should be called
* before other members.
*
* Parameters:
* romPtr Pointer to the drive board ROM (Z80 program). If this
* is NULL, then the drive board will not be emulated.
*
* Returns:
* FAIL if the drive board could not be initialized (prints own error
* message), otherwise OKAY. If the drive board is not attached
* because no ROM was passed to it, no error is generated and the
* drive board is silently disabled (detached).
*/
bool Init(const UINT8 *romPtr);
/*
* AttachInputs(InputsPtr, gameInputFlags):
*
* Attaches inputs to the drive board (for access to the steering wheel
* position).
*
* Parameters:
* inputs Pointer to the input object.
* gameInputFlags The current game's input flags.
*/
void AttachInputs(CInputs *inputs, unsigned gameInputFlags);
void AttachOutputs(COutputs *outputs);
/*
* Reset(void):
*
@ -221,8 +125,8 @@ public:
* config Run-time configuration. The reference should be held because
* this changes at run-time.
*/
CDriveBoard(const Util::Config::Node &config);
~CDriveBoard(void);
CJoyBoard(const Util::Config::Node &config);
~CJoyBoard(void);
/*
* Read8(addr):
@ -237,7 +141,6 @@ public:
* Returns:
* A byte of data from the address or port.
*/
UINT8 Read8(UINT32 addr);
UINT8 IORead8(UINT32 portNum);
/*
@ -251,40 +154,18 @@ public:
* portNum Port address (0-255).
* data Byte to write.
*/
void Write8(UINT32 addr, UINT8 data);
void IOWrite8(UINT32 portNum, UINT8 data);
protected:
void Disable(void);
private:
const Util::Config::Node &m_config;
bool m_attached; // True if drive board is attached
bool m_tmpDisabled; // True if temporarily disabled by loading an incompatible save state
bool m_simulated; // True if drive board should be simulated rather than emulated
UINT8 m_dip1; // Value of DIP switch 1
UINT8 m_dip2; // Value of DIP switch 2
const UINT8* m_rom; // 32k ROM
UINT8* m_ram; // 8k RAM
CZ80 m_z80; // Z80 CPU @ 4MHz
CInputs *m_inputs;
unsigned m_inputFlags;
COutputs *m_outputs;
// Emulation state
bool m_initialized; // True if drive board has finished initialization
bool m_allowInterrupts; // True if drive board has enabled NMI interrupts
UINT8 m_seg1Digit1; // Current value of left digit on 7-segment display 1
UINT8 m_seg1Digit2; // Current value of right digit on 7-segment display 1
UINT8 m_seg2Digit1; // Current value of left digit on 7-segment display 2
UINT8 m_seg2Digit2; // Current value of right digit on 7-segment display 2
UINT8 m_dataSent; // Last command sent by main board
UINT8 m_dataReceived; // Data to send back to main board
UINT16 m_adcPortRead; // ADC port currently reading from
UINT8 m_adcPortBit; // Bit number currently reading on ADC port
@ -299,15 +180,6 @@ private:
UINT8 m_uncenterVal1; // First part of pending uncenter command
UINT8 m_uncenterVal2; // Second part of pending uncenter command
// Simulation state
UINT8 m_initState;
UINT8 m_statusFlags;
UINT8 m_boardMode;
UINT8 m_readMode;
UINT8 m_wheelCenter;
UINT8 m_cockpitCenter;
UINT8 m_echoVal;
// Feedback state
INT8 m_lastConstForce; // Last constant force command sent
INT8 m_lastConstForceY; // Last constant force command sent y axis
@ -321,10 +193,6 @@ private:
void SimulateFrame(void);
void EmulateFrame(void);
void ProcessEncoderCmd(void);
void ProcessEncoderCmdJoystick(void);
void SendStopAll(void);
@ -348,4 +216,4 @@ private:
uint8_t ReadADCChannel4();
};
#endif // INCLUDED_DRIVEBOARD_H
#endif // INCLUDED_JOYBOARD_H

View file

@ -0,0 +1,266 @@
/**
** 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/>.
**/
/*
* 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 <cstdio>
#include <cmath>
#include <algorithm>
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<bool>(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)
{
case 0x0: return m_statusFlags; // Status flags
case 0x1: return m_dip1; // DIP switch 1 value
case 0x2: return m_dip2; // DIP switch 2 value
case 0x3: return m_wheelCenter; // Wheel center
case 0x4: return 0x80; // Cockpit banking center
case 0x5: return m_inputs->skiX->value; // Wheel position
case 0x6: return 0x80; // Cockpit banking position
case 0x7: return m_echoVal; // Init status/echo test
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;
m_dip2 = 0xFF;
DebugLog("Built Drive Board (ski pad)\n");
}
CSkiBoard::~CSkiBoard(void)
{
}

View file

@ -0,0 +1,158 @@
/**
** 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/>.
**/
/*
* SkiBoard.h
*
* Header for the CSkiBoard (rumble skipad emulation) class.
*/
#ifndef INCLUDED_SKIBOARD_H
#define INCLUDED_SKIBOARD_H
#include "Util/NewConfig.h"
#include "Game.h"
/*
* CSkiBoard
*/
class CSkiBoard : public CDriveBoard
{
public:
/*
* GetType(void):
*
* Returns:
* Drive board type.
*/
Game::DriveBoardType GetType(void);
unsigned GetForceFeedbackStrength(void);
void SetForceFeedbackStrength(unsigned strength);
/*
* SaveState(SaveState):
*
* Saves the drive board state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
//void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Restores the drive board state.
*
* Parameters:
* SaveState Block file to load save state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* Init(romPtr):
*
* Initializes (and "attaches") the drive board. This should be called
* before other members.
*
* Parameters:
* romPtr Pointer to the drive board ROM (Z80 program). If this
* is NULL, then the drive board will not be emulated.
*
* Returns:
* FAIL if the drive board could not be initialized (prints own error
* message), otherwise OKAY. If the drive board is not attached
* because no ROM was passed to it, no error is generated and the
* drive board is silently disabled (detached).
*/
bool Init(const UINT8 *romPtr);
/*
* Reset(void):
*
* Resets the drive board.
*/
void Reset(void);
/*
* Read():
*
* Reads data from the drive board.
*
* Returns:
* Data read.
*/
UINT8 Read(void);
/*
* Write(data):
*
* Writes data to the drive board.
*
* Parameters:
* data Data to send.
*/
void Write(UINT8 data);
/*
* RunFrame(void):
*
* Emulates a single frame's worth of time on the drive board.
*/
void RunFrame(void);
/*
* CWheelBoard(config):
* ~CWheelBoard():
*
* Constructor and destructor. Memory is freed by destructor.
*
* Paramters:
* config Run-time configuration. The reference should be held because
* this changes at run-time.
*/
CSkiBoard(const Util::Config::Node &config);
~CSkiBoard(void);
// needed by abstract class
UINT8 Read8(UINT32 addr);
void Write8(UINT32 addr, UINT8 data);
protected:
void Disable(void);
private:
// Feedback state
UINT8 m_lastVibrate; // Last vibrate command sent
UINT8 SimulateRead(void);
void SimulateWrite(UINT8 data);
void SendVibrate(UINT8 val);
};
#endif // INCLUDED_SKIBOARD_H

View file

@ -0,0 +1,749 @@
/**
** 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/>.
**/
/*
* WheelBoard.cpp
*
* Implementation of the CWheelBoard class: drive board (force feedback for wheel)
* emulation.
*
* NOTE: Simulation does not yet work. Drive board ROMs are required.
*/
#include "Supermodel.h"
#include <cstdio>
#include <cmath>
#include <algorithm>
Game::DriveBoardType CWheelBoard::GetType(void)
{
return Game::DRIVE_BOARD_WHEEL;
}
void CWheelBoard::Get7SegDisplays(UINT8 &seg1Digit1, UINT8 &seg1Digit2, UINT8 &seg2Digit1, UINT8 &seg2Digit2)
{
seg1Digit1 = m_seg1Digit1;
seg1Digit2 = m_seg1Digit2;
seg2Digit1 = m_seg2Digit1;
seg2Digit2 = m_seg2Digit2;
}
void CWheelBoard::SaveState(CBlockFile *SaveState)
{
CDriveBoard::SaveState(SaveState);
SaveState->NewBlock("WheelBoard", __FILE__);
SaveState->Write(&m_simulated, sizeof(m_simulated));
if (m_simulated)
{
// TODO - save board simulation state
}
else
{
// Save DIP switches and digit displays
SaveState->Write(&m_dip1, sizeof(m_dip1));
SaveState->Write(&m_dip2, sizeof(m_dip2));
SaveState->Write(&m_adcPortRead, sizeof(m_adcPortRead));
SaveState->Write(&m_adcPortBit, sizeof(m_adcPortBit));
SaveState->Write(&m_uncenterVal1, sizeof(m_uncenterVal1));
SaveState->Write(&m_uncenterVal2, sizeof(m_uncenterVal2));
}
}
void CWheelBoard::LoadState(CBlockFile *SaveState)
{
if (SaveState->FindBlock("WheelBoard") != OKAY)
{
// Fall back to old "DriveBoad" state format
LoadLegacyState(SaveState);
return;
}
bool wasSimulated;
SaveState->Read(&wasSimulated, sizeof(wasSimulated));
if (wasSimulated)
{
// Simulation has never existed
ErrorLog("Save state contains unexpected data. Halting drive board emulation.");
Disable();
return;
}
else
{
// Load DIP switches and digit displays
SaveState->Read(&m_dip1, sizeof(m_dip1));
SaveState->Read(&m_dip2, sizeof(m_dip2));
SaveState->Read(&m_adcPortRead, sizeof(m_adcPortRead));
SaveState->Read(&m_adcPortBit, sizeof(m_adcPortBit));
SaveState->Read(&m_uncenterVal1, sizeof(m_uncenterVal1));
SaveState->Read(&m_uncenterVal2, sizeof(m_uncenterVal2));
}
CDriveBoard::LoadState(SaveState);
}
// Load save states created prior to DriveBoard refactor of SVN 847
void CWheelBoard::LoadLegacyState(CBlockFile *SaveState)
{
if (SaveState->FindBlock("DriveBoard") != OKAY)
{
// No wheel board or legacy drive board data found
ErrorLog("Unable to load wheel drive board state. Save state file is corrupt.");
Disable();
return;
}
CDriveBoard::LegacyDriveBoardState state;
bool isEnabled = !IsDisabled();
bool wasEnabled = false;
bool wasSimulated = false;
SaveState->Read(&wasEnabled, sizeof(wasEnabled));
if (wasEnabled)
{
SaveState->Read(&wasSimulated, sizeof(wasSimulated));
if (wasSimulated)
{
// Simulation has never actually existed
ErrorLog("Save state contains unexpected data. Halting drive board emulation.");
Disable();
return;
}
else
{
SaveState->Read(&state.dip1, sizeof(state.dip1));
SaveState->Read(&state.dip2, sizeof(state.dip2));
SaveState->Read(state.ram, 0x2000);
SaveState->Read(&state.initialized, sizeof(state.initialized));
SaveState->Read(&state.allowInterrupts, sizeof(state.allowInterrupts));
SaveState->Read(&state.dataSent, sizeof(state.dataSent));
SaveState->Read(&state.dataReceived, sizeof(state.dataReceived));
SaveState->Read(&state.adcPortRead, sizeof(state.adcPortRead));
SaveState->Read(&state.adcPortBit, sizeof(state.adcPortBit));
SaveState->Read(&state.uncenterVal1, sizeof(state.uncenterVal1));
SaveState->Read(&state.uncenterVal2, sizeof(state.uncenterVal2));
}
}
if (wasEnabled != isEnabled)
{
// If the board was not in the same activity state when the save file was
// generated, we cannot safely resume and must disable it
Disable();
ErrorLog("Halting drive board emulation due to mismatch in active and restored states.");
}
else
{
// Success: pass along to base class
CDriveBoard::LoadLegacyState(state, SaveState);
}
}
void CWheelBoard::Disable(void)
{
SendStopAll();
CDriveBoard::Disable();
}
void CWheelBoard::Reset(void)
{
CDriveBoard::Reset();
m_seg1Digit1 = 0xFF;
m_seg1Digit2 = 0xFF;
m_seg2Digit1 = 0xFF;
m_seg2Digit2 = 0xFF;
m_adcPortRead = 0;
m_adcPortBit = 0;
m_port42Out = 0;
m_port46Out = 0;
m_prev42Out = 0;
m_prev46Out = 0;
m_uncenterVal1 = 0;
m_uncenterVal2 = 0;
m_lastConstForce = 0;
m_lastSelfCenter = 0;
m_lastFriction = 0;
m_lastVibrate = 0;
m_simulated = false; //TODO: make this run-time configurable when simulation mode is supported
if (!m_config["ForceFeedback"].ValueAsDefault<bool>(false))
Disable();
// Stop any effects that may still be playing
if (!IsDisabled())
SendStopAll();
}
UINT8 CWheelBoard::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)
if (m_simulated)
return SimulateRead();
else
return CDriveBoard::Read();
}
void CWheelBoard::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);
else
{
CDriveBoard::Write(data);
if (data == 0xCB)
m_initialized = false;
}
}
UINT8 CWheelBoard::SimulateRead(void)
{
if (m_initialized)
{
switch (m_readMode)
{
case 0x0: return m_statusFlags; // Status flags
case 0x1: return m_dip1; // DIP switch 1 value
case 0x2: return m_dip2; // DIP switch 2 value
case 0x3: return m_wheelCenter; // Wheel center
case 0x4: return 0x80; // Cockpit banking center
case 0x5: return m_inputs->steering->value; // Wheel position
case 0x6: return 0x80; // Cockpit banking position
case 0x7: return m_echoVal; // Init status/echo test
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 CWheelBoard::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;
switch (type)
{
case 0: // 0x00-0F Play sequence
/* TODO */
break;
case 1: // 0x10-1F Set centering strength
if (val == 0)
// Disable auto-centering
// TODO - is 0x10 for disable?
SendSelfCenter(0);
else
// Enable auto-centering (0x1 = weakest, 0xF = strongest)
SendSelfCenter(val * 0x11);
break;
case 2: // 0x20-2F Friction strength
if (val == 0)
// Disable friction
// TODO - is 0x20 for disable?
SendFriction(0);
else
// Enable friction (0x1 = weakest, 0xF = strongest)
SendFriction(val * 0x11);
break;
case 3: // 0x30-3F Uncentering (vibrate)
if (val == 0)
// Disable uncentering
SendVibrate(0);
else
// Enable uncentering (0x1 = weakest, 0xF = strongest)
SendVibrate(val * 0x11);
break;
case 4: // 0x40-4F Play power-slide sequence
/* TODO */
break;
case 5: // 0x50-5F Rotate wheel right
SendConstantForce((val + 1) * 0x5);
break;
case 6: // 0x60-6F Rotate wheel left
SendConstantForce(-(val + 1) * 0x5);
break;
case 7: // 0x70-7F Set steering parameters
/* TODO */
break;
case 8: // 0x80-8F Test Mode
switch (val & 0x7)
{
case 0: SendStopAll(); break; // 0x80 Stop motor
case 1: SendConstantForce(20); break; // 0x81 Roll wheel right
case 2: SendConstantForce(-20); break; // 0x82 Roll wheel left
case 3: /* Ignore - no clutch */ break; // 0x83 Clutch on
case 4: /* Ignore - no clutch */ break; // 0x84 Clutch off
case 5: m_wheelCenter = m_inputs->steering->value; break; // 0x85 Set wheel center position
case 6: /* Ignore */ break; // 0x86 Set cockpit banking position
case 7: /* Ignore */ break; // 0x87 Lamp on/off
}
case 0x9: // 0x90-9F ??? Don't appear to have any effect with Scud Race ROM
/* TODO */
break;
case 0xA: // 0xA0-AF ??? Don't appear to have any effect with Scud Race ROM
/* TODO */
break;
case 0xB: // 0xB0-BF Invalid command (reserved for use by PPC to send cabinet type 0xB0 or 0xB1 during initialization)
/* Ignore */
break;
case 0xC: // 0xC0-CF Set board mode (0xCB = reset board)
SendStopAll();
if (val >= 0xB)
{
// Reset board
m_initialized = false;
m_initState = 0;
}
else
m_boardMode = val;
break;
case 0xD: // 0xD0-DF Set read mode
m_readMode = val & 0x7;
break;
case 0xE: // 0xE0-EF Invalid command
/* Ignore */
break;
case 0xF: // 0xF0-FF Echo test
m_echoVal = val;
break;
}
}
void CWheelBoard::RunFrame(void)
{
if (IsDisabled())
{
return;
}
if (m_simulated)
SimulateFrame();
else
CDriveBoard::RunFrame();
}
void CWheelBoard::SimulateFrame(void)
{
if (!m_initialized)
m_initState++;
// TODO - update m_statusFlags and play preset scripts according to board mode
}
UINT8 CWheelBoard::IORead8(UINT32 portNum)
{
UINT8 adcVal;
switch (portNum)
{
case 0x20: // DIP 1 value
return m_dip1;
case 0x21: // DIP 2 value
return m_dip2;
case 0x24: // ADC channel 1 - Y analog axis for joystick
case 0x25: // ADC channel 2 - steering wheel position (0x00 = full left, 0x80 = center, 0xFF = full right) and X analog axis for joystick
case 0x26: // ADC channel 3 - cockpit bank position (deluxe cabinets) (0x00 = full left, 0x80 = center, 0xFF = full right)
case 0x27: // ADC channel 4 - not connected
if (portNum == m_adcPortRead && m_adcPortBit-- > 0)
{
switch (portNum)
{
case 0x24: // Y analog axis for joystick
adcVal = ReadADCChannel1();
break;
case 0x25: // Steering wheel for twin racing cabinets - TODO - check actual range of steering, suspect it is not really 0x00-0xFF
adcVal = ReadADCChannel2();
break;
case 0x26: // Cockpit bank position for deluxe racing cabinets
adcVal = ReadADCChannel3();
break;
case 0x27: // Not connected
adcVal = ReadADCChannel4();
break;
default:
DebugLog("Unhandled Z80 input on ADC port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return 0xFF;
}
return (adcVal >> m_adcPortBit) & 0x01;
}
else
{
DebugLog("Unhandled Z80 input on ADC port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return 0xFF;
}
case 0x28: // PPC command
return m_dataSent;
case 0x2c: // Encoder error reporting (kept at 0x00 for no error)
// Bit 1 0
// 0 0 = encoder okay, no error
// 0 1 = encoder error 1 - overcurrent error
// 1 0 = encoder error 2 - overheat error
// 1 1 = encoder error 3 - encoder error, reinitializes board
return 0x00;
default:
DebugLog("Unhandled Z80 input on port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return 0xFF;
}
}
void CWheelBoard::IOWrite8(UINT32 portNum, UINT8 data)
{
switch (portNum)
{
case 0x10: // Unsure? - single byte 0x03 sent at initialization, then occasionally writes 0x07 & 0xFA to port
return;
case 0x11: // Interrupt control
if (data == 0x57)
m_allowInterrupts = true;
else if (data == 0x53) // Strictly speaking 0x53 then 0x04
m_allowInterrupts = false;
return;
case 0x1c: // Unsure? - two bytes 0xFF, 0xFF sent at initialization only
case 0x1d: // Unsure? - two bytes 0x0F, 0x17 sent at initialization only
case 0x1e: // Unsure? - same as port 28
case 0x1f: // Unsure? - same as port 31
return;
case 0x20: // Left digit of 7-segment display 1
m_seg1Digit1 = data;
return;
case 0x21: // Right digit of 7-segment display 1
m_seg1Digit2 = data;
return;
case 0x22: // Left digit of 7-segment display 2
m_seg2Digit1 = data;
return;
case 0x23: // Right digit of 7-segment display 2
m_seg2Digit2 = data;
return;
case 0x24: // ADC channel 1 control
case 0x25: // ADC channel 2 control
case 0x26: // ADC channel 3 control
case 0x27: // ADC channel 4 control
m_adcPortRead = portNum;
m_adcPortBit = 8;
return;
case 0x29: // Reply for PPC
m_dataReceived = data;
if (data == 0xCC)
m_initialized = true;
return;
case 0x2a: // Encoder motor data (x axis)
m_port42Out = data;
ProcessEncoderCmd();
return;
case 0x2d: // Clutch/lamp control (deluxe cabinets) ( or y axis)
return;
case 0x2e: // Encoder motor control
m_port46Out = data;
return;
case 0xf0: // Unsure? - single byte 0xBB sent at initialization only
return;
case 0xf1: // Unsure? - single byte 0x4E sent regularly - some sort of watchdog?
return;
default:
DebugLog("Unhandled Z80 output on port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
return;
}
}
void CWheelBoard::ProcessEncoderCmd(void)
{
if (m_prev42Out != m_port42Out || m_prev46Out != m_port46Out)
{
//DebugLog("46 [%02X] / 42 [%02X]\n", m_port46Out, m_port42Out);
switch (m_port46Out)
{
case 0xFB:
// TODO - friction? Sent during power slide. 0xFF = strongest or 0x00?
//SendFriction(m_port42Out);
break;
case 0xFC:
// Centering / uncentering (vibrate)
// Bit 2 = on for centering, off for uncentering
if (m_port42Out&0x04)
{
// Centering
// Bit 7 = on for disable, off for enable
if (m_port42Out&0x80)
{
// Disable centering
SendSelfCenter(0);
}
else
{
// Bits 3-6 = centering strength 0x0-0xF. This is scaled to range 0x0F-0xFF
UINT8 strength = ((m_port42Out&0x78)>>3) * 0x10 + 0xF;
SendSelfCenter(strength);
}
}
else
{
// Uncentering
// Bits 0-1 = data sequence number 0-3
UINT8 seqNum = m_port42Out&0x03;
// Bits 4-7 = data values
UINT16 data = (m_port42Out&0xF0)>>4;
switch (seqNum)
{
case 0: m_uncenterVal1 = data<<4; break;
case 1: m_uncenterVal1 |= data; break;
case 2: m_uncenterVal2 = data<<4; break;
case 3: m_uncenterVal2 |= data; break;
}
if (seqNum == 0 && m_uncenterVal1 == 0)
{
// Disable uncentering
SendVibrate(0);
}
else if (seqNum == 3 && m_uncenterVal1 > 0)
{
// Uncentering - unsure exactly how values sent map to strength or whether they specify some other attributes of effect
// For now just attempting to map them to a sensible value in range 0x00-0xFF
UINT8 strength = ((m_uncenterVal1>>1) - 7) * 0x50 + ((m_uncenterVal2>>1) - 5) * 0x10 + 0xF;
SendVibrate(strength);
}
}
break;
case 0xFD:
// TODO - unsure? Sent as velocity changes, similar to self-centering
break;
case 0xFE:
// Apply constant force to wheel
// Value is: 0x80 = stop motor, 0x81-0xC0 = roll wheel left, 0x40-0x7F = roll wheel right, scale to range -0x80-0x7F
// Note: seems to often output 0x7F or 0x81 for stop motor, so narrowing wheel ranges to 0x40-0x7E and 0x82-0xC0
if (m_port42Out > 0x81)
{
if (m_port42Out <= 0xC0)
SendConstantForce(2 * (0x81 - m_port42Out));
else
SendConstantForce(-0x80);
}
else if (m_port42Out < 0x7F)
{
if (m_port42Out >= 0x40)
SendConstantForce(2 * (0x7F - m_port42Out));
else
SendConstantForce(0x7F);
}
else
SendConstantForce(0);
break;
case 0xFF:
// Stop all effects
if (m_port42Out == 0)
SendStopAll();
break;
default:
//DebugLog("Unknown = 46 [%02X] / 42 [%02X]\n", m_port46Out, m_port42Out);
break;
}
m_prev42Out = m_port42Out;
m_prev46Out = m_port46Out;
}
}
void CWheelBoard::SendStopAll(void)
{
//DebugLog(">> Stop All Effects\n");
ForceFeedbackCmd ffCmd;
ffCmd.id = FFStop;
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
m_lastConstForce = 0;
m_lastSelfCenter = 0;
m_lastFriction = 0;
m_lastVibrate = 0;
}
void CWheelBoard::SendConstantForce(INT8 val)
{
if (val == m_lastConstForce)
return;
/*
if (val > 0)
{
DebugLog(">> Force Right %02X [%8s", val, "");
for (unsigned i = 0; i < 8; i++)
DebugLog(i == 0 || i <= (val + 1) / 16 ? ">" : " ");
DebugLog("]\n");
}
else if (val < 0)
{
DebugLog(">> Force Left %02X [", -val);
for (unsigned i = 0; i < 8; i++)
DebugLog(i == 7 || i >= (val + 128) / 16 ? "<" : " ");
DebugLog("%8s]\n", "");
}
else
DebugLog(">> Stop Force [%16s]\n", "");
*/
ForceFeedbackCmd ffCmd;
ffCmd.id = FFConstantForce;
ffCmd.force = (float)val / (val >= 0 ? 127.0f : 128.0f);
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
m_lastConstForce = val;
}
void CWheelBoard::SendSelfCenter(UINT8 val)
{
if (val == m_lastSelfCenter)
return;
/*
if (val == 0)
DebugLog(">> Stop Self-Center\n");
else
DebugLog(">> Self-Center %02X\n", val);
*/
ForceFeedbackCmd ffCmd;
ffCmd.id = FFSelfCenter;
ffCmd.force = (float)val / 255.0f;
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
m_lastSelfCenter = val;
}
void CWheelBoard::SendFriction(UINT8 val)
{
if (val == m_lastFriction)
return;
/*
if (val == 0)
DebugLog(">> Stop Friction\n");
else
DebugLog(">> Friction %02X\n", val);
*/
ForceFeedbackCmd ffCmd;
ffCmd.id = FFFriction;
ffCmd.force = (float)val / 255.0f;
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
m_lastFriction = val;
}
void CWheelBoard::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->steering->SendForceFeedbackCmd(ffCmd);
m_lastVibrate = val;
}
uint8_t CWheelBoard::ReadADCChannel1()
{
return 0x00;
}
uint8_t CWheelBoard::ReadADCChannel2()
{
if (m_initialized)
return (UINT8)m_inputs->steering->value;
else
return 0x80; // If not initialized, return 0x80 so that wheel centering test does not fail
}
uint8_t CWheelBoard::ReadADCChannel3()
{
return 0x80;
}
uint8_t CWheelBoard::ReadADCChannel4()
{
return 0x00;
}
CWheelBoard::CWheelBoard(const Util::Config::Node &config)
: CDriveBoard(config)
{
m_dip1 = 0xCF;
m_dip2 = 0xFF;
DebugLog("Built Drive Board (wheel)\n");
}
CWheelBoard::~CWheelBoard(void)
{
}

View file

@ -0,0 +1,215 @@
/**
** 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/>.
**/
/*
* WheelBoard.h
*
* Header for the CWheelBoard (force feedback emulation for wheel) class.
*/
#ifndef INCLUDED_WHEELBOARD_H
#define INCLUDED_WHEELBOARD_H
#include "Util/NewConfig.h"
#include "Game.h"
/*
* CWheelBoard
*/
class CWheelBoard : public CDriveBoard
{
public:
/*
* GetType(void):
*
* Returns:
* Drive board type.
*/
Game::DriveBoardType GetType(void);
/*
* Get7SegDisplays(seg1Digit1, seg1Digit2, seg2Digit1, seg2Digit2):
*
* Reads the 7-segment displays.
*
* Parameters:
* seg1Digit1 Reference of variable to store digit 1 of the first 7-
* segment display to.
* seg1Digit2 First display, second digit.
* seg2Digit1 Second display, first digit.
* seg2Digit2 Second display, second digit.
*/
void Get7SegDisplays(UINT8 &seg1Digit, UINT8 &seg1Digit2, UINT8 &seg2Digit1, UINT8 &seg2Digit2);
/*
* SaveState(SaveState):
*
* Saves the drive board state.
*
* Parameters:
* SaveState Block file to save state information to.
*/
void SaveState(CBlockFile *SaveState);
/*
* LoadState(SaveState):
*
* Restores the drive board state.
*
* Parameters:
* SaveState Block file to load save state information from.
*/
void LoadState(CBlockFile *SaveState);
/*
* Reset(void):
*
* Resets the drive board.
*/
void Reset(void);
/*
* Read():
*
* Reads data from the drive board.
*
* Returns:
* Data read.
*/
UINT8 Read(void);
/*
* Write(data):
*
* Writes data to the drive board.
*
* Parameters:
* data Data to send.
*/
void Write(UINT8 data);
/*
* RunFrame(void):
*
* Emulates a single frame's worth of time on the drive board.
*/
void RunFrame(void);
/*
* CWheelBoard(config):
* ~CWheelBoard():
*
* Constructor and destructor. Memory is freed by destructor.
*
* Paramters:
* config Run-time configuration. The reference should be held because
* this changes at run-time.
*/
CWheelBoard(const Util::Config::Node &config);
~CWheelBoard(void);
/*
* Read8(addr):
* IORead8(portNum):
*
* Methods for reading from Z80's memory and IO space. Required by CBus.
*
* Parameters:
* addr Address in memory (0-0xFFFF).
* portNum Port address (0-255).
*
* Returns:
* A byte of data from the address or port.
*/
UINT8 IORead8(UINT32 portNum);
/*
* Write8(addr, data):
* IORead8(portNum, data):
*
* Methods for writing to Z80's memory and IO space. Required by CBus.
*
* Parameters:
* addr Address in memory (0-0xFFFF).
* portNum Port address (0-255).
* data Byte to write.
*/
void IOWrite8(UINT32 portNum, UINT8 data);
protected:
void Disable(void);
private:
void LoadLegacyState(CBlockFile *SaveState);
UINT8 m_seg1Digit1; // Current value of left digit on 7-segment display 1
UINT8 m_seg1Digit2; // Current value of right digit on 7-segment display 1
UINT8 m_seg2Digit1; // Current value of left digit on 7-segment display 2
UINT8 m_seg2Digit2; // Current value of right digit on 7-segment display 2
UINT16 m_adcPortRead; // ADC port currently reading from
UINT8 m_adcPortBit; // Bit number currently reading on ADC port
UINT8 m_port42Out; // Last value sent to Z80 I/O port 42 (encoder motor data)
UINT8 m_port46Out; // Last value sent to Z80 I/O port 46 (encoder motor control)
UINT8 m_prev42Out; // Previous value sent to Z80 I/O port 42
UINT8 m_prev46Out; // Previous value sent to Z80 I/O port 46
UINT8 m_uncenterVal1; // First part of pending uncenter command
UINT8 m_uncenterVal2; // Second part of pending uncenter command
// Feedback state
INT8 m_lastConstForce; // Last constant force command sent
UINT8 m_lastSelfCenter; // Last self center command sent
UINT8 m_lastFriction; // Last friction command sent
UINT8 m_lastVibrate; // Last vibrate command sent
UINT8 SimulateRead(void);
void SimulateWrite(UINT8 data);
void SimulateFrame(void);
void ProcessEncoderCmd(void);
void SendStopAll(void);
void SendConstantForce(INT8 val);
void SendSelfCenter(UINT8 val);
void SendFriction(UINT8 val);
void SendVibrate(UINT8 val);
uint8_t ReadADCChannel1();
uint8_t ReadADCChannel2();
uint8_t ReadADCChannel3();
uint8_t ReadADCChannel4();
};
#endif // INCLUDED_WHEELBOARD_H

View file

@ -1,7 +1,8 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011-2019 Bart Trzynadlowski, Nik Henson, Ian Curtis
** Copyright 2011-2021 Bart Trzynadlowski, Nik Henson, Ian Curtis,
** Harry Tuttle, and Spindizzi
**
** This file is part of Supermodel.
**
@ -463,8 +464,11 @@ UINT8 CModel3::ReadInputs(unsigned reg)
data = 0xFF;
if (DriveBoard.IsAttached())
data = DriveBoard.Read();
if (DriveBoard->IsAttached() && DriveBoard->GetType() != Game::DRIVE_BOARD_BILLBOARD)
{
// If driveboard is set as billboard, don't read BillBoard reg (no inputs)
data = DriveBoard->Read();
}
if ((m_game.inputs & Game::INPUT_JOYSTICK2))
{
@ -643,8 +647,7 @@ void CModel3::WriteInputs(unsigned reg, UINT8 data)
break;
case 0x10: // Drive board
if (DriveBoard.IsAttached())
DriveBoard.Write(data);
DriveBoard->Write(data);
if (NULL != Outputs) // TODO - check gameInputs
Outputs->SetValue(OutputRawDrive, data);
break;
@ -1947,7 +1950,7 @@ void CModel3::SaveState(CBlockFile *SaveState)
TileGen.SaveState(SaveState);
GPU.SaveState(SaveState);
SoundBoard.SaveState(SaveState); // also saves DSB state
DriveBoard.SaveState(SaveState);
DriveBoard->SaveState(SaveState);
m_cryptoDevice.SaveState(SaveState);
m_jtag.SaveState(SaveState);
}
@ -1986,7 +1989,7 @@ void CModel3::LoadState(CBlockFile *SaveState)
IRQ.LoadState(SaveState);
ppc_load_state(SaveState);
SoundBoard.LoadState(SaveState);
DriveBoard.LoadState(SaveState);
DriveBoard->LoadState(SaveState);
m_cryptoDevice.LoadState(SaveState);
m_jtag.LoadState(SaveState);
}
@ -2035,7 +2038,7 @@ void CModel3::RunFrame(void)
// Wake threads for PPC main board (if multi-threading GPU), sound board (if sync'd) and drive board (if attached) so they can process a frame
if ((m_gpuMultiThreaded && !ppcBrdThreadSync->Post()) ||
(syncSndBrdThread && !sndBrdThreadSync->Post()) ||
(DriveBoard.IsAttached() && !drvBrdThreadSync->Post()))
(DriveBoard->IsAttached() && !drvBrdThreadSync->Post()))
goto ThreadError;
// If not multi-threading GPU, then run PPC main board for a frame and sync GPUs now in this thread
@ -2055,7 +2058,7 @@ void CModel3::RunFrame(void)
// Wait for PPC main board, sound board and drive board threads to finish their work (if they are running and haven't finished already)
while ((m_gpuMultiThreaded && !ppcBrdThreadDone) ||
(syncSndBrdThread && !sndBrdThreadDone) ||
(DriveBoard.IsAttached() && !drvBrdThreadDone))
(DriveBoard->IsAttached() && !drvBrdThreadDone))
{
if (!notifySync->Wait(notifyLock))
goto ThreadError;
@ -2082,7 +2085,7 @@ void CModel3::RunFrame(void)
SyncGPUs();
RenderFrame();
RunSoundBoardFrame();
if (DriveBoard.IsAttached())
if (DriveBoard->IsAttached())
RunDriveBoardFrame();
#ifdef NET_BOARD
if (NetBoard.IsAttached() && (m_config["EmulateNet"].ValueAs<bool>()) && ((*(UINT16 *)&netBuffer[(0xc00100C0 & 0x3FFFF)] == 0xFFFF) || (netBuffer[(0xc00100C0 & 0x3FFFF)] == 0xFF) || (*(UINT16 *)&netBuffer[(0xc00100C0 & 0x3FFFF)] == 0x0001)) && (NetBoard.CodeReady == true))
@ -2237,7 +2240,7 @@ bool CModel3::RunSoundBoardFrame(void)
void CModel3::RunDriveBoardFrame(void)
{
UINT32 start = CThread::GetTicks();
DriveBoard.RunFrame();
DriveBoard->RunFrame();
timings.drvTicks = CThread::GetTicks() - start;
}
@ -2269,7 +2272,7 @@ bool CModel3::StartThreads(void)
sndBrdNotifySync = CThread::CreateCondVar();
if (sndBrdNotifySync == NULL)
goto ThreadError;
if (DriveBoard.IsAttached())
if (DriveBoard->IsAttached())
{
drvBrdThreadSync = CThread::CreateSemaphore(0);
if (drvBrdThreadSync == NULL)
@ -2303,7 +2306,7 @@ bool CModel3::StartThreads(void)
goto ThreadError;
// Create drive board thread, if drive board is attached
if (DriveBoard.IsAttached())
if (DriveBoard->IsAttached())
{
drvBrdThread = CThread::CreateThread("DriveBoard", StartDriveBoardThread, this);
if (drvBrdThread == NULL)
@ -2877,8 +2880,8 @@ void CModel3::Reset(void)
SoundBoard.Reset();
m_jtag.Reset();
if (DriveBoard.IsAttached())
DriveBoard.Reset();
if (DriveBoard->IsAttached())
DriveBoard->Reset();
m_cryptoDevice.Reset();
@ -2952,7 +2955,6 @@ bool CModel3::LoadGame(const Game &game, const ROMSet &rom_set)
rom_set.get_rom("mpeg_program").CopyTo(dsbROM, 128*1024);
rom_set.get_rom("mpeg_music").CopyTo(mpegROM, 16*0x100000);
rom_set.get_rom("driveboard_program").CopyTo(driveROM, 64*1024);
rom_set.get_rom("ffb_program").CopyTo(driveROM, 64 * 1024);
// Convert PowerPC and 68K ROMs to little endian words
Util::FlipEndian32(crom, 8*0x100000 + 128*0x100000);
@ -3047,27 +3049,37 @@ bool CModel3::LoadGame(const Game &game, const ROMSet &rom_set)
SoundBoard.AttachDSB(DSB);
// Drive board (if present)
if (rom_set.get_rom("driveboard_program").size)
if (game.driveboard_type == Game::DRIVE_BOARD_WHEEL && rom_set.get_rom("driveboard_program").size)
{
if (DriveBoard.Init(driveROM))
DriveBoard = new CWheelBoard(m_config);
if (DriveBoard->Init(driveROM))
return FAIL;
else
DriveBoard.m_boardType = DriveBoard.Wheel;
}
else if (rom_set.get_rom("ffb_program").size)
else if (game.driveboard_type == Game::DRIVE_BOARD_JOYSTICK && rom_set.get_rom("driveboard_program").size)
{
if (DriveBoard.Init(driveROM))
DriveBoard = new CJoyBoard(m_config);
if (DriveBoard->Init(driveROM))
return FAIL;
else
DriveBoard.m_boardType = DriveBoard.Joystick;
}
else if (game.name == "skichamp")
else if (game.driveboard_type == Game::DRIVE_BOARD_BILLBOARD && rom_set.get_rom("driveboard_program").size)
{
DriveBoard.m_boardType = DriveBoard.SkiPad;
DriveBoard.Init(NULL); // no external driveboard rom for skichamp, we need to simulate it
DriveBoard = new CBillBoard(m_config);
if (DriveBoard->Init(driveROM))
return FAIL;
}
else if (game.driveboard_type == Game::DRIVE_BOARD_SKI)
{
DriveBoard = new CSkiBoard(m_config);
if (DriveBoard->Init(driveROM)) // no actual ROM data loaded (ski feedback is simulated)
return FAIL;
}
else
DriveBoard.Init(NULL);
{
// Dummy drive board (presents itself as not attached)
DriveBoard = new CDriveBoard(m_config);
if (DriveBoard->Init())
return FAIL;
}
// Security board encryption device
m_cryptoDevice.Init(game.encryption_key, std::bind(&CModel3::ReadSecurityRAM, this, std::placeholders::_1));
@ -3079,10 +3091,13 @@ bool CModel3::LoadGame(const Game &game, const ROMSet &rom_set)
if (DSB)
extra_hw.insert(Util::Format() << "Digital Sound Board (Type " << game.mpeg_board << ")");
if (rom_set.get_rom("driveboard_program").size)
if (DriveBoard->IsAttached())
{
if (DriveBoard->GetType() == Game::DRIVE_BOARD_BILLBOARD)
extra_hw.insert("Billboard");
else
extra_hw.insert("Drive Board");
if (rom_set.get_rom("ffb_program").size)
extra_hw.insert("Joystick FFB Board");
}
if (game.encryption_key)
extra_hw.insert("Security Board");
if (netboard_present.compare("true")==0)
@ -3122,8 +3137,8 @@ void CModel3::AttachInputs(CInputs *InputsPtr)
{
Inputs = InputsPtr;
if (DriveBoard.IsAttached())
DriveBoard.AttachInputs(Inputs, m_game.inputs);
if (DriveBoard->IsAttached())
DriveBoard->AttachInputs(Inputs, m_game.inputs);
DebugLog("Model 3 attached inputs\n");
}
@ -3134,8 +3149,8 @@ void CModel3::AttachOutputs(COutputs *OutputsPtr)
Outputs->SetGame(m_game);
Outputs->Attached();
if (DriveBoard.IsAttached())
DriveBoard.AttachOutputs(Outputs);
if (DriveBoard->IsAttached())
DriveBoard->AttachOutputs(Outputs);
DebugLog("Model 3 attached outputs\n");
}
@ -3234,7 +3249,7 @@ CSoundBoard *CModel3::GetSoundBoard(void)
CDriveBoard *CModel3::GetDriveBoard(void)
{
return &DriveBoard;
return DriveBoard;
}
#ifdef NET_BOARD
@ -3251,7 +3266,6 @@ CModel3::CModel3(const Util::Config::Node &config)
TileGen(config),
GPU(config),
SoundBoard(config),
DriveBoard(config),
#ifdef NET_BOARD
NetBoard(config),
#endif
@ -3277,6 +3291,7 @@ CModel3::CModel3(const Util::Config::Node &config)
netBuffer = NULL;
DSB = NULL;
DriveBoard = NULL;
securityPtr = 0;
@ -3351,6 +3366,12 @@ CModel3::~CModel3(void)
DSB = NULL;
}
if (DriveBoard != NULL)
{
delete DriveBoard;
DriveBoard = NULL;
}
Inputs = NULL;
Outputs = NULL;
ram = NULL;

View file

@ -1,7 +1,8 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011 Bart Trzynadlowski, Nik Henson
** Copyright 2011-2021 Bart Trzynadlowski, Nik Henson, Ian Curtis,
** Harry Tuttle, and Spindizzi
**
** This file is part of Supermodel.
**
@ -301,7 +302,7 @@ private:
CReal3D GPU; // Real3D graphics hardware
CSoundBoard SoundBoard; // Sound board
CDSB *DSB; // Digital Sound Board (type determined dynamically at load time)
CDriveBoard DriveBoard; // Drive board
CDriveBoard *DriveBoard; // Drive board
CCrypto m_cryptoDevice; // Encryption device
CJTAG m_jtag; // JTAG interface
#ifdef NET_BOARD

View file

@ -35,7 +35,12 @@ const char *COutputs::s_outputNames[] =
"LampView4",
"LampLeader",
"RawDrive",
"RawLamps"
"RawLamps",
"BillDigit1",
"BillDigit2",
"BillDigit3",
"BillDigit4",
"BillDigit5"
};
const char *COutputs::GetOutputName(EOutputs output)

View file

@ -45,10 +45,15 @@ enum EOutputs
OutputLampView4,
OutputLampLeader,
OutputRawDrive,
OutputRawLamps
OutputRawLamps,
OutputBill1,
OutputBill2,
OutputBill3,
OutputBill4,
OutputBill5
};
#define NUM_OUTPUTS 9
#define NUM_OUTPUTS 14
class COutputs
{

View file

@ -1,7 +1,7 @@
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
** Copyright 2011-2020 Bart Trzynadlowski, Nik Henson, Ian Curtis,
** Copyright 2011-2021 Bart Trzynadlowski, Nik Henson, Ian Curtis,
** Harry Tuttle, and Spindizzi
**
** This file is part of Supermodel.
@ -1443,7 +1443,7 @@ static Util::Config::Node DefaultConfig()
static void Title(void)
{
puts("Supermodel: A Sega Model 3 Arcade Emulator (Version " SUPERMODEL_VERSION ")");
puts("Copyright 2011-2020 by Bart Trzynadlowski, Nik Henson, Ian Curtis,");
puts("Copyright 2011-2021 by Bart Trzynadlowski, Nik Henson, Ian Curtis,");
puts(" Harry Tuttle, and Spindizzi\n");
}

View file

@ -147,7 +147,11 @@
#include "Sound/SCSP.h"
#include "Model3/SoundBoard.h"
#include "Model3/DSB.h"
#include "Model3/DriveBoard.h"
#include "Model3/DriveBoard/DriveBoard.h"
#include "Model3/DriveBoard/WheelBoard.h"
#include "Model3/DriveBoard/JoystickBoard.h"
#include "Model3/DriveBoard/SkiBoard.h"
#include "Model3/DriveBoard/BillBoard.h"
#ifdef NET_BOARD
#include "Network/NetBoard.h"
#endif

View file

@ -44,7 +44,7 @@ namespace Util
public:
class const_iterator;
class iterator: public std::iterator<std::forward_iterator_tag, Node>
class iterator
{
private:
ptr_t m_node;
@ -87,7 +87,7 @@ namespace Util
}
};
class const_iterator: public std::iterator<std::forward_iterator_tag, const Node>
class const_iterator
{
private:
const_ptr_t m_node;