721 lines
18 KiB
C#
721 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.ComponentModel;
|
|
|
|
using Essgee.Emulation.Configuration;
|
|
using Essgee.Emulation.CPU;
|
|
using Essgee.Emulation.Video.Nintendo;
|
|
using Essgee.Emulation.Audio;
|
|
using Essgee.Emulation.Cartridges.Nintendo;
|
|
using Essgee.Emulation.ExtDevices.Nintendo;
|
|
using Essgee.EventArguments;
|
|
using Essgee.Exceptions;
|
|
using Essgee.Utilities;
|
|
|
|
namespace Essgee.Emulation.Machines
|
|
{
|
|
[MachineIndex(5)]
|
|
public class GameBoy : IMachine
|
|
{
|
|
const double masterClock = 4194304;
|
|
const double refreshRate = 59.727500569606;
|
|
|
|
const int wramSize = 8 * 1024;
|
|
const int hramSize = 0x7F;
|
|
|
|
const int serialCycleCount = 512;
|
|
|
|
public event EventHandler<SendLogMessageEventArgs> SendLogMessage;
|
|
protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); }
|
|
|
|
public event EventHandler<EventArgs> EmulationReset;
|
|
protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); }
|
|
|
|
public event EventHandler<RenderScreenEventArgs> RenderScreen
|
|
{
|
|
add { video.RenderScreen += value; }
|
|
remove { video.RenderScreen -= value; }
|
|
}
|
|
|
|
public event EventHandler<SizeScreenEventArgs> SizeScreen
|
|
{
|
|
add { video.SizeScreen += value; }
|
|
remove { video.SizeScreen -= value; }
|
|
}
|
|
|
|
public event EventHandler<ChangeViewportEventArgs> ChangeViewport;
|
|
protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); }
|
|
|
|
public event EventHandler<PollInputEventArgs> PollInput;
|
|
protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); }
|
|
|
|
public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples
|
|
{
|
|
add { audio.EnqueueSamples += value; }
|
|
remove { audio.EnqueueSamples -= value; }
|
|
}
|
|
|
|
public event EventHandler<SaveExtraDataEventArgs> SaveExtraData;
|
|
protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); }
|
|
|
|
public event EventHandler<EventArgs> EnableRumble;
|
|
protected virtual void OnEnableRumble(EventArgs e) { EnableRumble?.Invoke(this, EventArgs.Empty); }
|
|
|
|
public string ManufacturerName => "Nintendo";
|
|
public string ModelName => "Game Boy";
|
|
public string DatFilename => "Nintendo - Game Boy.dat";
|
|
public (string Extension, string Description) FileFilter => (".gb", "Game Boy ROMs");
|
|
public bool HasBootstrap => true;
|
|
public double RefreshRate => refreshRate;
|
|
public double PixelAspectRatio => 1.0;
|
|
public (string Name, string Description)[] RuntimeOptions => video.RuntimeOptions.Concat(audio.RuntimeOptions).ToArray();
|
|
|
|
byte[] bootstrap;
|
|
IGameBoyCartridge cartridge;
|
|
byte[] wram, hram;
|
|
byte ie;
|
|
SM83 cpu;
|
|
DMGVideo video;
|
|
DMGAudio audio;
|
|
ISerialDevice serialDevice;
|
|
|
|
// FF00 - P1/JOYP
|
|
byte joypadRegister;
|
|
|
|
// FF01 - SB
|
|
byte serialData;
|
|
// FF02 - SC
|
|
bool serialUseInternalClock, serialTransferInProgress;
|
|
|
|
// FF04 - DIV
|
|
byte divider;
|
|
// FF05 - TIMA
|
|
byte timerCounter;
|
|
//
|
|
ushort clockCycleCount;
|
|
|
|
// FF06 - TMA
|
|
byte timerModulo;
|
|
|
|
// FF07 - TAC
|
|
bool timerRunning;
|
|
byte timerInputClock;
|
|
//
|
|
bool timerOverflow, timerLoading;
|
|
|
|
// FF0F - IF
|
|
bool irqVBlank, irqLCDCStatus, irqTimerOverflow, irqSerialIO, irqKeypad;
|
|
|
|
// FF50
|
|
bool bootstrapDisabled;
|
|
|
|
[Flags]
|
|
enum JoypadInputs : byte
|
|
{
|
|
Right = (1 << 0),
|
|
Left = (1 << 1),
|
|
Up = (1 << 2),
|
|
Down = (1 << 3),
|
|
A = (1 << 4),
|
|
B = (1 << 5),
|
|
Select = (1 << 6),
|
|
Start = (1 << 7)
|
|
}
|
|
|
|
JoypadInputs inputsPressed;
|
|
|
|
int serialBitsCounter, serialCycles;
|
|
|
|
int currentMasterClockCyclesInFrame, totalMasterClockCyclesInFrame;
|
|
|
|
public Configuration.GameBoy configuration { get; private set; }
|
|
|
|
public GameBoy() { }
|
|
|
|
public void Initialize()
|
|
{
|
|
bootstrap = null;
|
|
cartridge = null;
|
|
|
|
wram = new byte[wramSize];
|
|
hram = new byte[hramSize];
|
|
cpu = new SM83(ReadMemory, WriteMemory);
|
|
video = new DMGVideo(ReadMemory, cpu.RequestInterrupt);
|
|
audio = new DMGAudio();
|
|
|
|
video.EndOfScanline += (s, e) =>
|
|
{
|
|
PollInputEventArgs pollInputEventArgs = new PollInputEventArgs();
|
|
OnPollInput(pollInputEventArgs);
|
|
ParseInput(pollInputEventArgs);
|
|
};
|
|
}
|
|
|
|
public void SetConfiguration(IConfiguration config)
|
|
{
|
|
configuration = (Configuration.GameBoy)config;
|
|
|
|
ReconfigureSystem();
|
|
}
|
|
|
|
public object GetRuntimeOption(string name)
|
|
{
|
|
if (name.StartsWith("Graphics"))
|
|
return video.GetRuntimeOption(name);
|
|
else if (name.StartsWith("Audio"))
|
|
return audio.GetRuntimeOption(name);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
public void SetRuntimeOption(string name, object value)
|
|
{
|
|
if (name.StartsWith("Graphics"))
|
|
video.SetRuntimeOption(name, value);
|
|
else if (name.StartsWith("Audio"))
|
|
audio.SetRuntimeOption(name, value);
|
|
}
|
|
|
|
private void ReconfigureSystem()
|
|
{
|
|
/* Video */
|
|
video?.SetClockRate(masterClock);
|
|
video?.SetRefreshRate(refreshRate);
|
|
video?.SetRevision(0);
|
|
|
|
/* Audio */
|
|
audio?.SetSampleRate(EmuStandInfo.Configuration.SampleRate);
|
|
audio?.SetOutputChannels(2);
|
|
audio?.SetClockRate(masterClock);
|
|
audio?.SetRefreshRate(refreshRate);
|
|
|
|
/* Cartridge */
|
|
if (cartridge is GBCameraCartridge camCartridge)
|
|
camCartridge.SetImageSource(configuration.CameraSource, configuration.CameraImageFile);
|
|
|
|
/* Serial */
|
|
if (serialDevice != null)
|
|
{
|
|
serialDevice.SaveExtraData -= SaveExtraData;
|
|
serialDevice.Shutdown();
|
|
}
|
|
|
|
serialDevice = (ISerialDevice)Activator.CreateInstance(configuration.SerialDevice);
|
|
serialDevice.SaveExtraData += SaveExtraData;
|
|
serialDevice.Initialize();
|
|
|
|
/* Misc timing */
|
|
currentMasterClockCyclesInFrame = 0;
|
|
totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / refreshRate);
|
|
|
|
/* Announce viewport */
|
|
OnChangeViewport(new ChangeViewportEventArgs(video.Viewport));
|
|
}
|
|
|
|
private void LoadBootstrap()
|
|
{
|
|
if (configuration.UseBootstrap)
|
|
{
|
|
var (type, bootstrapRomData) = CartridgeLoader.Load(configuration.BootstrapRom, "Game Boy Bootstrap");
|
|
bootstrap = new byte[bootstrapRomData.Length];
|
|
Buffer.BlockCopy(bootstrapRomData, 0, bootstrap, 0, bootstrap.Length);
|
|
}
|
|
}
|
|
|
|
public void Startup()
|
|
{
|
|
LoadBootstrap();
|
|
|
|
cpu.Startup();
|
|
video.Startup();
|
|
audio.Startup();
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
cpu.Reset();
|
|
video.Reset();
|
|
audio.Reset();
|
|
|
|
if (configuration.UseBootstrap)
|
|
{
|
|
cpu.SetProgramCounter(0x0000);
|
|
cpu.SetStackPointer(0x0000);
|
|
}
|
|
else
|
|
{
|
|
cpu.SetProgramCounter(0x0100);
|
|
cpu.SetStackPointer(0xFFFE);
|
|
cpu.SetRegisterAF(0x01B0);
|
|
cpu.SetRegisterBC(0x0013);
|
|
cpu.SetRegisterDE(0x00D8);
|
|
cpu.SetRegisterHL(0x014D);
|
|
|
|
video.WritePort(0x40, 0x91);
|
|
video.WritePort(0x42, 0x00);
|
|
video.WritePort(0x43, 0x00);
|
|
video.WritePort(0x45, 0x00);
|
|
video.WritePort(0x47, 0xFC);
|
|
video.WritePort(0x48, 0xFF);
|
|
video.WritePort(0x49, 0xFF);
|
|
video.WritePort(0x4A, 0x00);
|
|
video.WritePort(0x4B, 0x00);
|
|
}
|
|
|
|
joypadRegister = 0x0F;
|
|
|
|
serialData = 0xFF;
|
|
serialUseInternalClock = serialTransferInProgress = false;
|
|
|
|
timerCounter = 0;
|
|
clockCycleCount = 0;
|
|
|
|
timerModulo = 0;
|
|
|
|
timerRunning = false;
|
|
timerInputClock = 0;
|
|
|
|
timerOverflow = timerLoading = false;
|
|
|
|
irqVBlank = irqLCDCStatus = irqTimerOverflow = irqSerialIO = irqKeypad = false;
|
|
|
|
bootstrapDisabled = !configuration.UseBootstrap;
|
|
|
|
inputsPressed = 0;
|
|
|
|
serialBitsCounter = serialCycles = 0;
|
|
|
|
OnEmulationReset(EventArgs.Empty);
|
|
}
|
|
|
|
public void Shutdown()
|
|
{
|
|
if (serialDevice != null)
|
|
{
|
|
serialDevice.SaveExtraData -= SaveExtraData;
|
|
serialDevice.Shutdown();
|
|
}
|
|
|
|
if (cartridge is MBC5Cartridge mbc5Cartridge)
|
|
mbc5Cartridge.EnableRumble -= EnableRumble;
|
|
|
|
cpu?.Shutdown();
|
|
video?.Shutdown();
|
|
audio?.Shutdown();
|
|
}
|
|
|
|
public void SetState(Dictionary<string, dynamic> state)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public Dictionary<string, dynamic> GetState()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public Dictionary<string, dynamic> GetDebugInformation()
|
|
{
|
|
var dict = new Dictionary<string, dynamic>
|
|
{
|
|
{ "CyclesInFrame", currentMasterClockCyclesInFrame },
|
|
};
|
|
|
|
return dict;
|
|
}
|
|
|
|
public void Load(byte[] romData, byte[] ramData, Type mapperType)
|
|
{
|
|
cartridge = SpecializedLoader.CreateCartridgeInstance(romData, ramData, mapperType);
|
|
|
|
if (cartridge is GBCameraCartridge camCartridge)
|
|
camCartridge.SetImageSource(configuration.CameraSource, configuration.CameraImageFile);
|
|
|
|
if (cartridge is MBC5Cartridge mbc5Cartridge)
|
|
mbc5Cartridge.EnableRumble += EnableRumble;
|
|
}
|
|
|
|
public byte[] GetCartridgeRam()
|
|
{
|
|
return cartridge?.GetRamData();
|
|
}
|
|
|
|
public bool IsCartridgeRamSaveNeeded()
|
|
{
|
|
if (cartridge == null) return false;
|
|
return cartridge.IsRamSaveNeeded();
|
|
}
|
|
|
|
public virtual void RunFrame()
|
|
{
|
|
while (currentMasterClockCyclesInFrame < totalMasterClockCyclesInFrame)
|
|
RunStep();
|
|
|
|
currentMasterClockCyclesInFrame = 0;
|
|
}
|
|
|
|
public void RunStep()
|
|
{
|
|
var clockCyclesInStep = cpu.Step();
|
|
|
|
for (var s = 0; s < clockCyclesInStep / 4; s++)
|
|
{
|
|
HandleTimerOverflow();
|
|
UpdateCycleCounter((ushort)(clockCycleCount + 4));
|
|
|
|
HandleSerialIO(4);
|
|
|
|
video.Step(4);
|
|
audio.Step(4);
|
|
cartridge?.Step(4);
|
|
|
|
currentMasterClockCyclesInFrame += 4;
|
|
}
|
|
}
|
|
|
|
private void IncrementTimer()
|
|
{
|
|
timerCounter++;
|
|
if (timerCounter == 0) timerOverflow = true;
|
|
}
|
|
|
|
private bool GetTimerBit(byte value, ushort cycles)
|
|
{
|
|
switch (value & 0b11)
|
|
{
|
|
case 0: return (cycles & (1 << 9)) != 0;
|
|
case 1: return (cycles & (1 << 3)) != 0;
|
|
case 2: return (cycles & (1 << 5)) != 0;
|
|
case 3: return (cycles & (1 << 7)) != 0;
|
|
default: throw new EmulationException("Unhandled timer state");
|
|
}
|
|
}
|
|
|
|
private void UpdateCycleCounter(ushort value)
|
|
{
|
|
if (timerRunning)
|
|
{
|
|
if (!GetTimerBit(timerInputClock, value) && GetTimerBit(timerInputClock, clockCycleCount))
|
|
IncrementTimer();
|
|
}
|
|
|
|
clockCycleCount = value;
|
|
divider = (byte)(clockCycleCount >> 8);
|
|
}
|
|
|
|
private void HandleTimerOverflow()
|
|
{
|
|
timerLoading = false;
|
|
|
|
if (timerOverflow)
|
|
{
|
|
cpu.RequestInterrupt(SM83.InterruptSource.TimerOverflow);
|
|
timerOverflow = false;
|
|
|
|
timerCounter = timerModulo;
|
|
timerLoading = true;
|
|
}
|
|
}
|
|
|
|
private void HandleSerialIO(int clockCyclesInStep)
|
|
{
|
|
if (serialTransferInProgress)
|
|
{
|
|
if (serialUseInternalClock)
|
|
{
|
|
for (var c = 0; c < clockCyclesInStep; c++)
|
|
{
|
|
serialCycles++;
|
|
if (serialCycles == serialCycleCount)
|
|
{
|
|
serialCycles = 0;
|
|
|
|
serialBitsCounter--;
|
|
|
|
var bitToSend = (byte)((serialData >> 7) & 0b1);
|
|
var bitReceived = serialDevice.ExchangeBit(serialBitsCounter, bitToSend);
|
|
serialData = (byte)((serialData << 1) | (bitReceived & 0b1));
|
|
|
|
if (serialBitsCounter == 0)
|
|
{
|
|
cpu.RequestInterrupt(SM83.InterruptSource.SerialIO);
|
|
serialTransferInProgress = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ParseInput(PollInputEventArgs eventArgs)
|
|
{
|
|
inputsPressed = 0;
|
|
|
|
/* Keyboard */
|
|
if (eventArgs.Keyboard.Contains(configuration.ControlsRight) && !eventArgs.Keyboard.Contains(configuration.ControlsLeft))
|
|
inputsPressed |= JoypadInputs.Right;
|
|
if (eventArgs.Keyboard.Contains(configuration.ControlsLeft) && !eventArgs.Keyboard.Contains(configuration.ControlsRight))
|
|
inputsPressed |= JoypadInputs.Left;
|
|
if (eventArgs.Keyboard.Contains(configuration.ControlsUp) && !eventArgs.Keyboard.Contains(configuration.ControlsDown))
|
|
inputsPressed |= JoypadInputs.Up;
|
|
if (eventArgs.Keyboard.Contains(configuration.ControlsDown) && !eventArgs.Keyboard.Contains(configuration.ControlsUp))
|
|
inputsPressed |= JoypadInputs.Down;
|
|
if (eventArgs.Keyboard.Contains(configuration.ControlsA))
|
|
inputsPressed |= JoypadInputs.A;
|
|
if (eventArgs.Keyboard.Contains(configuration.ControlsB))
|
|
inputsPressed |= JoypadInputs.B;
|
|
if (eventArgs.Keyboard.Contains(configuration.ControlsSelect))
|
|
inputsPressed |= JoypadInputs.Select;
|
|
if (eventArgs.Keyboard.Contains(configuration.ControlsStart))
|
|
inputsPressed |= JoypadInputs.Start;
|
|
|
|
/* XInput controller */
|
|
if (eventArgs.ControllerState.IsAnyRightDirectionPressed() && !eventArgs.ControllerState.IsAnyLeftDirectionPressed())
|
|
inputsPressed |= JoypadInputs.Right;
|
|
if (eventArgs.ControllerState.IsAnyLeftDirectionPressed() && !eventArgs.ControllerState.IsAnyRightDirectionPressed())
|
|
inputsPressed |= JoypadInputs.Left;
|
|
if (eventArgs.ControllerState.IsAnyUpDirectionPressed() && !eventArgs.ControllerState.IsAnyDownDirectionPressed())
|
|
inputsPressed |= JoypadInputs.Up;
|
|
if (eventArgs.ControllerState.IsAnyDownDirectionPressed() && !eventArgs.ControllerState.IsAnyUpDirectionPressed())
|
|
inputsPressed |= JoypadInputs.Down;
|
|
if (eventArgs.ControllerState.IsAPressed())
|
|
inputsPressed |= JoypadInputs.A;
|
|
if (eventArgs.ControllerState.IsXPressed() || eventArgs.ControllerState.IsBPressed())
|
|
inputsPressed |= JoypadInputs.B;
|
|
if (eventArgs.ControllerState.IsBackPressed())
|
|
inputsPressed |= JoypadInputs.Select;
|
|
if (eventArgs.ControllerState.IsStartPressed())
|
|
inputsPressed |= JoypadInputs.Start;
|
|
}
|
|
|
|
private byte ReadMemory(ushort address)
|
|
{
|
|
if (address >= 0x0000 && address <= 0x7FFF)
|
|
{
|
|
if (configuration.UseBootstrap && address < 0x0100 && !bootstrapDisabled)
|
|
return bootstrap[address & 0x00FF];
|
|
else
|
|
return (cartridge != null ? cartridge.Read(address) : (byte)0xFF);
|
|
}
|
|
else if (address >= 0x8000 && address <= 0x9FFF)
|
|
{
|
|
return video.ReadVram(address);
|
|
}
|
|
else if (address >= 0xA000 && address <= 0xBFFF)
|
|
{
|
|
return (cartridge != null ? cartridge.Read(address) : (byte)0xFF);
|
|
}
|
|
else if (address >= 0xC000 && address <= 0xFDFF)
|
|
{
|
|
return wram[address & (wramSize - 1)];
|
|
}
|
|
else if (address >= 0xFE00 && address <= 0xFE9F)
|
|
{
|
|
return video.ReadOam(address);
|
|
}
|
|
else if (address >= 0xFF00 && address <= 0xFF7F)
|
|
{
|
|
return ReadIo(address);
|
|
}
|
|
else if (address >= 0xFF80 && address <= 0xFFFE)
|
|
{
|
|
return hram[address - 0xFF80];
|
|
}
|
|
else if (address == 0xFFFF)
|
|
{
|
|
return ie;
|
|
}
|
|
|
|
/* Cannot read from address, return 0 */
|
|
return 0x00;
|
|
}
|
|
|
|
private byte ReadIo(ushort address)
|
|
{
|
|
if ((address & 0xFFF0) == 0xFF40)
|
|
return video.ReadPort((byte)(address & 0xFF));
|
|
else if ((address & 0xFFF0) == 0xFF10 || (address & 0xFFF0) == 0xFF20 || (address & 0xFFF0) == 0xFF30)
|
|
return audio.ReadPort((byte)(address & 0xFF));
|
|
else
|
|
{
|
|
switch (address)
|
|
{
|
|
case 0xFF00:
|
|
// P1/JOYP
|
|
return joypadRegister;
|
|
|
|
case 0xFF01:
|
|
// SB
|
|
return serialData;
|
|
|
|
case 0xFF02:
|
|
// SC
|
|
return (byte)(
|
|
0x7E |
|
|
(serialUseInternalClock ? (1 << 0) : 0) |
|
|
(serialTransferInProgress ? (1 << 7) : 0));
|
|
|
|
case 0xFF04:
|
|
// DIV
|
|
return divider;
|
|
|
|
case 0xFF05:
|
|
// TIMA
|
|
return timerCounter;
|
|
|
|
case 0xFF06:
|
|
// TMA
|
|
return timerModulo;
|
|
|
|
case 0xFF07:
|
|
// TAC
|
|
return (byte)(
|
|
0xF8 |
|
|
(timerRunning ? (1 << 2) : 0) |
|
|
(timerInputClock & 0b11));
|
|
|
|
case 0xFF0F:
|
|
// IF
|
|
return (byte)(
|
|
0xE0 |
|
|
(irqVBlank ? (1 << 0) : 0) |
|
|
(irqLCDCStatus ? (1 << 1) : 0) |
|
|
(irqTimerOverflow ? (1 << 2) : 0) |
|
|
(irqSerialIO ? (1 << 3) : 0) |
|
|
(irqKeypad ? (1 << 4) : 0));
|
|
|
|
case 0xFF50:
|
|
// Bootstrap disable
|
|
return (byte)(
|
|
0xFE |
|
|
(bootstrapDisabled ? (1 << 0) : 0));
|
|
|
|
default:
|
|
return 0xFF;// throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void WriteMemory(ushort address, byte value)
|
|
{
|
|
if (address >= 0x0000 && address <= 0x7FFF)
|
|
{
|
|
cartridge?.Write(address, value);
|
|
}
|
|
else if (address >= 0x8000 && address <= 0x9FFF)
|
|
{
|
|
video.WriteVram(address, value);
|
|
}
|
|
else if (address >= 0xA000 && address <= 0xBFFF)
|
|
{
|
|
cartridge?.Write(address, value);
|
|
}
|
|
else if (address >= 0xC000 && address <= 0xFDFF)
|
|
{
|
|
wram[address & (wramSize - 1)] = value;
|
|
}
|
|
else if (address >= 0xFE00 && address <= 0xFE9F)
|
|
{
|
|
video.WriteOam(address, value);
|
|
}
|
|
else if (address >= 0xFF00 && address <= 0xFF7F)
|
|
{
|
|
WriteIo(address, value);
|
|
}
|
|
else if (address >= 0xFF80 && address <= 0xFFFE)
|
|
{
|
|
hram[address - 0xFF80] = value;
|
|
}
|
|
else if (address == 0xFFFF)
|
|
{
|
|
ie = value;
|
|
}
|
|
}
|
|
|
|
private void WriteIo(ushort address, byte value)
|
|
{
|
|
if ((address & 0xFFF0) == 0xFF40)
|
|
video.WritePort((byte)(address & 0xFF), value);
|
|
else if ((address & 0xFFF0) == 0xFF10 || (address & 0xFFF0) == 0xFF20 || (address & 0xFFF0) == 0xFF30)
|
|
audio.WritePort((byte)(address & 0xFF), value);
|
|
else
|
|
{
|
|
switch (address)
|
|
{
|
|
case 0xFF00:
|
|
joypadRegister = (byte)((joypadRegister & 0xC0) | (value & 0x30));
|
|
if ((joypadRegister & 0x30) == 0x20)
|
|
joypadRegister |= (byte)(((byte)inputsPressed & 0x0F) ^ 0x0F);
|
|
else if ((joypadRegister & 0x30) == 0x10)
|
|
joypadRegister |= (byte)((((byte)inputsPressed & 0xF0) >> 4) ^ 0x0F);
|
|
else
|
|
joypadRegister = 0xFF;
|
|
break;
|
|
|
|
case 0xFF01:
|
|
serialData = value;
|
|
break;
|
|
|
|
case 0xFF02:
|
|
serialUseInternalClock = (value & (1 << 0)) != 0;
|
|
serialTransferInProgress = (value & (1 << 7)) != 0;
|
|
|
|
if (serialTransferInProgress) serialCycles = 0;
|
|
serialBitsCounter = 8;
|
|
break;
|
|
|
|
case 0xFF04:
|
|
UpdateCycleCounter(0);
|
|
break;
|
|
|
|
case 0xFF05:
|
|
if (!timerLoading)
|
|
{
|
|
timerCounter = value;
|
|
timerOverflow = false;
|
|
}
|
|
break;
|
|
|
|
case 0xFF06:
|
|
timerModulo = value;
|
|
if (timerLoading)
|
|
timerCounter = value;
|
|
break;
|
|
|
|
case 0xFF07:
|
|
{
|
|
var newTimerRunning = (value & (1 << 2)) != 0;
|
|
var newTimerInputClock = (byte)(value & 0b11);
|
|
|
|
var oldBit = timerRunning && GetTimerBit(timerInputClock, clockCycleCount);
|
|
var newBit = newTimerRunning && GetTimerBit(newTimerInputClock, clockCycleCount);
|
|
|
|
if (oldBit && !newBit)
|
|
IncrementTimer();
|
|
|
|
timerRunning = newTimerRunning;
|
|
timerInputClock = newTimerInputClock;
|
|
}
|
|
break;
|
|
|
|
case 0xFF0F:
|
|
irqVBlank = (value & (1 << 0)) != 0;
|
|
irqLCDCStatus = (value & (1 << 1)) != 0;
|
|
irqTimerOverflow = (value & (1 << 2)) != 0;
|
|
irqSerialIO = (value & (1 << 3)) != 0;
|
|
irqKeypad = (value & (1 << 4)) != 0;
|
|
break;
|
|
|
|
case 0xFF50:
|
|
if (!bootstrapDisabled)
|
|
bootstrapDisabled = (value & (1 << 0)) != 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|