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

@ -2,9 +2,9 @@
Supermodel
A Sega Model 3 Arcade Emulator.
Copyright 2011-2017 Bart Trzynadlowski, Nik Henson, Ian Curtis
Games.xml
This file defines ROM sets and is required in order to recognize and properly
load them. Do not modify this unless you really know what you're doing!
-->
@ -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" />
@ -295,7 +297,7 @@
<file offset="0x4000002" name="mpr-21192.11" crc32="0x60CBB1FA" />
<file offset="0x4000004" name="mpr-21191.10" crc32="0xA2BDCFE0" />
<file offset="0x4000006" name="mpr-21190.9" crc32="0x984D56EB" />
<!-- CROM3 -->
<!-- CROM3 -->
<file offset="0x6000000" name="mpr-21197.16" crc32="0x04015247" />
<file offset="0x6000002" name="mpr-21196.15" crc32="0x0AB46DB5" />
<file offset="0x6000004" name="mpr-21195.14" crc32="0x7F39761C" />
@ -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>
@ -468,7 +473,7 @@
</region>
</roms>
</game>
<game name="dirtdvlsj" parent="dirtdvls">
<identity>
<title>Dirt Devils</title>
@ -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" />
@ -689,7 +699,7 @@
</region>
</roms>
</game>
<game name="ecaj" parent="eca">
<identity>
<title>Emergency Call Ambulance</title>
@ -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,9 +809,12 @@
<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>
<game name="fvipers2o" parent="fvipers2">
<identity>
<title>Fighting Vipers 2</title>
@ -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...) -->
@ -1272,7 +1288,7 @@
<input type="common" />
<input type="analog_gun1" />
<input type="analog_gun2" />
</inputs>
</inputs>
<encryption_key>0x292B6A01</encryption_key>
</hardware>
<roms>
@ -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" />
@ -1650,7 +1672,7 @@
<file offset="0x2000002" name="mpr-20328.11" crc32="0x5FA5E9F5" />
<file offset="0x2000004" name="mpr-20327.10" crc32="0xF55F51B2" />
<file offset="0x2000006" name="mpr-20326.9" crc32="0xB63E1CB4" />
<!-- CROM3 -->
<!-- CROM3 -->
<file offset="0x3000000" name="mpr-20333.16" crc32="0x76B8E0FA" />
<file offset="0x3000002" name="mpr-20332.15" crc32="0x500DB1EE" />
<file offset="0x3000004" name="mpr-20331.14" crc32="0xC4C45FB1" />
@ -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" />
@ -2001,7 +2025,7 @@
</region>
</roms>
</game>
<game name="srally2pa" parent="srally2">
<identity>
<title>Sega Rally 2</title>
@ -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" />
@ -2049,7 +2074,7 @@
</region>
</roms>
</game>
<game name="srally2dx" parent="srally2">
<identity>
<title>Sega Rally 2</title>
@ -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" />
@ -2076,14 +2102,14 @@
</hardware>
<roms>
<patches>
<!--
<!--
Base offset of program in CROM space: 0 (lines up perfectly with RAM
offsets). The game gets stuck in a region of code that appears to
wait on some exception to occur. It also appears to access the
offsets). The game gets stuck in a region of code that appears to
wait on some exception to occur. It also appears to access the
tilegen space, indicating that this is perhaps related to frame
timing and IRQs (pure speculation; I haven't really looked at it).
Multiple places can be patched out - another one that works is
0x66ae0.
0x66ae0.
-->
<patch region="crom" bits="32" offset="0x66aa8" value="0x60000000" />
</patches>
@ -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" />
@ -2226,11 +2253,11 @@
<file offset="0xC00000" name="mpr-21378.24" crc32="0x1FCF715E" />
</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>
</roms>
</game>
@ -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" />
@ -2274,7 +2302,7 @@
</region>
</roms>
</game>
<game name="swtrilgyp" parent="swtrilgy">
<identity>
<title>Star Wars Trilogy Arcade</title>
@ -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>
@ -2646,7 +2688,7 @@
</region>
</roms>
</game>
<game name="von2o" parent="von2">
<identity>
<title>Virtual On 2: Oratorio Tangram</title>
@ -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" />
@ -2825,10 +2875,10 @@
</hardware>
<roms>
<patches>
<!--
Offset of program relative to CROM base: 0x600000 (0x200000 in the
<!--
Offset of program relative to CROM base: 0x600000 (0x200000 in the
ROM itself). Inexplicably, at PC=AFC1C, a call is made to FC78, which
is right in the middle of some totally unrelated initialization code
is right in the middle of some totally unrelated initialization code
(ASIC checks). This causes an invalid pointer to be fetched. Perhaps
FC78 should be overwritten with other program data by then? Why is it
not? Or, 300138 needs to be written with a non-zero value, it is
@ -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

@ -25,7 +25,7 @@ struct Game
INPUT_UI = 0, // special code reserved for Supermodel UI inputs
INPUT_COMMON = 0x00000001, // common controls (coins, service, test)
INPUT_VEHICLE = 0x00000002, // vehicle controls
INPUT_JOYSTICK1 = 0x00000004, // joystick 1
INPUT_JOYSTICK1 = 0x00000004, // joystick 1
INPUT_JOYSTICK2 = 0x00000008, // joystick 2
INPUT_FIGHTING = 0x00000010, // fighting game controls
INPUT_VR4 = 0x00000020, // four VR view buttons
@ -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,12 +32,13 @@ 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;
bool FindFileIndexByOffset(size_t *idx, uint32_t offset) const;
};
// Game information from XML
std::map<std::string, Game> m_game_info_by_game;
@ -45,18 +46,18 @@ private:
// sets do not inherit parent patches, which may complicate merging.
typedef std::map<std::string, std::vector<ROM::BigEndianPatch>> PatchesByRegion_t;
std::map<std::string, PatchesByRegion_t> m_patches_by_game;
// Parsed XML
typedef std::map<std::string, Region::ptr_t> RegionsByName_t;
std::map<std::string, RegionsByName_t> m_regions_by_game; // all games as defined in XML
std::map<std::string, RegionsByName_t> m_regions_by_merged_game; // only child sets merged w/ parents
std::string m_xml_filename;
// Single compressed file inside of a zip archive
struct ZippedFile
{
unzFile zf = nullptr;
std::string zipfilename; // zip archive
std::string zipfilename; // zip archive
std::string filename; // file inside the zip archive
size_t uncompressed_size = 0;
uint32_t crc32 = 0;

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,12 +1,13 @@
/**
** 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.
**
** 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
** 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.
**
@ -18,90 +19,32 @@
** 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
* 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,9 +141,8 @@ public:
* Returns:
* A byte of data from the address or port.
*/
UINT8 Read8(UINT32 addr);
UINT8 IORead8(UINT32 portNum);
/*
* Write8(addr, data):
* IORead8(portNum, data):
@ -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

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,13 @@
/**
** 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.
**
** 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
** 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.
**
@ -18,10 +19,10 @@
** You should have received a copy of the GNU General Public License along
** with Supermodel. If not, see <http://www.gnu.org/licenses/>.
**/
/*
* Model3.h
*
*
* Header file defining the CModel3 and CModel3Config classes.
*/
@ -79,7 +80,7 @@ public:
void Reset(void);
const Game &GetGame(void) const;
void AttachRenderers(CRender2D *Render2DPtr, IRender3D *Render3DPtr);
void AttachInputs(CInputs *InputsPtr);
void AttachInputs(CInputs *InputsPtr);
void AttachOutputs(COutputs *OutputsPtr);
bool Init(void);
@ -96,7 +97,7 @@ public:
void Write16(UINT32 addr, UINT16 data);
void Write32(UINT32 addr, UINT32 data);
void Write64(UINT32 addr, UINT64 data);
/*
* LoadGame(game, rom_set):
*
@ -114,7 +115,7 @@ public:
/*
* GetSoundBoard(void):
*
*
* Returns a reference to the sound board.
*
* Returns:
@ -162,8 +163,8 @@ public:
* CModel3(config):
* ~CModel3(void):
*
* Constructor and destructor for Model 3 class. Constructor performs a
* bare-bones initialization of object; does not perform any memory
* Constructor and destructor for Model 3 class. Constructor performs a
* bare-bones initialization of object; does not perform any memory
* allocation or any actions that can fail. The destructor will deallocate
* memory and free resources used by the object (and its child objects).
*
@ -207,7 +208,7 @@ private:
static int StartDriveBoardThread(void *data); // Callback to start drive board thread
static void AudioCallback(void *data); // Audio buffer callback
bool WakeSoundBoardThread(void); // Used by audio callback to wake sound board thread (when not sync'd with render thread)
int RunMainBoardThread(void); // Runs PPC main board thread (sync'd in step with render thread)
int RunSoundBoardThread(void); // Runs sound board thread (not sync'd in step with render thread, ie running at full speed)
@ -221,20 +222,20 @@ private:
// Game and hardware information
Game m_game;
// Game inputs and outputs
CInputs *Inputs;
COutputs *Outputs;
// Input registers (game controls)
UINT8 inputBank;
UINT8 serialFIFO1, serialFIFO2;
UINT8 gunReg;
int adcChannel;
// MIDI port
UINT8 midiCtrlPort; // controls MIDI (SCSP) IRQ behavior
// Emulated core Model 3 memory regions
UINT8 *memoryPool; // single allocated region for all ROM and system RAM
UINT8 *ram; // 8 MB PowerPC RAM
@ -252,12 +253,12 @@ private:
// Banked CROM
UINT8 *cromBank; // currently mapped in CROM bank
unsigned cromBankReg; // the CROM bank register
unsigned cromBankReg; // the CROM bank register
// Security device
bool m_securityFirstRead = true;
unsigned securityPtr; // pointer to current offset in security data
// PowerPC
PPC_FETCH_REGION PPCFetchRegions[3];
@ -285,11 +286,11 @@ private:
CCondVar *sndBrdNotifySync;
CSemaphore *drvBrdThreadSync;
CMutex *notifyLock;
CCondVar *notifySync;
CCondVar *notifySync;
// Frame timings
FrameTimings timings;
// Other devices
CIRQ IRQ; // Model 3 IRQ controller
CMPC10x PCIBridge; // MPC10x PCI/bridge/memory controller
@ -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,11 +44,11 @@ namespace Util
public:
class const_iterator;
class iterator: public std::iterator<std::forward_iterator_tag, Node>
class iterator
{
private:
ptr_t m_node;
friend class const_iterator;
friend class const_iterator;
public:
inline iterator(ptr_t node = ptr_t())
: m_node(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;
@ -147,7 +147,7 @@ namespace Util
{
return iterator();
}
inline const_iterator begin() const
{
return iterator(m_first_child);
@ -217,7 +217,7 @@ namespace Util
}
// Add a child node. Multiple nodes of the same key may be added but only
// when specified as leaves. For example, adding "foo/bar" twice will
// when specified as leaves. For example, adding "foo/bar" twice will
// result in one "foo" with two "bar" children.
template <typename T>
Node &Add(const std::string &path, const T &value)
@ -271,7 +271,7 @@ namespace Util
{
return !m_value;
}
// True if value exists (is not empty)
inline bool Exists() const
{
@ -293,11 +293,11 @@ namespace Util
// Always succeeds -- failed lookups permanently create an empty node.
// Use with caution. Intended for hard-coded lookups.
const Node &operator[](const std::string &path) const;
// These throw if the node is missing
Node &Get(const std::string &path);
const Node &Get(const std::string &path) const;
// This returns nullptr if node is missing
Node *TryGet(const std::string &path);
const Node *TryGet(const std::string &path) const;