mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-22 13:55:38 +00:00
3290 lines
93 KiB
C++
3290 lines
93 KiB
C++
/**
|
|
** Supermodel
|
|
** A Sega Model 3 Arcade Emulator.
|
|
** Copyright 2011 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
|
|
* ----------
|
|
* - 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 (hah!)
|
|
* - 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
|
|
*/
|
|
|
|
#include <new>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include "Supermodel.h"
|
|
|
|
/******************************************************************************
|
|
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
|
|
}
|
|
return data;
|
|
|
|
case 0x08: // game-specific inputs
|
|
|
|
data = 0xFF;
|
|
|
|
if ((Game->inputFlags&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 ((Game->inputFlags&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 ((Game->inputFlags&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 ((Game->inputFlags&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 ((Game->inputFlags&GAME_INPUT_VR))
|
|
{
|
|
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 ((Game->inputFlags&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 ((Game->inputFlags&GAME_INPUT_RALLY))
|
|
{
|
|
data &= ~(Inputs->viewChange->value<<0); // View change
|
|
data &= ~(Inputs->handBrake->value<<1); // Hand brake
|
|
}
|
|
|
|
if ((Game->inputFlags&GAME_INPUT_GUN1))
|
|
data &= ~(Inputs->trigger[0]->value<<0); // P1 Trigger
|
|
|
|
if ((Game->inputFlags&GAME_INPUT_ANALOG_JOYSTICK))
|
|
{
|
|
data &= ~(Inputs->analogJoyTrigger->value<<5); // Trigger
|
|
data &= ~(Inputs->analogJoyEvent->value<<0); // Event Button
|
|
}
|
|
|
|
if ((Game->inputFlags&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;
|
|
}
|
|
|
|
return data;
|
|
|
|
case 0x0C: // game-specific inputs
|
|
|
|
data = 0xFF;
|
|
|
|
if (DriveBoard.IsAttached())
|
|
data = DriveBoard.Read();
|
|
|
|
if ((Game->inputFlags&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 ((Game->inputFlags&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 ((Game->inputFlags&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 ((Game->inputFlags&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 ((Game->inputFlags&GAME_INPUT_GUN2))
|
|
data &= ~(Inputs->trigger[1]->value<<0); // P2 Trigger
|
|
|
|
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 ((Game->inputFlags&GAME_INPUT_VEHICLE))
|
|
{
|
|
adc[0] = (UINT8)Inputs->steering->value;
|
|
adc[1] = (UINT8)Inputs->accelerator->value;
|
|
adc[2] = (UINT8)Inputs->brake->value;
|
|
}
|
|
if ((Game->inputFlags&GAME_INPUT_ANALOG_JOYSTICK))
|
|
{
|
|
adc[0] = (UINT8)Inputs->analogJoyY->value;
|
|
adc[1] = (UINT8)Inputs->analogJoyX->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);
|
|
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 ((Game->inputFlags&GAME_INPUT_GUN1||Game->inputFlags&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.
|
|
******************************************************************************/
|
|
|
|
static const UINT16 spikeoutSecurity[] =
|
|
{
|
|
0x0000,
|
|
0x4f4d, 0x4544, 0x2d4c, 0x2033, 0x7953, 0x7473, 0x6d65, 0x5020,
|
|
0x6f72, 0x7267, 0x6d61, 0x4320, 0x706f, 0x7279, 0x6769, 0x7468,
|
|
0x2820, 0x2943, 0x3120, 0x3939, 0x2035, 0x4553, 0x4147, 0x4520,
|
|
0x746e, 0x7265, 0x7270, 0x7369, 0x7365, 0x4c2c, 0x4454, 0x202e,
|
|
0x6c41, 0x206c, 0x6972, 0x6867, 0x2074, 0x6572, 0x6573, 0x7672,
|
|
0x6465, 0x202e, 0x2020, 0x0020
|
|
};
|
|
|
|
static const UINT16 vs298Security[] =
|
|
{
|
|
0x0000, // dummy read
|
|
0x4A20, 0x5041, 0x4E41, 0x4920, 0x4154, 0x594C, 0x4220, 0x4152, 0x4953, 0x204C,
|
|
0x5241, 0x4547, 0x544E, 0x4E49, 0x2041, 0x4547, 0x4D52, 0x4E41, 0x2059, 0x4E45,
|
|
0x4C47, 0x4E41, 0x2044, 0x454E, 0x4854, 0x5245, 0x414C, 0x444E, 0x2053, 0x5246,
|
|
0x4E41, 0x4543, 0x4320, 0x4C4F, 0x4D4F, 0x4942, 0x2041, 0x4150, 0x4152, 0x5547,
|
|
0x5941, 0x4220, 0x4C55, 0x4147, 0x4952, 0x2041, 0x5053, 0x4941, 0x204E, 0x5243,
|
|
0x414F, 0x4954, 0x2041, 0x4542, 0x474C, 0x5549, 0x204D, 0x494E, 0x4547, 0x4952,
|
|
0x2041, 0x4153, 0x4455, 0x2049, 0x4F4B, 0x4552, 0x2041, 0x4544, 0x4D4E, 0x5241,
|
|
0x204B, 0x4F52, 0x414D, 0x494E, 0x2041, 0x4353, 0x544F, 0x414C, 0x444E, 0x5520,
|
|
0x4153, 0x5320, 0x554F, 0x4854, 0x4641, 0x4952, 0x4143, 0x4D20, 0x5845, 0x4349,
|
|
0x204F, 0x5559, 0x4F47, 0x4C53, 0x5641, 0x4149, 0x4620, 0x5F43, 0x4553, 0x4147
|
|
};
|
|
|
|
static const UINT16 ecaSecurity[] =
|
|
{
|
|
0x0000,
|
|
0x2d2f, 0x202d, 0x4d45, 0x5245, 0x4547, 0x434e, 0x2059, 0x4143,
|
|
0x4c4c, 0x4120, 0x424d, 0x4c55, 0x4e41, 0x4543, 0x2d20, 0x0a2d,
|
|
0x6f43, 0x7970, 0x6952, 0x6867, 0x2074, 0x4553, 0x4147, 0x4520,
|
|
0x746e, 0x7265, 0x7270, 0x7369, 0x7365, 0x202c, 0x744c, 0x2e64,
|
|
0x530a, 0x666f, 0x7774, 0x7261, 0x2065, 0x2652, 0x2044, 0x6544,
|
|
0x7470, 0x202e, 0x3123, 0x660a, 0x726f, 0x7420, 0x7365, 0x0a74,
|
|
};
|
|
|
|
static const UINT16 oceanhunSecurity[57] =
|
|
{
|
|
0x0000, // dummy read
|
|
|
|
0x3d3d, 0x203d, 0x434f, 0x4145, 0x204e, 0x5548, 0x544e, 0x5245,
|
|
0x3d20, 0x3d3d, 0x430a, 0x706f, 0x5279, 0x6769, 0x7468, 0x5320,
|
|
0x4745, 0x2041, 0x6e45, 0x6574, 0x7072, 0x6972, 0x6573, 0x2c73,
|
|
0x4c20, 0x6474, 0x0a2e, 0x6d41, 0x7375, 0x6d65, 0x6e65, 0x2074,
|
|
0x2652, 0x2044, 0x6544, 0x7470, 0x202e, 0x3123, 0x4b0a, 0x7a61,
|
|
0x6e75, 0x7261, 0x2069, 0x7354, 0x6b75, 0x6d61, 0x746f, 0x206f,
|
|
0x6553, 0x7463, 0x6f69, 0x206e, 0x614d, 0x616e, 0x6567, 0x0a72
|
|
};
|
|
|
|
static const UINT16 swtrilgySecurity[57] =
|
|
{
|
|
0xffff,
|
|
0x3d3d, 0x3d3d, 0x203d, 0x5453, 0x5241, 0x5720, 0x5241, 0x2053,
|
|
0x3d3d, 0x3d3d, 0x0a3d, 0x6f43, 0x7970, 0x6952, 0x6867, 0x2074,
|
|
0x4553, 0x4147, 0x4520, 0x746e, 0x7265, 0x7270, 0x7369, 0x7365,
|
|
0x202c, 0x744c, 0x2e64, 0x410a, 0x756d, 0x6573, 0x656d, 0x746e,
|
|
0x5220, 0x4426, 0x4420, 0x7065, 0x2e74, 0x2320, 0x3231, 0x4b0a,
|
|
0x7461, 0x7573, 0x6179, 0x7573, 0x4120, 0x646e, 0x206f, 0x2026,
|
|
0x614b, 0x6f79, 0x6f6b, 0x5920, 0x6d61, 0x6d61, 0x746f, 0x0a6f
|
|
};
|
|
|
|
static const UINT16 fvipers2Security[65] =
|
|
{
|
|
0x2a2a,
|
|
0x2a2a, 0x2a2a, 0x2a2a, 0x2a2a, 0x2a2a, 0x2a2a, 0x202a, 0x5b5b,
|
|
0x4620, 0x6769, 0x7468, 0x6e69, 0x2067, 0x6956, 0x6570, 0x7372,
|
|
0x3220, 0x5d20, 0x205d, 0x6e69, 0x3c20, 0x4d3c, 0x444f, 0x4c45,
|
|
0x332d, 0x3e3e, 0x4320, 0x706f, 0x7279, 0x6769, 0x7468, 0x2820,
|
|
0x2943, 0x3931, 0x3839, 0x5320, 0x4745, 0x2041, 0x6e45, 0x6574,
|
|
0x7072, 0x6972, 0x6573, 0x2c73, 0x544c, 0x2e44, 0x2020, 0x4120,
|
|
0x6c6c, 0x7220, 0x6769, 0x7468, 0x7220, 0x7365, 0x7265, 0x6576,
|
|
0x2e64, 0x2a20, 0x2a2a, 0x2a2a, 0x2a2a, 0x2a2a, 0x2a2a, 0x2a2a
|
|
};
|
|
|
|
UINT32 CModel3::ReadSecurity(unsigned reg)
|
|
{
|
|
UINT32 data;
|
|
|
|
switch (reg)
|
|
{
|
|
case 0x00: // Status
|
|
return 0;
|
|
case 0x1C: // Data
|
|
if (!strcmp(Game->id, "spikeout") || !strcmp(Game->id, "spikeofe"))
|
|
{
|
|
data = (spikeoutSecurity[securityPtr++] << 16);
|
|
securityPtr %= (sizeof(spikeoutSecurity)/sizeof(UINT16));
|
|
}
|
|
else if (!strcmp(Game->id, "vs298"))
|
|
{
|
|
data = (vs298Security[securityPtr++] << 16);
|
|
securityPtr %= (sizeof(vs298Security)/sizeof(UINT16));
|
|
}
|
|
else if (!strcmp(Game->id, "eca") || !strcmp(Game->id, "ecax"))
|
|
{
|
|
data = (ecaSecurity[securityPtr++] << 16);
|
|
securityPtr %= (sizeof(ecaSecurity)/sizeof(UINT16));
|
|
}
|
|
else if (!strcmp(Game->id, "oceanhun"))
|
|
{
|
|
data = (oceanhunSecurity[securityPtr++] << 16);
|
|
securityPtr %= (sizeof(oceanhunSecurity)/sizeof(UINT16));
|
|
}
|
|
else if (!strcmp(Game->id, "swtrilgy") || !strcmp(Game->id, "swtrilgya"))
|
|
{
|
|
data = (swtrilgySecurity[securityPtr++] << 16);
|
|
securityPtr %= (sizeof(swtrilgySecurity)/sizeof(UINT16));
|
|
}
|
|
else if (!strcmp(Game->id, "fvipers2"))
|
|
{
|
|
data = (fvipers2Security[securityPtr++] << 16);
|
|
securityPtr %= (sizeof(fvipers2Security)/sizeof(UINT16));
|
|
}
|
|
else if (!strcmp(Game->id, "von2"))
|
|
data = 0xFFFFFFFF;
|
|
else
|
|
{
|
|
data = 0xFFFFFFFF;
|
|
DebugLog("Security read: reg=%X, PC=%08X, LR=%08X\n", reg, ppc_get_pc(), ppc_get_lr());
|
|
}
|
|
return data;
|
|
default:
|
|
DebugLog("Security read: reg=%X\n", reg);
|
|
break;
|
|
}
|
|
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
void CModel3::WriteSecurity(unsigned reg, UINT32 data)
|
|
{
|
|
DebugLog("Security write: reg=%X, data=%08X (PC=%08X, LR=%08X)\n", reg, data, ppc_get_pc(), ppc_get_lr());
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
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());
|
|
//printf("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
|
|
DebugLog("IRQ SETTING! %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;
|
|
}
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Optimized Address Space Access Handlers
|
|
|
|
Although I have not yet profiled the code, these ought to be more optimal,
|
|
especially if the compiler can generate jump tables.
|
|
|
|
NOTE: Testing of some of the address ranges is not strict enough, especially
|
|
for the MPC10x. Write32() handles the MPC10x most correctly. For now,
|
|
accesses outside of the handled ranges have not been observed. Use the DEBUG
|
|
version of these handlers for validation of new games.
|
|
******************************************************************************/
|
|
|
|
#ifndef DEBUG
|
|
|
|
/*
|
|
* 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 0xFF; // one of these bits (0x80?) indicates "ready"
|
|
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;
|
|
|
|
// 53C810 SCSI
|
|
case 0xC0: // only on Step 1.0
|
|
if (Game->step != 0x10) // check for Step 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;
|
|
|
|
// 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:
|
|
if (addr==0xF1180000) // fixes 2D graphics (TO-DO: integrate register reads into TileGen.cpp)
|
|
return 0;
|
|
|
|
// Tile generator accesses its RAM as little endian, must flip for big endian PowerPC
|
|
if (addr < 0xF1120000)
|
|
{
|
|
data = TileGen.ReadRAM(addr&0x1FFFFF);
|
|
return FLIPENDIAN32(data);
|
|
}
|
|
|
|
break;
|
|
|
|
// 53C810 SCSI
|
|
case 0xC0: // only on Step 1.0
|
|
if (Game->step != 0x10) // 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;
|
|
|
|
// MPC105/106
|
|
case 0xF8:
|
|
PCIBridge.WriteRegister(addr&0xFF,data);
|
|
break;
|
|
|
|
// 53C810 SCSI
|
|
case 0xC0: // only on Step 1.0
|
|
if (Game->step != 0x10)
|
|
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;
|
|
|
|
// 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:
|
|
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-90000018
|
|
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 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:
|
|
*(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.WriteRAM(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 (Game->step != 0x10)
|
|
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:
|
|
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);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
/******************************************************************************
|
|
Debug Mode (Strict) Address Space Access Handlers
|
|
|
|
Enabled only if DEBUG is defined. These perform stricter checks than the
|
|
"optimized" handlers but may be slower.
|
|
******************************************************************************/
|
|
|
|
#ifdef DEBUG
|
|
|
|
/*
|
|
* CModel3::Read8(addr):
|
|
* CModel3::Read16(addr):
|
|
* CModel3::Read32(addr):
|
|
* CModel3::Read64(addr):
|
|
*
|
|
* Read handlers.
|
|
*/
|
|
UINT8 CModel3::Read8(UINT32 addr)
|
|
{
|
|
if (addr<0x00800000)
|
|
return ram[addr^3];
|
|
else if ((addr>=0xFF000000) && (addr<0xFF800000))
|
|
return cromBank[(addr&0x7FFFFF)^3];
|
|
else if (addr>=0xFF800000)
|
|
return crom[(addr&0x7FFFFF)^3];
|
|
else if ((addr>=0xC2000000) && (addr<0xC2000100))
|
|
return GPU.ReadDMARegister8(addr&0xFF);
|
|
else if (((addr>=0xF0040000) && (addr<0xF0040040)) || ((addr>=0xFE040000) && (addr<0xFE040040)))
|
|
return ReadInputs(addr&0x3F);
|
|
else if (((addr>=0xF0080000) && (addr<=0xF0080007)) || ((addr>=0xFE080000) && (addr<=0xFE080007)))
|
|
{
|
|
if ((addr&0xF) == 4) // MIDI control port
|
|
return 0xFF;
|
|
}
|
|
else if (((addr>=0xF00C0000) && (addr<0xF00DFFFF)) || ((addr>=0xFE0C0000) && (addr<0xFE0DFFFF)))
|
|
return backupRAM[(addr&0x1FFFF)^3];
|
|
else if (((addr>=0xF0100000) && (addr<0xF0100040)) || ((addr>=0xFE100000) && (addr<0xFE100040)))
|
|
return ReadSystemRegister(addr&0x3F);
|
|
else if (((addr>=0xF0140000) && (addr<0xF0140040)) || ((addr>=0xFE140000) && (addr<0xFE140040)))
|
|
{
|
|
if ((addr&3)==1) // battery voltage test
|
|
return 0x03;
|
|
else if ((addr&3)==0)
|
|
return RTC.ReadRegister((addr>>2)&0xF);
|
|
return 0;
|
|
}
|
|
else if (((addr>=0xF9000000) && (addr<0xF9000100)) || ((addr>=0xC1000000) && (addr<0xC1000100)) || ((Game->step==0x10) && ((addr>=0xC0000000) && (addr<0xC0000100))))
|
|
return SCSI.ReadRegister(addr&0xFF);
|
|
|
|
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;
|
|
}
|
|
|
|
if (addr<0x00800000)
|
|
return *(UINT16 *) &ram[addr^2];
|
|
else if ((addr>=0xFF000000) && (addr<0xFF800000))
|
|
return *(UINT16 *) &cromBank[(addr&0x7FFFFF)^2];
|
|
else if (addr>=0xFF800000)
|
|
return *(UINT16 *) &crom[(addr&0x7FFFFF)^2];
|
|
else if (((addr>=0xF00C0000) && (addr<0xF00DFFFF)) || ((addr>=0xFE0C0000) && (addr<0xFE0DFFFF)))
|
|
return *(UINT16 *) &backupRAM[(addr&0x1FFFF)^2];
|
|
else if ((addr>=0xF0C00CF8) && (addr<0xF0C00D00)) // MPC105
|
|
return PCIBridge.ReadPCIConfigData(16,addr&3);
|
|
else if ((addr>=0xFEE00000) && (addr<0xFEF00000)) // MPC106
|
|
return PCIBridge.ReadPCIConfigData(16,addr&3);
|
|
|
|
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;
|
|
}
|
|
|
|
if (addr<0x00800000)
|
|
return *(UINT32 *) &ram[addr];
|
|
else if ((addr>=0xFF000000) && (addr<0xFF800000))
|
|
return *(UINT32 *) &cromBank[(addr&0x7FFFFF)];
|
|
else if (addr>=0xFF800000)
|
|
return *(UINT32 *) &crom[(addr&0x7FFFFF)];
|
|
else if ((addr>=0x84000000) && (addr<0x8400003F))
|
|
return GPU.ReadRegister(addr&0x3F);
|
|
else if ((addr>=0xC2000000) && (addr<0xC2000100))
|
|
{
|
|
data = GPU.ReadDMARegister32(addr&0xFF);
|
|
return FLIPENDIAN32(data);
|
|
}
|
|
else if (((addr>=0xF0040000) && (addr<0xF0040040)) || ((addr>=0xFE040000) && (addr<0xFE040040)))
|
|
{
|
|
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;
|
|
}
|
|
else if (((addr>=0xF00C0000) && (addr<0xF00DFFFF)) || ((addr>=0xFE0C0000) && (addr<0xFE0DFFFF)))
|
|
return *(UINT32 *) &backupRAM[(addr&0x1FFFF)];
|
|
else if (((addr>=0xF0100000) && (addr<0xF0100040)) || ((addr>=0xFE100000) && (addr<0xFE100040)))
|
|
{
|
|
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;
|
|
}
|
|
else if ((addr>=0xF0C00CF8) && (addr<0xF0C00D00)) // MPC105
|
|
return PCIBridge.ReadPCIConfigData(32,0);
|
|
else if ((addr>=0xFEE00000) && (addr<0xFEF00000)) // MPC106
|
|
return PCIBridge.ReadPCIConfigData(32,0);
|
|
else if (((addr>=0xF0140000) && (addr<0xF0140040)) || ((addr>=0xFE140000) && (addr<0xFE140040)))
|
|
{
|
|
data = (RTC.ReadRegister((addr>>2)&0xF) << 24);
|
|
data |= 0x00030000; // set these bits to pass battery voltage test
|
|
return data;
|
|
}
|
|
else if (((addr>=0xF0180000) && (addr<0xF019FFFF)) || ((addr>=0xFE180000) && (addr<0xFE19FFFF)))
|
|
return *(UINT32 *) &securityRAM[(addr&0x1FFFF)]; // so far, only 32-bit access observed, so we use little endian access
|
|
else if (((addr>=0xF01A0000) && (addr<0xF01A003F)) || ((addr>=0xFE1A0000) && (addr<0xFE1A003F)))
|
|
return ReadSecurity(addr&0x3F);
|
|
else if ((addr>=0xF1000000) && (addr<0xF1120000))
|
|
{
|
|
// Tile generator accesses its RAM as little endian, must flip for big endian PowerPC
|
|
data = TileGen.ReadRAM(addr&0x1FFFFF);
|
|
return FLIPENDIAN32(data);
|
|
}
|
|
else if (((addr>=0xF9000000) && (addr<0xF9000100)) || ((addr>=0xC1000000) && (addr<0xC1000100)) || ((Game->step==0x10) && ((addr>=0xC0000000) && (addr<0xC0000100))))
|
|
{
|
|
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;
|
|
}
|
|
|
|
// FIXES 2D GRAPHICS (to-do: integrate this into tilegen.cpp)
|
|
if (addr==0xF1180000)
|
|
return 0;
|
|
|
|
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)
|
|
{
|
|
if (addr<0x00800000)
|
|
ram[addr^3] = data;
|
|
else if ((addr>=0xC2000000) && (addr<0xC2000100))
|
|
GPU.WriteDMARegister8(addr&0xFF,data);
|
|
else if (((addr>=0xF0040000) && (addr<0xF0040040)) || ((addr>=0xFE040000) && (addr<0xFE040040)))
|
|
WriteInputs(addr&0x3F,data);
|
|
else if (((addr>=0xF0080000) && (addr<=0xF0080007)) || ((addr>=0xFE080000) && (addr<=0xFE080007)))
|
|
{
|
|
if ((addr&0xF) == 0) // MIDI data port
|
|
SoundBoard.WriteMIDIPort(data);
|
|
else if ((addr&0xF) == 4) // MIDI control port
|
|
midiCtrlPort = data;
|
|
}
|
|
else if (((addr>=0xF00C0000) && (addr<0xF00E0000)) || ((addr>=0xFE0C0000) && (addr<0xFE0E0000)))
|
|
backupRAM[(addr&0x1FFFF)^3] = data;
|
|
else if (((addr>=0xF0100000) && (addr<0xF0100040)) || ((addr>=0xFE100000) && (addr<0xFE100040)))
|
|
WriteSystemRegister(addr&0x3F,data);
|
|
else if (((addr>=0xF0140000) && (addr<0xF0140040)) || ((addr>=0xFE140000) && (addr<0xFE140040)))
|
|
{
|
|
if ((addr&3)==0)
|
|
RTC.WriteRegister((addr>>2)&0xF,data);
|
|
}
|
|
else if ((addr>=0xF8FFF000) && (addr<0xF8FFF100))
|
|
PCIBridge.WriteRegister(addr&0xFF,data);
|
|
else if (((addr>=0xF9000000) && (addr<0xF9000100)) || ((addr>=0xC1000000) && (addr<0xC1000100)) || ((Game->step==0x10) && ((addr>=0xC0000000) && (addr<0xC0000100))))
|
|
SCSI.WriteRegister(addr&0xFF,data);
|
|
else
|
|
{
|
|
DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data);
|
|
//printf("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data);
|
|
}
|
|
}
|
|
|
|
void CModel3::Write16(UINT32 addr, UINT16 data)
|
|
{
|
|
if ((addr&1))
|
|
{
|
|
Write8(addr+0,data>>8);
|
|
Write8(addr+1,data&0xFF);
|
|
return;
|
|
}
|
|
|
|
if (addr<0x00800000)
|
|
*(UINT16 *) &ram[addr^2] = data;
|
|
else if (((addr>=0xF00C0000) && (addr<0xF00E0000)) || ((addr>=0xFE0C0000) && (addr<0xFE0E0000)))
|
|
*(UINT16 *) &backupRAM[(addr&0x1FFFF)^2] = data;
|
|
else if ((addr>=0xF0C00CF8) && (addr<0xF0C00D00))
|
|
PCIBridge.WritePCIConfigData(16,addr&2,data);
|
|
else if ((addr>=0xF8FFF000) && (addr<0xF8FFF100))
|
|
{
|
|
// Write in big endian order, like a real PowerPC
|
|
PCIBridge.WriteRegister((addr&0xFF)+0,data>>8);
|
|
PCIBridge.WriteRegister((addr&0xFF)+1,data&0xFF);
|
|
}
|
|
else
|
|
{
|
|
DebugLog("PC=%08X\twrite16: %08X=%04X\n", ppc_get_pc(), addr, data);
|
|
//printf("PC=%08X\twrite16: %08X=%04X\n", ppc_get_pc(), addr, data);
|
|
}
|
|
}
|
|
|
|
void CModel3::Write32(UINT32 addr, UINT32 data)
|
|
{
|
|
if ((addr&3))
|
|
{
|
|
Write16(addr+0,data>>16);
|
|
Write16(addr+2,data);
|
|
return;
|
|
}
|
|
|
|
if (addr<0x00800000)
|
|
*(UINT32 *) &ram[addr] = data;
|
|
else if ((addr>=0x88000000) && (addr<0x88000008))
|
|
GPU.Flush();
|
|
else if ((addr>=0x8C000000) && (addr<0x8C400000))
|
|
GPU.WriteLowCullingRAM(addr&0x3FFFFF,FLIPENDIAN32(data));
|
|
else if ((addr>=0x8E000000) && (addr<0x8E100000))
|
|
GPU.WriteHighCullingRAM(addr&0xFFFFF,FLIPENDIAN32(data));
|
|
else if ((addr>=0x90000000) && (addr<0x90000018))
|
|
GPU.WriteTexturePort(addr&0xFF,FLIPENDIAN32(data));
|
|
else if ((addr>=0x94000000) && (addr<0x94100000))
|
|
GPU.WriteTextureFIFO(FLIPENDIAN32(data));
|
|
else if ((addr>=0x98000000) && (addr<0x98400000))
|
|
GPU.WritePolygonRAM(addr&0x3FFFFF,FLIPENDIAN32(data));
|
|
else if ((addr>=0xC2000000) && (addr<0xC2000100))
|
|
GPU.WriteDMARegister32(addr&0xFF,FLIPENDIAN32(data));
|
|
else if (((addr>=0xF0040000) && (addr<0xF0040040)) || ((addr>=0xFE040000) && (addr<0xFE040040)))
|
|
{
|
|
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);
|
|
}
|
|
else if (((addr>=0xF00C0000) && (addr<0xF00E0000)) || ((addr>=0xFE0C0000) && (addr<0xFE0E0000)))
|
|
*(UINT32 *) &backupRAM[(addr&0x1FFFF)] = data;
|
|
else if ((addr>=0xF0800CF8) && (addr<0xF0800D00)) // MPC105
|
|
PCIBridge.WritePCIConfigAddress(data);
|
|
else 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);
|
|
else if (((addr>=0xF0100000) && (addr<0xF0100040)) || ((addr>=0xFE100000) && (addr<0xFE100040)))
|
|
{
|
|
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);
|
|
}
|
|
else if (((addr>=0xF0140000) && (addr<0xF0140040)) || ((addr>=0xFE140000) && (addr<0xFE140040)))
|
|
RTC.WriteRegister((addr>>2)&0xF,data);
|
|
else if (((addr>=0xF0180000) && (addr<0xF019FFFF)) || ((addr>=0xFE180000) && (addr<0xFE19FFFF)))
|
|
*(UINT32 *) &securityRAM[(addr&0x1FFFF)] = data; // so far, only 32-bit access observed, so just store little endian
|
|
else if (((addr>=0xF01A0000) && (addr<0xF01A003F)) || ((addr>=0xFE1A0000) && (addr<0xFE1A003F)))
|
|
WriteSecurity(addr&0x3F,data);
|
|
else if ((addr>=0xF1000000) && (addr<0xF1120000))
|
|
{
|
|
// Tile generator accesses its RAM as little endian, must flip for big endian PowerPC
|
|
data = FLIPENDIAN32(data);
|
|
TileGen.WriteRAM(addr&0x1FFFFF,data);
|
|
}
|
|
else if ((addr>=0xF1180000) && (addr<0xF1180100))
|
|
TileGen.WriteRegister(addr&0xFF,FLIPENDIAN32(data));
|
|
else if ((addr>=0xF8FFF000) && (addr<0xF8FFF100))
|
|
{
|
|
// 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);
|
|
}
|
|
else if (((addr>=0xF9000000) && (addr<0xF9000100)) || ((addr>=0xC1000000) && (addr<0xC1000100)) || ((Game->step==0x10) && ((addr>=0xC0000000) && (addr<0xC0000100))))
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
//printf("%08X=%08X\n", addr, data);
|
|
DebugLog("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data);
|
|
}
|
|
}
|
|
|
|
void CModel3::Write64(UINT32 addr, UINT64 data)
|
|
{
|
|
Write32(addr+0, (UINT32) (data>>32));
|
|
Write32(addr+4, (UINT32) data);
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
/******************************************************************************
|
|
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));
|
|
|
|
// 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);
|
|
}
|
|
|
|
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));
|
|
|
|
// 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);
|
|
}
|
|
|
|
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 (g_Config.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 (g_Config.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 (!g_Config.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 (g_Config.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 (g_Config.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();
|
|
}
|
|
|
|
frameTicks = CThread::GetTicks() - start;
|
|
|
|
return;
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in CModel3::RunFrame: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
g_Config.multiThreaded = false;
|
|
}
|
|
|
|
void CModel3::RunMainBoardFrame(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
// Compute display and VBlank timings
|
|
unsigned frameCycles = g_Config.GetPowerPCFrequency()*1000000/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;
|
|
|
|
// VBlank
|
|
if (gpusReady)
|
|
{
|
|
TileGen.BeginVBlank();
|
|
GPU.BeginVBlank();
|
|
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?)
|
|
|
|
++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);
|
|
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
|
|
|
|
ppcTicks = CThread::GetTicks() - start;
|
|
}
|
|
|
|
void CModel3::SyncGPUs(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
syncSize = GPU.SyncSnapshots() + TileGen.SyncSnapshots();
|
|
gpusReady = true;
|
|
|
|
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();
|
|
GPU.RenderFrame();
|
|
GPU.EndFrame();
|
|
TileGen.EndFrame();
|
|
}
|
|
|
|
EndFrameVideo();
|
|
|
|
renderTicks = CThread::GetTicks() - start;
|
|
}
|
|
|
|
bool CModel3::RunSoundBoardFrame(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
bool bufferFull = SoundBoard.RunFrame();
|
|
|
|
sndTicks = CThread::GetTicks() - start;
|
|
|
|
return bufferFull;
|
|
}
|
|
|
|
void CModel3::RunDriveBoardFrame(void)
|
|
{
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
DriveBoard.RunFrame();
|
|
|
|
drvTicks = CThread::GetTicks() - start;
|
|
}
|
|
|
|
bool CModel3::StartThreads(void)
|
|
{
|
|
if (startedThreads)
|
|
return true;
|
|
|
|
// Create synchronization objects
|
|
if (g_Config.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;
|
|
|
|
// Create PPC main board thread, if multi-threading GPU
|
|
if (g_Config.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();
|
|
g_Config.multiThreaded = false;
|
|
return false;
|
|
}
|
|
|
|
bool CModel3::PauseThreads(void)
|
|
{
|
|
if (!startedThreads)
|
|
return true;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Wait for all threads to finish their processing
|
|
pausedThreads = 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());
|
|
g_Config.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
|
|
pausedThreads = 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());
|
|
g_Config.multiThreaded = false;
|
|
return false;
|
|
}
|
|
|
|
void CModel3::StopThreads(void)
|
|
{
|
|
if (!startedThreads)
|
|
return;
|
|
|
|
// If sound board thread is unsync'd then remove audio callback
|
|
if (!syncSndBrdThread)
|
|
SetAudioCallback(NULL, NULL);
|
|
|
|
// Pause threads so that can safely delete thread objects
|
|
PauseThreads();
|
|
|
|
DeleteThreadObjects();
|
|
startedThreads = false;
|
|
}
|
|
|
|
void CModel3::DeleteThreadObjects(void)
|
|
{
|
|
// Delete (which in turn kills) PPC main board, sound board and drive board threads
|
|
// Note that can do so here safely because threads will always be waiting on their semaphores when this method is called
|
|
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",
|
|
ppcTicks, (ppcTicks > renderTicks ? '!' : ','),
|
|
renderTicks, (renderTicks > ppcTicks ? '!' : ','),
|
|
syncSize / 1024, (syncSize / 1024 > 128 ? '!' : ','), syncTicks, (syncTicks > 1 ? '!' : ','),
|
|
sndTicks, (sndTicks > 10 ? '!' : ','),
|
|
drvTicks, (drvTicks > 10 ? '!' : ','),
|
|
frameTicks, (frameTicks > 16 ? '!' : ' '));
|
|
}
|
|
|
|
int CModel3::StartMainBoardThread(void *data)
|
|
{
|
|
// Call method on CModel3 to run PPC main board thread
|
|
CModel3 *model3 = (CModel3*)data;
|
|
model3->RunMainBoardThread();
|
|
return 0;
|
|
}
|
|
|
|
int CModel3::StartSoundBoardThread(void *data)
|
|
{
|
|
// Call method on CModel3 to run sound board thread (unsync'd)
|
|
CModel3 *model3 = (CModel3*)data;
|
|
model3->RunSoundBoardThread();
|
|
return 0;
|
|
}
|
|
|
|
int CModel3::StartSoundBoardThreadSyncd(void *data)
|
|
{
|
|
// Call method on CModel3 to run sound board thread (sync'd)
|
|
CModel3 *model3 = (CModel3*)data;
|
|
model3->RunSoundBoardThreadSyncd();
|
|
return 0;
|
|
}
|
|
|
|
int CModel3::StartDriveBoardThread(void *data)
|
|
{
|
|
// Call method on CModel3 to run drive board thread
|
|
CModel3 *model3 = (CModel3*)data;
|
|
model3->RunDriveBoardThread();
|
|
return 0;
|
|
}
|
|
|
|
void CModel3::RunMainBoardThread(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
bool wait = true;
|
|
while (wait)
|
|
{
|
|
// Wait on PPC main board thread semaphore
|
|
if (!ppcBrdThreadSync->Wait())
|
|
goto ThreadError;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Check threads not paused
|
|
if (!pausedThreads)
|
|
{
|
|
wait = false;
|
|
ppcBrdThreadRunning = true;
|
|
}
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
|
|
// 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());
|
|
g_Config.multiThreaded = false;
|
|
}
|
|
|
|
void CModel3::AudioCallback(void *data)
|
|
{
|
|
// Call method on CModel3 to wake sound board thread
|
|
CModel3 *model3 = (CModel3*)data;
|
|
model3->WakeSoundBoardThread();
|
|
}
|
|
|
|
void CModel3::WakeSoundBoardThread(void)
|
|
{
|
|
// Enter sound board notify critical section
|
|
if (!sndBrdNotifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Signal to sound board thread that it should start processing again
|
|
if (!sndBrdNotifySync->Signal())
|
|
goto ThreadError;
|
|
|
|
// Exit sound board notify critical section
|
|
if (!sndBrdNotifyLock->Unlock())
|
|
goto ThreadError;
|
|
return;
|
|
|
|
ThreadError:
|
|
ErrorLog("Threading error in WakeSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
g_Config.multiThreaded = false;
|
|
}
|
|
|
|
void CModel3::RunSoundBoardThread(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
bool wait = true;
|
|
while (wait)
|
|
{
|
|
// Enter sound board notify critical section
|
|
if (!sndBrdNotifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Wait for notification from audio callback
|
|
if (!sndBrdNotifySync->Wait(sndBrdNotifyLock))
|
|
goto ThreadError;
|
|
|
|
// Exit sound board notify critical section
|
|
if (!sndBrdNotifyLock->Unlock())
|
|
goto ThreadError;
|
|
|
|
// Enter main notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Check threads not paused
|
|
if (!pausedThreads)
|
|
{
|
|
wait = false;
|
|
sndBrdThreadRunning = true;
|
|
}
|
|
|
|
// Leave main notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
|
|
// Keep processing frames until paused or audio buffer is full
|
|
while (true)
|
|
{
|
|
// Enter main notify critical section
|
|
bool paused;
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
paused = pausedThreads;
|
|
|
|
// 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());
|
|
g_Config.multiThreaded = false;
|
|
}
|
|
|
|
void CModel3::RunSoundBoardThreadSyncd(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
bool wait = true;
|
|
while (wait)
|
|
{
|
|
// Wait on sound board thread semaphore
|
|
if (!sndBrdThreadSync->Wait())
|
|
goto ThreadError;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Check threads not paused
|
|
if (!pausedThreads)
|
|
{
|
|
wait = false;
|
|
sndBrdThreadRunning = true;
|
|
}
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
|
|
// 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());
|
|
g_Config.multiThreaded = false;
|
|
}
|
|
|
|
void CModel3::RunDriveBoardThread(void)
|
|
{
|
|
for (;;)
|
|
{
|
|
bool wait = true;
|
|
while (wait)
|
|
{
|
|
// Wait on drive board thread semaphore
|
|
if (!drvBrdThreadSync->Wait())
|
|
goto ThreadError;
|
|
|
|
// Enter notify critical section
|
|
if (!notifyLock->Lock())
|
|
goto ThreadError;
|
|
|
|
// Check threads not paused
|
|
if (!pausedThreads)
|
|
{
|
|
wait = false;
|
|
drvBrdThreadRunning = true;
|
|
}
|
|
|
|
// Leave notify critical section
|
|
if (!notifyLock->Unlock())
|
|
goto ThreadError;
|
|
}
|
|
|
|
// 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());
|
|
g_Config.multiThreaded = false;
|
|
}
|
|
|
|
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;
|
|
|
|
// 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();
|
|
|
|
gpusReady = false;
|
|
ppcTicks = 0;
|
|
syncSize = 0;
|
|
syncTicks = 0;
|
|
renderTicks = 0;
|
|
sndTicks = 0;
|
|
drvTicks = 0;
|
|
frameTicks = 0;
|
|
|
|
DebugLog("Model 3 reset\n");
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
Initialization, Shutdown, and ROM Management
|
|
******************************************************************************/
|
|
|
|
// Apply patches to games
|
|
void CModel3::Patch(void)
|
|
{
|
|
if (!strcmp(Game->id, "vf3") || !strcmp(Game->id, "vf3a"))
|
|
{
|
|
// Base offset of program in CROM: 0x710000
|
|
//*(UINT32 *) &crom[0x713C7C] = 0x60000000; // this patch may not be needed anymore (find out why)
|
|
*(UINT32 *) &crom[0x713E54] = 0x60000000; // affects timing but prevents game from coining up -- investigate this carefully
|
|
//*(UINT32 *) &crom[0x7125B0] = 0x60000000; // this patch may not be needed anymore (find out why)
|
|
//*(UINT32 *) &crom[0x7125D0] = 0x60000000; // this patch may not be needed anymore (find out why)
|
|
}
|
|
else if (!strcmp(Game->id, "lemans24"))
|
|
{
|
|
// Base offset of program in CROM: 6473C0
|
|
*(UINT32 *) &crom[0x6D8C4C] = 0x00000002; // comm. mode: 00=master, 01=slave, 02=satellite
|
|
*(UINT32 *) &crom[0x73FE38] = 0x38840004; // an actual bug in the game code
|
|
*(UINT32 *) &crom[0x73EB5C] = 0x60000000;
|
|
*(UINT32 *) &crom[0x73EDD0] = 0x60000000;
|
|
*(UINT32 *) &crom[0x73EDC4] = 0x60000000;
|
|
//*(UINT32 *) &crom[0x6473C0+0xF8BD0] = 0x60000000; // waiting for something from network card, called at F8CD8
|
|
//*(UINT32 *) &crom[0x6473C0+0xF8B80] = 0x60000000; // "", called at 0xF8D90
|
|
}
|
|
else if (!strcmp(Game->id, "scud"))
|
|
{
|
|
// Base offset of program in CROM: 0x710000
|
|
*(UINT32 *) &crom[0x712734] = 0x60000000; // skips some ridiculously slow delay loop during boot-up
|
|
*(UINT32 *) &crom[0x71AEBC] = 0x60000000; // waiting for some flag in RAM that never gets modified (IRQ problem? try emulating VBL on Real3D)
|
|
*(UINT32 *) &crom[0x712268] = 0x60000000; // this corrects the boot-up menu (but why?)
|
|
crom[0x787B36^3] = 0x00; // Link ID: 00=single, 01=master, 02=slave (can bypass network board error)
|
|
*(UINT32 *) &crom[0x71277C] = 0x60000000; // seems to allow the game to start
|
|
*(UINT32 *) &crom[0x74072C] = 0x60000000; // ... ditto
|
|
|
|
//*(UINT32 *)&crom[0x799DE8] = 0x00050208; // debug menu
|
|
}
|
|
else if (!strcmp(Game->id, "scuda"))
|
|
{
|
|
*(UINT32 *) &crom[0x712734] = 0x60000000; // skips some ridiculously slow delay loop during boot-up
|
|
}
|
|
else if (!strcmp(Game->id, "scudj"))
|
|
{
|
|
*(UINT32 *) &crom[0x7126C8] = 0x60000000; // skips some ridiculously slow delay loop during boot-up
|
|
}
|
|
else if (!strcmp(Game->id, "scudp"))
|
|
{
|
|
/*
|
|
* RAM program structure:
|
|
*
|
|
* 1540: Reset vector transfers control here. Effective start of
|
|
* program. On error, game often resets here.
|
|
* 14844: Appears to be beginning of the actual boot-up process.
|
|
*/
|
|
|
|
// Base offset of program in CROM: 710000
|
|
// *(UINT32 *) &crom[0x713724] = 0x60000000;
|
|
// *(UINT32 *) &crom[0x713744] = 0x60000000;
|
|
// *(UINT32 *) &crom[0x741f48] = 0x60000000;
|
|
|
|
*(UINT32 *) &crom[0x741f68] = 0x60000000;
|
|
*(UINT32 *) &crom[0x7126B8] = 0x60000000; // waits for something in RAM
|
|
|
|
crom[0x7C62B2^3] = 0x00; // link ID is copied to 0x10011E, set it to single
|
|
}
|
|
else if (!strcmp(Game->id, "von2"))
|
|
{
|
|
*(UINT32 *) &crom[0x1B0] = 0x7C631A78; // eliminate annoyingly long delay loop
|
|
*(UINT32 *) &crom[0x1B4] = 0x60000000; // ""
|
|
}
|
|
else if (!strcmp(Game->id, "lostwsga"))
|
|
{
|
|
*(UINT32 *) &crom[0x7374f4] = 0x38840004; // an actual bug in the game code
|
|
}
|
|
else if (!strcmp(Game->id, "vs215"))
|
|
{
|
|
// VS215 is a modification of VS2 that runs on Step 1.5 hardware. I
|
|
// suspect the code here is trying to detect the system type but am too
|
|
// lazy to figure it out right now.
|
|
*(UINT32 *) &crom[0x7001A8] = 0x48000000+(0xFFF01630-0xFFF001A8); // force a jump to FFF01630
|
|
}
|
|
else if (!strcmp(Game->id, "vs298"))
|
|
{
|
|
// Base offset of program in CROM: 600000
|
|
// Inexplicably, at PC=AFC1C, a call is made to FC78, which 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 loaded from EEPROM but is 0.
|
|
*(UINT32 *) &crom[0x6AFC1C] = 0x60000000;
|
|
}
|
|
else if (!strcmp(Game->id, "srally2"))
|
|
{
|
|
*(UINT32 *) &crom[0x7C0C4] = 0x60000000;
|
|
*(UINT32 *) &crom[0x7C0C8] = 0x60000000;
|
|
*(UINT32 *) &crom[0x7C0CC] = 0x60000000;
|
|
}
|
|
else if (!strcmp(Game->id, "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)
|
|
*(UINT32 *) &crom[0x68468c] = 0x60000000; // protection device
|
|
*(UINT32 *) &crom[0x6063c4] = 0x60000000; // needed to allow levels to load
|
|
*(UINT32 *) &crom[0x616434] = 0x60000000; // prevents PPC from executing invalid code (MMU?)
|
|
*(UINT32 *) &crom[0x69f4e4] = 0x60000000; // ""
|
|
*(UINT32 *) &crom[0x600000+0x4C744] = 0x60000000; // decrementer loop?
|
|
}
|
|
else if (!strcmp(Game->id, "dayto2pe"))
|
|
{
|
|
*(UINT32 *) &crom[0x606784] = 0x60000000;
|
|
*(UINT32 *) &crom[0x69A3FC] = 0x60000000; // MAME says: jump to encrypted code
|
|
*(UINT32 *) &crom[0x618B28] = 0x60000000; // MAME says: jump to encrypted code
|
|
*(UINT32 *) &crom[0x64CA34] = 0x60000000; // decrementer
|
|
}
|
|
else if (!strcmp(Game->id, "fvipers2"))
|
|
{
|
|
/*
|
|
* Game code is copied to RAM in a non-trivial fashion (it may be
|
|
* compressed) just prior to the following sequence of code, which then
|
|
* transfers control to the RAM program:
|
|
*
|
|
* FFF0153C: 3C200070 li r1,0x00700000
|
|
* FFF01540: 3C60FF80 li r3,0xFF800000
|
|
* FFF01544: 38800000 li r4,0x00000000
|
|
* FFF01548: 48000751 bl 0xFFF01C98
|
|
* FFF0154C: 7C7F42A6 mfspr r3,pvr
|
|
* FFF01550: 90600080 stw r3,0x00000080
|
|
* FFF01554: 92E00084 stw r23,0x00000084
|
|
* FFF01558: 38600100 li r3,0x00000100
|
|
* FFF0155C: 7C6803A6 mtspr lr,r3
|
|
* FFF01560: 4E800020 bclr 0x14,0
|
|
*
|
|
* In order to patch the necessary portions of the RAM program, we must
|
|
* insert a routine that executes after the program is loaded. There is
|
|
* ample room in the vector table between 0xFFF00004 and 0xFFF000FC.
|
|
* The patching routine must terminate with a "bclr 0x14,0" to jump to
|
|
* the RAM program.
|
|
*/
|
|
*(UINT32 *) &crom[0xFFF01560-0xFF800000] = 0x4BF00006; // ba 0xFFF00004
|
|
*(UINT32 *) &crom[0xFFF00004-0xFF800000] = (31<<26)|(316<<1); // xor r0,r0,r0 ; R0 = 0
|
|
*(UINT32 *) &crom[0xFFF00008-0xFF800000] = (15<<26)|(2<<21)|0x6000; // addis r2,0,0x6000 ; R2 = nop
|
|
*(UINT32 *) &crom[0xFFF0000C-0xFF800000] = (24<<26)|(1<<16)|0xA2E8; // ori r1,r0,0xA2E8 ; [A2E8] <- nop (decrementer loop)
|
|
*(UINT32 *) &crom[0xFFF00010-0xFF800000] = (36<<26)|(2<<21)|(1<<16); // stw r2,0(r1)
|
|
*(UINT32 *) &crom[0xFFF00014-0xFF800000] = 0x4E800020; // bclr 0x14,0 ; return to RAM code
|
|
|
|
/* // Security board patch for Andy
|
|
*(UINT32 *) &crom[0xFFF01560-0xFF800000] = 0x4BF00006; // ba 0xFFF00004
|
|
*(UINT32 *) &crom[0xFFF00004-0xFF800000] = (31<<26)|(316<<1); // xor r0,r0,r0 ; R0 = 0
|
|
*(UINT32 *) &crom[0xFFF00008-0xFF800000] = (15<<26)|(2<<21)|0x3860; // addis r2,0,0x3860 ; R2 = "li r3,0xFFFFFFFF"
|
|
*(UINT32 *) &crom[0xFFF0000C-0xFF800000] = (24<<26)|(2<<21)|(2<<16)|0xFFFF; // ori r2,r2,0xFFFF
|
|
*(UINT32 *) &crom[0xFFF00010-0xFF800000] = (24<<26)|(1<<16)|0x9CF8; // ori r1,r0,0x9CF8 ; [9CF8] <- set return value to "success"
|
|
*(UINT32 *) &crom[0xFFF00014-0xFF800000] = (36<<26)|(2<<21)|(1<<16); // stw r2,0(r1)
|
|
*(UINT32 *) &crom[0xFFF00018-0xFF800000] = (15<<26)|(2<<21)|0x4800; // addis r2,0,0x4800 ; R2 = "ba 0x9D40"
|
|
*(UINT32 *) &crom[0xFFF0001C-0xFF800000] = (24<<26)|(2<<21)|(2<<16)|0x9D42; // ori r2,r2,0xFFFF
|
|
*(UINT32 *) &crom[0xFFF00020-0xFF800000] = (24<<26)|(1<<16)|0x9CFC; // ori r1,r0,0x9CFC ; [9CFC] <- jump to end of security board routine
|
|
*(UINT32 *) &crom[0xFFF00024-0xFF800000] = (36<<26)|(2<<21)|(1<<16); // stw r2,0(r1)
|
|
*(UINT32 *) &crom[0xFFF00028-0xFF800000] = 0x4E800020; // bclr 0x14,0 ; return to RAM code
|
|
*/
|
|
|
|
// NOTE: At 32714, a test is made that determines the message: ONE PROCESSOR DETECTED, TWO "", etc.
|
|
}
|
|
else if (!strcmp(Game->id, "harley"))
|
|
{
|
|
*(UINT32 *) &crom[0x50E8D4] = 0x60000000;
|
|
*(UINT32 *) &crom[0x50E8F4] = 0x60000000;
|
|
*(UINT32 *) &crom[0x50FB84] = 0x60000000;
|
|
|
|
*(UINT32 *) &crom[0x4F736C] = 0x60000000;
|
|
*(UINT32 *) &crom[0x4F738C] = 0x60000000;
|
|
}
|
|
else if (!strcmp(Game->id, "harleyb"))
|
|
{
|
|
*(UINT32 *) &crom[0x50ECB4] = 0x60000000;
|
|
*(UINT32 *) &crom[0x50ECD4] = 0x60000000;
|
|
*(UINT32 *) &crom[0x50FF64] = 0x60000000;
|
|
|
|
*(UINT32 *) &crom[0x4F774C] = 0x60000000;
|
|
*(UINT32 *) &crom[0x4F776C] = 0x60000000;
|
|
}
|
|
else if (!strcmp(Game->id, "oceanhun"))
|
|
{
|
|
// Base address of program in CROM: 588FD8-108FD8=480000
|
|
//*(UINT32 *) &crom[0x480000+0x108FE0] = 0x60000000; // bad DMA copies from CROM
|
|
//*(UINT32 *) &crom[0x480000+0x112020] = 0x60000000; // reads from invalid addresses (due to CROM?)
|
|
*(UINT32 *) &crom[0x480000+0xF995C] = 0x60000000; // decrementer
|
|
}
|
|
else if (!strcmp(Game->id, "swtrilgy"))
|
|
{
|
|
*(UINT32 *) &crom[0xF0E48] = 0x60000000;
|
|
*(UINT32 *) &crom[0x043DC] = 0x48000090;
|
|
*(UINT32 *) &crom[0x029A0] = 0x60000000;
|
|
*(UINT32 *) &crom[0x02A0C] = 0x60000000;
|
|
}
|
|
else if (!strcmp(Game->id, "swtrilgya"))
|
|
{
|
|
*(UINT32 *) &crom[0xF6DD0] = 0x60000000; // from MAME
|
|
|
|
//*(UINT32 *) &crom[0xF1128] = 0x60000000; // these bypass required delay loops and break game timing
|
|
//*(UINT32 *) &crom[0xF10E0] = 0x60000000;
|
|
}
|
|
else if (!strcmp(Game->id, "eca") || !strcmp(Game->id, "ecax"))
|
|
{
|
|
*(UINT32 *) &crom[0x535580] = 0x60000000;
|
|
*(UINT32 *) &crom[0x5023B4] = 0x60000000;
|
|
*(UINT32 *) &crom[0x5023D4] = 0x60000000;
|
|
|
|
*(UINT32 *) &crom[0x535560] = 0x60000000; // decrementer loop
|
|
}
|
|
else if (!strcmp(Game->id, "spikeout"))
|
|
{
|
|
/*
|
|
* Decrementer loop at 0x31994 seems to work until a few frames into
|
|
* the attract mode and game, at which point a very large value is
|
|
* loaded into the decrementer and locks up the CPU (the usual
|
|
* decrementer problem). "Insert Coin" keeps flashing because it is
|
|
* managed via an IRQ, evidently.
|
|
*
|
|
* 0x00031994: 0x7C9602A6 mfspr r4,dec
|
|
* 0x00031998: 0x2C040000 cmpi cr0,0,r4,0x0000
|
|
* 0x0003199C: 0x41A0FFF8 bt cr0[lt],0x00031994
|
|
*/
|
|
|
|
*(UINT32 *) &crom[0x600000+0x3199C] = 0x60000000;
|
|
}
|
|
else if (!strcmp(Game->id, "spikeofe"))
|
|
{
|
|
*(UINT32 *) &crom[0x600000+0x36F2C] = 0x60000000; // decrementer loop (see Spikeout)
|
|
}
|
|
else if (!strcmp(Game->id, "skichamp"))
|
|
{
|
|
// Base address of program in CROM: 0x480000
|
|
*(UINT32 *) &crom[0x480000+0x96B9C] = 0x60000000; // decrementer loop
|
|
}
|
|
}
|
|
|
|
// Reverses all aligned 16-bit words, thereby switching their endianness (assumes buffer size is divisible by 2)
|
|
static void Reverse16(UINT8 *buf, unsigned size)
|
|
{
|
|
unsigned i;
|
|
UINT8 tmp;
|
|
|
|
for (i = 0; i < size; i += 2)
|
|
{
|
|
tmp = buf[i+0];
|
|
buf[i+0] = buf[i+1];
|
|
buf[i+1] = tmp;
|
|
}
|
|
}
|
|
|
|
// Reverses all aligned 32-bit words, thereby switching their endianness (assumes buffer size is divisible by 4)
|
|
static void Reverse32(UINT8 *buf, unsigned size)
|
|
{
|
|
unsigned i;
|
|
UINT8 tmp1, tmp2;
|
|
|
|
for (i = 0; i < size; i += 4)
|
|
{
|
|
tmp1 = buf[i+0];
|
|
tmp2 = buf[i+1];
|
|
buf[i+0] = buf[i+3];
|
|
buf[i+1] = buf[i+2];
|
|
buf[i+2] = tmp2;
|
|
buf[i+3] = tmp1;
|
|
}
|
|
}
|
|
|
|
// Reads in CROMs directly (for debugging purposes only)
|
|
static void ReadCROMDirectly(UINT8 *crom, char *fileName[4], unsigned combinedSize)
|
|
{
|
|
FILE *fp[4] = { NULL, NULL, NULL, NULL };
|
|
long size[4];
|
|
|
|
// Open all files
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
fp[i] = fopen(fileName[i],"rb");
|
|
if (NULL == fp[i])
|
|
{
|
|
ErrorLog("Unable to open file for reading: %s.", fileName[i]);
|
|
goto ReadCleanup;
|
|
}
|
|
}
|
|
|
|
// Read and interleave 2 bytes at a time
|
|
for (unsigned i = 0; i < combinedSize; i += 8)
|
|
{
|
|
fread(&crom[i+0], 1, 2, fp[0]);
|
|
fread(&crom[i+2], 1, 2, fp[1]);
|
|
fread(&crom[i+4], 1, 2, fp[2]);
|
|
fread(&crom[i+6], 1, 2, fp[3]);
|
|
}
|
|
Reverse16(crom, combinedSize); // byte reverse ROMs
|
|
|
|
// Reverse 32 bit words for Supermodel
|
|
Reverse32(crom, combinedSize);
|
|
|
|
ReadCleanup:
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (fp[i] != NULL)
|
|
fclose(fp[i]);
|
|
}
|
|
}
|
|
|
|
// Dumps a memory region to a file for debugging purposes
|
|
static void Dump(const char *file, UINT8 *buf, unsigned size, bool reverse32, bool reverse16)
|
|
{
|
|
FILE *fp;
|
|
|
|
fp = fopen(file, "wb");
|
|
if (NULL != fp)
|
|
{
|
|
if (reverse32)
|
|
Reverse32(buf, size);
|
|
else if (reverse16)
|
|
Reverse16(buf, size);
|
|
fwrite(buf, sizeof(UINT8), size, fp);
|
|
fclose(fp);
|
|
printf("dumped %s\n", file);
|
|
}
|
|
else
|
|
printf("unable to dump %s\n", file);
|
|
}
|
|
|
|
// 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 struct GameInfo * CModel3::GetGameInfo(void)
|
|
{
|
|
return Game;
|
|
}
|
|
|
|
// Stepping-dependent parameters (MPC10x type, etc.) are initialized here
|
|
bool CModel3::LoadROMSet(const struct GameInfo *GameList, const char *zipFile)
|
|
{
|
|
struct ROMMap Map[] =
|
|
{
|
|
{ "CROM", crom },
|
|
{ "CROMxx", &crom[0x800000] },
|
|
{ "VROM", vrom },
|
|
{ "SndProg", soundROM },
|
|
{ "Samples", sampleROM },
|
|
{ "DSBProg", dsbROM },
|
|
{ "DSBMPEG", mpegROM },
|
|
{ "DriveBd", driveROM },
|
|
{ NULL, NULL }
|
|
};
|
|
PPC_CONFIG PPCConfig;
|
|
|
|
// Magic numbers to detect if optional ROMs are loaded
|
|
*(UINT64 *) driveROM = MAGIC_NUMBER;
|
|
|
|
// Load game
|
|
Game = LoadROMSetFromZIPFile(Map, GameList, zipFile, true);
|
|
if (NULL == Game)
|
|
return ErrorLog("Failed to load ROM set.");
|
|
|
|
// Perform mirroring as necessary
|
|
if (Game->vromSize < 0x4000000) // VROM is actually 64 MB
|
|
CopyRegion(vrom, Game->vromSize, 0x4000000, vrom, Game->vromSize);
|
|
if (Game->cromSize < 0x800000) // low part of fixed CROM region contains CROM0
|
|
CopyRegion(crom, 0, 0x800000-Game->cromSize, &crom[0x800000], 0x800000);
|
|
if (Game->mirrorLow64MB) // for games w/ 64 MB or less banked CROM, mirror to upper 128 MB
|
|
CopyRegion(&crom[0x800000], 0x4000000, 0x8000000, &crom[0x800000], 0x4000000);
|
|
if (Game->sampleSize < 0x1000000) // if less than 16 MB of sample ROMs, mirror
|
|
CopyRegion(sampleROM, 0x800000, 0x1000000, sampleROM, 0x800000);
|
|
|
|
// Byte reverse the PowerPC ROMs (convert to little endian words)
|
|
Reverse32(crom, 0x800000+0x8000000);
|
|
|
|
// Byte swap sound board 68K ROMs (convert to little endian words)
|
|
Reverse16(soundROM, 0x80000);
|
|
Reverse16(sampleROM, 0x1000000);
|
|
|
|
// Initialize CPU and configure hardware (CPU speed is set in Init())
|
|
if (Game->step >= 0x20) // Step 2.0+
|
|
{
|
|
PPCConfig.pvr = PPC_MODEL_603R; // 166 MHz
|
|
PPCConfig.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
PPCConfig.bus_frequency_multiplier = 0x25; // 2.5X multiplier
|
|
PCIBridge.SetModel(0x106); // MPC106
|
|
}
|
|
else if (Game->step == 0x15) // Step 1.5
|
|
{
|
|
PPCConfig.pvr = PPC_MODEL_603E; // 100 MHz
|
|
PPCConfig.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
PPCConfig.bus_frequency_multiplier = 0x15; // 1.5X multiplier
|
|
PCIBridge.SetModel(0x105); // MPC105
|
|
}
|
|
else if (Game->step == 0x10) // Step 1.0
|
|
{
|
|
PPCConfig.pvr = PPC_MODEL_603R; // 66 MHz
|
|
PPCConfig.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
PPCConfig.bus_frequency_multiplier = 0x10; // 1X multiplier
|
|
if (!strcmp(Game->id, "bass") || !strcmp(Game->id, "getbass")) // some Step 1.0 games use MPC106
|
|
PCIBridge.SetModel(0x106);
|
|
else
|
|
PCIBridge.SetModel(0x105); // MPC105
|
|
}
|
|
else
|
|
return ErrorLog("Game uses an unrecognized stepping (%d.%d), cannot configure Model 3.", (Game->step>>4)&0xF, Game->step&0xF);
|
|
|
|
GPU.SetStep(Game->step);
|
|
|
|
ppc_init(&PPCConfig);
|
|
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);
|
|
|
|
// DSB board (if present)
|
|
if (Game->mpegBoard == 1) // Z80 board, do not byte swap program ROM
|
|
{
|
|
DSB = new(std::nothrow) CDSB1();
|
|
if (NULL == DSB)
|
|
return ErrorLog("Insufficient memory for Digital Sound Board object.");
|
|
if (OKAY != DSB->Init(dsbROM,mpegROM))
|
|
return FAIL;
|
|
}
|
|
else if (Game->mpegBoard == 2) // 68K board
|
|
{
|
|
Reverse16(dsbROM, 0x20000); // byte swap program ROM
|
|
DSB = new(std::nothrow) CDSB2();
|
|
if (NULL == DSB)
|
|
return ErrorLog("Insufficient memory for Digital Sound Board object.");
|
|
if (OKAY != DSB->Init(dsbROM,mpegROM))
|
|
return FAIL;
|
|
}
|
|
SoundBoard.AttachDSB(DSB);
|
|
|
|
// Drive board (if present)
|
|
if (Game->driveBoard)
|
|
{
|
|
// Was the optional drive board ROM loaded?
|
|
if (MAGIC_NUMBER != *(UINT64 *) driveROM) // magic number overwritten by ROM
|
|
{
|
|
if (DriveBoard.Init(driveROM))
|
|
return FAIL;
|
|
}
|
|
else
|
|
DriveBoard.Init(NULL);
|
|
}
|
|
else
|
|
DriveBoard.Init(NULL); // disable
|
|
|
|
// Apply ROM patches
|
|
Patch();
|
|
|
|
// Print game information
|
|
printf(" Title: %s\n", Game->title);
|
|
printf(" ROM Set: %s\n", Game->id);
|
|
printf(" Developer: %s\n", Game->mfgName);
|
|
printf(" Year: %d\n", Game->year);
|
|
printf(" Step: %d.%d\n", (Game->step>>4)&0xF, Game->step&0xF);
|
|
if (Game->mpegBoard)
|
|
{
|
|
printf(" Extra Hardware: Digital Sound Board (Type %d)", Game->mpegBoard);
|
|
if (Game->driveBoard)
|
|
printf(", Drive Board");
|
|
printf("\n");
|
|
}
|
|
else if (Game->driveBoard)
|
|
printf(" Extra Hardware: Drive Board\n");
|
|
printf("\n");
|
|
|
|
// Load patched CROM directly (for testing out ROM patches)
|
|
//char *fileName[4] = { "epr-20599a.patched.20","epr-20598a.patched.19","epr-20597a.patched.18","epr-20596a.patched.17" };
|
|
//ReadCROMDirectly(crom, fileName, 0x800000);
|
|
|
|
return OKAY;
|
|
}
|
|
|
|
void CModel3::AttachRenderers(CRender2D *Render2DPtr, CRender3D *Render3DPtr)
|
|
{
|
|
TileGen.AttachRenderer(Render2DPtr);
|
|
GPU.AttachRenderer(Render3DPtr);
|
|
}
|
|
|
|
void CModel3::AttachInputs(CInputs *InputsPtr)
|
|
{
|
|
Inputs = InputsPtr;
|
|
|
|
if (DriveBoard.IsAttached())
|
|
DriveBoard.AttachInputs(InputsPtr, Game->inputFlags);
|
|
|
|
DebugLog("Model 3 attached inputs\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);
|
|
|
|
// 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 and DSB 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(void)
|
|
{
|
|
// Initialize pointers so dtor can know whether to free them
|
|
memoryPool = NULL;
|
|
|
|
// Various uninitialized pointers
|
|
Game = 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;
|
|
pausedThreads = 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");
|
|
}
|
|
|
|
CModel3::~CModel3(void)
|
|
{
|
|
// Debug: dump some files
|
|
#if 0
|
|
//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);
|
|
#endif
|
|
|
|
// Stop all threads
|
|
StopThreads();
|
|
|
|
// Free memory
|
|
if (memoryPool != NULL)
|
|
{
|
|
delete [] memoryPool;
|
|
memoryPool = NULL;
|
|
}
|
|
|
|
if (DSB != NULL)
|
|
{
|
|
delete DSB;
|
|
DSB = NULL;
|
|
}
|
|
|
|
Game = 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");
|
|
} |