mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-23 14:15:40 +00:00
2932 lines
81 KiB
C++
2932 lines
81 KiB
C++
/**
|
|
** Supermodel
|
|
** A Sega Model 3 Arcade Emulator.
|
|
** Copyright 2011-2016 Bart Trzynadlowski, Nik Henson
|
|
**
|
|
** 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/>.
|
|
**/
|
|
|
|
/*
|
|
* Model3.cpp
|
|
*
|
|
* Implementation of the CModel3 class: a complete Model 3 machine.
|
|
*
|
|
* To-Do List
|
|
* ----------
|
|
* - Save state format has changed slightly. No longer need dmaUnknownRegister
|
|
* in Real3D.cpp and security board-related variable was added to Model 3
|
|
* state. PowerPC timing variables have changed. Before 0.3a release,
|
|
* important to change format version #.
|
|
* - ROM sets should probably be handled with a class that manages ROM
|
|
* loading, the game list, as well as ROM patching
|
|
* - Wrap up CPU emulation inside a class.
|
|
* - Update the to-do list! I forgot lots of other stuff here :)
|
|
*
|
|
* PowerPC Address Map (may be slightly out of date/incomplete)
|
|
* -------------------
|
|
* 00000000-007FFFFF RAM
|
|
* 84000000-8400003F Real3D Status Registers
|
|
* 88000000-88000007 Real3D Command Port
|
|
* 8C000000-8C3FFFFF Real3D Culling RAM (Low)
|
|
* 8E000000-8E0FFFFF Real3D Culling RAM (High)
|
|
* 90000000-9000000B Real3D VROM Texture Port
|
|
* 94000000-940FFFFF Real3D Texture FIFO
|
|
* 98000000-980FFFFF Real3D Polygon RAM
|
|
* C0000000-C00000FF SCSI (Step 1.x)
|
|
* C1000000-C10000FF SCSI (Step 1.x) (Lost World expects it here)
|
|
* C2000000-C20000FF Real3D DMA (Step 2.x)
|
|
* F0040000-F004003F Input (Controls) Registers
|
|
* F0080000-F0080007 Sound Board Registers
|
|
* F00C0000-F00DFFFF Backup RAM
|
|
* F0100000-F010003F System Registers
|
|
* F0140000-F014003F Real-Time Clock
|
|
* F0180000-F019FFFF Security Board RAM
|
|
* F01A0000-F01A003F Security Board Registers
|
|
* F0800CF8-F0800CFF MPC105 CONFIG_ADDR (Step 1.x)
|
|
* F0C00CF8-F0800CFF MPC105 CONFIG_DATA (Step 1.x)
|
|
* F1000000-F10F7FFF Tile Generator Pattern Table
|
|
* F10F8000-F10FFFFF Tile Generator Name Table
|
|
* F1100000-F111FFFF Tile Generator Palette
|
|
* F1180000-F11800FF Tile Generator Registers
|
|
* F8FFF000-F8FFF0FF MPC105 (Step 1.x) or MPC106 (Step 2.x) Registers
|
|
* F9000000-F90000FF NCR 53C810 Registers (Step 1.x?)
|
|
* FEC00000-FEDFFFFF MPC106 CONFIG_ADDR (Step 2.x)
|
|
* FEE00000-FEFFFFFF MPC106 CONFIG_DATA (Step 2.x)
|
|
* FF000000-FF7FFFFF Banked CROM (CROMxx)
|
|
* FF800000-FFFFFFFF Fixed CROM
|
|
*
|
|
* Endianness
|
|
* ----------
|
|
* We assume a little endian machine and so for speed, PowerPC RAM and ROM
|
|
* regions are byte reversed, which means that aligned words can be read and
|
|
* written without any conversion. Problems arise when the PowerPC accesses
|
|
* little endian devices, like the tile generator, MPC10x, or Real3D. Then, the
|
|
* access must be carried out carefully one byte at a time or by manually byte
|
|
* reversing first (because the PowerPC will already have byte reversed it).
|
|
*
|
|
* System Registers
|
|
* ----------------
|
|
*
|
|
* F0100014: IRQ Enable
|
|
* 7 6 5 4 3 2 1 0
|
|
* +---+---+---+---+---+---+---+---+
|
|
* | ? |SND| ? |NET|VD3|VD2|VBL|VD0|
|
|
* +---+---+---+---+---+---+---+---+
|
|
* SND SCSP (sound)
|
|
* NET Network
|
|
* VD3 Unknown video-related
|
|
* VD2 Unknown video-related
|
|
* VBL VBlank start
|
|
* VD0 Unknown video-related (?)
|
|
* 0 = Disable, 1 = Enable
|
|
*
|
|
* Game Buttons
|
|
* ------------
|
|
*
|
|
* For further information, see ReadInputs().
|
|
*
|
|
* Offset 0x04, bank 0:
|
|
*
|
|
* 7 6 5 4 3 2 1 0
|
|
* +---+---+---+---+---+---+---+---+
|
|
* | ? | ? |ST2|ST1|SVA|TSA|CN2|CN1|
|
|
* +---+---+---+---+---+---+---+---+
|
|
* CNx Coin 1, Coin 2
|
|
* TSA Test Button A
|
|
* SVA Service Button A
|
|
* STx Start 1, Start 2
|
|
*
|
|
* Offset 0x04, bank 1:
|
|
*
|
|
* 7 6 5 4 3 2 1 0
|
|
* +---+---+---+---+---+---+---+---+
|
|
* |TSB|SVB|EEP| ? | ? | ? | ? | ? |
|
|
* +---+---+---+---+---+---+---+---+
|
|
* EEP Mapped to EEPROM (values written here are ignored)
|
|
* SVB Service Button B
|
|
* TSB Test Button B
|
|
*
|
|
* Offset 0x08:
|
|
*
|
|
* 7 6 5 4 3 2 1 0
|
|
* +---+---+---+---+---+---+---+---+
|
|
* |G27|G26|G25|G24|G23|G22|G21|G20|
|
|
* +---+---+---+---+---+---+---+---+
|
|
* G2x Game-specific
|
|
*
|
|
* Offset 0x0C:
|
|
*
|
|
* 7 6 5 4 3 2 1 0
|
|
* +---+---+---+---+---+---+---+---+
|
|
* |G37|G36|G35|G34|G33|G32|G31|G30|
|
|
* +---+---+---+---+---+---+---+---+
|
|
* G3x Game-specific
|
|
*
|
|
* Game-specific buttons:
|
|
*
|
|
* Scud Race:
|
|
* G27 ---
|
|
* G26 Shift 2 when combined w/ G25, Shift 1 when combined w/ G24
|
|
* G25 Shift 4
|
|
* G24 Shift 3
|
|
* G23 VR4 Green
|
|
* G22 VR3 Yellow
|
|
* G21 VR2 Blue
|
|
* G20 VR1 Red
|
|
*
|
|
* Virtua Fighter 3, Fighting Vipers 2:
|
|
* G27 Right
|
|
* G26 Left
|
|
* G25 Down
|
|
* G24 Up
|
|
* G23 Punch
|
|
* G22 Kick
|
|
* G21 Guard
|
|
* G20 Escape (VF3 only)
|
|
*
|
|
* Sega Rally 2:
|
|
* G21 Hand Brake
|
|
* G20 View Change
|
|
*
|
|
* Lost World, LA Machineguns:
|
|
* G20 Gun trigger
|
|
*
|
|
* Star Wars Trilogy:
|
|
* G20 Event button
|
|
* G25 Trigger
|
|
*
|
|
* Virtual On 2:
|
|
* G27 Left Lever Left
|
|
* G26 Left Lever Right
|
|
* G25 Left Lever Up
|
|
* G24 Left Lever Down
|
|
* G23 ---
|
|
* G22 ---
|
|
* G21 Left Turbo
|
|
* G20 Left Shot Trigger
|
|
* G37 Right Lever Left
|
|
* G36 Right Lever Right
|
|
* G35 Right Lever Up
|
|
* G34 Right Lever Down
|
|
* G33 ---
|
|
* G32 ---
|
|
* G31 Right Turbo
|
|
* G30 Right Shot Trigger
|
|
*
|
|
* Misc. Notes
|
|
* -----------
|
|
*
|
|
* daytona2:
|
|
* - Base address of program in CROM: 0x600000
|
|
* - 0x10019E is the location in RAM which contains link type.
|
|
* - Region menu can be accessed by entering test mode, holding start, and
|
|
* pressing: green, green, blue, yellow, red, yellow, blue
|
|
* (VR4,4,2,3,1,3,2).
|
|
*/
|
|
|
|
#include <new>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include "Supermodel.h"
|
|
#include "Game.h"
|
|
#include "ROMSet.h"
|
|
#include "Util/Format.h"
|
|
#include "Util/ByteSwap.h"
|
|
#include <functional>
|
|
#include <set>
|
|
#include <iostream>
|
|
|
|
/******************************************************************************
|
|
Model 3 Inputs
|
|
|
|
Game controls. The EEPROM is mapped here as well.
|
|
******************************************************************************/
|
|
|
|
UINT8 CModel3::ReadInputs(unsigned reg)
|
|
{
|
|
UINT8 adc[8];
|
|
UINT8 data;
|
|
|
|
reg &= 0x3F;
|
|
switch (reg)
|
|
{
|
|
case 0x00: // input bank
|
|
return inputBank;
|
|
|
|
case 0x04: // current input bank
|
|
|
|
data = 0xFF;
|
|
|
|
if ((inputBank&1) == 0)
|
|
{
|
|
data &= ~(Inputs->coin[0]->value); // Coin 1
|
|
data &= ~(Inputs->coin[1]->value<<1); // Coin 2
|
|
data &= ~(Inputs->test[0]->value<<2); // Test A
|
|
data &= ~(Inputs->service[0]->value<<3); // Service A
|
|
data &= ~(Inputs->start[0]->value<<4); // Start 1
|
|
data &= ~(Inputs->start[1]->value<<5); // Start 2
|
|
}
|
|
else
|
|
{
|
|
data &= ~(Inputs->service[1]->value<<6); // Service B
|
|
data &= ~(Inputs->test[1]->value<<7); // Test B
|
|
data = (data&0xDF)|(EEPROM.Read()<<5); // bank 1 contains EEPROM data bit
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_SKI))
|
|
{
|
|
if ((inputBank&1) == 0)
|
|
{
|
|
data &= ~(Inputs->skiPollLeft->value<<5);
|
|
data &= ~(Inputs->skiSelect1->value<<6);
|
|
data &= ~(Inputs->skiSelect2->value<<7);
|
|
data &= ~(Inputs->skiSelect3->value<<4);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
|
|
case 0x08: // game-specific inputs
|
|
|
|
data = 0xFF;
|
|
|
|
if ((m_game.inputs & Game::INPUT_SKI))
|
|
{
|
|
data &= ~(Inputs->skiPollRight->value<<0);
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_JOYSTICK1))
|
|
{
|
|
data &= ~(Inputs->up[0]->value<<5); // P1 Up
|
|
data &= ~(Inputs->down[0]->value<<4); // P1 Down
|
|
data &= ~(Inputs->left[0]->value<<7); // P1 Left
|
|
data &= ~(Inputs->right[0]->value<<6); // P1 Right
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_FIGHTING))
|
|
{
|
|
data &= ~(Inputs->escape[0]->value<<3); // P1 Escape
|
|
data &= ~(Inputs->guard[0]->value<<2); // P1 Guard
|
|
data &= ~(Inputs->kick[0]->value<<1); // P1 Kick
|
|
data &= ~(Inputs->punch[0]->value<<0); // P1 Punch
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_SPIKEOUT))
|
|
{
|
|
data &= ~(Inputs->shift->value<<2); // Shift
|
|
data &= ~(Inputs->beat->value<<0); // Beat
|
|
data &= ~(Inputs->charge->value<<1); // Charge
|
|
data &= ~(Inputs->jump->value<<3); // Jump
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_SOCCER))
|
|
{
|
|
data &= ~(Inputs->shortPass[0]->value<<2); // P1 Short Pass
|
|
data &= ~(Inputs->longPass[0]->value<<0); // P1 Long Pass
|
|
data &= ~(Inputs->shoot[0]->value<<1); // P1 Shoot
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_VR4))
|
|
{
|
|
data &= ~(Inputs->vr[0]->value<<0); // VR1 Red
|
|
data &= ~(Inputs->vr[1]->value<<1); // VR2 Blue
|
|
data &= ~(Inputs->vr[2]->value<<2); // VR3 Yellow
|
|
data &= ~(Inputs->vr[3]->value<<3); // VR4 Green
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_VIEWCHANGE))
|
|
{
|
|
// Harley is wired slightly differently
|
|
if ((m_game.inputs & Game::INPUT_HARLEY))
|
|
data &= ~(Inputs->viewChange->value<<1); // View change
|
|
else
|
|
data &= ~(Inputs->viewChange->value<<0); // View change
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_SHIFT4))
|
|
{
|
|
if (Inputs->gearShift4->value == 2) // Shift 2
|
|
data &= ~0x60;
|
|
else if (Inputs->gearShift4->value == 4) // Shift 4
|
|
data &= ~0x20;
|
|
if (Inputs->gearShift4->value == 1) // Shift 1
|
|
data &= ~0x50;
|
|
else if (Inputs->gearShift4->value == 3) // Shift 3
|
|
data &= ~0x10;
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_SHIFTUPDOWN))
|
|
{
|
|
// Harley is wired slightly differently
|
|
if ((m_game.inputs & Game::INPUT_HARLEY))
|
|
{
|
|
if (Inputs->gearShiftUp->value) // Shift up
|
|
data &= ~0x20;
|
|
else if (Inputs->gearShiftDown->value) // Shift down
|
|
data &= ~0x10;
|
|
}
|
|
else
|
|
{
|
|
if (Inputs->gearShiftUp->value) // Shift up
|
|
data &= ~0x50;
|
|
else if (Inputs->gearShiftDown->value) // Shift down
|
|
data &= ~0x60;
|
|
}
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_HANDBRAKE))
|
|
data &= ~(Inputs->handBrake->value<<1); // Hand brake
|
|
|
|
if ((m_game.inputs & Game::INPUT_HARLEY))
|
|
data &= ~(Inputs->musicSelect->value<<0); // Music select
|
|
|
|
if ((m_game.inputs & Game::INPUT_GUN1))
|
|
data &= ~(Inputs->trigger[0]->value<<0); // P1 Trigger
|
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_JOYSTICK))
|
|
{
|
|
data &= ~(Inputs->analogJoyTrigger1->value<<5); // Trigger 1
|
|
data &= ~(Inputs->analogJoyTrigger2->value<<4); // Trigger 2
|
|
data &= ~(Inputs->analogJoyEvent1->value<<0); // Event Button 1
|
|
data &= ~(Inputs->analogJoyEvent2->value<<1); // Event Button 2
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_TWIN_JOYSTICKS)) // First twin joystick
|
|
{
|
|
/*
|
|
* Process left joystick inputs first
|
|
*/
|
|
|
|
// Shot trigger and Turbo
|
|
data &= ~(Inputs->twinJoyShot1->value<<0);
|
|
data &= ~(Inputs->twinJoyTurbo1->value<<1);
|
|
|
|
// Stick
|
|
data &= ~(Inputs->twinJoyLeft1->value<<7);
|
|
data &= ~(Inputs->twinJoyRight1->value<<6);
|
|
data &= ~(Inputs->twinJoyUp1->value<<5);
|
|
data &= ~(Inputs->twinJoyDown1->value<<4);
|
|
|
|
/*
|
|
* Next, process twin joystick macro inputs (higher level inputs
|
|
* that map to actions on both joysticks simultaneously).
|
|
*/
|
|
|
|
/*
|
|
* Forward/reverse/turn are mutually exclusive.
|
|
*
|
|
* Turn Left: 1D 2U
|
|
* Turn Right: 1U 2D
|
|
* Forward: 1U 2U
|
|
* Reverse: 1D 2D
|
|
*/
|
|
if (Inputs->twinJoyTurnLeft->value)
|
|
data &= ~0x10;
|
|
else if (Inputs->twinJoyTurnRight->value)
|
|
data &= ~0x20;
|
|
else if (Inputs->twinJoyForward->value)
|
|
data &= ~0x20;
|
|
else if (Inputs->twinJoyReverse->value)
|
|
data &= ~0x10;
|
|
|
|
/*
|
|
* Strafe/crouch/jump are mutually exclusive.
|
|
*
|
|
* Strafe Left: 1L 2L
|
|
* Strafe Right: 1R 2R
|
|
* Jump: 1L 2R
|
|
* Crouch: 1R 2L
|
|
*/
|
|
if (Inputs->twinJoyStrafeLeft->value)
|
|
data &= ~0x80;
|
|
else if (Inputs->twinJoyStrafeRight->value)
|
|
data &= ~0x40;
|
|
else if (Inputs->twinJoyJump->value)
|
|
data &= ~0x80;
|
|
else if (Inputs->twinJoyCrouch->value)
|
|
data &= ~0x40;
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_GUN1))
|
|
{
|
|
data &= ~(Inputs->analogTriggerLeft[0]->value<<0);
|
|
data &= ~(Inputs->analogTriggerRight[0]->value<<1);
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_MAGTRUCK))
|
|
data &= ~(Inputs->magicalPedal1->value << 0);
|
|
|
|
if ((m_game.inputs & Game::INPUT_FISHING))
|
|
{
|
|
data &= ~(Inputs->fishingCast->value << 0);
|
|
data &= ~(Inputs->fishingSelect->value << 1);
|
|
}
|
|
return data;
|
|
|
|
case 0x0C: // game-specific inputs
|
|
|
|
data = 0xFF;
|
|
|
|
if (DriveBoard.IsAttached())
|
|
data = DriveBoard.Read();
|
|
|
|
if ((m_game.inputs & Game::INPUT_JOYSTICK2))
|
|
{
|
|
data &= ~(Inputs->up[1]->value<<5); // P2 Up
|
|
data &= ~(Inputs->down[1]->value<<4); // P2 Down
|
|
data &= ~(Inputs->left[1]->value<<7); // P2 Left
|
|
data &= ~(Inputs->right[1]->value<<6); // P2 Right
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_FIGHTING))
|
|
{
|
|
data &= ~(Inputs->escape[1]->value<<3); // P2 Escape
|
|
data &= ~(Inputs->guard[1]->value<<2); // P2 Guard
|
|
data &= ~(Inputs->kick[1]->value<<1); // P2 Kick
|
|
data &= ~(Inputs->punch[1]->value<<0); // P2 Punch
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_SOCCER))
|
|
{
|
|
data &= ~(Inputs->shortPass[1]->value<<2); // P2 Short Pass
|
|
data &= ~(Inputs->longPass[1]->value<<0); // P2 Long Pass
|
|
data &= ~(Inputs->shoot[1]->value<<1); // P2 Shoot
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_TWIN_JOYSTICKS)) // Second twin joystick (see register 0x08 for comments)
|
|
{
|
|
|
|
data &= ~(Inputs->twinJoyShot2->value<<0);
|
|
data &= ~(Inputs->twinJoyTurbo2->value<<1);
|
|
|
|
data &= ~(Inputs->twinJoyLeft2->value<<7);
|
|
data &= ~(Inputs->twinJoyRight2->value<<6);
|
|
data &= ~(Inputs->twinJoyUp2->value<<5);
|
|
data &= ~(Inputs->twinJoyDown2->value<<4);
|
|
|
|
if (Inputs->twinJoyTurnLeft->value)
|
|
data &= ~0x20;
|
|
else if (Inputs->twinJoyTurnRight->value)
|
|
data &= ~0x10;
|
|
else if (Inputs->twinJoyForward->value)
|
|
data &= ~0x20;
|
|
else if (Inputs->twinJoyReverse->value)
|
|
data &= ~0x10;
|
|
|
|
if (Inputs->twinJoyStrafeLeft->value)
|
|
data &= ~0x80;
|
|
else if (Inputs->twinJoyStrafeRight->value)
|
|
data &= ~0x40;
|
|
else if (Inputs->twinJoyJump->value)
|
|
data &= ~0x40;
|
|
else if (Inputs->twinJoyCrouch->value)
|
|
data &= ~0x80;
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_GUN2))
|
|
data &= ~(Inputs->trigger[1]->value<<0); // P2 Trigger
|
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_GUN2))
|
|
{
|
|
data &= ~(Inputs->analogTriggerLeft[1]->value<<0);
|
|
data &= ~(Inputs->analogTriggerRight[1]->value<<1);
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_MAGTRUCK))
|
|
data &= ~(Inputs->magicalPedal2->value << 0);
|
|
|
|
return data;
|
|
|
|
case 0x2C: // Serial FIFO 1
|
|
return serialFIFO1;
|
|
|
|
case 0x30: // Serial FIFO 2
|
|
return serialFIFO2;
|
|
|
|
case 0x34: // Serial FIFO full/empty flags
|
|
return 0x0C;
|
|
|
|
case 0x3C: // ADC
|
|
|
|
// Load ADC channels with input data
|
|
memset(adc, 0, sizeof(adc));
|
|
if ((m_game.inputs & Game::INPUT_VEHICLE))
|
|
{
|
|
adc[0] = (UINT8)Inputs->steering->value;
|
|
adc[1] = (UINT8)Inputs->accelerator->value;
|
|
adc[2] = (UINT8)Inputs->brake->value;
|
|
if ((m_game.inputs & Game::INPUT_HARLEY))
|
|
adc[3] = (UINT8)Inputs->rearBrake->value;
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_JOYSTICK))
|
|
{
|
|
adc[0] = (UINT8)Inputs->analogJoyY->value;
|
|
adc[1] = (UINT8)Inputs->analogJoyX->value;
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_GUN1)||(m_game.inputs & Game::INPUT_ANALOG_GUN2))
|
|
{
|
|
adc[0] = (UINT8)Inputs->analogGunX[0]->value;
|
|
adc[2] = (UINT8)Inputs->analogGunY[0]->value;
|
|
adc[1] = (UINT8)Inputs->analogGunX[1]->value;
|
|
adc[3] = (UINT8)Inputs->analogGunY[1]->value;
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_SKI))
|
|
{
|
|
adc[0] = (UINT8)Inputs->skiY->value;
|
|
adc[1] = (UINT8)Inputs->skiX->value;
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_MAGTRUCK))
|
|
{
|
|
adc[0] = uint8_t(Inputs->magicalLever1->value);
|
|
adc[1] = uint8_t(Inputs->magicalLever2->value);
|
|
}
|
|
|
|
if ((m_game.inputs & Game::INPUT_FISHING))
|
|
{
|
|
adc[0] = uint8_t(Inputs->fishingRodY->value);
|
|
adc[1] = uint8_t(Inputs->fishingRodX->value);
|
|
adc[3] = uint8_t(Inputs->fishingReel->value);
|
|
adc[5] = uint8_t(Inputs->fishingStickX->value);
|
|
adc[4] = uint8_t(Inputs->fishingStickY->value);
|
|
}
|
|
|
|
// Read out appropriate channel
|
|
data = adc[adcChannel&7];
|
|
++adcChannel;
|
|
return data;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0xFF; // controls are active low
|
|
}
|
|
|
|
void CModel3::WriteInputs(unsigned reg, UINT8 data)
|
|
{
|
|
switch (reg&0x3F)
|
|
{
|
|
case 0:
|
|
EEPROM.Write((data>>6)&1,(data>>7)&1,(data>>5)&1);
|
|
inputBank = data;
|
|
break;
|
|
|
|
case 0x10: // Drive board
|
|
if (DriveBoard.IsAttached())
|
|
DriveBoard.Write(data);
|
|
if (NULL != Outputs) // TODO - check gameInputs
|
|
Outputs->SetValue(OutputRawDrive, data);
|
|
break;
|
|
|
|
case 0x14: // Lamp outputs (Daytona/Scud Race/Sega Rally/Le Mans 24)
|
|
if (NULL != Outputs) // TODO - check gameInputs
|
|
{
|
|
Outputs->SetValue(OutputLampStart, !!(data&0x04));
|
|
Outputs->SetValue(OutputLampView1, !!(data&0x08));
|
|
Outputs->SetValue(OutputLampView2, !!(data&0x10));
|
|
Outputs->SetValue(OutputLampView3, !!(data&0x20));
|
|
Outputs->SetValue(OutputLampView4, !!(data&0x40));
|
|
Outputs->SetValue(OutputLampLeader, !!(data&0x80));
|
|
Outputs->SetValue(OutputRawLamps, data);
|
|
}
|
|
break;
|
|
|
|
case 0x24: // Serial FIFO 1
|
|
switch (data) // Command
|
|
{
|
|
case 0x00: // Light gun register select
|
|
gunReg = serialFIFO2;
|
|
break;
|
|
case 0x87: // Read light gun register
|
|
serialFIFO1 = 0; // clear serial FIFO 1
|
|
serialFIFO2 = 0;
|
|
if ((m_game.inputs & Game::INPUT_GUN1||m_game.inputs & Game::INPUT_GUN2))
|
|
{
|
|
switch (gunReg)
|
|
{
|
|
case 0: // Player 1 gun Y (low 8 bits)
|
|
serialFIFO2 = Inputs->gunY[0]->value&0xFF;
|
|
break;
|
|
case 1: // Player 1 gun Y (high 2 bits)
|
|
serialFIFO2 = (Inputs->gunY[0]->value>>8)&3;
|
|
break;
|
|
case 2: // Player 1 gun X (low 8 bits)
|
|
serialFIFO2 = Inputs->gunX[0]->value&0xFF;
|
|
break;
|
|
case 3: // Player 1 gun X (high 2 bits)
|
|
serialFIFO2 = (Inputs->gunX[0]->value>>8)&3;
|
|
break;
|
|
case 4: // Player 2 gun Y (low 8 bits)
|
|
serialFIFO2 = Inputs->gunY[1]->value&0xFF;
|
|
break;
|
|
case 5: // Player 2 gun Y (high 2 bits)
|
|
serialFIFO2 = (Inputs->gunY[1]->value>>8)&3;
|
|
break;
|
|
case 6: // Player 2 gun X (low 8 bits)
|
|
serialFIFO2 = Inputs->gunX[1]->value&0xFF;
|
|
break;
|
|
case 7: // Player 2 gun X (high 2 bits)
|
|
serialFIFO2 = (Inputs->gunX[1]->value>>8)&3;
|
|
break;
|
|
case 8: // Off-screen indicator (bit 0 = player 1, bit 1 = player 2, set indicates off screen)
|
|
serialFIFO2 = (Inputs->trigger[1]->offscreenValue<<1)|Inputs->trigger[0]->offscreenValue;
|
|
break;
|
|
default:
|
|
DebugLog("Unknown gun register: %X\n", gunReg);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
DebugLog("Uknown command to serial FIFO: %02X\n", data);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x28: // Serial FIFO 2
|
|
serialFIFO2 = data;
|
|
break;
|
|
case 0x3C:
|
|
adcChannel = data&7;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
//printf("Controls: %X=%02X\n", reg, data);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Model 3 Security Device
|
|
|
|
The security device is present in some games. Virtual On and Dirt Devils read
|
|
tile pattern data from it. Spikeout calls a routine at PC=0x6FAC8 that writes/
|
|
reads the security device and, if the return value in R3 is 0, prints "ILLEGAL
|
|
ROM" and locks the game. Our habit of returning all 1's for unknown reads
|
|
seems to help avoid this.
|
|
******************************************************************************/
|
|
|
|
uint16_t CModel3::ReadSecurityRAM(uint32_t addr)
|
|
{
|
|
if (addr < 0x8000)
|
|
return (*(uint32_t *) &securityRAM[addr * 4]) >> 16;
|
|
return 0;
|
|
}
|
|
|
|
UINT32 CModel3::ReadSecurity(unsigned reg)
|
|
{
|
|
switch (reg)
|
|
{
|
|
case 0x00: // Status
|
|
return 0;
|
|
case 0x1C: // Data
|
|
if (m_securityFirstRead)
|
|
{
|
|
m_securityFirstRead = false;
|
|
return 0xFFFF0000;
|
|
}
|
|
else
|
|
{
|
|
uint8_t *base_ptr;
|
|
return m_cryptoDevice.Decrypt(&base_ptr) << 16;
|
|
}
|
|
default:
|
|
DebugLog("Security read: reg=%X\n", reg);
|
|
break;
|
|
}
|
|
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
void CModel3::WriteSecurity(unsigned reg, UINT32 data)
|
|
{
|
|
switch (reg)
|
|
{
|
|
case 0x10:
|
|
case 0x14:
|
|
m_cryptoDevice.SetAddressLow(0);
|
|
m_cryptoDevice.SetAddressHigh(0);
|
|
m_securityFirstRead = true;
|
|
break;
|
|
case 0x18:
|
|
{
|
|
uint16_t subKey = data >> 16;
|
|
subKey = ((subKey & 0xFF00) >> 8) | ((subKey & 0x00FF) << 8);
|
|
m_cryptoDevice.SetSubKey(subKey);
|
|
break;
|
|
}
|
|
default:
|
|
DebugLog("Security write: reg=%X, data=%08X (PC=%08X, LR=%08X)\n", reg, data, ppc_get_pc(), ppc_get_lr());
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
PCI Devices
|
|
|
|
Unknown PCI devices are handled here.
|
|
******************************************************************************/
|
|
|
|
UINT32 CModel3::ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset)
|
|
{
|
|
if ((bits==8) || (bits==16))
|
|
{
|
|
DebugLog("Model 3: %d-bit PCI read request for reg=%02X\n", bits, reg);
|
|
return 0;
|
|
}
|
|
|
|
switch (device)
|
|
{
|
|
case 16: // Used by Daytona 2
|
|
switch (reg)
|
|
{
|
|
case 0: // PCI vendor and device ID
|
|
return 0x182711DB;
|
|
default:
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DebugLog("Model 3: PCI %d-bit write request for device=%d, reg=%02X\n", bits, device, reg);
|
|
return 0;
|
|
}
|
|
|
|
void CModel3::WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset, UINT32 data)
|
|
{
|
|
DebugLog("Model 3: PCI %d-bit write request for device=%d, reg=%02X, data=%08X\n", bits, device, reg, data);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Model 3 System Registers
|
|
|
|
NOTE: Proper IRQ handling requires a "deassert" function in the PowerPC core,
|
|
which the interpreter presently lacks. This is because different modules that
|
|
generate IRQs, like the tilegen, Real3D, and SCSP, should each call
|
|
IRQ.Assert() on their own, which will assert the CPU IRQ line. Right now,
|
|
the CPU processes an interrupt and clears the line by itself, which means that
|
|
if multiple interrupts are asserted simultaneously, depending on the IRQ
|
|
handler code, only one may be processed. Keep an eye on this!
|
|
******************************************************************************/
|
|
|
|
// Set the CROM bank index (active low logic)
|
|
void CModel3::SetCROMBank(unsigned idx)
|
|
{
|
|
cromBankReg = idx;
|
|
idx = (~idx) & 0xF;
|
|
cromBank = &crom[0x800000 + (idx*0x800000)];
|
|
DebugLog("CROM bank setting: %d (%02X), PC=%08X, LR=%08X\n", idx, cromBankReg, ppc_get_pc(), ppc_get_lr());
|
|
}
|
|
|
|
UINT8 CModel3::ReadSystemRegister(unsigned reg)
|
|
{
|
|
switch (reg&0x3F)
|
|
{
|
|
case 0x08: // CROM bank
|
|
return cromBankReg;
|
|
case 0x14: // IRQ enable
|
|
return IRQ.ReadIRQEnable();
|
|
case 0x18: // IRQ pending
|
|
return IRQ.ReadIRQState();
|
|
case 0x1C: // unknown (apparently expects some or all bits set)
|
|
//DebugLog("System register %02X read\n", reg);
|
|
return 0xFF;
|
|
case 0x10: // JTAG Test Access Port
|
|
return (GPU.ReadTAP()<< 5);
|
|
default:
|
|
//DebugLog("System register %02X read\n", reg);
|
|
break;
|
|
}
|
|
|
|
return 0xFF;
|
|
}
|
|
|
|
void CModel3::WriteSystemRegister(unsigned reg, UINT8 data)
|
|
{
|
|
switch (reg&0x3F)
|
|
{
|
|
case 0x08: // CROM bank
|
|
SetCROMBank(data);
|
|
break;
|
|
case 0x14: // IRQ enable
|
|
IRQ.WriteIRQEnable(data);
|
|
DebugLog("IRQ ENABLE=%02X\n", data);
|
|
break;
|
|
case 0x18: // IRQ acknowledge
|
|
{
|
|
// MAME: Clear bits are ack. Reverse order from other IRQ regs.
|
|
//uint8_t ack = 0;
|
|
//for (int bit = 0; bit < 8; bit++)
|
|
//{
|
|
// if (!(data & (1 << bit)))
|
|
// ack |= (1 << (7-bit));
|
|
//}
|
|
//IRQ.Deassert(ack);
|
|
DebugLog("IRQ ACK? %02X=%02X\n", reg, data);
|
|
break;
|
|
}
|
|
case 0x0C: // JTAG Test Access Port
|
|
GPU.WriteTAP((data>>6)&1,(data>>2)&1,(data>>5)&1,(data>>7)&1); // TCK, TMS, TDI, TRST
|
|
break;
|
|
case 0x0D:
|
|
case 0x0E:
|
|
case 0x0F:
|
|
case 0x1C: // LED control?
|
|
break;
|
|
default:
|
|
//DebugLog("System register %02X=%02X\n", reg, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Address Space Access Handlers
|
|
|
|
NOTE: Testing of some of the address ranges is not strict enough, especially
|
|
for the MPC10x. Write32() handles the MPC10x most correctly.
|
|
******************************************************************************/
|
|
|
|
/*
|
|
* CModel3::Read8(addr):
|
|
* CModel3::Read16(addr):
|
|
* CModel3::Read32(addr):
|
|
* CModel3::Read64(addr):
|
|
*
|
|
* Read handlers.
|
|
*/
|
|
UINT8 CModel3::Read8(UINT32 addr)
|
|
{
|
|
// RAM (most frequently accessed)
|
|
if (addr<0x00800000)
|
|
return ram[addr^3];
|
|
|
|
// Other
|
|
switch ((addr>>24))
|
|
{
|
|
// CROM
|
|
case 0xFF:
|
|
if (addr < 0xFF800000)
|
|
return cromBank[(addr&0x7FFFFF)^3];
|
|
else
|
|
return crom[(addr&0x7FFFFF)^3];
|
|
|
|
// Real3D DMA
|
|
case 0xC2:
|
|
return GPU.ReadDMARegister8(addr&0xFF);
|
|
|
|
// Various
|
|
case 0xF0:
|
|
case 0xFE: // mirror
|
|
|
|
switch ((addr>>16)&0xFF)
|
|
{
|
|
// Inputs
|
|
case 0x04:
|
|
return ReadInputs(addr&0x3F);
|
|
|
|
// Sound Board
|
|
case 0x08:
|
|
if ((addr & 0xF) == 4) // MIDI control port
|
|
return 0x83; // magtruck country check
|
|
else
|
|
return 0;
|
|
break;
|
|
|
|
// System registers
|
|
case 0x10:
|
|
return ReadSystemRegister(addr&0x3F);
|
|
|
|
// RTC
|
|
case 0x14:
|
|
if ((addr&3)==1) // battery voltage test
|
|
return 0x03;
|
|
else if ((addr&3)==0)
|
|
return RTC.ReadRegister((addr>>2)&0xF);
|
|
return 0;
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
// Tile generator
|
|
case 0xF1:
|
|
if (addr < 0xF1120000)
|
|
{
|
|
// Tile generator accesses its RAM as little endian, no adjustment needed here
|
|
return TileGen.ReadRAM8(addr&0x1FFFFF);
|
|
}
|
|
break;
|
|
|
|
// 53C810 SCSI
|
|
case 0xC0: // only on Step 1.0
|
|
if (m_game.stepping != "1.0")
|
|
break;
|
|
case 0xF9:
|
|
case 0xC1:
|
|
return SCSI.ReadRegister(addr&0xFF);
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DebugLog("PC=%08X\tread8 : %08X\n", ppc_get_pc(), addr);
|
|
return 0xFF;
|
|
}
|
|
|
|
UINT16 CModel3::Read16(UINT32 addr)
|
|
{
|
|
UINT16 data;
|
|
|
|
if ((addr&1))
|
|
{
|
|
data = Read8(addr+0)<<8;
|
|
data |= Read8(addr+1);
|
|
return data;
|
|
}
|
|
|
|
// RAM (most frequently accessed)
|
|
if (addr<0x00800000)
|
|
return *(UINT16 *) &ram[addr^2];
|
|
|
|
// Other
|
|
switch ((addr>>24))
|
|
{
|
|
// CROM
|
|
case 0xFF:
|
|
if (addr < 0xFF800000)
|
|
return *(UINT16 *) &cromBank[(addr&0x7FFFFF)^2];
|
|
else
|
|
return *(UINT16 *) &crom[(addr&0x7FFFFF)^2];
|
|
|
|
// Various
|
|
case 0xF0:
|
|
case 0xFE: // mirror
|
|
|
|
switch ((addr>>16)&0xFF)
|
|
{
|
|
// Backup RAM
|
|
case 0x0C:
|
|
case 0x0D:
|
|
return *(UINT16 *) &backupRAM[(addr&0x1FFFF)^2];
|
|
|
|
// Sound Board
|
|
case 0x08:
|
|
//printf("PPC: Read16 %08X\n", addr);
|
|
break;
|
|
|
|
// MPC105
|
|
case 0xC0: // F0C00CF8
|
|
return PCIBridge.ReadPCIConfigData(16,addr&3);
|
|
|
|
// MPC106
|
|
case 0xE0:
|
|
case 0xE1:
|
|
case 0xE2:
|
|
case 0xE3:
|
|
case 0xE4:
|
|
case 0xE5:
|
|
case 0xE6:
|
|
case 0xE7:
|
|
case 0xE8:
|
|
case 0xE9:
|
|
case 0xEA:
|
|
case 0xEB:
|
|
case 0xEC:
|
|
case 0xED:
|
|
case 0xEE:
|
|
case 0xEF:
|
|
return PCIBridge.ReadPCIConfigData(16,addr&3);
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
// Tile generator
|
|
case 0xF1:
|
|
if (addr < 0xF1120000)
|
|
{
|
|
// Tile generator accesses its RAM as little endian, no adjustment needed here
|
|
uint16_t data = TileGen.ReadRAM16(addr&0x1FFFFF);
|
|
return FLIPENDIAN16(data);
|
|
}
|
|
break;
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DebugLog("PC=%08X\tread16: %08X\n", ppc_get_pc(), addr);
|
|
return 0xFFFF;
|
|
}
|
|
|
|
UINT32 CModel3::Read32(UINT32 addr)
|
|
{
|
|
UINT32 data;
|
|
|
|
if ((addr&3))
|
|
{
|
|
data = Read16(addr+0)<<16;
|
|
data |= Read16(addr+2);
|
|
return data;
|
|
}
|
|
|
|
// RAM (most frequently accessed)
|
|
if (addr < 0x00800000)
|
|
return *(UINT32 *) &ram[addr];
|
|
|
|
// Other
|
|
switch ((addr>>24))
|
|
{
|
|
// CROM
|
|
case 0xFF:
|
|
if (addr < 0xFF800000)
|
|
return *(UINT32 *) &cromBank[(addr&0x7FFFFF)];
|
|
else
|
|
return *(UINT32 *) &crom[(addr&0x7FFFFF)];
|
|
|
|
// Real3D registers
|
|
case 0x84:
|
|
return GPU.ReadRegister(addr&0x3F);
|
|
|
|
// Real3D DMA
|
|
case 0xC2:
|
|
data = GPU.ReadDMARegister32(addr&0xFF);
|
|
return FLIPENDIAN32(data);
|
|
|
|
// Various
|
|
case 0xF0:
|
|
case 0xFE: // mirror
|
|
|
|
switch ((addr>>16)&0xFF)
|
|
{
|
|
// Inputs
|
|
case 0x04:
|
|
data = ReadInputs((addr&0x3F)+0) << 24;
|
|
data |= ReadInputs((addr&0x3F)+1) << 16;
|
|
data |= ReadInputs((addr&0x3F)+2) << 8;
|
|
data |= ReadInputs((addr&0x3F)+3) << 0;
|
|
return data;
|
|
|
|
// Sound Board
|
|
case 0x08:
|
|
//printf("PPC: Read32 %08X\n", addr);
|
|
break;
|
|
|
|
// Backup RAM
|
|
case 0x0C:
|
|
case 0x0D:
|
|
return *(UINT32 *) &backupRAM[(addr&0x1FFFF)];
|
|
|
|
// System registers
|
|
case 0x10:
|
|
data = ReadSystemRegister((addr&0x3F)+0) << 24;
|
|
data |= ReadSystemRegister((addr&0x3F)+1) << 16;
|
|
data |= ReadSystemRegister((addr&0x3F)+2) << 8;
|
|
data |= ReadSystemRegister((addr&0x3F)+3) << 0;
|
|
return data;
|
|
|
|
// MPC105
|
|
case 0xC0: // F0C00CF8
|
|
return PCIBridge.ReadPCIConfigData(32,0);
|
|
|
|
// MPC106
|
|
case 0xE0:
|
|
case 0xE1:
|
|
case 0xE2:
|
|
case 0xE3:
|
|
case 0xE4:
|
|
case 0xE5:
|
|
case 0xE6:
|
|
case 0xE7:
|
|
case 0xE8:
|
|
case 0xE9:
|
|
case 0xEA:
|
|
case 0xEB:
|
|
case 0xEC:
|
|
case 0xED:
|
|
case 0xEE:
|
|
case 0xEF:
|
|
return PCIBridge.ReadPCIConfigData(32,0);
|
|
|
|
// RTC
|
|
case 0x14:
|
|
data = (RTC.ReadRegister((addr>>2)&0xF) << 24);
|
|
data |= 0x00030000; // set these bits to pass battery voltage test
|
|
return data;
|
|
|
|
// Security board RAM
|
|
case 0x18:
|
|
case 0x19:
|
|
return *(UINT32 *) &securityRAM[(addr&0x1FFFF)]; // so far, only 32-bit access observed, so we use little endian access
|
|
|
|
// Security board registers
|
|
case 0x1A:
|
|
return ReadSecurity(addr&0x3F);
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
// Tile generator
|
|
case 0xF1:
|
|
// Tile generator accesses its RAM as little endian, must flip for big endian PowerPC
|
|
if (addr < 0xF1120000)
|
|
{
|
|
data = TileGen.ReadRAM32(addr&0x1FFFFF);
|
|
return FLIPENDIAN32(data);
|
|
}
|
|
else if ((addr>=0xF1180000) && (addr<0xF1180100))
|
|
{
|
|
data = TileGen.ReadRegister(addr & 0xFF);
|
|
return FLIPENDIAN32(data);
|
|
}
|
|
|
|
break;
|
|
|
|
// 53C810 SCSI
|
|
case 0xC0: // only on Step 1.0
|
|
if (m_game.stepping != "1.0") // check for Step 1.0
|
|
break;
|
|
case 0xF9:
|
|
case 0xC1:
|
|
data = (SCSI.ReadRegister((addr+0)&0xFF) << 24);
|
|
data |= (SCSI.ReadRegister((addr+1)&0xFF) << 16);
|
|
data |= (SCSI.ReadRegister((addr+2)&0xFF) << 8);
|
|
data |= (SCSI.ReadRegister((addr+3)&0xFF) << 0);
|
|
return data;
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DebugLog("PC=%08X\tread32: %08X\n", ppc_get_pc(), addr);
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
UINT64 CModel3::Read64(UINT32 addr)
|
|
{
|
|
UINT64 data;
|
|
|
|
data = Read32(addr+0);
|
|
data <<= 32;
|
|
data |= Read32(addr+4);
|
|
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
* CModel3::Write8(addr, data):
|
|
* CModel3::Write16(addr, data):
|
|
* CModel3::Write32(addr, data):
|
|
* CModel3::Write64(addr, data):
|
|
*
|
|
* Write handlers.
|
|
*/
|
|
void CModel3::Write8(UINT32 addr, UINT8 data)
|
|
{
|
|
// RAM (most frequently accessed)
|
|
if (addr < 0x00800000)
|
|
{
|
|
ram[addr^3] = data;
|
|
return;
|
|
}
|
|
|
|
// Other
|
|
switch ((addr>>24))
|
|
{
|
|
// Real3D DMA
|
|
case 0xC2:
|
|
GPU.WriteDMARegister8(addr&0xFF,data);
|
|
break;
|
|
|
|
// Various
|
|
case 0xF0:
|
|
case 0xFE: // mirror
|
|
|
|
switch ((addr>>16)&0xFF)
|
|
{
|
|
// Inputs
|
|
case 0x04:
|
|
WriteInputs(addr&0x3F,data);
|
|
break;
|
|
|
|
// Sound Board
|
|
case 0x08:
|
|
//printf("PPC: %08X=%02X * (PC=%08X, LR=%08X)\n", addr, data, ppc_get_pc(), ppc_get_lr());
|
|
if ((addr&0xF) == 0) // MIDI data port
|
|
SoundBoard.WriteMIDIPort(data);
|
|
else if ((addr&0xF) == 4) // MIDI control port
|
|
midiCtrlPort = data;
|
|
break;
|
|
|
|
// Backup RAM
|
|
case 0x0C:
|
|
case 0x0D:
|
|
backupRAM[(addr&0x1FFFF)^3] = data;
|
|
break;
|
|
|
|
// System registers
|
|
case 0x10:
|
|
WriteSystemRegister(addr&0x3F,data);
|
|
break;
|
|
|
|
// RTC
|
|
case 0x14:
|
|
if ((addr&3)==0)
|
|
RTC.WriteRegister((addr>>2)&0xF,data);
|
|
break;
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data);
|
|
break;
|
|
|
|
// Tile generator
|
|
case 0xF1:
|
|
if (addr < 0xF1120000)
|
|
{
|
|
// Tile generator accesses its RAM as little endian, no adjustment needed here
|
|
TileGen.WriteRAM8(addr&0x1FFFFF, data);
|
|
break;
|
|
}
|
|
goto Unknown8;
|
|
|
|
// MPC105/106
|
|
case 0xF8:
|
|
PCIBridge.WriteRegister(addr&0xFF,data);
|
|
break;
|
|
|
|
// 53C810 SCSI
|
|
case 0xC0: // only on Step 1.0
|
|
if (m_game.stepping != "1.0")
|
|
goto Unknown8;
|
|
case 0xF9:
|
|
case 0xC1:
|
|
SCSI.WriteRegister(addr&0xFF,data);
|
|
break;
|
|
|
|
// Unknown:
|
|
default:
|
|
Unknown8:
|
|
DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CModel3::Write16(UINT32 addr, UINT16 data)
|
|
{
|
|
if ((addr&1))
|
|
{
|
|
Write8(addr+0,data>>8);
|
|
Write8(addr+1,data&0xFF);
|
|
return;
|
|
}
|
|
|
|
// RAM (most frequently accessed)
|
|
if (addr < 0x00800000)
|
|
{
|
|
*(UINT16 *) &ram[addr^2] = data;
|
|
return;
|
|
}
|
|
|
|
// Other
|
|
switch ((addr>>24))
|
|
{
|
|
// Various
|
|
case 0xF0:
|
|
case 0xFE: // mirror
|
|
|
|
switch ((addr>>16)&0xFF)
|
|
{
|
|
// Sound Board
|
|
case 0x08:
|
|
//printf("%08X=%04X\n", addr, data);
|
|
break;
|
|
|
|
// Backup RAM
|
|
case 0x0C:
|
|
case 0x0D:
|
|
*(UINT16 *) &backupRAM[(addr&0x1FFFF)^2] = data;
|
|
break;
|
|
|
|
// MPC105
|
|
case 0xC0: // F0C00CF8
|
|
PCIBridge.WritePCIConfigData(16,addr&2,data);
|
|
break;
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DebugLog("PC=%08X\twrite16 : %08X=%04X\n", ppc_get_pc(), addr, data);
|
|
break;
|
|
|
|
// Tile generator
|
|
case 0xF1:
|
|
if (addr < 0xF1120000)
|
|
{
|
|
// Tile generator accesses its RAM as little endian, no adjustment needed here
|
|
TileGen.WriteRAM16(addr&0x1FFFFF, FLIPENDIAN16(data));
|
|
}
|
|
goto Unknown16;
|
|
|
|
// MPC105/106
|
|
case 0xF8:
|
|
// Write in big endian order, like a real PowerPC
|
|
PCIBridge.WriteRegister((addr&0xFF)+0,data>>8);
|
|
PCIBridge.WriteRegister((addr&0xFF)+1,data&0xFF);
|
|
break;
|
|
|
|
// Unknown
|
|
default:
|
|
Unknown16:
|
|
DebugLog("PC=%08X\twrite16: %08X=%04X\n", ppc_get_pc(), addr, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CModel3::Write32(UINT32 addr, UINT32 data)
|
|
{
|
|
if ((addr&3))
|
|
{
|
|
Write16(addr+0,data>>16);
|
|
Write16(addr+2,data);
|
|
return;
|
|
}
|
|
|
|
// RAM (most frequently accessed)
|
|
if (addr<0x00800000)
|
|
{
|
|
*(UINT32 *) &ram[addr] = data;
|
|
return;
|
|
}
|
|
|
|
// Other
|
|
switch ((addr>>24))
|
|
{
|
|
// Real3D trigger
|
|
case 0x88: // 88000000
|
|
GPU.Flush();
|
|
break;
|
|
|
|
// Real3D low culling RAM
|
|
case 0x8C: // 8C000000-8C400000
|
|
GPU.WriteLowCullingRAM(addr&0x3FFFFF,FLIPENDIAN32(data));
|
|
break;
|
|
|
|
// Real3D high culling RAM
|
|
case 0x8E: // 8E000000-8E100000
|
|
GPU.WriteHighCullingRAM(addr&0xFFFFF,FLIPENDIAN32(data));
|
|
break;
|
|
|
|
// Real3D texture port
|
|
case 0x90: // 90000000-90??????
|
|
GPU.WriteTexturePort(addr&0xFF,FLIPENDIAN32(data));
|
|
break;
|
|
|
|
// Real3D texture FIFO
|
|
case 0x94: // 94000000-94100000
|
|
GPU.WriteTextureFIFO(FLIPENDIAN32(data));
|
|
break;
|
|
|
|
// Real3D polygon RAM
|
|
case 0x98: // 98000000-98400000
|
|
GPU.WritePolygonRAM(addr&0x3FFFFF,FLIPENDIAN32(data));
|
|
break;
|
|
|
|
// Real3D configuration registers
|
|
case 0x9C: // 9Cxxxxxx
|
|
//printf("%08X=%08X\n", addr, data); //TODO: flip endian?
|
|
break;
|
|
|
|
// Real3D DMA
|
|
case 0xC2: // C2000000-C2000100
|
|
GPU.WriteDMARegister32(addr&0xFF,FLIPENDIAN32(data));
|
|
break;
|
|
|
|
// Various
|
|
case 0xF0:
|
|
case 0xFE: // mirror
|
|
|
|
switch ((addr>>16)&0xFF)
|
|
{
|
|
// Inputs
|
|
case 0x04:
|
|
WriteInputs((addr&0x3F)+0,(data>>24)&0xFF);
|
|
WriteInputs((addr&0x3F)+1,(data>>16)&0xFF);
|
|
WriteInputs((addr&0x3F)+2,(data>>8)&0xFF);
|
|
WriteInputs((addr&0x3F)+3,(data>>0)&0xFF);
|
|
break;
|
|
|
|
// Sound Board
|
|
case 0x08:
|
|
//printf("PPC: %08X=%08X\n", addr, data);
|
|
break;
|
|
|
|
// Backup RAM
|
|
case 0x0C:
|
|
case 0x0D:
|
|
*(UINT32 *) &backupRAM[(addr&0x1FFFF)] = data;
|
|
break;
|
|
|
|
// MPC105
|
|
case 0x80: // F0800CF8 (never observed at 0xFExxxxxx)
|
|
PCIBridge.WritePCIConfigAddress(data);
|
|
break;
|
|
|
|
// MPC105/106
|
|
case 0xC0: case 0xD0: case 0xE0:
|
|
case 0xC1: case 0xD1: case 0xE1:
|
|
case 0xC2: case 0xD2: case 0xE2:
|
|
case 0xC3: case 0xD3: case 0xE3:
|
|
case 0xC4: case 0xD4: case 0xE4:
|
|
case 0xC5: case 0xD5: case 0xE5:
|
|
case 0xC6: case 0xD6: case 0xE6:
|
|
case 0xC7: case 0xD7: case 0xE7:
|
|
case 0xC8: case 0xD8: case 0xE8:
|
|
case 0xC9: case 0xD9: case 0xE9:
|
|
case 0xCA: case 0xDA: case 0xEA:
|
|
case 0xCB: case 0xDB: case 0xEB:
|
|
case 0xCC: case 0xDC: case 0xEC:
|
|
case 0xCD: case 0xDD: case 0xED:
|
|
case 0xCE: case 0xDE: case 0xEE:
|
|
case 0xCF: case 0xDF: case 0xEF:
|
|
if ((addr>=0xF0C00CF8) && (addr<0xF0C00D00)) // MPC105
|
|
PCIBridge.WritePCIConfigData(32,0,data);
|
|
else if ((addr>=0xFEC00000) && (addr<0xFEE00000)) // MPC106
|
|
PCIBridge.WritePCIConfigAddress(data);
|
|
else if ((addr>=0xFEE00000) && (addr<0xFEF00000)) // MPC106
|
|
PCIBridge.WritePCIConfigData(32,0,data);
|
|
break;
|
|
|
|
// System registers
|
|
case 0x10:
|
|
WriteSystemRegister((addr&0x3F)+0,(data>>24)&0xFF);
|
|
WriteSystemRegister((addr&0x3F)+1,(data>>16)&0xFF);
|
|
WriteSystemRegister((addr&0x3F)+2,(data>>8)&0xFF);
|
|
WriteSystemRegister((addr&0x3F)+3,(data>>0)&0xFF);
|
|
break;
|
|
|
|
// RTC
|
|
case 0x14:
|
|
RTC.WriteRegister((addr>>2)&0xF,data);
|
|
break;
|
|
|
|
// Security board RAM
|
|
case 0x18:
|
|
case 0x19:
|
|
*(UINT32 *) &securityRAM[(addr&0x1FFFF)] = data;
|
|
break;
|
|
|
|
// Security board registers
|
|
case 0x1A:
|
|
WriteSecurity(addr&0x3F,data);
|
|
break;
|
|
|
|
// Unknown
|
|
default:
|
|
break;
|
|
}
|
|
|
|
DebugLog("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data);
|
|
break;
|
|
|
|
// Tile generator
|
|
case 0xF1:
|
|
if (addr < 0xF1120000)
|
|
{
|
|
// Tile generator accesses its RAM as little endian, must flip for big endian PowerPC
|
|
data = FLIPENDIAN32(data);
|
|
TileGen.WriteRAM32(addr&0x1FFFFF,data);
|
|
break;
|
|
}
|
|
else if ((addr>=0xF1180000) && (addr<0xF1180100))
|
|
{
|
|
TileGen.WriteRegister(addr&0xFF,FLIPENDIAN32(data));
|
|
break;
|
|
}
|
|
|
|
goto Unknown32;
|
|
|
|
// MPC105/106
|
|
case 0xF8: // F8FFF000-F8FFF100
|
|
// Write in big endian order, like a real PowerPC
|
|
PCIBridge.WriteRegister((addr&0xFF)+0,(data>>24)&0xFF);
|
|
PCIBridge.WriteRegister((addr&0xFF)+1,(data>>16)&0xFF);
|
|
PCIBridge.WriteRegister((addr&0xFF)+2,(data>>8)&0xFF);
|
|
PCIBridge.WriteRegister((addr&0xFF)+3,data&0xFF);
|
|
break;
|
|
|
|
// 53C810 SCSI
|
|
case 0xC0: // step 1.0 only
|
|
if (m_game.stepping != "1.0")
|
|
goto Unknown32;
|
|
case 0xF9:
|
|
case 0xC1:
|
|
SCSI.WriteRegister((addr&0xFF)+0,(data>>24)&0xFF);
|
|
SCSI.WriteRegister((addr&0xFF)+1,(data>>16)&0xFF);
|
|
SCSI.WriteRegister((addr&0xFF)+2,(data>>8)&0xFF);
|
|
SCSI.WriteRegister((addr&0xFF)+3,data&0xFF);
|
|
break;
|
|
|
|
// Unknown
|
|
default:
|
|
Unknown32:
|
|
//printf("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data);
|
|
DebugLog("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CModel3::Write64(UINT32 addr, UINT64 data)
|
|
{
|
|
Write32(addr+0, (UINT32) (data>>32));
|
|
Write32(addr+4, (UINT32) data);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Emulation and Interface Functions
|
|
******************************************************************************/
|
|
|
|
void CModel3::SaveState(CBlockFile *SaveState)
|
|
{
|
|
// Write Model 3 state
|
|
SaveState->NewBlock("Model 3", __FILE__);
|
|
SaveState->Write(&inputBank, sizeof(inputBank));
|
|
SaveState->Write(&serialFIFO1, sizeof(serialFIFO1));
|
|
SaveState->Write(&serialFIFO2, sizeof(serialFIFO2));
|
|
SaveState->Write(&gunReg, sizeof(gunReg));
|
|
SaveState->Write(&adcChannel, sizeof(adcChannel));
|
|
SaveState->Write(&cromBankReg, sizeof(cromBankReg));
|
|
SaveState->Write(&securityPtr, sizeof(securityPtr));
|
|
SaveState->Write(ram, 0x800000);
|
|
SaveState->Write(backupRAM, 0x20000);
|
|
SaveState->Write(securityRAM, 0x20000);
|
|
SaveState->Write(&midiCtrlPort, sizeof(midiCtrlPort));
|
|
int32_t securityFirstRead = m_securityFirstRead;
|
|
SaveState->Write(&securityFirstRead, sizeof(securityFirstRead));
|
|
|
|
// All devices...
|
|
ppc_save_state(SaveState);
|
|
IRQ.SaveState(SaveState);
|
|
PCIBridge.SaveState(SaveState);
|
|
SCSI.SaveState(SaveState);
|
|
EEPROM.SaveState(SaveState);
|
|
TileGen.SaveState(SaveState);
|
|
GPU.SaveState(SaveState);
|
|
SoundBoard.SaveState(SaveState); // also saves DSB state
|
|
DriveBoard.SaveState(SaveState);
|
|
m_cryptoDevice.SaveState(SaveState);
|
|
}
|
|
|
|
void CModel3::LoadState(CBlockFile *SaveState)
|
|
{
|
|
// Load Model 3 state
|
|
if (OKAY != SaveState->FindBlock("Model 3"))
|
|
{
|
|
ErrorLog("Unable to load Model 3 core state. Save state file is corrupt.");
|
|
return;
|
|
}
|
|
|
|
SaveState->Read(&inputBank, sizeof(inputBank));
|
|
SaveState->Read(&serialFIFO1, sizeof(serialFIFO1));
|
|
SaveState->Read(&serialFIFO2, sizeof(serialFIFO2));
|
|
SaveState->Read(&gunReg, sizeof(gunReg));
|
|
SaveState->Read(&adcChannel, sizeof(adcChannel));
|
|
SaveState->Read(&cromBankReg, sizeof(cromBankReg));
|
|
SetCROMBank(cromBankReg); // update CROM bank
|
|
SaveState->Read(&securityPtr, sizeof(securityPtr));
|
|
SaveState->Read(ram, 0x800000);
|
|
SaveState->Read(backupRAM, 0x20000);
|
|
SaveState->Read(securityRAM, 0x20000);
|
|
SaveState->Read(&midiCtrlPort, sizeof(midiCtrlPort));
|
|
int32_t securityFirstRead;
|
|
SaveState->Write(&securityFirstRead, sizeof(securityFirstRead));
|
|
m_securityFirstRead = securityFirstRead != 0;
|
|
|
|
// All devices...
|
|
GPU.LoadState(SaveState);
|
|
TileGen.LoadState(SaveState);
|
|
EEPROM.LoadState(SaveState);
|
|
SCSI.LoadState(SaveState);
|
|
PCIBridge.LoadState(SaveState);
|
|
IRQ.LoadState(SaveState);
|
|
ppc_load_state(SaveState);
|
|
SoundBoard.LoadState(SaveState);
|
|
DriveBoard.LoadState(SaveState);
|
|
m_cryptoDevice.LoadState(SaveState);
|
|
}
|
|
|
|
void CModel3::SaveNVRAM(CBlockFile *NVRAM)
|
|
{
|
|
// Load EEPROM
|
|
EEPROM.SaveState(NVRAM);
|
|
|
|
// Save backup RAM
|
|
NVRAM->NewBlock("Backup RAM", __FILE__);
|
|
NVRAM->Write(backupRAM, 0x20000);
|
|
}
|
|
|
|
void CModel3::LoadNVRAM(CBlockFile *NVRAM)
|
|
{
|
|
// Load EEPROM
|
|
EEPROM.LoadState(NVRAM);
|
|
|
|
// Load backup RAM
|
|
if (OKAY != NVRAM->FindBlock("Backup RAM"))
|
|
{
|
|
ErrorLog("Unable to load Model 3 backup RAM. NVRAM file is corrupt.");
|
|
return;
|
|
}
|
|
NVRAM->Read(backupRAM, 0x20000);
|
|
}
|
|
|
|
void CModel3::ClearNVRAM(void)
|
|
{
|
|
memset(backupRAM, 0, 0x20000);
|
|
EEPROM.Clear();
|
|
}
|
|
|
|
void CModel3::RunFrame(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
// See if currently running multi-threaded
|
|
if (m_multiThreaded)
|
|
{
|
|
// If so, check all threads are up and running
|
|
if (!StartThreads())
|
|
goto ThreadError;
|
|
|
|
// Wake threads for PPC main board (if multi-threading GPU), sound board (if sync'd) and drive board (if attached) so they can process a frame
|
|
if ((m_gpuMultiThreaded && !ppcBrdThreadSync->Post()) ||
|
|
(syncSndBrdThread && !sndBrdThreadSync->Post()) ||
|
|
(DriveBoard.IsAttached() && !drvBrdThreadSync->Post()))
|
|
goto ThreadError;
|
|
|
|
// If not multi-threading GPU, then run PPC main board for a frame and sync GPUs now in this thread
|
|
if (!m_gpuMultiThreaded)
|
|
{
|
|
RunMainBoardFrame();
|
|
SyncGPUs();
|
|
}
|
|
|
|
// Render frame
|
|
RenderFrame();
|
|
|
|
// Enter notify wait critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Wait for PPC main board, sound board and drive board threads to finish their work (if they are running and haven't finished already)
|
|
while ((m_gpuMultiThreaded && !ppcBrdThreadDone) ||
|
|
(syncSndBrdThread && !sndBrdThreadDone) ||
|
|
(DriveBoard.IsAttached() && !drvBrdThreadDone))
|
|
{
|
|
if (!notifySync->Wait(notifyLock))
|
|
goto ThreadError;
|
|
}
|
|
ppcBrdThreadDone = false;
|
|
sndBrdThreadDone = false;
|
|
drvBrdThreadDone = false;
|
|
|
|
// Leave notify wait critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
|
|
// If multi-threading GPU, then sync GPUs last while PPC main board thread is waiting
|
|
if (m_gpuMultiThreaded)
|
|
SyncGPUs();
|
|
}
|
|
else
|
|
{
|
|
// If not multi-threaded, then just process and render a single frame for PPC main board, sound board and drive board in turn in this thread
|
|
RunMainBoardFrame();
|
|
SyncGPUs();
|
|
RenderFrame();
|
|
RunSoundBoardFrame();
|
|
if (DriveBoard.IsAttached())
|
|
RunDriveBoardFrame();
|
|
}
|
|
|
|
timings.frameTicks = CThread::GetTicks() - start;
|
|
|
|
return;
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in CModel3::RunFrame: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
}
|
|
|
|
void CModel3::RunMainBoardFrame(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
// Compute display and VBlank timings
|
|
unsigned ppcCycles = m_config["PowerPCFrequency"].ValueAs<unsigned>() * 1000000;
|
|
unsigned frameCycles = ppcCycles / 60;
|
|
unsigned vblCycles = (unsigned)((float) frameCycles * 2.5f/100.0f); // 2.5% vblank (ridiculously short and wrong but bigger values cause flicker in Daytona)
|
|
unsigned dispCycles = frameCycles - vblCycles;
|
|
|
|
// Scale PPC timer ratio according to speed at which the PowerPC is being emulated so that the observed running frequency of the PPC timer
|
|
// registers is more or less correct. This is needed to get the Virtua Striker 2 series of games running at the right speed (they are
|
|
// too slow otherwise). Other games appear to not be affected by this ratio so much as their running speed depends more on the timing of
|
|
// the Real3D status bit below.
|
|
ppc_set_timer_ratio(ppc_get_bus_freq_multipler() * 2 * ppcCycles / ppc_get_cycles_per_sec());
|
|
|
|
// Compute timing of the Real3D status bit. This value directly affects the speed at which all the games except Virtua Stiker 2 run.
|
|
// Currently it is not known exactly what this bit represents nor why such wildly varying values are needed for the different step models.
|
|
// The values below were arrived at by trial and error and clearly more investigation is required. If it turns out that the status bit is
|
|
// connected to the end of VBlank then the code below should be removed and the timing handled via GPU.VBlankEnd() instead.
|
|
unsigned statusCycles;
|
|
if (m_game.stepping == "2.0" || m_game.stepping == "2.1")
|
|
{
|
|
// For some reason, Fighting Vipers 2 and Daytona USA 2 require completely different timing to the rest of the step 2.x games
|
|
if (m_game.name == "daytona2" || (m_game.stepping == "2.0" && m_game.name == "fvipers2"))
|
|
statusCycles = (unsigned)((float)frameCycles * 24.0f/100.0f);
|
|
else
|
|
statusCycles = (unsigned)((float)frameCycles * 9.12f/100.0f);
|
|
}
|
|
else if (m_game.stepping == "1.5")
|
|
statusCycles = (unsigned)((float)frameCycles * 5.5f/100.0f);
|
|
else
|
|
statusCycles = (unsigned)((float)frameCycles * 48.0f/100.0f);
|
|
|
|
// VBlank
|
|
if (gpusReady)
|
|
{
|
|
TileGen.BeginVBlank();
|
|
GPU.BeginVBlank(statusCycles);
|
|
IRQ.Assert(0x02);
|
|
ppc_execute(vblCycles);
|
|
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
|
|
|
|
/*
|
|
* Sound:
|
|
*
|
|
* Bit 0x20 of the MIDI control port appears to enable periodic interrupts,
|
|
* which are used to send MIDI commands. Often games will write 0x27, send
|
|
* a series of commands, and write 0x06 to stop. Other games, like Star
|
|
* Wars Trilogy and Sega Rally 2, will enable interrupts at the beginning
|
|
* by writing 0x37 and will disable/enable interrupts to control command
|
|
* output.
|
|
*/
|
|
//printf("\t-- BEGIN (Ctrl=%02X, IRQEn=%02X, IRQPend=%02X) --\n", midiCtrlPort, IRQ.ReadIRQEnable()&0x40, IRQ.ReadIRQState());
|
|
int irqCount = 0;
|
|
while ((midiCtrlPort&0x20))
|
|
//while (midiCtrlPort == 0x27) // 27 triggers IRQ sequence, 06 stops it
|
|
{
|
|
// Don't waste time firing MIDI interrupts if game has disabled them
|
|
if ((IRQ.ReadIRQEnable()&0x40) == 0)
|
|
break;
|
|
|
|
// Process MIDI interrupt
|
|
IRQ.Assert(0x40);
|
|
ppc_execute(200); // give PowerPC time to acknowledge IRQ
|
|
IRQ.Deassert(0x40);
|
|
ppc_execute(200); // acknowledge that IRQ was deasserted (TODO: is this really needed?)
|
|
dispCycles -= 400;
|
|
|
|
++irqCount;
|
|
if (irqCount > 128)
|
|
{
|
|
//printf("\tMIDI FIFO OVERFLOW! (IRQEn=%02X, IRQPend=%02X)\n", IRQ.ReadIRQEnable()&0x40, IRQ.ReadIRQState());
|
|
break;
|
|
}
|
|
}
|
|
//printf("\t-- END --\n");
|
|
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
|
|
|
|
// End VBlank
|
|
GPU.EndVBlank();
|
|
TileGen.EndVBlank();
|
|
IRQ.Assert(0x0D);
|
|
}
|
|
|
|
// Run the PowerPC for the active display part of the frame
|
|
ppc_execute(dispCycles);
|
|
// MAME believes 0x0C should occur on every scanline
|
|
//for (int i = 0; i < 384; i++)
|
|
//{
|
|
// ppc_execute(dispCycles / 384);
|
|
// IRQ.Assert(0x0C);
|
|
//}
|
|
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
|
|
|
|
timings.ppcTicks = CThread::GetTicks() - start;
|
|
}
|
|
|
|
void CModel3::SyncGPUs(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
timings.syncSize = GPU.SyncSnapshots() + TileGen.SyncSnapshots();
|
|
gpusReady = true;
|
|
|
|
timings.syncTicks = CThread::GetTicks() - start;
|
|
}
|
|
|
|
void CModel3::RenderFrame(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
// Call OSD video callbacks
|
|
if (BeginFrameVideo() && gpusReady)
|
|
{
|
|
// Render frame
|
|
TileGen.BeginFrame();
|
|
GPU.BeginFrame();
|
|
TileGen.PreRenderFrame();
|
|
TileGen.RenderFrameBottom();
|
|
GPU.RenderFrame();
|
|
TileGen.RenderFrameTop();
|
|
GPU.EndFrame();
|
|
TileGen.EndFrame();
|
|
}
|
|
|
|
EndFrameVideo();
|
|
|
|
timings.renderTicks = CThread::GetTicks() - start;
|
|
}
|
|
|
|
bool CModel3::RunSoundBoardFrame(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
bool bufferFull = SoundBoard.RunFrame();
|
|
timings.sndTicks = CThread::GetTicks() - start;
|
|
return bufferFull;
|
|
}
|
|
|
|
void CModel3::RunDriveBoardFrame(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
DriveBoard.RunFrame();
|
|
timings.drvTicks = CThread::GetTicks() - start;
|
|
}
|
|
|
|
bool CModel3::StartThreads(void)
|
|
{
|
|
if (startedThreads)
|
|
return true;
|
|
|
|
// Create synchronization objects
|
|
if (m_gpuMultiThreaded)
|
|
{
|
|
ppcBrdThreadSync = CThread::CreateSemaphore(0);
|
|
if (ppcBrdThreadSync == NULL)
|
|
goto ThreadError;
|
|
}
|
|
sndBrdThreadSync = CThread::CreateSemaphore(0);
|
|
if (sndBrdThreadSync == NULL)
|
|
goto ThreadError;
|
|
sndBrdNotifyLock = CThread::CreateMutex();
|
|
if (sndBrdNotifyLock == NULL)
|
|
goto ThreadError;
|
|
sndBrdNotifySync = CThread::CreateCondVar();
|
|
if (sndBrdNotifySync == NULL)
|
|
goto ThreadError;
|
|
if (DriveBoard.IsAttached())
|
|
{
|
|
drvBrdThreadSync = CThread::CreateSemaphore(0);
|
|
if (drvBrdThreadSync == NULL)
|
|
goto ThreadError;
|
|
}
|
|
notifyLock = CThread::CreateMutex();
|
|
if (notifyLock == NULL)
|
|
goto ThreadError;
|
|
notifySync = CThread::CreateCondVar();
|
|
if (notifySync == NULL)
|
|
goto ThreadError;
|
|
|
|
// Reset thread flags
|
|
pauseThreads = false;
|
|
stopThreads = false;
|
|
|
|
// Create PPC main board thread, if multi-threading GPU
|
|
if (m_gpuMultiThreaded)
|
|
{
|
|
ppcBrdThread = CThread::CreateThread(StartMainBoardThread, this);
|
|
if (ppcBrdThread == NULL)
|
|
goto ThreadError;
|
|
}
|
|
|
|
// Create sound board thread (sync'd or unsync'd)
|
|
if (syncSndBrdThread)
|
|
sndBrdThread = CThread::CreateThread(StartSoundBoardThreadSyncd, this);
|
|
else
|
|
sndBrdThread = CThread::CreateThread(StartSoundBoardThread, this);
|
|
if (sndBrdThread == NULL)
|
|
goto ThreadError;
|
|
|
|
// Create drive board thread, if drive board is attached
|
|
if (DriveBoard.IsAttached())
|
|
{
|
|
drvBrdThread = CThread::CreateThread(StartDriveBoardThread, this);
|
|
if (drvBrdThread == NULL)
|
|
goto ThreadError;
|
|
}
|
|
|
|
// Set audio callback if sound board thread is unsync'd
|
|
if (!syncSndBrdThread)
|
|
SetAudioCallback(AudioCallback, this);
|
|
|
|
startedThreads = true;
|
|
return true;
|
|
|
|
ThreadError:
|
|
ErrorLog("Unable to create threads and/or synchronization objects: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
DeleteThreadObjects();
|
|
m_multiThreaded = false;
|
|
return false;
|
|
}
|
|
|
|
bool CModel3::PauseThreads(void)
|
|
{
|
|
if (!startedThreads)
|
|
return true;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Let threads know that they should pause and wait for all of them to do so
|
|
pauseThreads = true;
|
|
while (ppcBrdThreadRunning || sndBrdThreadRunning || drvBrdThreadRunning)
|
|
{
|
|
if (!notifySync->Wait(notifyLock))
|
|
goto ThreadError;
|
|
}
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
return true;
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in CModel3::PauseThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
return false;
|
|
}
|
|
|
|
bool CModel3::ResumeThreads(void)
|
|
{
|
|
if (!startedThreads)
|
|
return true;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Let all threads know that they can continue running
|
|
pauseThreads = false;
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
return true;
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in CModel3::ResumeThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
return false;
|
|
}
|
|
|
|
bool CModel3::StopThreads(void)
|
|
{
|
|
if (!startedThreads)
|
|
return true;
|
|
|
|
// If sound board thread is unsync'd then remove audio callback
|
|
if (!syncSndBrdThread)
|
|
SetAudioCallback(NULL, NULL);
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Let threads know that they should pause and wait for all of them to do so
|
|
pauseThreads = true;
|
|
while (ppcBrdThreadRunning || sndBrdThreadRunning || drvBrdThreadRunning)
|
|
{
|
|
if (!notifySync->Wait(notifyLock))
|
|
goto ThreadError;
|
|
}
|
|
|
|
// Now let threads know that they should exit
|
|
stopThreads = true;
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
|
|
// Resume each thread in turn and wait for them to exit
|
|
if (ppcBrdThread != NULL)
|
|
{
|
|
if (ppcBrdThreadSync->Post())
|
|
ppcBrdThread->Wait();
|
|
}
|
|
if (sndBrdThread != NULL)
|
|
{
|
|
if (syncSndBrdThread)
|
|
{
|
|
if (sndBrdThreadSync->Post())
|
|
sndBrdThread->Wait();
|
|
}
|
|
else
|
|
{
|
|
if (WakeSoundBoardThread())
|
|
sndBrdThread->Wait();
|
|
}
|
|
}
|
|
if (drvBrdThread != NULL)
|
|
{
|
|
if (drvBrdThreadSync->Post())
|
|
drvBrdThread->Wait();
|
|
}
|
|
|
|
// Delete all thread and synchronization objects
|
|
DeleteThreadObjects();
|
|
startedThreads = false;
|
|
return true;
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in CModel3::StopThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
return false;
|
|
}
|
|
|
|
void CModel3::DeleteThreadObjects(void)
|
|
{
|
|
// Delete PPC main board, sound board and drive board threads
|
|
if (ppcBrdThread != NULL)
|
|
{
|
|
delete ppcBrdThread;
|
|
ppcBrdThread = NULL;
|
|
}
|
|
if (sndBrdThread != NULL)
|
|
{
|
|
delete sndBrdThread;
|
|
sndBrdThread = NULL;
|
|
}
|
|
if (drvBrdThread != NULL)
|
|
{
|
|
delete drvBrdThread;
|
|
drvBrdThread = NULL;
|
|
}
|
|
|
|
// Delete synchronization objects
|
|
if (ppcBrdThreadSync != NULL)
|
|
{
|
|
delete ppcBrdThreadSync;
|
|
ppcBrdThreadSync = NULL;
|
|
}
|
|
if (sndBrdThreadSync != NULL)
|
|
{
|
|
delete sndBrdThreadSync;
|
|
sndBrdThreadSync = NULL;
|
|
}
|
|
if (drvBrdThreadSync != NULL)
|
|
{
|
|
delete drvBrdThreadSync;
|
|
drvBrdThreadSync = NULL;
|
|
}
|
|
if (sndBrdNotifyLock != NULL)
|
|
{
|
|
delete sndBrdNotifyLock;
|
|
sndBrdNotifyLock = NULL;
|
|
}
|
|
if (sndBrdNotifySync != NULL)
|
|
{
|
|
delete sndBrdNotifySync;
|
|
sndBrdNotifySync = NULL;
|
|
}
|
|
if (notifyLock != NULL)
|
|
{
|
|
delete notifyLock;
|
|
notifyLock = NULL;
|
|
}
|
|
if (notifySync != NULL)
|
|
{
|
|
delete notifySync;
|
|
notifySync = NULL;
|
|
}
|
|
}
|
|
|
|
void CModel3::DumpTimings(void)
|
|
{
|
|
printf("PPC:%3ums%c render:%3ums%c sync:%4uK%c%3ums%c snd:%3ums%c drv:%3ums%c frame:%3ums%c\n",
|
|
timings.ppcTicks, (timings.ppcTicks > timings.renderTicks ? '!' : ','),
|
|
timings.renderTicks, (timings.renderTicks > timings.ppcTicks ? '!' : ','),
|
|
timings.syncSize / 1024, (timings.syncSize / 1024 > 128 ? '!' : ','),
|
|
timings.syncTicks, (timings.syncTicks > 1 ? '!' : ','),
|
|
timings.sndTicks, (timings.sndTicks > 10 ? '!' : ','),
|
|
timings.drvTicks, (timings.drvTicks > 10 ? '!' : ','),
|
|
timings.frameTicks, (timings.frameTicks > 16 ? '!' : ' '));
|
|
}
|
|
|
|
FrameTimings CModel3::GetTimings(void)
|
|
{
|
|
return timings;
|
|
}
|
|
|
|
int CModel3::StartMainBoardThread(void *data)
|
|
{
|
|
// Call method on CModel3 to run PPC main board thread
|
|
CModel3 *model3 = (CModel3*)data;
|
|
return model3->RunMainBoardThread();
|
|
}
|
|
|
|
int CModel3::StartSoundBoardThread(void *data)
|
|
{
|
|
// Call method on CModel3 to run sound board thread (unsync'd)
|
|
CModel3 *model3 = (CModel3*)data;
|
|
return model3->RunSoundBoardThread();
|
|
}
|
|
|
|
int CModel3::StartSoundBoardThreadSyncd(void *data)
|
|
{
|
|
// Call method on CModel3 to run sound board thread (sync'd)
|
|
CModel3 *model3 = (CModel3*)data;
|
|
return model3->RunSoundBoardThreadSyncd();
|
|
}
|
|
|
|
int CModel3::StartDriveBoardThread(void *data)
|
|
{
|
|
// Call method on CModel3 to run drive board thread
|
|
CModel3 *model3 = (CModel3*)data;
|
|
return model3->RunDriveBoardThread();
|
|
}
|
|
|
|
int CModel3::RunMainBoardThread(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
bool wait = true;
|
|
bool exit = false;
|
|
while (wait && !exit)
|
|
{
|
|
// Wait on PPC main board thread semaphore
|
|
if (!ppcBrdThreadSync->Wait())
|
|
goto ThreadError;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Check threads are not being stopped or paused
|
|
if (stopThreads)
|
|
exit = true;
|
|
else if (!pauseThreads)
|
|
{
|
|
wait = false;
|
|
ppcBrdThreadRunning = true;
|
|
}
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
if (exit)
|
|
return 0;
|
|
|
|
// Process a single frame for PPC main board
|
|
RunMainBoardFrame();
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Let other threads know processing has finished
|
|
ppcBrdThreadRunning = false;
|
|
ppcBrdThreadDone = true;
|
|
if (!notifySync->SignalAll())
|
|
goto ThreadError;
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in RunMainBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
return 1;
|
|
}
|
|
|
|
void CModel3::AudioCallback(void *data)
|
|
{
|
|
// Call method on CModel3 to wake sound board thread
|
|
CModel3 *model3 = (CModel3*)data;
|
|
model3->WakeSoundBoardThread();
|
|
}
|
|
|
|
bool CModel3::WakeSoundBoardThread(void)
|
|
{
|
|
// Enter sound board notify critical section
|
|
bool wake;
|
|
if (!sndBrdNotifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Enter main notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// See if sound board thread is currently running
|
|
wake = !sndBrdThreadRunning;
|
|
|
|
// Leave main notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
|
|
// Only send wake notification to sound board thread if it was not running
|
|
if (wake)
|
|
{
|
|
// Signal to sound board thread that it should start processing again
|
|
sndBrdWakeNotify = true;
|
|
if (!sndBrdNotifySync->Signal())
|
|
goto ThreadError;
|
|
}
|
|
|
|
// Leave sound board notify critical section
|
|
if (!sndBrdNotifyLock->Unlock())
|
|
goto ThreadError;
|
|
return wake;
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in WakeSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
return false;
|
|
}
|
|
|
|
int CModel3::RunSoundBoardThread(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
bool wait = true;
|
|
bool exit = false;
|
|
while (wait && !exit)
|
|
{
|
|
// Enter sound board notify critical section
|
|
if (!sndBrdNotifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Wait for notification from audio callback
|
|
while (!sndBrdWakeNotify)
|
|
{
|
|
if (!sndBrdNotifySync->Wait(sndBrdNotifyLock))
|
|
goto ThreadError;
|
|
}
|
|
sndBrdWakeNotify = false;
|
|
|
|
// Enter main notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Check threads are not being stopped or paused
|
|
if (stopThreads)
|
|
exit = true;
|
|
else if (!pauseThreads)
|
|
{
|
|
wait = false;
|
|
sndBrdThreadRunning = true;
|
|
}
|
|
|
|
// Leave main notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
|
|
// Leave sound board notify critical section
|
|
if (!sndBrdNotifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
if (exit)
|
|
return 0;
|
|
|
|
// Keep processing frames until pausing or audio buffer is full
|
|
while (true)
|
|
{
|
|
// Enter main notify critical section
|
|
bool paused;
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
paused = pauseThreads;
|
|
|
|
// Leave main notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
|
|
if (paused || RunSoundBoardFrame())
|
|
break;
|
|
//printf("Rerunning sound board\n");
|
|
}
|
|
|
|
// Enter main notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Let other threads know processing has finished
|
|
sndBrdThreadRunning = false;
|
|
sndBrdThreadDone = true;
|
|
if (!notifySync->SignalAll())
|
|
goto ThreadError;
|
|
|
|
// Leave main notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in RunSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
return 1;
|
|
}
|
|
|
|
int CModel3::RunSoundBoardThreadSyncd(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
bool wait = true;
|
|
bool exit = false;
|
|
while (wait && !exit)
|
|
{
|
|
// Wait on sound board thread semaphore
|
|
if (!sndBrdThreadSync->Wait())
|
|
goto ThreadError;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Check threads are not being stopped or paused
|
|
if (stopThreads)
|
|
exit = true;
|
|
else if (!pauseThreads)
|
|
{
|
|
wait = false;
|
|
sndBrdThreadRunning = true;
|
|
}
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
if (exit)
|
|
return 0;
|
|
|
|
// Process a single frame for sound board
|
|
RunSoundBoardFrame();
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Let other threads know processing has finished
|
|
sndBrdThreadRunning = false;
|
|
sndBrdThreadDone = true;
|
|
if (!notifySync->SignalAll())
|
|
goto ThreadError;
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in RunSoundBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
return 1;
|
|
}
|
|
|
|
int CModel3::RunDriveBoardThread(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
bool wait = true;
|
|
bool exit = false;
|
|
while (wait && !exit)
|
|
{
|
|
// Wait on drive board thread semaphore
|
|
if (!drvBrdThreadSync->Wait())
|
|
goto ThreadError;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Check threads are not being stopped or paused
|
|
if (stopThreads)
|
|
exit = true;
|
|
else if (!pauseThreads)
|
|
{
|
|
wait = false;
|
|
drvBrdThreadRunning = true;
|
|
}
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
if (exit)
|
|
return 0;
|
|
|
|
// Process a single frame for drive board
|
|
RunDriveBoardFrame();
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Let other threads know processing has finished
|
|
drvBrdThreadRunning = false;
|
|
drvBrdThreadDone = true;
|
|
if (!notifySync->SignalAll())
|
|
goto ThreadError;
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in RunDriveBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
m_multiThreaded = false;
|
|
return 1;
|
|
}
|
|
|
|
void CModel3::Reset(void)
|
|
{
|
|
// Clear memory (but do not modify backup RAM!)
|
|
memset(ram, 0, 0x800000);
|
|
|
|
// Initial bank is bank 0
|
|
SetCROMBank(0xFF);
|
|
|
|
// Reset security device
|
|
securityPtr = 0;
|
|
m_securityFirstRead = true;
|
|
|
|
// Reset inputs
|
|
inputBank = 0;
|
|
serialFIFO1 = 0;
|
|
serialFIFO2 = 0;
|
|
adcChannel = 0;
|
|
|
|
// MIDI
|
|
midiCtrlPort = 0;
|
|
|
|
// Reset all devices
|
|
ppc_reset();
|
|
IRQ.Reset();
|
|
PCIBridge.Reset();
|
|
PCIBus.Reset();
|
|
SCSI.Reset();
|
|
RTC.Reset();
|
|
EEPROM.Reset();
|
|
TileGen.Reset();
|
|
GPU.Reset();
|
|
SoundBoard.Reset();
|
|
|
|
if (DriveBoard.IsAttached())
|
|
DriveBoard.Reset();
|
|
|
|
m_cryptoDevice.Reset();
|
|
|
|
gpusReady = false;
|
|
|
|
timings.ppcTicks = 0;
|
|
timings.syncSize = 0;
|
|
timings.syncTicks = 0;
|
|
timings.renderTicks = 0;
|
|
timings.sndTicks = 0;
|
|
timings.drvTicks = 0;
|
|
timings.frameTicks = 0;
|
|
|
|
DebugLog("Model 3 reset\n");
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Initialization, Shutdown, and ROM Management
|
|
******************************************************************************/
|
|
|
|
// Offsets of memory regions within Model 3's pool
|
|
#define OFFSET_RAM 0 // 8 MB
|
|
#define OFFSET_CROM 0x800000 // 8 MB (fixed CROM)
|
|
#define OFFSET_CROMxx 0x1000000 // 128 MB (banked CROM0-3 must follow fixed CROM)
|
|
#define OFFSET_VROM 0x9000000 // 64 MB
|
|
#define OFFSET_BACKUPRAM 0xD000000 // 128 KB
|
|
#define OFFSET_SECURITYRAM 0xD020000 // 128 KB
|
|
#define OFFSET_SOUNDROM 0xD040000 // 512 KB (68K sound board program)
|
|
#define OFFSET_SAMPLEROM 0xD0C0000 // 16 MB (sound board samples)
|
|
#define OFFSET_DSBPROGROM 0xE0C0000 // 128 KB (DSB program)
|
|
#define OFFSET_DSBMPEGROM 0xE0E0000 // 16 MB (DSB MPEG data -- Z80 version only uses 8MB)
|
|
#define OFFSET_DRIVEROM 0xF0E0000 // 64 KB
|
|
#define MEMORY_POOL_SIZE (0x800000 + 0x800000 + 0x8000000 + 0x4000000 + 0x20000 + 0x20000 + 0x80000 + 0x1000000 + 0x20000 + 0x1000000 + 0x10000)
|
|
|
|
// 64-bit magic number used to detect loading of optional ROMs
|
|
#define MAGIC_NUMBER 0x4C444D5245505553ULL
|
|
|
|
const Game &CModel3::GetGame() const
|
|
{
|
|
return m_game;
|
|
}
|
|
|
|
// Stepping-dependent parameters (MPC10x type, etc.) are initialized here
|
|
bool CModel3::LoadGame(const Game &game, const ROMSet &rom_set)
|
|
{
|
|
m_game = Game();
|
|
|
|
/*
|
|
* Copy in ROM data with mirroring as necessary for the following cases:
|
|
*
|
|
* - VROM: 64MB. If <= 32MB, mirror to high 32MB.
|
|
* - Banked CROM: 128MB. If <= 64MB, mirror to high 64MB.
|
|
* - Fixed CROM: 8MB. If < 8MB, loaded only in high part of space and low
|
|
* part is a mirror of (banked) CROM0.
|
|
* - Sample ROM: 16MB. If <= 8MB, mirror to high 8MB.
|
|
*/
|
|
if (rom_set.get_rom("vrom").size <= 32*0x100000)
|
|
{
|
|
rom_set.get_rom("vrom").CopyTo(&vrom[0], 32*100000);
|
|
rom_set.get_rom("vrom").CopyTo(&vrom[32*0x100000], 32*0x100000);
|
|
}
|
|
else
|
|
rom_set.get_rom("vrom").CopyTo(vrom, 64*0x100000);
|
|
if (rom_set.get_rom("banked_crom").size <= 64*0x100000)
|
|
{
|
|
rom_set.get_rom("banked_crom").CopyTo(&crom[8*0x100000 + 0], 64*0x100000);
|
|
rom_set.get_rom("banked_crom").CopyTo(&crom[8*0x100000 + 64*0x100000], 64*0x100000);
|
|
}
|
|
else
|
|
rom_set.get_rom("banked_crom").CopyTo(&crom[8*0x100000 + 0], 128*0x100000);
|
|
size_t crom_size = rom_set.get_rom("crom").size;
|
|
rom_set.get_rom("crom").CopyTo(&crom[8*0x100000 - crom_size], crom_size);
|
|
if (crom_size < 8*0x100000)
|
|
rom_set.get_rom("banked_crom").CopyTo(&crom[0], 8*0x100000 - crom_size);
|
|
if (rom_set.get_rom("sound_samples").size <= 8*0x100000)
|
|
{
|
|
rom_set.get_rom("sound_samples").CopyTo(&sampleROM[0], 8*0x100000);
|
|
rom_set.get_rom("sound_samples").CopyTo(&sampleROM[8*0x100000], 8*0x100000);
|
|
}
|
|
else
|
|
rom_set.get_rom("sound_samples").CopyTo(sampleROM, 16*0x100000);
|
|
rom_set.get_rom("sound_program").CopyTo(soundROM, 512*1024);
|
|
rom_set.get_rom("mpeg_program").CopyTo(dsbROM, 128*1024);
|
|
rom_set.get_rom("mpeg_music").CopyTo(mpegROM, 16*0x100000);
|
|
rom_set.get_rom("driveboard_program").CopyTo(driveROM, 64*1024);
|
|
|
|
// Convert PowerPC and 68K ROMs to little endian words
|
|
Util::FlipEndian32(crom, 8*0x100000 + 128*0x100000);
|
|
Util::FlipEndian16(soundROM, 512*1024);
|
|
Util::FlipEndian16(sampleROM, 16*0x100000);
|
|
|
|
// Initialize CPU
|
|
PPC_CONFIG ppc_config;
|
|
if (game.stepping == "2.0" || game.stepping == "2.1")
|
|
{
|
|
ppc_config.pvr = PPC_MODEL_603R; // 166 MHz
|
|
ppc_config.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
ppc_config.bus_frequency_multiplier = 0x25; // 2.5X multiplier
|
|
PCIBridge.SetModel(0x106); // MPC106
|
|
}
|
|
else if (game.stepping == "1.5")
|
|
{
|
|
ppc_config.pvr = PPC_MODEL_603E; // 100 MHz
|
|
ppc_config.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
ppc_config.bus_frequency_multiplier = 0x15; // 1.5X multiplier
|
|
if (game.name == "scudp1")
|
|
PCIBridge.SetModel(0x106); // some Step 1.x games use MPC106
|
|
else
|
|
PCIBridge.SetModel(0x105); // MPC105
|
|
}
|
|
else if (game.stepping == "1.0")
|
|
{
|
|
ppc_config.pvr = PPC_MODEL_603R; // 66 MHz
|
|
ppc_config.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
ppc_config.bus_frequency_multiplier = 0x10; // 1X multiplier
|
|
if (game.name == "bass" || game.name == "getbass")
|
|
PCIBridge.SetModel(0x106); // some Step 1.x games use MPC106
|
|
else
|
|
PCIBridge.SetModel(0x105); // MPC105
|
|
}
|
|
else
|
|
{
|
|
ErrorLog("Cannot configure Model 3 because game uses unrecognized stepping (%s).", game.stepping.c_str());
|
|
return FAIL;
|
|
}
|
|
ppc_init(&ppc_config);
|
|
ppc_attach_bus(this);
|
|
PPCFetchRegions[0].start = 0;
|
|
PPCFetchRegions[0].end = 0x007FFFFF;
|
|
PPCFetchRegions[0].ptr = (UINT32 *) ram;
|
|
PPCFetchRegions[1].start = 0xFF800000;
|
|
PPCFetchRegions[1].end = 0xFFFFFFFF;
|
|
PPCFetchRegions[1].ptr = (UINT32 *) crom;
|
|
PPCFetchRegions[2].start = 0;
|
|
PPCFetchRegions[2].end = 0;
|
|
PPCFetchRegions[2].ptr = NULL;
|
|
ppc_set_fetch(PPCFetchRegions);
|
|
|
|
// Initialize Real3D
|
|
int stepping = ((game.stepping[0] - '0') << 4) | (game.stepping[2] - '0');
|
|
GPU.SetStepping(stepping);
|
|
|
|
// MPEG board (if present)
|
|
if (rom_set.get_rom("mpeg_program").size)
|
|
{
|
|
if (game.mpeg_board == "DSB1")
|
|
{
|
|
DSB = new(std::nothrow) CDSB1(m_config);
|
|
if (NULL == DSB)
|
|
return ErrorLog("Insufficient memory for Digital Sound Board object.");
|
|
}
|
|
else if (game.mpeg_board == "DSB2")
|
|
{
|
|
Util::FlipEndian16(dsbROM, 128*1024); // 68K program needs to be byte swapped
|
|
DSB = new(std::nothrow) CDSB2(m_config);
|
|
if (NULL == DSB)
|
|
return ErrorLog("Insufficient memory for Digital Sound Board object.");
|
|
}
|
|
else if (game.mpeg_board.empty())
|
|
ErrorLog("No MPEG board type defined in game XML for MPEG ROMs.");
|
|
else
|
|
ErrorLog("Unknown MPEG board type '%s'. Only 'DSB1' and 'DSB2' are supported.", game.mpeg_board.c_str());
|
|
if (DSB && OKAY != DSB->Init(dsbROM, mpegROM))
|
|
return FAIL;
|
|
}
|
|
SoundBoard.AttachDSB(DSB);
|
|
|
|
// Drive board (if present)
|
|
if (rom_set.get_rom("driveboard_program").size)
|
|
{
|
|
if (DriveBoard.Init(driveROM))
|
|
return FAIL;
|
|
}
|
|
else
|
|
DriveBoard.Init(NULL);
|
|
|
|
// Security board encryption device
|
|
m_cryptoDevice.Init(game.encryption_key, std::bind(&CModel3::ReadSecurityRAM, this, std::placeholders::_1));
|
|
|
|
// Print game information
|
|
std::set<std::string> extra_hw;
|
|
if (DSB)
|
|
extra_hw.insert(Util::Format() << "Digital Sound Board (Type " << game.mpeg_board << ")");
|
|
if (rom_set.get_rom("driveboard_program").size)
|
|
extra_hw.insert("Drive Board");
|
|
if (game.encryption_key)
|
|
extra_hw.insert("Security Board");
|
|
if (!game.version.empty())
|
|
std::cout << " Title: " << game.title << " (" << game.version << ")" << std::endl;
|
|
else
|
|
std::cout << " Title: " << game.title << std::endl;
|
|
std::cout << " ROM Set: " << game.name << std::endl;
|
|
std::cout << " Developer: " << game.manufacturer << std::endl;
|
|
std::cout << " Year: " << game.year << std::endl;
|
|
std::cout << " Stepping: " << game.stepping << std::endl;
|
|
if (!extra_hw.empty())
|
|
std::cout << " Extra Hardware: " << Util::Format(", ").Join(extra_hw) << std::endl;
|
|
std::cout << std::endl;
|
|
|
|
m_game = game;
|
|
return OKAY;
|
|
}
|
|
|
|
void CModel3::AttachRenderers(CRender2D *Render2DPtr, IRender3D *Render3DPtr)
|
|
{
|
|
TileGen.AttachRenderer(Render2DPtr);
|
|
GPU.AttachRenderer(Render3DPtr);
|
|
}
|
|
|
|
void CModel3::AttachInputs(CInputs *InputsPtr)
|
|
{
|
|
Inputs = InputsPtr;
|
|
|
|
if (DriveBoard.IsAttached())
|
|
DriveBoard.AttachInputs(Inputs, m_game.inputs);
|
|
|
|
DebugLog("Model 3 attached inputs\n");
|
|
}
|
|
|
|
void CModel3::AttachOutputs(COutputs *OutputsPtr)
|
|
{
|
|
Outputs = OutputsPtr;
|
|
Outputs->SetGame(m_game);
|
|
Outputs->Attached();
|
|
|
|
if (DriveBoard.IsAttached())
|
|
DriveBoard.AttachOutputs(Outputs);
|
|
|
|
DebugLog("Model 3 attached outputs\n");
|
|
}
|
|
|
|
// Model 3 initialization. Some initialization is deferred until ROMs are loaded in LoadROMSet()
|
|
bool CModel3::Init(void)
|
|
{
|
|
float memSizeMB = (float)MEMORY_POOL_SIZE/(float)0x100000;
|
|
|
|
// Allocate all memory for ROMs and PPC RAM
|
|
memoryPool = new(std::nothrow) UINT8[MEMORY_POOL_SIZE];
|
|
if (NULL == memoryPool)
|
|
return ErrorLog("Insufficient memory for Model 3 object (needs %1.1f MB).", memSizeMB);
|
|
memset(memoryPool, 0, MEMORY_POOL_SIZE);
|
|
|
|
// Set up pointers
|
|
ram = &memoryPool[OFFSET_RAM];
|
|
crom = &memoryPool[OFFSET_CROM];
|
|
vrom = &memoryPool[OFFSET_VROM];
|
|
soundROM = &memoryPool[OFFSET_SOUNDROM];
|
|
sampleROM = &memoryPool[OFFSET_SAMPLEROM];
|
|
dsbROM = &memoryPool[OFFSET_DSBPROGROM];
|
|
mpegROM = &memoryPool[OFFSET_DSBMPEGROM];
|
|
backupRAM = &memoryPool[OFFSET_BACKUPRAM];
|
|
securityRAM = &memoryPool[OFFSET_SECURITYRAM];
|
|
driveROM = &memoryPool[OFFSET_DRIVEROM];
|
|
SetCROMBank(0xFF);
|
|
|
|
// Initialize other devices (PowerPC, DSB, and security board initialized after ROMs loaded)
|
|
IRQ.Init();
|
|
PCIBridge.Init();
|
|
PCIBus.Init();
|
|
SCSI.Init(this,&IRQ,0x100); // SCSI is actually a non-maskable interrupt, so we give it a bit number outside of 8-bit range
|
|
RTC.Init();
|
|
EEPROM.Init();
|
|
if (OKAY != TileGen.Init(&IRQ))
|
|
return FAIL;
|
|
if (OKAY != GPU.Init(vrom,this,&IRQ,0x100)) // same for Real3D DMA interrupt
|
|
return FAIL;
|
|
if (OKAY != SoundBoard.Init(soundROM,sampleROM))
|
|
return FAIL;
|
|
|
|
PCIBridge.AttachPCIBus(&PCIBus);
|
|
PCIBus.AttachDevice(13,&GPU);
|
|
PCIBus.AttachDevice(14,&SCSI);
|
|
PCIBus.AttachDevice(16,this);
|
|
|
|
DebugLog("Initialized Model 3 (allocated %1.1f MB)\n", memSizeMB);
|
|
|
|
return OKAY;
|
|
}
|
|
|
|
CSoundBoard *CModel3::GetSoundBoard(void)
|
|
{
|
|
return &SoundBoard;
|
|
}
|
|
|
|
CDriveBoard *CModel3::GetDriveBoard(void)
|
|
{
|
|
return &DriveBoard;
|
|
}
|
|
|
|
CModel3::CModel3(const Util::Config::Node &config)
|
|
: m_config(config),
|
|
m_multiThreaded(config["MultiThreaded"].ValueAs<bool>()),
|
|
m_gpuMultiThreaded(config["GPUMultiThreaded"].ValueAs<bool>()),
|
|
TileGen(config),
|
|
GPU(config),
|
|
SoundBoard(config),
|
|
DriveBoard(config)
|
|
{
|
|
// Initialize pointers so dtor can know whether to free them
|
|
memoryPool = NULL;
|
|
|
|
// Various uninitialized pointers
|
|
Inputs = NULL;
|
|
Outputs = NULL;
|
|
ram = NULL;
|
|
crom = NULL;
|
|
vrom = NULL;
|
|
soundROM = NULL;
|
|
sampleROM = NULL;
|
|
dsbROM = NULL;
|
|
mpegROM = NULL;
|
|
cromBank = NULL;
|
|
backupRAM = NULL;
|
|
securityRAM = NULL;
|
|
|
|
DSB = NULL;
|
|
|
|
securityPtr = 0;
|
|
|
|
startedThreads = false;
|
|
pauseThreads = false;
|
|
stopThreads = false;
|
|
ppcBrdThread = NULL;
|
|
sndBrdThread = NULL;
|
|
drvBrdThread = NULL;
|
|
ppcBrdThreadRunning = false;
|
|
ppcBrdThreadDone = false;
|
|
sndBrdThreadRunning = false;
|
|
sndBrdThreadDone = false;
|
|
drvBrdThreadRunning = false;
|
|
drvBrdThreadDone = false;
|
|
syncSndBrdThread = false;
|
|
ppcBrdThreadSync = NULL;
|
|
sndBrdThreadSync = NULL;
|
|
drvBrdThreadSync = NULL;
|
|
notifyLock = NULL;
|
|
notifySync = NULL;
|
|
|
|
DebugLog("Built Model 3\n");
|
|
}
|
|
|
|
// Dumps a memory region to a file for debugging purposes
|
|
/*
|
|
static void Dump(const char *file, uint8_t *buf, size_t size, bool reverse32, bool reverse16)
|
|
{
|
|
FILE *fp = fopen(file, "wb");
|
|
if (NULL != fp)
|
|
{
|
|
if (reverse32)
|
|
Util::FlipEndian32(buf, size);
|
|
else if (reverse16)
|
|
Util::FlipEndian16(buf, size);
|
|
fwrite(buf, sizeof(UINT8), size, fp);
|
|
fclose(fp);
|
|
printf("dumped %s\n", file);
|
|
}
|
|
else
|
|
printf("unable to dump %s\n", file);
|
|
}
|
|
*/
|
|
|
|
CModel3::~CModel3(void)
|
|
{
|
|
// Debug: dump some files
|
|
//Dump("ram", ram, 0x800000, true, false);
|
|
//Dump("vrom", vrom, 0x4000000, true, false);
|
|
//Dump("crom", crom, 0x800000, true, false);
|
|
//Dump("bankedCrom", &crom[0x800000], 0x7000000, true, false);
|
|
//Dump("soundROM", soundROM, 0x80000, false, true);
|
|
//Dump("sampleROM", sampleROM, 0x800000, false, true);
|
|
|
|
// Stop all threads
|
|
StopThreads();
|
|
|
|
// Free memory
|
|
if (memoryPool != NULL)
|
|
{
|
|
delete [] memoryPool;
|
|
memoryPool = NULL;
|
|
}
|
|
|
|
if (DSB != NULL)
|
|
{
|
|
delete DSB;
|
|
DSB = NULL;
|
|
}
|
|
|
|
Inputs = NULL;
|
|
Outputs = NULL;
|
|
ram = NULL;
|
|
crom = NULL;
|
|
vrom = NULL;
|
|
soundROM = NULL;
|
|
sampleROM = NULL;
|
|
dsbROM = NULL;
|
|
mpegROM = NULL;
|
|
cromBank = NULL;
|
|
backupRAM = NULL;
|
|
securityRAM = NULL;
|
|
|
|
DebugLog("Destroyed Model 3\n");
|
|
} |