2011-04-24 01:14:00 +00:00
|
|
|
/**
|
|
|
|
** Supermodel
|
|
|
|
** A Sega Model 3 Arcade Emulator.
|
2016-04-02 21:50:40 +00:00
|
|
|
** Copyright 2011-2016 Bart Trzynadlowski, Nik Henson
|
2011-04-24 01:14:00 +00:00
|
|
|
**
|
|
|
|
** 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
|
|
|
|
* ----------
|
2012-07-12 06:13:24 +00:00
|
|
|
* - Save state format has changed slightly. No longer need dmaUnknownRegister
|
2016-04-02 21:50:40 +00:00
|
|
|
* in Real3D.cpp and security board-related variable was added to Model 3
|
|
|
|
* state. PowerPC timing variables have changed. Before 0.3a release,
|
|
|
|
* important to change format version #.
|
2017-08-29 01:28:21 +00:00
|
|
|
* - Remove FLIPENDIAN32() macros and have little endian devices flip data
|
|
|
|
* around themselves. Bus standard should be big endian.
|
2016-04-02 21:50:40 +00:00
|
|
|
* - ROM sets should probably be handled with a class that manages ROM
|
|
|
|
* loading, the game list, as well as ROM patching
|
|
|
|
* - Wrap up CPU emulation inside a class.
|
|
|
|
* - Update the to-do list! I forgot lots of other stuff here :)
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
|
|
|
* PowerPC Address Map (may be slightly out of date/incomplete)
|
|
|
|
* -------------------
|
2016-04-02 21:50:40 +00:00
|
|
|
* 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
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
|
|
|
* 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|
|
|
|
|
* +---+---+---+---+---+---+---+---+
|
2016-04-02 21:50:40 +00:00
|
|
|
* SND SCSP (sound)
|
|
|
|
* NET Network
|
|
|
|
* VD3 Unknown video-related
|
|
|
|
* VD2 Unknown video-related
|
|
|
|
* VBL VBlank start
|
|
|
|
* VD0 Unknown video-related (?)
|
|
|
|
* 0 = Disable, 1 = Enable
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
|
|
|
* Game Buttons
|
|
|
|
* ------------
|
|
|
|
*
|
|
|
|
* For further information, see ReadInputs().
|
|
|
|
*
|
|
|
|
* Offset 0x04, bank 0:
|
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* 7 6 5 4 3 2 1 0
|
|
|
|
* +---+---+---+---+---+---+---+---+
|
2011-04-24 01:14:00 +00:00
|
|
|
* | ? | ? |ST2|ST1|SVA|TSA|CN2|CN1|
|
2016-04-02 21:50:40 +00:00
|
|
|
* +---+---+---+---+---+---+---+---+
|
|
|
|
* CNx Coin 1, Coin 2
|
|
|
|
* TSA Test Button A
|
|
|
|
* SVA Service Button A
|
|
|
|
* STx Start 1, Start 2
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
|
|
|
* Offset 0x04, bank 1:
|
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* 7 6 5 4 3 2 1 0
|
|
|
|
* +---+---+---+---+---+---+---+---+
|
2011-04-24 01:14:00 +00:00
|
|
|
* |TSB|SVB|EEP| ? | ? | ? | ? | ? |
|
2016-04-02 21:50:40 +00:00
|
|
|
* +---+---+---+---+---+---+---+---+
|
|
|
|
* EEP Mapped to EEPROM (values written here are ignored)
|
|
|
|
* SVB Service Button B
|
|
|
|
* TSB Test Button B
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
|
|
|
* Offset 0x08:
|
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* 7 6 5 4 3 2 1 0
|
|
|
|
* +---+---+---+---+---+---+---+---+
|
2011-04-24 01:14:00 +00:00
|
|
|
* |G27|G26|G25|G24|G23|G22|G21|G20|
|
2016-04-02 21:50:40 +00:00
|
|
|
* +---+---+---+---+---+---+---+---+
|
|
|
|
* G2x Game-specific
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
|
|
|
* Offset 0x0C:
|
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* 7 6 5 4 3 2 1 0
|
|
|
|
* +---+---+---+---+---+---+---+---+
|
2011-04-24 01:14:00 +00:00
|
|
|
* |G37|G36|G35|G34|G33|G32|G31|G30|
|
2016-04-02 21:50:40 +00:00
|
|
|
* +---+---+---+---+---+---+---+---+
|
|
|
|
* G3x Game-specific
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
|
|
|
* Game-specific buttons:
|
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* 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
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* Virtua Fighter 3, Fighting Vipers 2:
|
|
|
|
* G27 Right
|
|
|
|
* G26 Left
|
|
|
|
* G25 Down
|
|
|
|
* G24 Up
|
|
|
|
* G23 Punch
|
|
|
|
* G22 Kick
|
|
|
|
* G21 Guard
|
|
|
|
* G20 Escape (VF3 only)
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* Sega Rally 2:
|
|
|
|
* G21 Hand Brake
|
|
|
|
* G20 View Change
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* Lost World, LA Machineguns:
|
|
|
|
* G20 Gun trigger
|
2011-09-12 05:43:37 +00:00
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* Star Wars Trilogy:
|
|
|
|
* G20 Event button
|
|
|
|
* G25 Trigger
|
2011-04-24 01:14:00 +00:00
|
|
|
*
|
2016-04-02 21:50:40 +00:00
|
|
|
* Virtual On 2:
|
|
|
|
* G27 Left Lever Left
|
|
|
|
* G26 Left Lever Right
|
|
|
|
* G25 Left Lever Up
|
|
|
|
* G24 Left Lever Down
|
|
|
|
* G23 ---
|
|
|
|
* G22 ---
|
|
|
|
* G21 Left Turbo
|
|
|
|
* G20 Left Shot Trigger
|
|
|
|
* G37 Right Lever Left
|
|
|
|
* G36 Right Lever Right
|
|
|
|
* G35 Right Lever Up
|
|
|
|
* G34 Right Lever Down
|
|
|
|
* G33 ---
|
|
|
|
* G32 ---
|
|
|
|
* G31 Right Turbo
|
|
|
|
* G30 Right Shot Trigger
|
|
|
|
*
|
|
|
|
* Misc. Notes
|
|
|
|
* -----------
|
|
|
|
*
|
|
|
|
* daytona2:
|
|
|
|
* - Base address of program in CROM: 0x600000
|
|
|
|
* - 0x10019E is the location in RAM which contains link type.
|
|
|
|
* - Region menu can be accessed by entering test mode, holding start, and
|
|
|
|
* pressing: green, green, blue, yellow, red, yellow, blue
|
|
|
|
* (VR4,4,2,3,1,3,2).
|
2011-04-24 01:14:00 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <new>
|
2011-08-19 20:43:07 +00:00
|
|
|
#include <cstdio>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
2011-04-24 01:14:00 +00:00
|
|
|
#include "Supermodel.h"
|
2017-03-27 03:19:15 +00:00
|
|
|
#include "Game.h"
|
|
|
|
#include "ROMSet.h"
|
2016-04-02 21:50:40 +00:00
|
|
|
#include "Util/Format.h"
|
2017-03-27 03:19:15 +00:00
|
|
|
#include "Util/ByteSwap.h"
|
2016-04-02 21:50:40 +00:00
|
|
|
#include <functional>
|
|
|
|
#include <set>
|
2017-03-27 03:19:15 +00:00
|
|
|
#include <iostream>
|
2011-04-24 01:14:00 +00:00
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
Model 3 Inputs
|
|
|
|
|
|
|
|
Game controls. The EEPROM is mapped here as well.
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
UINT8 CModel3::ReadInputs(unsigned reg)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_SKI))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
if ((inputBank&1) == 0)
|
|
|
|
{
|
|
|
|
data &= ~(Inputs->skiPollLeft->value<<5);
|
|
|
|
data &= ~(Inputs->skiSelect1->value<<6);
|
|
|
|
data &= ~(Inputs->skiSelect2->value<<7);
|
|
|
|
data &= ~(Inputs->skiSelect3->value<<4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
|
|
|
case 0x08: // game-specific inputs
|
|
|
|
|
|
|
|
data = 0xFF;
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_SKI))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
data &= ~(Inputs->skiPollRight->value<<0);
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_JOYSTICK1))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_FIGHTING))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_SPIKEOUT))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
data &= ~(Inputs->shift->value<<2); // Shift
|
|
|
|
data &= ~(Inputs->beat->value<<0); // Beat
|
|
|
|
data &= ~(Inputs->charge->value<<1); // Charge
|
|
|
|
data &= ~(Inputs->jump->value<<3); // Jump
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_SOCCER))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_VR4))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_VIEWCHANGE))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
// Harley is wired slightly differently
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_HARLEY))
|
2016-04-02 21:50:40 +00:00
|
|
|
data &= ~(Inputs->viewChange->value<<1); // View change
|
|
|
|
else
|
|
|
|
data &= ~(Inputs->viewChange->value<<0); // View change
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_SHIFT4))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_SHIFTUPDOWN))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
// Harley is wired slightly differently
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_HARLEY))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
if (Inputs->gearShiftUp->value) // Shift up
|
2017-04-07 04:30:27 +00:00
|
|
|
data &= ~0x20;
|
2016-04-02 21:50:40 +00:00
|
|
|
else if (Inputs->gearShiftDown->value) // Shift down
|
2017-04-07 04:30:27 +00:00
|
|
|
data &= ~0x10;
|
2016-04-02 21:50:40 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Inputs->gearShiftUp->value) // Shift up
|
|
|
|
data &= ~0x50;
|
|
|
|
else if (Inputs->gearShiftDown->value) // Shift down
|
|
|
|
data &= ~0x60;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_HANDBRAKE))
|
2016-04-02 21:50:40 +00:00
|
|
|
data &= ~(Inputs->handBrake->value<<1); // Hand brake
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_HARLEY))
|
2016-04-02 21:50:40 +00:00
|
|
|
data &= ~(Inputs->musicSelect->value<<0); // Music select
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_GUN1))
|
2016-04-02 21:50:40 +00:00
|
|
|
data &= ~(Inputs->trigger[0]->value<<0); // P1 Trigger
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_JOYSTICK))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
data &= ~(Inputs->analogJoyTrigger1->value<<5); // Trigger 1
|
|
|
|
data &= ~(Inputs->analogJoyTrigger2->value<<4); // Trigger 2
|
|
|
|
data &= ~(Inputs->analogJoyEvent1->value<<0); // Event Button 1
|
|
|
|
data &= ~(Inputs->analogJoyEvent2->value<<1); // Event Button 2
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_TWIN_JOYSTICKS)) // First twin joystick
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_GUN1))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
data &= ~(Inputs->analogTriggerLeft[0]->value<<0);
|
|
|
|
data &= ~(Inputs->analogTriggerRight[0]->value<<1);
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_MAGTRUCK))
|
2016-04-13 03:28:04 +00:00
|
|
|
data &= ~(Inputs->magicalPedal1->value << 0);
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_FISHING))
|
2016-05-26 04:13:22 +00:00
|
|
|
{
|
|
|
|
data &= ~(Inputs->fishingCast->value << 0);
|
|
|
|
data &= ~(Inputs->fishingSelect->value << 1);
|
|
|
|
}
|
2016-04-02 21:50:40 +00:00
|
|
|
return data;
|
|
|
|
|
|
|
|
case 0x0C: // game-specific inputs
|
|
|
|
|
|
|
|
data = 0xFF;
|
|
|
|
|
|
|
|
if (DriveBoard.IsAttached())
|
|
|
|
data = DriveBoard.Read();
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_JOYSTICK2))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_FIGHTING))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_SOCCER))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_TWIN_JOYSTICKS)) // Second twin joystick (see register 0x08 for comments)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_GUN2))
|
2016-04-02 21:50:40 +00:00
|
|
|
data &= ~(Inputs->trigger[1]->value<<0); // P2 Trigger
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_GUN2))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
data &= ~(Inputs->analogTriggerLeft[1]->value<<0);
|
|
|
|
data &= ~(Inputs->analogTriggerRight[1]->value<<1);
|
|
|
|
}
|
2016-04-13 03:28:04 +00:00
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_MAGTRUCK))
|
2016-04-13 03:28:04 +00:00
|
|
|
data &= ~(Inputs->magicalPedal2->value << 0);
|
2016-05-26 04:13:22 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
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));
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_VEHICLE))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
adc[0] = (UINT8)Inputs->steering->value;
|
|
|
|
adc[1] = (UINT8)Inputs->accelerator->value;
|
|
|
|
adc[2] = (UINT8)Inputs->brake->value;
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_HARLEY))
|
2016-04-02 21:50:40 +00:00
|
|
|
adc[3] = (UINT8)Inputs->rearBrake->value;
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_JOYSTICK))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
adc[0] = (UINT8)Inputs->analogJoyY->value;
|
|
|
|
adc[1] = (UINT8)Inputs->analogJoyX->value;
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_ANALOG_GUN1)||(m_game.inputs & Game::INPUT_ANALOG_GUN2))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
adc[0] = (UINT8)Inputs->analogGunX[0]->value;
|
|
|
|
adc[2] = (UINT8)Inputs->analogGunY[0]->value;
|
|
|
|
adc[1] = (UINT8)Inputs->analogGunX[1]->value;
|
|
|
|
adc[3] = (UINT8)Inputs->analogGunY[1]->value;
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_SKI))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
adc[0] = (UINT8)Inputs->skiY->value;
|
|
|
|
adc[1] = (UINT8)Inputs->skiX->value;
|
|
|
|
}
|
2016-04-13 03:28:04 +00:00
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_MAGTRUCK))
|
2016-04-13 03:28:04 +00:00
|
|
|
{
|
|
|
|
adc[0] = uint8_t(Inputs->magicalLever1->value);
|
|
|
|
adc[1] = uint8_t(Inputs->magicalLever2->value);
|
|
|
|
}
|
2016-04-02 21:50:40 +00:00
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_FISHING))
|
2016-05-26 04:13:22 +00:00
|
|
|
{
|
|
|
|
adc[0] = uint8_t(Inputs->fishingRodY->value);
|
|
|
|
adc[1] = uint8_t(Inputs->fishingRodX->value);
|
|
|
|
adc[3] = uint8_t(Inputs->fishingReel->value);
|
|
|
|
adc[5] = uint8_t(Inputs->fishingStickX->value);
|
|
|
|
adc[4] = uint8_t(Inputs->fishingStickY->value);
|
|
|
|
}
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Read out appropriate channel
|
|
|
|
data = adc[adcChannel&7];
|
|
|
|
++adcChannel;
|
|
|
|
return data;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0xFF; // controls are active low
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::WriteInputs(unsigned reg, UINT8 data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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);
|
2016-06-07 01:51:16 +00:00
|
|
|
if (NULL != Outputs) // TODO - check gameInputs
|
|
|
|
Outputs->SetValue(OutputRawDrive, data);
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x14: // Lamp outputs (Daytona/Scud Race/Sega Rally/Le Mans 24)
|
|
|
|
if (NULL != Outputs) // TODO - check gameInputs
|
|
|
|
{
|
|
|
|
Outputs->SetValue(OutputLampStart, !!(data&0x04));
|
|
|
|
Outputs->SetValue(OutputLampView1, !!(data&0x08));
|
|
|
|
Outputs->SetValue(OutputLampView2, !!(data&0x10));
|
|
|
|
Outputs->SetValue(OutputLampView3, !!(data&0x20));
|
|
|
|
Outputs->SetValue(OutputLampView4, !!(data&0x40));
|
|
|
|
Outputs->SetValue(OutputLampLeader, !!(data&0x80));
|
2016-06-07 01:51:16 +00:00
|
|
|
Outputs->SetValue(OutputRawLamps, data);
|
2016-04-02 21:50:40 +00:00
|
|
|
}
|
|
|
|
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;
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_game.inputs & Game::INPUT_GUN1||m_game.inputs & Game::INPUT_GUN2))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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);
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
Model 3 Security Device
|
|
|
|
|
2011-07-20 06:20:40 +00:00
|
|
|
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.
|
2011-04-24 01:14:00 +00:00
|
|
|
******************************************************************************/
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
uint16_t CModel3::ReadSecurityRAM(uint32_t addr)
|
|
|
|
{
|
|
|
|
if (addr < 0x8000)
|
|
|
|
return (*(uint32_t *) &securityRAM[addr * 4]) >> 16;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-04-24 01:14:00 +00:00
|
|
|
UINT32 CModel3::ReadSecurity(unsigned reg)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
switch (reg)
|
|
|
|
{
|
|
|
|
case 0x00: // Status
|
|
|
|
return 0;
|
|
|
|
case 0x1C: // Data
|
|
|
|
if (m_securityFirstRead)
|
|
|
|
{
|
|
|
|
m_securityFirstRead = false;
|
|
|
|
return 0xFFFF0000;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
uint8_t *base_ptr;
|
|
|
|
return m_cryptoDevice.Decrypt(&base_ptr) << 16;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
DebugLog("Security read: reg=%X\n", reg);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0xFFFFFFFF;
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::WriteSecurity(unsigned reg, UINT32 data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
switch (reg)
|
|
|
|
{
|
|
|
|
case 0x10:
|
|
|
|
case 0x14:
|
|
|
|
m_cryptoDevice.SetAddressLow(0);
|
|
|
|
m_cryptoDevice.SetAddressHigh(0);
|
|
|
|
m_securityFirstRead = true;
|
|
|
|
break;
|
|
|
|
case 0x18:
|
|
|
|
{
|
|
|
|
uint16_t subKey = data >> 16;
|
|
|
|
subKey = ((subKey & 0xFF00) >> 8) | ((subKey & 0x00FF) << 8);
|
|
|
|
m_cryptoDevice.SetSubKey(subKey);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
DebugLog("Security write: reg=%X, data=%08X (PC=%08X, LR=%08X)\n", reg, data, ppc_get_pc(), ppc_get_lr());
|
|
|
|
break;
|
|
|
|
}
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
PCI Devices
|
|
|
|
|
|
|
|
Unknown PCI devices are handled here.
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
UINT32 CModel3::ReadPCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2011-04-24 01:14:00 +00:00
|
|
|
void CModel3::WritePCIConfigSpace(unsigned device, unsigned reg, unsigned bits, unsigned offset, UINT32 data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
DebugLog("Model 3: PCI %d-bit write request for device=%d, reg=%02X, data=%08X\n", bits, device, reg, data);
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
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)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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());
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
UINT8 CModel3::ReadSystemRegister(unsigned reg)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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
|
2017-09-24 20:52:48 +00:00
|
|
|
return m_jtag.Read() << 5;
|
2016-04-02 21:50:40 +00:00
|
|
|
default:
|
|
|
|
//DebugLog("System register %02X read\n", reg);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0xFF;
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::WriteSystemRegister(unsigned reg, UINT8 data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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
|
2017-08-29 01:28:21 +00:00
|
|
|
IRQ.Deassert(data);
|
2016-05-10 03:08:03 +00:00
|
|
|
DebugLog("IRQ ACK? %02X=%02X\n", reg, data);
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
case 0x0C: // JTAG Test Access Port
|
2017-09-24 20:52:48 +00:00
|
|
|
{
|
|
|
|
uint8_t tck = (data >> 6) & 1;
|
|
|
|
uint8_t tms = (data >> 2) & 1;
|
|
|
|
uint8_t tdi = (data >> 5) & 1;
|
|
|
|
uint8_t trst = (data >> 7) & 1; // not sure about this one (trst not required to exist by JTAG spec)
|
|
|
|
m_jtag.Write(tck, tms, tdi, trst);
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
2017-09-24 20:52:48 +00:00
|
|
|
}
|
2016-04-02 21:50:40 +00:00
|
|
|
case 0x0D:
|
|
|
|
case 0x0E:
|
|
|
|
case 0x0F:
|
|
|
|
case 0x1C: // LED control?
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//DebugLog("System register %02X=%02X\n", reg, data);
|
|
|
|
break;
|
|
|
|
}
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
2017-03-23 04:22:25 +00:00
|
|
|
Address Space Access Handlers
|
2011-09-12 05:43:37 +00:00
|
|
|
|
|
|
|
NOTE: Testing of some of the address ranges is not strict enough, especially
|
2017-03-23 04:22:25 +00:00
|
|
|
for the MPC10x. Write32() handles the MPC10x most correctly.
|
2011-04-24 01:14:00 +00:00
|
|
|
******************************************************************************/
|
2011-09-12 05:43:37 +00:00
|
|
|
|
2011-04-24 01:14:00 +00:00
|
|
|
/*
|
|
|
|
* CModel3::Read8(addr):
|
|
|
|
* CModel3::Read16(addr):
|
|
|
|
* CModel3::Read32(addr):
|
|
|
|
* CModel3::Read64(addr):
|
|
|
|
*
|
|
|
|
* Read handlers.
|
|
|
|
*/
|
2011-09-12 05:43:37 +00:00
|
|
|
UINT8 CModel3::Read8(UINT32 addr)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// RAM (most frequently accessed)
|
|
|
|
if (addr<0x00800000)
|
|
|
|
return ram[addr^3];
|
|
|
|
|
|
|
|
// Other
|
2018-01-07 14:07:59 +00:00
|
|
|
switch ((addr >> 24))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
2018-01-07 14:07:59 +00:00
|
|
|
// CROM
|
2016-04-02 21:50:40 +00:00
|
|
|
case 0xFF:
|
2018-01-07 14:07:59 +00:00
|
|
|
if (addr < 0xFF800000)
|
|
|
|
return cromBank[(addr & 0x7FFFFF) ^ 3];
|
|
|
|
else
|
|
|
|
return crom[(addr & 0x7FFFFF) ^ 3];
|
2016-04-02 21:50:40 +00:00
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
// Real3D DMA
|
2016-04-02 21:50:40 +00:00
|
|
|
case 0xC2:
|
2018-01-07 14:07:59 +00:00
|
|
|
return GPU.ReadDMARegister8(addr & 0xFF);
|
2016-04-02 21:50:40 +00:00
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
// Various
|
2016-04-02 21:50:40 +00:00
|
|
|
case 0xF0:
|
|
|
|
case 0xFE: // mirror
|
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
switch ((addr >> 16) & 0xFF)
|
|
|
|
{
|
|
|
|
// Inputs
|
|
|
|
case 0x04:
|
|
|
|
return ReadInputs(addr & 0x3F);
|
|
|
|
|
|
|
|
// Sound Board
|
|
|
|
case 0x08:
|
|
|
|
if ((addr & 0xF) == 4) // MIDI control port
|
|
|
|
return 0x83; // magtruck country check
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// System registers
|
|
|
|
case 0x10:
|
|
|
|
return ReadSystemRegister(addr & 0x3F);
|
|
|
|
|
|
|
|
// RTC
|
|
|
|
case 0x14:
|
|
|
|
if ((addr & 3) == 1) // battery voltage test
|
|
|
|
return 0x03;
|
|
|
|
else if ((addr & 3) == 0)
|
|
|
|
return RTC.ReadRegister((addr >> 2) & 0xF);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Unknown
|
|
|
|
default:
|
|
|
|
//printf("CMODEL3 : unknown R8 mirror : %x\n", addr >> 16);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Tile generator
|
2017-03-23 04:22:25 +00:00
|
|
|
case 0xF1:
|
2018-01-07 14:07:59 +00:00
|
|
|
if (addr < 0xF1120000)
|
|
|
|
{
|
|
|
|
// Tile generator accesses its RAM as little endian, no adjustment needed here
|
|
|
|
return TileGen.ReadRAM8(addr & 0x1FFFFF);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// 53C810 SCSI
|
2016-04-02 21:50:40 +00:00
|
|
|
case 0xC0: // only on Step 1.0
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifndef NET_BOARD
|
|
|
|
if (m_game.stepping != "1.0")
|
|
|
|
{
|
|
|
|
//printf("Model3 : Read8 %x\n", addr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef NET_BOARD
|
|
|
|
switch ((addr & 0x3ffff) >> 16)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
//printf("R8 netbuffer @%x=%x\n", (addr & 0xFFFF), netBuffer[(addr & 0xFFFF)]);
|
|
|
|
return netBuffer[(addr & 0xFFFF)];
|
|
|
|
|
|
|
|
case 1: // ioreg 32bits access in 16bits environment
|
|
|
|
if (addr > 0xc00101ff)
|
|
|
|
{
|
|
|
|
printf("R8 ATTENTION OUT OF RANGE\n");
|
|
|
|
MessageBox(NULL, "Out of Range", NULL, MB_OK);
|
|
|
|
}
|
|
|
|
printf("R8 ioreg @%x=%x\n", (addr & 0x1FF), netBuffer[0x10000 + ((addr & 0x1FF) / 2)]);
|
|
|
|
return netBuffer[0x10000 + ((addr & 0x1FF) / 2)];
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
case 3:
|
|
|
|
if (addr > 0xc002ffff)
|
|
|
|
{
|
|
|
|
printf("R8 ATTENTION OUT OF RANGE\n");
|
|
|
|
MessageBox(NULL, "Out of Range", NULL, MB_OK);
|
|
|
|
}
|
|
|
|
//printf("R8 netram @%x=%x\n", (addr & 0x1FFFF), netRAM[addr & 0x1ffff]);
|
|
|
|
return netRAM[((addr & 0x1FFFF) / 2)];
|
|
|
|
/*case 3:
|
|
|
|
//printf("R8 netram @%x=%x\n", (addr & 0x1FFFF), netRAM[addr & 0x1ffff]);
|
|
|
|
return netRAM[((addr & 0x1FFFF) / 2)];*/
|
|
|
|
|
|
|
|
default:
|
|
|
|
printf("R8 ATTENTION OUT OF RANGE\n");
|
|
|
|
MessageBox(NULL, "Out of Range", NULL, MB_OK);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
case 0xF9:
|
|
|
|
case 0xC1:
|
|
|
|
return SCSI.ReadRegister(addr&0xFF);
|
|
|
|
|
|
|
|
// Unknown
|
|
|
|
default:
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
printf("CMODEL3 : unknown R8 : %x\n", addr >> 24);
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DebugLog("PC=%08X\tread8 : %08X\n", ppc_get_pc(), addr);
|
|
|
|
return 0xFF;
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
2011-09-12 05:43:37 +00:00
|
|
|
UINT16 CModel3::Read16(UINT32 addr)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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:
|
2018-01-07 14:07:59 +00:00
|
|
|
//printf("CMODEL3 : unknown R16 mirror : %x\n", addr >> 16);
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
2017-03-23 04:22:25 +00:00
|
|
|
// Tile generator
|
|
|
|
case 0xF1:
|
|
|
|
if (addr < 0xF1120000)
|
|
|
|
{
|
|
|
|
// Tile generator accesses its RAM as little endian, no adjustment needed here
|
|
|
|
uint16_t data = TileGen.ReadRAM16(addr&0x1FFFFF);
|
|
|
|
return FLIPENDIAN16(data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
case 0xc0: // spikeout call this
|
|
|
|
{
|
|
|
|
UINT16 result;
|
|
|
|
switch ((addr & 0x3ffff) >> 16)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
printf("R16 netbuffer @%x=%x\n", (addr & 0xFFFF), FLIPENDIAN16(*(UINT16 *)&netBuffer[(addr & 0xFFFF)]));
|
|
|
|
result = *(UINT16 *)&netBuffer[(addr & 0xFFFF)];
|
|
|
|
return FLIPENDIAN16(result); // result
|
|
|
|
default:
|
|
|
|
printf("CMODEL3 : unknown R16 : %x (C0)\n", addr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
// Unknown
|
|
|
|
default:
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
printf("CMODEL3 : unknown R16 : %x (%x)\n", addr, addr >> 24);
|
|
|
|
MessageBox(NULL, "CMODEL3 : Unknown R16", NULL, MB_OK);
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DebugLog("PC=%08X\tread16: %08X\n", ppc_get_pc(), addr);
|
|
|
|
return 0xFFFF;
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
2011-09-12 05:43:37 +00:00
|
|
|
|
|
|
|
UINT32 CModel3::Read32(UINT32 addr)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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:
|
2017-08-29 01:28:21 +00:00
|
|
|
data = GPU.ReadRegister(addr&0x3F);
|
|
|
|
return FLIPENDIAN32(data);
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
// 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:
|
2018-01-07 14:07:59 +00:00
|
|
|
//printf("CModel 3 unknown R32 mirror %x", (addr >> 16) & 0xFF);
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Tile generator
|
|
|
|
case 0xF1:
|
|
|
|
// Tile generator accesses its RAM as little endian, must flip for big endian PowerPC
|
|
|
|
if (addr < 0xF1120000)
|
|
|
|
{
|
2017-03-23 04:22:25 +00:00
|
|
|
data = TileGen.ReadRAM32(addr&0x1FFFFF);
|
2016-04-02 21:50:40 +00:00
|
|
|
return FLIPENDIAN32(data);
|
|
|
|
}
|
2016-05-08 20:30:45 +00:00
|
|
|
else if ((addr>=0xF1180000) && (addr<0xF1180100))
|
|
|
|
{
|
|
|
|
data = TileGen.ReadRegister(addr & 0xFF);
|
|
|
|
return FLIPENDIAN32(data);
|
|
|
|
}
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
// 53C810 SCSI
|
|
|
|
case 0xC0: // only on Step 1.0
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifndef NET_BOARD
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_game.stepping != "1.0") // check for Step 1.0
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
2018-01-07 14:07:59 +00:00
|
|
|
#endif
|
|
|
|
#ifdef NET_BOARD
|
|
|
|
if (m_game.stepping != "1.0") // check for Step 1.0
|
|
|
|
{
|
|
|
|
UINT32 result;
|
|
|
|
|
|
|
|
switch ((addr & 0x3ffff) >> 16)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
//printf("R32 netbuffer @%x=%x\n", (addr & 0xFFFF), FLIPENDIAN32(*(UINT32 *)&netBuffer[(addr & 0xFFFF)]));
|
|
|
|
result = *(UINT32 *)&netBuffer[(addr & 0xFFFF)];
|
|
|
|
return FLIPENDIAN32(result); // result
|
|
|
|
|
|
|
|
case 1: // ioreg 32bits access to 16bits range
|
|
|
|
//printf("R32 ioreg @%x=%x\n", (addr & 0x1FF), FLIPENDIAN32(*(UINT32 *)&netBuffer[0x10000 + ((addr & 0x1FF) / 2)]));
|
|
|
|
if (addr > 0xc00101ff)
|
|
|
|
{
|
|
|
|
printf("R32 ATTENTION OUT OF RANGE\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT32 test;
|
|
|
|
test = (*(UINT32 *)&netBuffer[0x10000 + ((addr & 0x1FF) / 2)]);
|
|
|
|
if (((FLIPENDIAN32(test) & 0x00ff0000) != 0x00900000) && ((FLIPENDIAN32(test) & 0x00ff0000) != 0x00a00000) && ((FLIPENDIAN32(test) & 0x00ff0000) != 0x00b00000) && ((FLIPENDIAN32(test) & 0x00ff0000) != 0x00800000) && ((FLIPENDIAN32(test) & 0x00ff0000) != 0x00f00000))
|
|
|
|
{
|
|
|
|
printf("R32 ioreg @%x=%04x\n", (addr /*& 0x1FF*/), FLIPENDIAN32(test) >> 16);
|
|
|
|
}
|
|
|
|
result = (*(UINT32 *)&netBuffer[0x10000 + ((addr & 0x1FF) / 2)]) & 0x0000ffff;
|
|
|
|
return FLIPENDIAN32(result);
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
case 3:
|
|
|
|
//printf("R32 netram @%x=%x\n", (addr & 0x1FFFF), FLIPENDIAN32(*(UINT32 *)&netBuffer[(addr & 0x1FFFF)]));
|
|
|
|
|
|
|
|
if (addr > 0xc002ffff)
|
|
|
|
{
|
|
|
|
printf("R32 ATTENTION OUT OF RANGE\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
result = (*(UINT32 *)&netRAM[((addr & 0x1FFFF) / 2)]) & 0x0000ffff;
|
|
|
|
return FLIPENDIAN32(result); // result
|
|
|
|
/*case 3:
|
|
|
|
//printf("R32 netram @%x=%x\n", (addr & 0x1FFFF), FLIPENDIAN32(*(UINT32 *)&netBuffer[(addr & 0x1FFFF)]));
|
|
|
|
result = (*(UINT32 *)&netRAM[((addr & 0x1FFFF) / 2)]) & 0x0000ffff;
|
|
|
|
return FLIPENDIAN32(result); // result*/
|
|
|
|
|
|
|
|
default:
|
|
|
|
printf("R32 ATTENTION OUT OF RANGE\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
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:
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
printf("CMODEL3 : unknown R32 : %x\n", addr >> 24);
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DebugLog("PC=%08X\tread32: %08X\n", ppc_get_pc(), addr);
|
|
|
|
return 0xFFFFFFFF;
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
|
|
|
|
2011-04-24 01:14:00 +00:00
|
|
|
UINT64 CModel3::Read64(UINT32 addr)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
UINT64 data;
|
2011-04-24 01:14:00 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
data = Read32(addr+0);
|
|
|
|
data <<= 32;
|
|
|
|
data |= Read32(addr+4);
|
2018-01-07 14:07:59 +00:00
|
|
|
//printf("read64 %x = %x\n",addr,data);
|
2016-04-02 21:50:40 +00:00
|
|
|
return data;
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CModel3::Write8(addr, data):
|
|
|
|
* CModel3::Write16(addr, data):
|
|
|
|
* CModel3::Write32(addr, data):
|
|
|
|
* CModel3::Write64(addr, data):
|
|
|
|
*
|
|
|
|
* Write handlers.
|
|
|
|
*/
|
2011-09-12 05:43:37 +00:00
|
|
|
void CModel3::Write8(UINT32 addr, UINT8 data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// 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:
|
2018-01-07 14:07:59 +00:00
|
|
|
//printf("CMODEL3 : unknown W8 mirror : %x\n", addr >> 16);
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data);
|
|
|
|
break;
|
|
|
|
|
2017-03-23 04:22:25 +00:00
|
|
|
// Tile generator
|
|
|
|
case 0xF1:
|
|
|
|
if (addr < 0xF1120000)
|
|
|
|
{
|
|
|
|
// Tile generator accesses its RAM as little endian, no adjustment needed here
|
|
|
|
TileGen.WriteRAM8(addr&0x1FFFFF, data);
|
|
|
|
break;
|
|
|
|
}
|
2018-01-07 14:07:59 +00:00
|
|
|
goto Unknown8;
|
2017-03-23 04:22:25 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// MPC105/106
|
|
|
|
case 0xF8:
|
|
|
|
PCIBridge.WriteRegister(addr&0xFF,data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// 53C810 SCSI
|
|
|
|
case 0xC0: // only on Step 1.0
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifndef NET_BOARD
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_game.stepping != "1.0")
|
2016-04-02 21:50:40 +00:00
|
|
|
goto Unknown8;
|
2018-01-07 14:07:59 +00:00
|
|
|
#endif
|
|
|
|
#ifdef NET_BOARD
|
|
|
|
if (m_game.stepping != "1.0")
|
|
|
|
{
|
|
|
|
printf("CModel 3 : write8 %x<-%x\n", addr, data);
|
|
|
|
|
|
|
|
switch ((addr & 0x3ffff) >> 16)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
//printf("W8 netbuffer @%x<-%x\n", (addr & 0xFFFF), data);
|
|
|
|
*(UINT8 *)&netBuffer[(addr & 0xFFFF)] = data;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1: // ioreg 32bits access to 16bits range
|
|
|
|
if (addr > 0xc00101ff)
|
|
|
|
{
|
|
|
|
printf("W8 ATTENTION OUT OF RANGE\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("W8 ioreg @%x<-%x\n", (addr & 0x1FF), data);
|
|
|
|
*(UINT8 *)&netBuffer[0x10000 + ((addr & 0x1FF) / 2)] = data;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
case 3:
|
|
|
|
if (addr > 0xc002ffff)
|
|
|
|
{
|
|
|
|
printf("W8 ATTENTION OUT OF RANGE\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
//printf("W8 netram @%x<-%x\n", (addr & 0x1FFFF), data);
|
|
|
|
*(UINT8 *)&netRAM[(addr & 0x1FFFF)/2] = data;
|
|
|
|
break;
|
|
|
|
/*case 3:
|
|
|
|
//printf("W8 netram @%x<-%x\n", (addr & 0x1FFFF), data);
|
|
|
|
*(UINT8 *)&netRAM[(addr & 0x1FFFF) / 2] = data;
|
|
|
|
break;*/
|
|
|
|
|
|
|
|
default:
|
|
|
|
printf("W8 ATTENTION OUT OF RANGE\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((*(UINT8 *)&netBuffer[(0xc00100c0 & 0x3FFFF)] == 0xff) && NetBoard.CodeReady == false) // c0=180/2
|
|
|
|
{
|
|
|
|
printf("Network code copy ending\n");
|
|
|
|
NetBoard.CodeReady = true;
|
|
|
|
NetBoard.Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
case 0xF9:
|
|
|
|
case 0xC1:
|
|
|
|
SCSI.WriteRegister(addr&0xFF,data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Unknown:
|
|
|
|
default:
|
|
|
|
Unknown8:
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
//printf("CMODEL3 : unknown W8 : %x\n", addr >> 24); // harleyb unknown 0xF1
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
DebugLog("PC=%08X\twrite8 : %08X=%02X\n", ppc_get_pc(), addr, data);
|
|
|
|
break;
|
|
|
|
}
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
2011-09-12 05:43:37 +00:00
|
|
|
|
|
|
|
void CModel3::Write16(UINT32 addr, UINT16 data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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:
|
2018-01-07 14:07:59 +00:00
|
|
|
//printf("CMODEL3 : unknown W16 mirror : %x\n", addr >> 16);
|
2016-04-02 21:50:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DebugLog("PC=%08X\twrite16 : %08X=%04X\n", ppc_get_pc(), addr, data);
|
|
|
|
break;
|
|
|
|
|
2017-03-23 04:22:25 +00:00
|
|
|
// Tile generator
|
|
|
|
case 0xF1:
|
|
|
|
if (addr < 0xF1120000)
|
|
|
|
{
|
|
|
|
// Tile generator accesses its RAM as little endian, no adjustment needed here
|
|
|
|
TileGen.WriteRAM16(addr&0x1FFFFF, FLIPENDIAN16(data));
|
|
|
|
}
|
|
|
|
goto Unknown16;
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// 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:
|
2017-03-23 04:22:25 +00:00
|
|
|
Unknown16:
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
printf("CMODEL3 : unknown W16 : %x\n", addr >> 24);
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
DebugLog("PC=%08X\twrite16: %08X=%04X\n", ppc_get_pc(), addr, data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-09-12 05:43:37 +00:00
|
|
|
|
|
|
|
void CModel3::Write32(UINT32 addr, UINT32 data)
|
2011-04-24 01:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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
|
2016-04-10 23:57:55 +00:00
|
|
|
case 0x90: // 90000000-90??????
|
2016-04-02 21:50:40 +00:00
|
|
|
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;
|
|
|
|
|
2016-05-26 04:13:22 +00:00
|
|
|
// Real3D configuration registers
|
|
|
|
case 0x9C: // 9Cxxxxxx
|
|
|
|
//printf("%08X=%08X\n", addr, data); //TODO: flip endian?
|
|
|
|
break;
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// 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
|
2018-01-07 14:07:59 +00:00
|
|
|
case 0xC0: case 0xD0: case 0xE0:
|
2016-04-02 21:50:40 +00:00
|
|
|
case 0xC1: case 0xD1: case 0xE1:
|
|
|
|
case 0xC2: case 0xD2: case 0xE2:
|
|
|
|
case 0xC3: case 0xD3: case 0xE3:
|
|
|
|
case 0xC4: case 0xD4: case 0xE4:
|
|
|
|
case 0xC5: case 0xD5: case 0xE5:
|
|
|
|
case 0xC6: case 0xD6: case 0xE6:
|
|
|
|
case 0xC7: case 0xD7: case 0xE7:
|
|
|
|
case 0xC8: case 0xD8: case 0xE8:
|
|
|
|
case 0xC9: case 0xD9: case 0xE9:
|
|
|
|
case 0xCA: case 0xDA: case 0xEA:
|
|
|
|
case 0xCB: case 0xDB: case 0xEB:
|
|
|
|
case 0xCC: case 0xDC: case 0xEC:
|
|
|
|
case 0xCD: case 0xDD: case 0xED:
|
|
|
|
case 0xCE: case 0xDE: case 0xEE:
|
|
|
|
case 0xCF: case 0xDF: case 0xEF:
|
|
|
|
if ((addr>=0xF0C00CF8) && (addr<0xF0C00D00)) // MPC105
|
|
|
|
PCIBridge.WritePCIConfigData(32,0,data);
|
|
|
|
else if ((addr>=0xFEC00000) && (addr<0xFEE00000)) // MPC106
|
|
|
|
PCIBridge.WritePCIConfigAddress(data);
|
|
|
|
else if ((addr>=0xFEE00000) && (addr<0xFEF00000)) // MPC106
|
|
|
|
PCIBridge.WritePCIConfigData(32,0,data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// System registers
|
|
|
|
case 0x10:
|
|
|
|
WriteSystemRegister((addr&0x3F)+0,(data>>24)&0xFF);
|
|
|
|
WriteSystemRegister((addr&0x3F)+1,(data>>16)&0xFF);
|
|
|
|
WriteSystemRegister((addr&0x3F)+2,(data>>8)&0xFF);
|
|
|
|
WriteSystemRegister((addr&0x3F)+3,(data>>0)&0xFF);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// RTC
|
|
|
|
case 0x14:
|
|
|
|
RTC.WriteRegister((addr>>2)&0xF,data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Security board RAM
|
|
|
|
case 0x18:
|
|
|
|
case 0x19:
|
|
|
|
*(UINT32 *) &securityRAM[(addr&0x1FFFF)] = data;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Security board registers
|
|
|
|
case 0x1A:
|
|
|
|
WriteSecurity(addr&0x3F,data);
|
|
|
|
break;
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Unknown
|
|
|
|
default:
|
2018-01-07 14:07:59 +00:00
|
|
|
//printf("CMODEL3 : unknown W32 mirror : %x\n", addr >> 16);
|
2016-04-02 21:50:40 +00:00
|
|
|
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);
|
2017-03-23 04:22:25 +00:00
|
|
|
TileGen.WriteRAM32(addr&0x1FFFFF,data);
|
2016-04-02 21:50:40 +00:00
|
|
|
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
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifndef NET_BOARD
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_game.stepping != "1.0")
|
2018-01-07 14:07:59 +00:00
|
|
|
goto Unknown32;
|
|
|
|
#endif
|
|
|
|
#ifdef NET_BOARD
|
|
|
|
if (m_game.stepping != "1.0") // assuming there is no scsi card for step>1.0 because same address for network card (right or wrong ??)
|
|
|
|
{
|
|
|
|
switch ((addr & 0x3ffff) >> 16)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
//printf("W32 netbuffer @%x<-%x\n", (addr & 0xFFFF), data);
|
|
|
|
*(UINT32 *)&netBuffer[(addr & 0xFFFF)] = FLIPENDIAN32(data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1: // ioreg 32bits access to 16bits range
|
|
|
|
if (addr > 0xc00101ff)
|
|
|
|
{
|
|
|
|
printf("W32 ATTENTION OUT OF RANGE\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("W32 ioreg @%x<-%04x\n", (addr /*& 0x1FF*/), data>>16);
|
|
|
|
*(UINT16 *)&netBuffer[0x10000 + ((addr & 0x1FF) / 2)] = FLIPENDIAN16(data >> 16);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
case 3:
|
|
|
|
if (addr > 0xc002ffff)
|
|
|
|
{
|
|
|
|
printf("W32 ATTENTION OUT OF RANGE\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
//printf("W32 netram @%x<-%x\n", (addr & 0x1FFFF), data);
|
|
|
|
*(UINT16 *)&netRAM[((addr & 0x1FFFF) / 2)] = FLIPENDIAN16(data >> 16);
|
|
|
|
break;
|
|
|
|
/*case 3:
|
|
|
|
//printf("W32 netram @%x<-%x\n", (addr & 0x1FFFF), data);
|
|
|
|
*(UINT16 *)&netRAM[((addr & 0x1FFFF) / 2)] = FLIPENDIAN16(data >> 16);
|
|
|
|
break;*/
|
|
|
|
default:
|
|
|
|
printf("W32 ATTENTION OUT OF RANGE\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((*(UINT16 *)&netBuffer[(0xc0010088 & 0x3FFFF)] == FLIPENDIAN16(0x0080)) && NetBoard.CodeReady == false) // 88=110/2
|
|
|
|
{
|
|
|
|
printf("Network code copy ending\n");
|
|
|
|
NetBoard.CodeReady = true;
|
|
|
|
NetBoard.Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
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:
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
printf("CMODEL3 : unknown W32 : %x (%x) data=%d\n", addr,addr >> 24,data);
|
|
|
|
#endif
|
2016-05-26 04:13:22 +00:00
|
|
|
//printf("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data);
|
2016-04-02 21:50:40 +00:00
|
|
|
DebugLog("PC=%08X\twrite32: %08X=%08X\n", ppc_get_pc(), addr, data);
|
|
|
|
break;
|
|
|
|
}
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
2011-04-24 01:14:00 +00:00
|
|
|
|
|
|
|
void CModel3::Write64(UINT32 addr, UINT64 data)
|
|
|
|
{
|
2018-01-07 14:07:59 +00:00
|
|
|
//printf("write64 %x <- %x\n", addr, data);
|
|
|
|
Write32(addr+0, (UINT32) (data>>32));
|
2011-04-24 01:14:00 +00:00
|
|
|
Write32(addr+4, (UINT32) data);
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
|
|
|
|
2011-04-24 01:14:00 +00:00
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
Emulation and Interface Functions
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
void CModel3::SaveState(CBlockFile *SaveState)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Write Model 3 state
|
|
|
|
SaveState->NewBlock("Model 3", __FILE__);
|
|
|
|
SaveState->Write(&inputBank, sizeof(inputBank));
|
|
|
|
SaveState->Write(&serialFIFO1, sizeof(serialFIFO1));
|
|
|
|
SaveState->Write(&serialFIFO2, sizeof(serialFIFO2));
|
|
|
|
SaveState->Write(&gunReg, sizeof(gunReg));
|
|
|
|
SaveState->Write(&adcChannel, sizeof(adcChannel));
|
|
|
|
SaveState->Write(&cromBankReg, sizeof(cromBankReg));
|
|
|
|
SaveState->Write(&securityPtr, sizeof(securityPtr));
|
|
|
|
SaveState->Write(ram, 0x800000);
|
|
|
|
SaveState->Write(backupRAM, 0x20000);
|
|
|
|
SaveState->Write(securityRAM, 0x20000);
|
|
|
|
SaveState->Write(&midiCtrlPort, sizeof(midiCtrlPort));
|
|
|
|
int32_t securityFirstRead = m_securityFirstRead;
|
|
|
|
SaveState->Write(&securityFirstRead, sizeof(securityFirstRead));
|
|
|
|
|
|
|
|
// All devices...
|
|
|
|
ppc_save_state(SaveState);
|
|
|
|
IRQ.SaveState(SaveState);
|
|
|
|
PCIBridge.SaveState(SaveState);
|
|
|
|
SCSI.SaveState(SaveState);
|
|
|
|
EEPROM.SaveState(SaveState);
|
|
|
|
TileGen.SaveState(SaveState);
|
|
|
|
GPU.SaveState(SaveState);
|
|
|
|
SoundBoard.SaveState(SaveState); // also saves DSB state
|
|
|
|
DriveBoard.SaveState(SaveState);
|
|
|
|
m_cryptoDevice.SaveState(SaveState);
|
2017-09-24 20:52:48 +00:00
|
|
|
m_jtag.SaveState(SaveState);
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::LoadState(CBlockFile *SaveState)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Load Model 3 state
|
|
|
|
if (OKAY != SaveState->FindBlock("Model 3"))
|
|
|
|
{
|
|
|
|
ErrorLog("Unable to load Model 3 core state. Save state file is corrupt.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SaveState->Read(&inputBank, sizeof(inputBank));
|
|
|
|
SaveState->Read(&serialFIFO1, sizeof(serialFIFO1));
|
|
|
|
SaveState->Read(&serialFIFO2, sizeof(serialFIFO2));
|
|
|
|
SaveState->Read(&gunReg, sizeof(gunReg));
|
|
|
|
SaveState->Read(&adcChannel, sizeof(adcChannel));
|
|
|
|
SaveState->Read(&cromBankReg, sizeof(cromBankReg));
|
|
|
|
SetCROMBank(cromBankReg); // update CROM bank
|
|
|
|
SaveState->Read(&securityPtr, sizeof(securityPtr));
|
|
|
|
SaveState->Read(ram, 0x800000);
|
|
|
|
SaveState->Read(backupRAM, 0x20000);
|
|
|
|
SaveState->Read(securityRAM, 0x20000);
|
|
|
|
SaveState->Read(&midiCtrlPort, sizeof(midiCtrlPort));
|
|
|
|
int32_t securityFirstRead;
|
|
|
|
SaveState->Write(&securityFirstRead, sizeof(securityFirstRead));
|
2016-05-03 18:38:06 +00:00
|
|
|
m_securityFirstRead = securityFirstRead != 0;
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
// All devices...
|
|
|
|
GPU.LoadState(SaveState);
|
|
|
|
TileGen.LoadState(SaveState);
|
|
|
|
EEPROM.LoadState(SaveState);
|
|
|
|
SCSI.LoadState(SaveState);
|
|
|
|
PCIBridge.LoadState(SaveState);
|
|
|
|
IRQ.LoadState(SaveState);
|
|
|
|
ppc_load_state(SaveState);
|
|
|
|
SoundBoard.LoadState(SaveState);
|
|
|
|
DriveBoard.LoadState(SaveState);
|
|
|
|
m_cryptoDevice.LoadState(SaveState);
|
2017-09-24 20:52:48 +00:00
|
|
|
m_jtag.LoadState(SaveState);
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::SaveNVRAM(CBlockFile *NVRAM)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Load EEPROM
|
|
|
|
EEPROM.SaveState(NVRAM);
|
2011-04-24 01:14:00 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Save backup RAM
|
|
|
|
NVRAM->NewBlock("Backup RAM", __FILE__);
|
|
|
|
NVRAM->Write(backupRAM, 0x20000);
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::LoadNVRAM(CBlockFile *NVRAM)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// 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);
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::ClearNVRAM(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
memset(backupRAM, 0, 0x20000);
|
|
|
|
EEPROM.Clear();
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::RunFrame(void)
|
2011-09-12 05:43:37 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
|
|
|
|
// See if currently running multi-threaded
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_multiThreaded)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
// 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
|
2017-03-27 03:19:15 +00:00
|
|
|
if ((m_gpuMultiThreaded && !ppcBrdThreadSync->Post()) ||
|
|
|
|
(syncSndBrdThread && !sndBrdThreadSync->Post()) ||
|
|
|
|
(DriveBoard.IsAttached() && !drvBrdThreadSync->Post()))
|
2016-04-02 21:50:40 +00:00
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// If not multi-threading GPU, then run PPC main board for a frame and sync GPUs now in this thread
|
2017-03-27 03:19:15 +00:00
|
|
|
if (!m_gpuMultiThreaded)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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)
|
2017-03-27 03:19:15 +00:00
|
|
|
while ((m_gpuMultiThreaded && !ppcBrdThreadDone) ||
|
|
|
|
(syncSndBrdThread && !sndBrdThreadDone) ||
|
|
|
|
(DriveBoard.IsAttached() && !drvBrdThreadDone))
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
if (!notifySync->Wait(notifyLock))
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
ppcBrdThreadDone = false;
|
|
|
|
sndBrdThreadDone = false;
|
|
|
|
drvBrdThreadDone = false;
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// 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
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_gpuMultiThreaded)
|
2016-04-02 21:50:40 +00:00
|
|
|
SyncGPUs();
|
2018-01-07 14:07:59 +00:00
|
|
|
|
|
|
|
/*if (NetBoard.IsAttached())
|
|
|
|
RunNetBoardFrame();*/
|
2016-04-02 21:50:40 +00:00
|
|
|
}
|
|
|
|
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();
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
if (NetBoard.IsAttached() && (m_config["EmulateNet"].ValueAs<bool>()) && ((*(UINT16 *)&netBuffer[(0xc00100C0 & 0x3FFFF)] == 0xFFFF) || (netBuffer[(0xc00100C0 & 0x3FFFF)] == 0xFF) || (*(UINT16 *)&netBuffer[(0xc00100C0 & 0x3FFFF)] == 0x0001)) && (NetBoard.CodeReady == true))
|
|
|
|
{
|
|
|
|
// ppc irq network needed ? no effect, is it really active ?
|
|
|
|
RunNetBoardFrame();
|
|
|
|
IRQ.Assert(0x10);
|
|
|
|
ppc_execute(200); // give PowerPC time to acknowledge IRQ
|
|
|
|
//RunNetBoardFrame();
|
|
|
|
IRQ.Deassert(0x10);
|
|
|
|
ppc_execute(200); // acknowledge that IRQ was deasserted (TODO: is this really needed?)
|
|
|
|
//RunNetBoardFrame();
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
timings.frameTicks = CThread::GetTicks() - start;
|
|
|
|
|
|
|
|
return;
|
2011-07-20 21:14:00 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in CModel3::RunFrame: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2011-07-20 21:14:00 +00:00
|
|
|
}
|
|
|
|
|
2017-09-24 20:52:48 +00:00
|
|
|
#ifdef NEW_FRAME_TIMING
|
|
|
|
void CModel3::RunMainBoardFrame(void)
|
|
|
|
{
|
|
|
|
if (!gpusReady)
|
|
|
|
return;
|
|
|
|
|
|
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Display timing is assumed to be driven by the System 24 tile generator
|
|
|
|
* chip. Charles MacDonald's notes state:
|
|
|
|
*
|
|
|
|
* 656 pixels per scanline:
|
|
|
|
*
|
|
|
|
* 69 pixels from /HSYNC high to /BLANK high (left border)
|
|
|
|
* 496 pixels from /BLANK high to /BLANK low (active display)
|
|
|
|
* 43 pixels from /BLANK low to /HSYNC low (right border)
|
|
|
|
* 48 pixels from /HSYNC low to /HSYNC high (horizontal sync. pulse)
|
|
|
|
*
|
|
|
|
* 424 scanlines per frame:
|
|
|
|
*
|
|
|
|
* 25 scanlines from /VSYNC high to /BLANK high (top border)
|
|
|
|
* 384 scanlines from /BLANK high to /BLANK low (active display)
|
|
|
|
* 11 scanlines from /BLANK low to /VSYNC low (bottom border)
|
|
|
|
* 4 scanlines from /VSYNC low to /VSYNC high (vertical sync. pulse)
|
|
|
|
*
|
|
|
|
* The pixel clock is 16 MHz, giving an effetive frame rate of 57.52
|
|
|
|
* frames per second.
|
|
|
|
*/
|
|
|
|
float ppcCycles = m_config["PowerPCFrequency"].ValueAs<unsigned>() * 1e6;
|
|
|
|
float frameRate = 60; // actually, 57.52 Hz
|
|
|
|
float frameCycles = ppcCycles / frameRate;
|
|
|
|
float lineCycles = frameCycles / 424; // 424 scanlines per tile generator frame
|
|
|
|
unsigned topBorderLines = 25;
|
|
|
|
unsigned activeLines = 384;
|
|
|
|
unsigned bottomBorderLines = 11;
|
|
|
|
unsigned vblLines = 4;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Scale PPC timer ratio according to speed at which the PowerPC is being
|
|
|
|
* emulated so that the observed running frequency of the PPC timer registers
|
|
|
|
* is more or less correct. This is needed to get the Virtua Striker 2
|
|
|
|
* series of games running at the right speed (they are too slow otherwise).
|
|
|
|
* Other games appear to not be affected by this ratio so much as their
|
|
|
|
* running speed depends more on the timing of the Real3D status bit below.
|
|
|
|
*/
|
|
|
|
ppc_set_timer_ratio(ppc_get_bus_freq_multipler() * 2 * ppcCycles / ppc_get_cycles_per_sec());
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Active frame + bottom border. We treat this as one large chunk save for
|
|
|
|
* the sound IRQs, which we attempt to process first.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
unsigned remainingCycles = unsigned(activeLines * lineCycles);
|
|
|
|
unsigned irqCount = 0;
|
|
|
|
while ((midiCtrlPort & 0x20)) // 0x27 triggers IRQ sequence, 0x06 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?)
|
|
|
|
remainingCycles -= 400;
|
|
|
|
|
|
|
|
++irqCount;
|
|
|
|
if (irqCount > 128)
|
|
|
|
{
|
|
|
|
//printf("\tMIDI FIFO OVERFLOW! (IRQEn=%02X, IRQPend=%02X)\n", IRQ.ReadIRQEnable()&0x40, IRQ.ReadIRQState());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ppc_execute(remainingCycles/2);
|
|
|
|
GPU.BeginVBlank(0); // TODO: if this actually occurs before VBL, need to rename this function
|
|
|
|
ppc_execute(remainingCycles/2);
|
|
|
|
ppc_execute(bottomBorderLines * lineCycles);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* VBlank period
|
|
|
|
*/
|
|
|
|
TileGen.BeginVBlank();
|
|
|
|
//GPU.BeginVBlank(0); //TODO: remove this parameter
|
|
|
|
IRQ.Assert(0x02);
|
|
|
|
ppc_execute(vblLines * lineCycles);
|
|
|
|
IRQ.Deassert(0x02); // unnecessary because manually cleared, also probably self-clears within 1 line
|
|
|
|
GPU.EndVBlank();
|
|
|
|
TileGen.EndVBlank();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Top border/end of previous frame's VBlank: assuming here (without
|
|
|
|
* sufficient evidence) that IRQ 1 is end-of-VBL. It's certainly triggered
|
|
|
|
* once per frame, like IRQ 2, according to code I ran on a real board.
|
|
|
|
*
|
|
|
|
* We execute a number of miscellaneous, unknown IRQs on the last line of the
|
|
|
|
* top border, again without any proper justification other than to space
|
|
|
|
* them apart from known IRQs. Games will be doing most of their processing
|
|
|
|
* post-VBL (during the border and active display phases), so it seems like a
|
|
|
|
* good time to raise IRQs.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// One line for IRQ 1, assuming this is some VBL-related signal
|
|
|
|
IRQ.Assert(0x01);
|
|
|
|
ppc_execute(1 * lineCycles);
|
|
|
|
IRQ.Deassert(0x01);
|
|
|
|
|
|
|
|
// The bulk of the border lines
|
|
|
|
ppc_execute ((topBorderLines - 2) * lineCycles);
|
|
|
|
|
|
|
|
// Reserve one line for miscellaneous IRQs
|
|
|
|
IRQ.Assert(0x0C);
|
|
|
|
ppc_execute(1 * lineCycles);
|
|
|
|
IRQ.Deassert(0x0C);
|
|
|
|
|
|
|
|
timings.ppcTicks = CThread::GetTicks() - start;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef NEW_FRAME_TIMING
|
2012-01-16 23:21:14 +00:00
|
|
|
void CModel3::RunMainBoardFrame(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
|
|
|
|
// Compute display and VBlank timings
|
2017-03-27 03:19:15 +00:00
|
|
|
unsigned ppcCycles = m_config["PowerPCFrequency"].ValueAs<unsigned>() * 1000000;
|
2016-04-02 21:50:40 +00:00
|
|
|
unsigned frameCycles = ppcCycles / 60;
|
|
|
|
unsigned vblCycles = (unsigned)((float) frameCycles * 2.5f/100.0f); // 2.5% vblank (ridiculously short and wrong but bigger values cause flicker in Daytona)
|
|
|
|
unsigned dispCycles = frameCycles - vblCycles;
|
|
|
|
|
|
|
|
// Scale PPC timer ratio according to speed at which the PowerPC is being emulated so that the observed running frequency of the PPC timer
|
|
|
|
// registers is more or less correct. This is needed to get the Virtua Striker 2 series of games running at the right speed (they are
|
|
|
|
// too slow otherwise). Other games appear to not be affected by this ratio so much as their running speed depends more on the timing of
|
|
|
|
// the Real3D status bit below.
|
|
|
|
ppc_set_timer_ratio(ppc_get_bus_freq_multipler() * 2 * ppcCycles / ppc_get_cycles_per_sec());
|
|
|
|
|
|
|
|
// Compute timing of the Real3D status bit. This value directly affects the speed at which all the games except Virtua Stiker 2 run.
|
|
|
|
// Currently it is not known exactly what this bit represents nor why such wildly varying values are needed for the different step models.
|
|
|
|
// The values below were arrived at by trial and error and clearly more investigation is required. If it turns out that the status bit is
|
|
|
|
// connected to the end of VBlank then the code below should be removed and the timing handled via GPU.VBlankEnd() instead.
|
|
|
|
unsigned statusCycles;
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_game.stepping == "2.0" || m_game.stepping == "2.1")
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
// For some reason, Fighting Vipers 2 and Daytona USA 2 require completely different timing to the rest of the step 2.x games
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_game.name == "daytona2" || (m_game.stepping == "2.0" && m_game.name == "fvipers2"))
|
2016-04-02 21:50:40 +00:00
|
|
|
statusCycles = (unsigned)((float)frameCycles * 24.0f/100.0f);
|
|
|
|
else
|
|
|
|
statusCycles = (unsigned)((float)frameCycles * 9.12f/100.0f);
|
|
|
|
}
|
2017-03-27 03:19:15 +00:00
|
|
|
else if (m_game.stepping == "1.5")
|
2016-04-02 21:50:40 +00:00
|
|
|
statusCycles = (unsigned)((float)frameCycles * 5.5f/100.0f);
|
|
|
|
else
|
|
|
|
statusCycles = (unsigned)((float)frameCycles * 48.0f/100.0f);
|
|
|
|
|
|
|
|
// VBlank
|
|
|
|
if (gpusReady)
|
|
|
|
{
|
|
|
|
TileGen.BeginVBlank();
|
|
|
|
GPU.BeginVBlank(statusCycles);
|
|
|
|
IRQ.Assert(0x02);
|
|
|
|
ppc_execute(vblCycles);
|
|
|
|
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sound:
|
|
|
|
*
|
|
|
|
* Bit 0x20 of the MIDI control port appears to enable periodic interrupts,
|
|
|
|
* which are used to send MIDI commands. Often games will write 0x27, send
|
|
|
|
* a series of commands, and write 0x06 to stop. Other games, like Star
|
|
|
|
* Wars Trilogy and Sega Rally 2, will enable interrupts at the beginning
|
|
|
|
* by writing 0x37 and will disable/enable interrupts to control command
|
|
|
|
* output.
|
|
|
|
*/
|
|
|
|
//printf("\t-- BEGIN (Ctrl=%02X, IRQEn=%02X, IRQPend=%02X) --\n", midiCtrlPort, IRQ.ReadIRQEnable()&0x40, IRQ.ReadIRQState());
|
|
|
|
int irqCount = 0;
|
|
|
|
while ((midiCtrlPort&0x20))
|
|
|
|
//while (midiCtrlPort == 0x27) // 27 triggers IRQ sequence, 06 stops it
|
|
|
|
{
|
|
|
|
// Don't waste time firing MIDI interrupts if game has disabled them
|
|
|
|
if ((IRQ.ReadIRQEnable()&0x40) == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Process MIDI interrupt
|
|
|
|
IRQ.Assert(0x40);
|
|
|
|
ppc_execute(200); // give PowerPC time to acknowledge IRQ
|
|
|
|
IRQ.Deassert(0x40);
|
|
|
|
ppc_execute(200); // acknowledge that IRQ was deasserted (TODO: is this really needed?)
|
|
|
|
dispCycles -= 400;
|
|
|
|
|
|
|
|
++irqCount;
|
|
|
|
if (irqCount > 128)
|
|
|
|
{
|
|
|
|
//printf("\tMIDI FIFO OVERFLOW! (IRQEn=%02X, IRQPend=%02X)\n", IRQ.ReadIRQEnable()&0x40, IRQ.ReadIRQState());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//printf("\t-- END --\n");
|
|
|
|
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
|
|
|
|
|
|
|
|
// End VBlank
|
|
|
|
GPU.EndVBlank();
|
|
|
|
TileGen.EndVBlank();
|
|
|
|
IRQ.Assert(0x0D);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the PowerPC for the active display part of the frame
|
|
|
|
ppc_execute(dispCycles);
|
2016-05-10 03:08:03 +00:00
|
|
|
// MAME believes 0x0C should occur on every scanline
|
|
|
|
//for (int i = 0; i < 384; i++)
|
|
|
|
//{
|
|
|
|
// ppc_execute(dispCycles / 384);
|
|
|
|
// IRQ.Assert(0x0C);
|
|
|
|
//}
|
2016-04-02 21:50:40 +00:00
|
|
|
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
|
|
|
|
|
|
|
|
timings.ppcTicks = CThread::GetTicks() - start;
|
2012-01-16 23:21:14 +00:00
|
|
|
}
|
2017-09-24 20:52:48 +00:00
|
|
|
#endif
|
2012-01-16 23:21:14 +00:00
|
|
|
|
|
|
|
void CModel3::SyncGPUs(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
UINT32 start = CThread::GetTicks();
|
2012-01-16 23:21:14 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
timings.syncSize = GPU.SyncSnapshots() + TileGen.SyncSnapshots();
|
|
|
|
gpusReady = true;
|
2012-01-16 23:21:14 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
timings.syncTicks = CThread::GetTicks() - start;
|
2012-01-16 23:21:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::RenderFrame(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
UINT32 start = CThread::GetTicks();
|
2012-01-16 23:21:14 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Call OSD video callbacks
|
|
|
|
if (BeginFrameVideo() && gpusReady)
|
|
|
|
{
|
|
|
|
// Render frame
|
|
|
|
TileGen.BeginFrame();
|
|
|
|
GPU.BeginFrame();
|
2016-05-08 19:27:08 +00:00
|
|
|
TileGen.PreRenderFrame();
|
|
|
|
TileGen.RenderFrameBottom();
|
2016-04-02 21:50:40 +00:00
|
|
|
GPU.RenderFrame();
|
2016-05-08 19:27:08 +00:00
|
|
|
TileGen.RenderFrameTop();
|
2016-04-02 21:50:40 +00:00
|
|
|
GPU.EndFrame();
|
|
|
|
TileGen.EndFrame();
|
|
|
|
}
|
2012-02-13 23:37:48 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
EndFrameVideo();
|
2012-01-16 23:21:14 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
timings.renderTicks = CThread::GetTicks() - start;
|
2012-01-16 23:21:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CModel3::RunSoundBoardFrame(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
bool bufferFull = SoundBoard.RunFrame();
|
|
|
|
timings.sndTicks = CThread::GetTicks() - start;
|
|
|
|
return bufferFull;
|
2012-01-16 23:21:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::RunDriveBoardFrame(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
UINT32 start = CThread::GetTicks();
|
|
|
|
DriveBoard.RunFrame();
|
|
|
|
timings.drvTicks = CThread::GetTicks() - start;
|
2012-01-16 23:21:14 +00:00
|
|
|
}
|
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
void CModel3::RunNetBoardFrame(void)
|
|
|
|
{
|
|
|
|
NetBoard.RunFrame();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2011-09-12 05:43:37 +00:00
|
|
|
bool CModel3::StartThreads(void)
|
2011-07-20 21:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
if (startedThreads)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Create synchronization objects
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_gpuMultiThreaded)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
ppcBrdThreadSync = CThread::CreateSemaphore(0);
|
|
|
|
if (ppcBrdThreadSync == NULL)
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
sndBrdThreadSync = CThread::CreateSemaphore(0);
|
|
|
|
if (sndBrdThreadSync == NULL)
|
|
|
|
goto ThreadError;
|
|
|
|
sndBrdNotifyLock = CThread::CreateMutex();
|
|
|
|
if (sndBrdNotifyLock == NULL)
|
|
|
|
goto ThreadError;
|
|
|
|
sndBrdNotifySync = CThread::CreateCondVar();
|
|
|
|
if (sndBrdNotifySync == NULL)
|
|
|
|
goto ThreadError;
|
|
|
|
if (DriveBoard.IsAttached())
|
|
|
|
{
|
|
|
|
drvBrdThreadSync = CThread::CreateSemaphore(0);
|
|
|
|
if (drvBrdThreadSync == NULL)
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
notifyLock = CThread::CreateMutex();
|
|
|
|
if (notifyLock == NULL)
|
|
|
|
goto ThreadError;
|
|
|
|
notifySync = CThread::CreateCondVar();
|
|
|
|
if (notifySync == NULL)
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Reset thread flags
|
|
|
|
pauseThreads = false;
|
|
|
|
stopThreads = false;
|
|
|
|
|
|
|
|
// Create PPC main board thread, if multi-threading GPU
|
2017-03-27 03:19:15 +00:00
|
|
|
if (m_gpuMultiThreaded)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
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)
|
2018-01-07 14:07:59 +00:00
|
|
|
{
|
|
|
|
SetAudioCallback(AudioCallback, this);
|
|
|
|
}
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
startedThreads = true;
|
|
|
|
return true;
|
2011-07-20 21:14:00 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Unable to create threads and/or synchronization objects: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
|
|
|
DeleteThreadObjects();
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return false;
|
2011-07-20 21:14:00 +00:00
|
|
|
}
|
|
|
|
|
2011-09-12 05:43:37 +00:00
|
|
|
bool CModel3::PauseThreads(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
if (!startedThreads)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Let threads know that they should pause and wait for all of them to do so
|
|
|
|
pauseThreads = true;
|
|
|
|
while (ppcBrdThreadRunning || sndBrdThreadRunning || drvBrdThreadRunning)
|
|
|
|
{
|
|
|
|
if (!notifySync->Wait(notifyLock))
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
return true;
|
2011-09-12 05:43:37 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in CModel3::PauseThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return false;
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
|
|
|
|
2012-01-16 23:21:14 +00:00
|
|
|
bool CModel3::ResumeThreads(void)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
|
|
|
if (!startedThreads)
|
|
|
|
return true;
|
2012-01-16 23:21:14 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
2012-01-16 23:21:14 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Let all threads know that they can continue running
|
|
|
|
pauseThreads = false;
|
2012-01-16 23:21:14 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
return true;
|
2012-01-16 23:21:14 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in CModel3::ResumeThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return false;
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
|
|
|
|
2012-07-23 20:35:10 +00:00
|
|
|
bool CModel3::StopThreads(void)
|
2011-07-20 21:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
if (!startedThreads)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// If sound board thread is unsync'd then remove audio callback
|
|
|
|
if (!syncSndBrdThread)
|
|
|
|
SetAudioCallback(NULL, NULL);
|
|
|
|
|
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Let threads know that they should pause and wait for all of them to do so
|
|
|
|
pauseThreads = true;
|
|
|
|
while (ppcBrdThreadRunning || sndBrdThreadRunning || drvBrdThreadRunning)
|
|
|
|
{
|
|
|
|
if (!notifySync->Wait(notifyLock))
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now let threads know that they should exit
|
|
|
|
stopThreads = true;
|
|
|
|
|
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Resume each thread in turn and wait for them to exit
|
|
|
|
if (ppcBrdThread != NULL)
|
|
|
|
{
|
|
|
|
if (ppcBrdThreadSync->Post())
|
|
|
|
ppcBrdThread->Wait();
|
|
|
|
}
|
|
|
|
if (sndBrdThread != NULL)
|
|
|
|
{
|
|
|
|
if (syncSndBrdThread)
|
|
|
|
{
|
|
|
|
if (sndBrdThreadSync->Post())
|
|
|
|
sndBrdThread->Wait();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (WakeSoundBoardThread())
|
|
|
|
sndBrdThread->Wait();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (drvBrdThread != NULL)
|
|
|
|
{
|
|
|
|
if (drvBrdThreadSync->Post())
|
|
|
|
drvBrdThread->Wait();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete all thread and synchronization objects
|
|
|
|
DeleteThreadObjects();
|
|
|
|
startedThreads = false;
|
|
|
|
return true;
|
2012-07-23 20:35:10 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in CModel3::StopThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return false;
|
2011-07-20 21:14:00 +00:00
|
|
|
}
|
|
|
|
|
2011-09-12 05:43:37 +00:00
|
|
|
void CModel3::DeleteThreadObjects(void)
|
2011-07-20 21:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Delete PPC main board, sound board and drive board threads
|
|
|
|
if (ppcBrdThread != NULL)
|
|
|
|
{
|
|
|
|
delete ppcBrdThread;
|
|
|
|
ppcBrdThread = NULL;
|
|
|
|
}
|
|
|
|
if (sndBrdThread != NULL)
|
|
|
|
{
|
|
|
|
delete sndBrdThread;
|
|
|
|
sndBrdThread = NULL;
|
|
|
|
}
|
|
|
|
if (drvBrdThread != NULL)
|
|
|
|
{
|
|
|
|
delete drvBrdThread;
|
|
|
|
drvBrdThread = NULL;
|
|
|
|
}
|
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Delete synchronization objects
|
|
|
|
if (ppcBrdThreadSync != NULL)
|
|
|
|
{
|
|
|
|
delete ppcBrdThreadSync;
|
|
|
|
ppcBrdThreadSync = NULL;
|
|
|
|
}
|
|
|
|
if (sndBrdThreadSync != NULL)
|
|
|
|
{
|
|
|
|
delete sndBrdThreadSync;
|
|
|
|
sndBrdThreadSync = NULL;
|
|
|
|
}
|
|
|
|
if (drvBrdThreadSync != NULL)
|
|
|
|
{
|
|
|
|
delete drvBrdThreadSync;
|
|
|
|
drvBrdThreadSync = NULL;
|
|
|
|
}
|
2018-01-07 14:07:59 +00:00
|
|
|
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
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;
|
|
|
|
}
|
2011-07-20 21:14:00 +00:00
|
|
|
}
|
2011-09-12 05:43:37 +00:00
|
|
|
|
2012-01-16 23:21:14 +00:00
|
|
|
void CModel3::DumpTimings(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
printf("PPC:%3ums%c render:%3ums%c sync:%4uK%c%3ums%c snd:%3ums%c drv:%3ums%c frame:%3ums%c\n",
|
|
|
|
timings.ppcTicks, (timings.ppcTicks > timings.renderTicks ? '!' : ','),
|
|
|
|
timings.renderTicks, (timings.renderTicks > timings.ppcTicks ? '!' : ','),
|
|
|
|
timings.syncSize / 1024, (timings.syncSize / 1024 > 128 ? '!' : ','),
|
|
|
|
timings.syncTicks, (timings.syncTicks > 1 ? '!' : ','),
|
|
|
|
timings.sndTicks, (timings.sndTicks > 10 ? '!' : ','),
|
|
|
|
timings.drvTicks, (timings.drvTicks > 10 ? '!' : ','),
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
timings.netTicks, (timings.netTicks > 10 ? '!' : ','),
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
timings.frameTicks, (timings.frameTicks > 16 ? '!' : ' '));
|
Some updates to Supermodel made at beginning of the year but only now got around to checking in (better late than never...):
- hooked up the remaining controls in Supermodel (except for Magical Truck Adventure which does not work at all yet). The new controls are:
* InputAnalogJoyTrigger2 and InputAnalogJoyEvent2 for the additional second trigger and event buttons that were missing from Star Wars Trilogy,
* InputRearBrake and InputMusicSelect for the rear brake and music selection buttons that were missing from Harley Davidson,
* InputAnalogGunXXX, InputAnalogTriggerXXX, InputAnalogGunXXX2 and InputAnalogTriggerXXX2 for the analogue guns of Ocean Hunter and LA Machineguns (NOTE: these controls must be calibrated in the games' service menus otherwise they will not work properly. Also, the alignment of the gun cursor does not line up very well with the mouse position at the moment, but at least the games are a bit more playable now, although still with numerous graphical glitches...)
* InputSkiXXX for the controls of Ski Champ, making the game playable now.
- hooked up existing InputViewChange control to Harley Davidson's view change button
- improved the handling of InputGearShiftUp/Down inputs so that they work better with the driving games. With Dirt Devils, ECA, Harley and LeMans this means they map directly to the game's own shift up/down controls, while with the 4-speed games such as Daytona 2, Scud Racer and Sega Rally 2, they simulate the user shifting up and down through the gears
- added defaults for the new controls to Supermodel.ini
- other small code tweaks:
* fix small bug with handling of pos/neg inputs mapping to a control with inverted range (0XFF to 0x00) - this was needed to get Ski Champ's X-axis to work properly
* removed Wait method from InputSystem and added to CThread as CThread::Sleep instead
* added FrameTimings struct to hold all frame timings in a single place
No networking code yet as just haven't had a chance to work on it since initial progress at the beginning of the year - am *hoping* might have some time to pick it up again over Christmas...
2013-11-30 19:39:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FrameTimings CModel3::GetTimings(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
return timings;
|
2012-01-16 23:21:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int CModel3::StartMainBoardThread(void *data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Call method on CModel3 to run PPC main board thread
|
|
|
|
CModel3 *model3 = (CModel3*)data;
|
|
|
|
return model3->RunMainBoardThread();
|
2012-01-16 23:21:14 +00:00
|
|
|
}
|
|
|
|
|
2011-07-20 21:14:00 +00:00
|
|
|
int CModel3::StartSoundBoardThread(void *data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Call method on CModel3 to run sound board thread (unsync'd)
|
|
|
|
CModel3 *model3 = (CModel3*)data;
|
|
|
|
return model3->RunSoundBoardThread();
|
2011-07-20 21:14:00 +00:00
|
|
|
}
|
|
|
|
|
2011-09-12 05:43:37 +00:00
|
|
|
int CModel3::StartSoundBoardThreadSyncd(void *data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Call method on CModel3 to run sound board thread (sync'd)
|
|
|
|
CModel3 *model3 = (CModel3*)data;
|
|
|
|
return model3->RunSoundBoardThreadSyncd();
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
|
|
|
|
2012-01-16 23:21:14 +00:00
|
|
|
int CModel3::StartDriveBoardThread(void *data)
|
2011-07-20 21:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Call method on CModel3 to run drive board thread
|
|
|
|
CModel3 *model3 = (CModel3*)data;
|
|
|
|
return model3->RunDriveBoardThread();
|
2011-07-20 21:14:00 +00:00
|
|
|
}
|
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2012-07-23 20:35:10 +00:00
|
|
|
int CModel3::RunMainBoardThread(void)
|
2012-01-16 23:21:14 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
bool wait = true;
|
|
|
|
bool exit = false;
|
|
|
|
while (wait && !exit)
|
|
|
|
{
|
|
|
|
// Wait on PPC main board thread semaphore
|
|
|
|
if (!ppcBrdThreadSync->Wait())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Check threads are not being stopped or paused
|
|
|
|
if (stopThreads)
|
|
|
|
exit = true;
|
|
|
|
else if (!pauseThreads)
|
|
|
|
{
|
|
|
|
wait = false;
|
|
|
|
ppcBrdThreadRunning = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
if (exit)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Process a single frame for PPC main board
|
|
|
|
RunMainBoardFrame();
|
|
|
|
|
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Let other threads know processing has finished
|
|
|
|
ppcBrdThreadRunning = false;
|
|
|
|
ppcBrdThreadDone = true;
|
|
|
|
if (!notifySync->SignalAll())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
2012-01-16 23:21:14 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in RunMainBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return 1;
|
2012-01-16 23:21:14 +00:00
|
|
|
}
|
|
|
|
|
2011-09-12 05:43:37 +00:00
|
|
|
void CModel3::AudioCallback(void *data)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Call method on CModel3 to wake sound board thread
|
|
|
|
CModel3 *model3 = (CModel3*)data;
|
|
|
|
model3->WakeSoundBoardThread();
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
|
|
|
|
2012-07-23 20:35:10 +00:00
|
|
|
bool CModel3::WakeSoundBoardThread(void)
|
2011-09-12 05:43:37 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Enter sound board notify critical section
|
|
|
|
bool wake;
|
|
|
|
if (!sndBrdNotifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Enter main notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// See if sound board thread is currently running
|
|
|
|
wake = !sndBrdThreadRunning;
|
|
|
|
|
|
|
|
// Leave main notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Only send wake notification to sound board thread if it was not running
|
|
|
|
if (wake)
|
|
|
|
{
|
|
|
|
// Signal to sound board thread that it should start processing again
|
|
|
|
sndBrdWakeNotify = true;
|
|
|
|
if (!sndBrdNotifySync->Signal())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Leave sound board notify critical section
|
|
|
|
if (!sndBrdNotifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
return wake;
|
2011-09-12 05:43:37 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in WakeSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return false;
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
|
|
|
|
2012-07-23 20:35:10 +00:00
|
|
|
int CModel3::RunSoundBoardThread(void)
|
2011-07-20 21:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
bool wait = true;
|
|
|
|
bool exit = false;
|
|
|
|
while (wait && !exit)
|
|
|
|
{
|
|
|
|
// Enter sound board notify critical section
|
|
|
|
if (!sndBrdNotifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Wait for notification from audio callback
|
|
|
|
while (!sndBrdWakeNotify)
|
|
|
|
{
|
|
|
|
if (!sndBrdNotifySync->Wait(sndBrdNotifyLock))
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
sndBrdWakeNotify = false;
|
|
|
|
|
|
|
|
// Enter main notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Check threads are not being stopped or paused
|
|
|
|
if (stopThreads)
|
|
|
|
exit = true;
|
|
|
|
else if (!pauseThreads)
|
|
|
|
{
|
|
|
|
wait = false;
|
|
|
|
sndBrdThreadRunning = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Leave main notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Leave sound board notify critical section
|
|
|
|
if (!sndBrdNotifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
if (exit)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Keep processing frames until pausing or audio buffer is full
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
// Enter main notify critical section
|
|
|
|
bool paused;
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
paused = pauseThreads;
|
|
|
|
|
|
|
|
// Leave main notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
if (paused || RunSoundBoardFrame())
|
|
|
|
break;
|
|
|
|
//printf("Rerunning sound board\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enter main notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Let other threads know processing has finished
|
|
|
|
sndBrdThreadRunning = false;
|
|
|
|
sndBrdThreadDone = true;
|
|
|
|
if (!notifySync->SignalAll())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Leave main notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
2011-09-12 05:43:37 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in RunSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return 1;
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|
|
|
|
|
2012-07-23 20:35:10 +00:00
|
|
|
int CModel3::RunSoundBoardThreadSyncd(void)
|
2011-09-12 05:43:37 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
bool wait = true;
|
|
|
|
bool exit = false;
|
|
|
|
while (wait && !exit)
|
|
|
|
{
|
|
|
|
// Wait on sound board thread semaphore
|
|
|
|
if (!sndBrdThreadSync->Wait())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Check threads are not being stopped or paused
|
|
|
|
if (stopThreads)
|
|
|
|
exit = true;
|
|
|
|
else if (!pauseThreads)
|
|
|
|
{
|
|
|
|
wait = false;
|
|
|
|
sndBrdThreadRunning = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
if (exit)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Process a single frame for sound board
|
|
|
|
RunSoundBoardFrame();
|
|
|
|
|
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Let other threads know processing has finished
|
|
|
|
sndBrdThreadRunning = false;
|
|
|
|
sndBrdThreadDone = true;
|
|
|
|
if (!notifySync->SignalAll())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
2011-07-20 21:14:00 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in RunSoundBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return 1;
|
2011-07-20 21:14:00 +00:00
|
|
|
}
|
|
|
|
|
2012-07-23 20:35:10 +00:00
|
|
|
int CModel3::RunDriveBoardThread(void)
|
2011-07-20 21:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
bool wait = true;
|
|
|
|
bool exit = false;
|
|
|
|
while (wait && !exit)
|
|
|
|
{
|
|
|
|
// Wait on drive board thread semaphore
|
|
|
|
if (!drvBrdThreadSync->Wait())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Check threads are not being stopped or paused
|
|
|
|
if (stopThreads)
|
|
|
|
exit = true;
|
|
|
|
else if (!pauseThreads)
|
|
|
|
{
|
|
|
|
wait = false;
|
|
|
|
drvBrdThreadRunning = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
|
|
|
if (exit)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Process a single frame for drive board
|
|
|
|
RunDriveBoardFrame();
|
|
|
|
|
|
|
|
// Enter notify critical section
|
|
|
|
if (!notifyLock->Lock())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Let other threads know processing has finished
|
|
|
|
drvBrdThreadRunning = false;
|
|
|
|
drvBrdThreadDone = true;
|
|
|
|
if (!notifySync->SignalAll())
|
|
|
|
goto ThreadError;
|
|
|
|
|
|
|
|
// Leave notify critical section
|
|
|
|
if (!notifyLock->Unlock())
|
|
|
|
goto ThreadError;
|
|
|
|
}
|
2011-07-20 21:14:00 +00:00
|
|
|
|
|
|
|
ThreadError:
|
2016-04-02 21:50:40 +00:00
|
|
|
ErrorLog("Threading error in RunDriveBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
|
2017-03-27 03:19:15 +00:00
|
|
|
m_multiThreaded = false;
|
2016-04-02 21:50:40 +00:00
|
|
|
return 1;
|
2011-07-20 21:14:00 +00:00
|
|
|
}
|
2011-09-12 05:43:37 +00:00
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
|
|
|
|
|
2011-04-24 01:14:00 +00:00
|
|
|
void CModel3::Reset(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Clear memory (but do not modify backup RAM!)
|
|
|
|
memset(ram, 0, 0x800000);
|
|
|
|
|
|
|
|
// Initial bank is bank 0
|
|
|
|
SetCROMBank(0xFF);
|
|
|
|
|
|
|
|
// Reset security device
|
|
|
|
securityPtr = 0;
|
|
|
|
m_securityFirstRead = true;
|
|
|
|
|
|
|
|
// Reset inputs
|
|
|
|
inputBank = 0;
|
|
|
|
serialFIFO1 = 0;
|
|
|
|
serialFIFO2 = 0;
|
|
|
|
adcChannel = 0;
|
|
|
|
|
|
|
|
// MIDI
|
|
|
|
midiCtrlPort = 0;
|
|
|
|
|
|
|
|
// Reset all devices
|
|
|
|
ppc_reset();
|
|
|
|
IRQ.Reset();
|
|
|
|
PCIBridge.Reset();
|
|
|
|
PCIBus.Reset();
|
|
|
|
SCSI.Reset();
|
|
|
|
RTC.Reset();
|
|
|
|
EEPROM.Reset();
|
|
|
|
TileGen.Reset();
|
|
|
|
GPU.Reset();
|
|
|
|
SoundBoard.Reset();
|
2017-09-24 20:52:48 +00:00
|
|
|
m_jtag.Reset();
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
if (DriveBoard.IsAttached())
|
|
|
|
DriveBoard.Reset();
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
m_cryptoDevice.Reset();
|
|
|
|
|
|
|
|
gpusReady = false;
|
|
|
|
|
|
|
|
timings.ppcTicks = 0;
|
|
|
|
timings.syncSize = 0;
|
|
|
|
timings.syncTicks = 0;
|
|
|
|
timings.renderTicks = 0;
|
|
|
|
timings.sndTicks = 0;
|
|
|
|
timings.drvTicks = 0;
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
timings.netTicks = 0;
|
|
|
|
NetBoard.CodeReady = false;
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
timings.frameTicks = 0;
|
|
|
|
|
|
|
|
DebugLog("Model 3 reset\n");
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************
|
|
|
|
Initialization, Shutdown, and ROM Management
|
|
|
|
******************************************************************************/
|
|
|
|
|
|
|
|
// Offsets of memory regions within Model 3's pool
|
2016-04-02 21:50:40 +00:00
|
|
|
#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
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifndef NET_BOARD
|
2016-04-02 21:50:40 +00:00
|
|
|
#define MEMORY_POOL_SIZE (0x800000 + 0x800000 + 0x8000000 + 0x4000000 + 0x20000 + 0x20000 + 0x80000 + 0x1000000 + 0x20000 + 0x1000000 + 0x10000)
|
2018-01-07 14:07:59 +00:00
|
|
|
#endif
|
|
|
|
#ifdef NET_BOARD
|
|
|
|
#define OFFSET_NETBUFFER 0xC000000 // not really 128kb (64kb buffer 0000-ffff + i/o 10000-101ff)
|
|
|
|
#define OFFSET_NETRAM 0xC020000 // 128 KB (c0020000-c003ffff)
|
|
|
|
#define MEMORY_POOL_SIZE (0x800000 + 0x800000 + 0x8000000 + 0x4000000 + 0x20000 + 0x20000 + 0x80000 + 0x1000000 + 0x20000 + 0x1000000 + 0x10000 + 0x40000)
|
|
|
|
//8MB 8MB 128MB 64MB 128KB 128KB 512KB 16MB 128KB 16MB 64KB 256KB
|
|
|
|
#endif
|
2011-12-23 21:03:24 +00:00
|
|
|
// 64-bit magic number used to detect loading of optional ROMs
|
2016-04-02 21:50:40 +00:00
|
|
|
#define MAGIC_NUMBER 0x4C444D5245505553ULL
|
2011-04-24 01:14:00 +00:00
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
const Game &CModel3::GetGame() const
|
2011-04-24 01:14:00 +00:00
|
|
|
{
|
2017-03-27 03:19:15 +00:00
|
|
|
return m_game;
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
2016-04-02 21:50:40 +00:00
|
|
|
|
2011-04-24 01:14:00 +00:00
|
|
|
// Stepping-dependent parameters (MPC10x type, etc.) are initialized here
|
2017-03-27 03:19:15 +00:00
|
|
|
bool CModel3::LoadGame(const Game &game, const ROMSet &rom_set)
|
2011-04-24 01:14:00 +00:00
|
|
|
{
|
2017-03-27 03:19:15 +00:00
|
|
|
m_game = Game();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy in ROM data with mirroring as necessary for the following cases:
|
|
|
|
*
|
|
|
|
* - VROM: 64MB. If <= 32MB, mirror to high 32MB.
|
|
|
|
* - Banked CROM: 128MB. If <= 64MB, mirror to high 64MB.
|
|
|
|
* - Fixed CROM: 8MB. If < 8MB, loaded only in high part of space and low
|
|
|
|
* part is a mirror of (banked) CROM0.
|
|
|
|
* - Sample ROM: 16MB. If <= 8MB, mirror to high 8MB.
|
|
|
|
*/
|
|
|
|
if (rom_set.get_rom("vrom").size <= 32*0x100000)
|
|
|
|
{
|
|
|
|
rom_set.get_rom("vrom").CopyTo(&vrom[0], 32*100000);
|
|
|
|
rom_set.get_rom("vrom").CopyTo(&vrom[32*0x100000], 32*0x100000);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rom_set.get_rom("vrom").CopyTo(vrom, 64*0x100000);
|
|
|
|
if (rom_set.get_rom("banked_crom").size <= 64*0x100000)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
2017-03-27 03:19:15 +00:00
|
|
|
rom_set.get_rom("banked_crom").CopyTo(&crom[8*0x100000 + 0], 64*0x100000);
|
|
|
|
rom_set.get_rom("banked_crom").CopyTo(&crom[8*0x100000 + 64*0x100000], 64*0x100000);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rom_set.get_rom("banked_crom").CopyTo(&crom[8*0x100000 + 0], 128*0x100000);
|
|
|
|
size_t crom_size = rom_set.get_rom("crom").size;
|
|
|
|
rom_set.get_rom("crom").CopyTo(&crom[8*0x100000 - crom_size], crom_size);
|
|
|
|
if (crom_size < 8*0x100000)
|
|
|
|
rom_set.get_rom("banked_crom").CopyTo(&crom[0], 8*0x100000 - crom_size);
|
|
|
|
if (rom_set.get_rom("sound_samples").size <= 8*0x100000)
|
|
|
|
{
|
|
|
|
rom_set.get_rom("sound_samples").CopyTo(&sampleROM[0], 8*0x100000);
|
|
|
|
rom_set.get_rom("sound_samples").CopyTo(&sampleROM[8*0x100000], 8*0x100000);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rom_set.get_rom("sound_samples").CopyTo(sampleROM, 16*0x100000);
|
|
|
|
rom_set.get_rom("sound_program").CopyTo(soundROM, 512*1024);
|
|
|
|
rom_set.get_rom("mpeg_program").CopyTo(dsbROM, 128*1024);
|
|
|
|
rom_set.get_rom("mpeg_music").CopyTo(mpegROM, 16*0x100000);
|
|
|
|
rom_set.get_rom("driveboard_program").CopyTo(driveROM, 64*1024);
|
|
|
|
|
|
|
|
// Convert PowerPC and 68K ROMs to little endian words
|
|
|
|
Util::FlipEndian32(crom, 8*0x100000 + 128*0x100000);
|
|
|
|
Util::FlipEndian16(soundROM, 512*1024);
|
|
|
|
Util::FlipEndian16(sampleROM, 16*0x100000);
|
|
|
|
|
|
|
|
// Initialize CPU
|
|
|
|
PPC_CONFIG ppc_config;
|
|
|
|
if (game.stepping == "2.0" || game.stepping == "2.1")
|
|
|
|
{
|
|
|
|
ppc_config.pvr = PPC_MODEL_603R; // 166 MHz
|
|
|
|
ppc_config.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
|
|
ppc_config.bus_frequency_multiplier = 0x25; // 2.5X multiplier
|
|
|
|
PCIBridge.SetModel(0x106); // MPC106
|
2016-04-02 21:50:40 +00:00
|
|
|
}
|
2017-03-27 03:19:15 +00:00
|
|
|
else if (game.stepping == "1.5")
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
2017-03-27 03:19:15 +00:00
|
|
|
ppc_config.pvr = PPC_MODEL_603E; // 100 MHz
|
|
|
|
ppc_config.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
|
|
ppc_config.bus_frequency_multiplier = 0x15; // 1.5X multiplier
|
|
|
|
if (game.name == "scudp1")
|
|
|
|
PCIBridge.SetModel(0x106); // some Step 1.x games use MPC106
|
2016-04-02 21:50:40 +00:00
|
|
|
else
|
2017-03-27 03:19:15 +00:00
|
|
|
PCIBridge.SetModel(0x105); // MPC105
|
2016-04-02 21:50:40 +00:00
|
|
|
}
|
2017-03-27 03:19:15 +00:00
|
|
|
else if (game.stepping == "1.0")
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
2017-03-27 03:19:15 +00:00
|
|
|
ppc_config.pvr = PPC_MODEL_603R; // 66 MHz
|
|
|
|
ppc_config.bus_frequency = BUS_FREQUENCY_66MHZ;
|
|
|
|
ppc_config.bus_frequency_multiplier = 0x10; // 1X multiplier
|
|
|
|
if (game.name == "bass" || game.name == "getbass")
|
|
|
|
PCIBridge.SetModel(0x106); // some Step 1.x games use MPC106
|
2016-04-02 21:50:40 +00:00
|
|
|
else
|
2017-03-27 03:19:15 +00:00
|
|
|
PCIBridge.SetModel(0x105); // MPC105
|
2016-04-02 21:50:40 +00:00
|
|
|
}
|
|
|
|
else
|
2017-03-27 03:19:15 +00:00
|
|
|
{
|
|
|
|
ErrorLog("Cannot configure Model 3 because game uses unrecognized stepping (%s).", game.stepping.c_str());
|
|
|
|
return FAIL;
|
|
|
|
}
|
|
|
|
ppc_init(&ppc_config);
|
2016-04-02 21:50:40 +00:00
|
|
|
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);
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
// Initialize Real3D
|
|
|
|
int stepping = ((game.stepping[0] - '0') << 4) | (game.stepping[2] - '0');
|
|
|
|
GPU.SetStepping(stepping);
|
|
|
|
|
|
|
|
// MPEG board (if present)
|
|
|
|
if (rom_set.get_rom("mpeg_program").size)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
2017-03-27 03:19:15 +00:00
|
|
|
if (game.mpeg_board == "DSB1")
|
|
|
|
{
|
|
|
|
DSB = new(std::nothrow) CDSB1(m_config);
|
|
|
|
if (NULL == DSB)
|
|
|
|
return ErrorLog("Insufficient memory for Digital Sound Board object.");
|
|
|
|
}
|
|
|
|
else if (game.mpeg_board == "DSB2")
|
|
|
|
{
|
|
|
|
Util::FlipEndian16(dsbROM, 128*1024); // 68K program needs to be byte swapped
|
|
|
|
DSB = new(std::nothrow) CDSB2(m_config);
|
|
|
|
if (NULL == DSB)
|
|
|
|
return ErrorLog("Insufficient memory for Digital Sound Board object.");
|
|
|
|
}
|
|
|
|
else if (game.mpeg_board.empty())
|
|
|
|
ErrorLog("No MPEG board type defined in game XML for MPEG ROMs.");
|
|
|
|
else
|
|
|
|
ErrorLog("Unknown MPEG board type '%s'. Only 'DSB1' and 'DSB2' are supported.", game.mpeg_board.c_str());
|
|
|
|
if (DSB && OKAY != DSB->Init(dsbROM, mpegROM))
|
2016-04-02 21:50:40 +00:00
|
|
|
return FAIL;
|
|
|
|
}
|
|
|
|
SoundBoard.AttachDSB(DSB);
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Drive board (if present)
|
2017-03-27 03:19:15 +00:00
|
|
|
if (rom_set.get_rom("driveboard_program").size)
|
2016-04-02 21:50:40 +00:00
|
|
|
{
|
2017-03-27 03:19:15 +00:00
|
|
|
if (DriveBoard.Init(driveROM))
|
|
|
|
return FAIL;
|
2016-04-02 21:50:40 +00:00
|
|
|
}
|
|
|
|
else
|
2017-03-27 03:19:15 +00:00
|
|
|
DriveBoard.Init(NULL);
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
// Security board encryption device
|
2017-03-27 03:19:15 +00:00
|
|
|
m_cryptoDevice.Init(game.encryption_key, std::bind(&CModel3::ReadSecurityRAM, this, std::placeholders::_1));
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
// Print game information
|
2017-03-27 03:19:15 +00:00
|
|
|
std::set<std::string> extra_hw;
|
|
|
|
if (DSB)
|
|
|
|
extra_hw.insert(Util::Format() << "Digital Sound Board (Type " << game.mpeg_board << ")");
|
|
|
|
if (rom_set.get_rom("driveboard_program").size)
|
2018-01-07 14:07:59 +00:00
|
|
|
extra_hw.insert("Drive Board");
|
|
|
|
if (game.encryption_key)
|
|
|
|
extra_hw.insert("Security Board");
|
|
|
|
if (!game.version.empty())
|
|
|
|
std::cout << " Title: " << game.title << " (" << game.version << ")" << std::endl;
|
|
|
|
else
|
|
|
|
std::cout << " Title: " << game.title << std::endl;
|
|
|
|
std::cout << " ROM Set: " << game.name << std::endl;
|
|
|
|
std::cout << " Developer: " << game.manufacturer << std::endl;
|
|
|
|
std::cout << " Year: " << game.year << std::endl;
|
2017-03-27 03:19:15 +00:00
|
|
|
std::cout << " Stepping: " << game.stepping << std::endl;
|
|
|
|
if (!extra_hw.empty())
|
|
|
|
std::cout << " Extra Hardware: " << Util::Format(", ").Join(extra_hw) << std::endl;
|
|
|
|
std::cout << std::endl;
|
2016-04-02 21:50:40 +00:00
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
m_game = game;
|
2016-04-02 21:50:40 +00:00
|
|
|
return OKAY;
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
2016-03-21 23:25:29 +00:00
|
|
|
void CModel3::AttachRenderers(CRender2D *Render2DPtr, IRender3D *Render3DPtr)
|
2011-04-24 01:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
TileGen.AttachRenderer(Render2DPtr);
|
|
|
|
GPU.AttachRenderer(Render3DPtr);
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CModel3::AttachInputs(CInputs *InputsPtr)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
Inputs = InputsPtr;
|
2011-04-24 01:14:00 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
if (DriveBoard.IsAttached())
|
2017-03-27 03:19:15 +00:00
|
|
|
DriveBoard.AttachInputs(Inputs, m_game.inputs);
|
2011-09-07 07:21:56 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
DebugLog("Model 3 attached inputs\n");
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
2012-07-15 21:04:46 +00:00
|
|
|
void CModel3::AttachOutputs(COutputs *OutputsPtr)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
Outputs = OutputsPtr;
|
2017-03-27 03:19:15 +00:00
|
|
|
Outputs->SetGame(m_game);
|
2016-04-02 21:50:40 +00:00
|
|
|
Outputs->Attached();
|
2012-07-15 21:04:46 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
if (DriveBoard.IsAttached())
|
|
|
|
DriveBoard.AttachOutputs(Outputs);
|
2012-07-15 21:04:46 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
DebugLog("Model 3 attached outputs\n");
|
2012-07-15 21:04:46 +00:00
|
|
|
}
|
|
|
|
|
2011-07-31 02:37:31 +00:00
|
|
|
// Model 3 initialization. Some initialization is deferred until ROMs are loaded in LoadROMSet()
|
2011-09-08 06:34:18 +00:00
|
|
|
bool CModel3::Init(void)
|
2011-04-24 01:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
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);
|
2017-03-27 03:19:15 +00:00
|
|
|
memset(memoryPool, 0, MEMORY_POOL_SIZE);
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
// 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];
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
netRAM = &memoryPool[OFFSET_NETRAM];
|
|
|
|
netBuffer = &memoryPool[OFFSET_NETBUFFER];
|
|
|
|
#endif
|
2016-04-02 21:50:40 +00:00
|
|
|
SetCROMBank(0xFF);
|
|
|
|
|
|
|
|
// Initialize other devices (PowerPC, DSB, and security board initialized after ROMs loaded)
|
|
|
|
IRQ.Init();
|
|
|
|
PCIBridge.Init();
|
|
|
|
PCIBus.Init();
|
|
|
|
SCSI.Init(this,&IRQ,0x100); // SCSI is actually a non-maskable interrupt, so we give it a bit number outside of 8-bit range
|
|
|
|
RTC.Init();
|
|
|
|
EEPROM.Init();
|
|
|
|
if (OKAY != TileGen.Init(&IRQ))
|
|
|
|
return FAIL;
|
|
|
|
if (OKAY != GPU.Init(vrom,this,&IRQ,0x100)) // same for Real3D DMA interrupt
|
|
|
|
return FAIL;
|
|
|
|
if (OKAY != SoundBoard.Init(soundROM,sampleROM))
|
|
|
|
return FAIL;
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
if (OKAY != NetBoard.Init(netRAM, netBuffer))
|
|
|
|
return FAIL;
|
|
|
|
#endif
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
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;
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
2011-09-15 21:10:38 +00:00
|
|
|
CSoundBoard *CModel3::GetSoundBoard(void)
|
|
|
|
{
|
2018-01-07 14:07:59 +00:00
|
|
|
return &SoundBoard;
|
2011-09-15 21:10:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CDriveBoard *CModel3::GetDriveBoard(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
return &DriveBoard;
|
2011-09-15 21:10:38 +00:00
|
|
|
}
|
|
|
|
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
CNetBoard *CModel3::GetNetBoard(void)
|
|
|
|
{
|
|
|
|
return &NetBoard;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-03-27 03:19:15 +00:00
|
|
|
CModel3::CModel3(const Util::Config::Node &config)
|
|
|
|
: m_config(config),
|
|
|
|
m_multiThreaded(config["MultiThreaded"].ValueAs<bool>()),
|
|
|
|
m_gpuMultiThreaded(config["GPUMultiThreaded"].ValueAs<bool>()),
|
|
|
|
TileGen(config),
|
|
|
|
GPU(config),
|
|
|
|
SoundBoard(config),
|
2017-09-24 20:52:48 +00:00
|
|
|
DriveBoard(config),
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
2018-01-08 00:13:23 +00:00
|
|
|
NetBoard(config),
|
2018-01-07 14:07:59 +00:00
|
|
|
#endif
|
2018-01-08 00:13:23 +00:00
|
|
|
m_jtag(GPU)
|
2011-04-24 01:14:00 +00:00
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Initialize pointers so dtor can know whether to free them
|
|
|
|
memoryPool = NULL;
|
|
|
|
|
|
|
|
// Various uninitialized pointers
|
|
|
|
Inputs = NULL;
|
|
|
|
Outputs = NULL;
|
|
|
|
ram = NULL;
|
|
|
|
crom = NULL;
|
|
|
|
vrom = NULL;
|
|
|
|
soundROM = NULL;
|
|
|
|
sampleROM = NULL;
|
|
|
|
dsbROM = NULL;
|
|
|
|
mpegROM = NULL;
|
|
|
|
cromBank = NULL;
|
|
|
|
backupRAM = NULL;
|
|
|
|
securityRAM = NULL;
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
netRAM = NULL;
|
|
|
|
netBuffer = NULL;
|
|
|
|
#endif
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
DSB = NULL;
|
|
|
|
|
|
|
|
securityPtr = 0;
|
|
|
|
|
|
|
|
startedThreads = false;
|
|
|
|
pauseThreads = false;
|
|
|
|
stopThreads = false;
|
|
|
|
ppcBrdThread = NULL;
|
|
|
|
sndBrdThread = NULL;
|
|
|
|
drvBrdThread = NULL;
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
ppcBrdThreadRunning = false;
|
|
|
|
ppcBrdThreadDone = false;
|
|
|
|
sndBrdThreadRunning = false;
|
|
|
|
sndBrdThreadDone = false;
|
|
|
|
drvBrdThreadRunning = false;
|
|
|
|
drvBrdThreadDone = false;
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
syncSndBrdThread = false;
|
|
|
|
ppcBrdThreadSync = NULL;
|
|
|
|
sndBrdThreadSync = NULL;
|
|
|
|
drvBrdThreadSync = NULL;
|
2018-01-07 14:07:59 +00:00
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
notifyLock = NULL;
|
|
|
|
notifySync = NULL;
|
|
|
|
|
|
|
|
DebugLog("Built Model 3\n");
|
2011-04-24 01:14:00 +00:00
|
|
|
}
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
// Dumps a memory region to a file for debugging purposes
|
2016-05-26 04:13:22 +00:00
|
|
|
/*
|
2016-04-02 21:50:40 +00:00
|
|
|
static void Dump(const char *file, uint8_t *buf, size_t size, bool reverse32, bool reverse16)
|
|
|
|
{
|
|
|
|
FILE *fp = fopen(file, "wb");
|
|
|
|
if (NULL != fp)
|
|
|
|
{
|
|
|
|
if (reverse32)
|
2017-08-19 19:27:58 +00:00
|
|
|
Util::FlipEndian32(buf, size);
|
2016-04-02 21:50:40 +00:00
|
|
|
else if (reverse16)
|
2017-08-19 19:27:58 +00:00
|
|
|
Util::FlipEndian16(buf, size);
|
2016-04-02 21:50:40 +00:00
|
|
|
fwrite(buf, sizeof(UINT8), size, fp);
|
|
|
|
fclose(fp);
|
|
|
|
printf("dumped %s\n", file);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
printf("unable to dump %s\n", file);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2011-04-24 01:14:00 +00:00
|
|
|
CModel3::~CModel3(void)
|
|
|
|
{
|
2016-04-02 21:50:40 +00:00
|
|
|
// Debug: dump some files
|
|
|
|
//Dump("ram", ram, 0x800000, true, false);
|
|
|
|
//Dump("vrom", vrom, 0x4000000, true, false);
|
2017-08-19 19:27:58 +00:00
|
|
|
//Dump("crom", crom, 0x800000, true, false);
|
2016-04-02 21:50:40 +00:00
|
|
|
//Dump("bankedCrom", &crom[0x800000], 0x7000000, true, false);
|
|
|
|
//Dump("soundROM", soundROM, 0x80000, false, true);
|
2017-08-19 19:27:58 +00:00
|
|
|
//Dump("sampleROM", sampleROM, 0x800000, false, true);
|
2016-04-02 21:50:40 +00:00
|
|
|
|
|
|
|
// Stop all threads
|
|
|
|
StopThreads();
|
|
|
|
|
|
|
|
// Free memory
|
|
|
|
if (memoryPool != NULL)
|
|
|
|
{
|
|
|
|
delete [] memoryPool;
|
|
|
|
memoryPool = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DSB != NULL)
|
|
|
|
{
|
|
|
|
delete DSB;
|
|
|
|
DSB = NULL;
|
|
|
|
}
|
2018-01-07 14:07:59 +00:00
|
|
|
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
Inputs = NULL;
|
|
|
|
Outputs = NULL;
|
|
|
|
ram = NULL;
|
|
|
|
crom = NULL;
|
|
|
|
vrom = NULL;
|
|
|
|
soundROM = NULL;
|
|
|
|
sampleROM = NULL;
|
|
|
|
dsbROM = NULL;
|
|
|
|
mpegROM = NULL;
|
|
|
|
cromBank = NULL;
|
|
|
|
backupRAM = NULL;
|
|
|
|
securityRAM = NULL;
|
2018-01-07 14:07:59 +00:00
|
|
|
#ifdef NET_BOARD
|
|
|
|
netRAM = NULL;
|
|
|
|
netBuffer = NULL;
|
|
|
|
#endif
|
|
|
|
|
2016-04-02 21:50:40 +00:00
|
|
|
DebugLog("Destroyed Model 3\n");
|
2011-09-12 05:43:37 +00:00
|
|
|
}
|