GBHawk Updates and Sync
This commit is contained in:
parent
b2d453441e
commit
7749e1407e
|
@ -0,0 +1,213 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.BufferExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Audio Emulation
|
||||
public class Audio : ISoundProvider
|
||||
{
|
||||
public GBHawk Core { get; set; }
|
||||
|
||||
public const int NR10 = 0;
|
||||
public const int NR11 = 1;
|
||||
public const int NR12 = 2;
|
||||
public const int NR13 = 3;
|
||||
public const int NR14 = 4;
|
||||
public const int NR21 = 5;
|
||||
public const int NR22 = 6;
|
||||
public const int NR23 = 7;
|
||||
public const int NR24 = 8;
|
||||
public const int NR30 = 9;
|
||||
public const int NR31 = 10;
|
||||
public const int NR32 = 11;
|
||||
public const int NR33 = 12;
|
||||
public const int NR34 = 13;
|
||||
public const int NR41 = 14;
|
||||
public const int NR42 = 15;
|
||||
public const int NR43 = 16;
|
||||
public const int NR44 = 17;
|
||||
public const int NR50 = 18;
|
||||
public const int NR51 = 19;
|
||||
public const int NR52 = 20;
|
||||
|
||||
public static int[] unused_bits = new int[] { 0x80, 0x3F, 0x00, 0xFF, 0xBF,
|
||||
0x3F, 0x00, 0xFF, 0xBF,
|
||||
0x7F, 0xFF, 0x9F, 0xFF, 0xBF,
|
||||
0xFF, 0x00, 0x00, 0xBF,
|
||||
0x00, 0x00, 0x70};
|
||||
|
||||
public byte[] Audio_Regs = new byte[21];
|
||||
|
||||
public byte[] Wave_RAM = new byte [16];
|
||||
|
||||
|
||||
public byte ReadReg(int addr)
|
||||
{
|
||||
byte ret = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF10: ret = (byte)(Audio_Regs[NR10] | unused_bits[NR10]); break; // NR10 (sweep)
|
||||
case 0xFF11: ret = (byte)(Audio_Regs[NR11] | unused_bits[NR11]); break; // NR11 (sound length / wave pattern duty %)
|
||||
case 0xFF12: ret = (byte)(Audio_Regs[NR12] | unused_bits[NR12]); break; // NR12 (envelope)
|
||||
case 0xFF13: ret = (byte)(Audio_Regs[NR13] | unused_bits[NR13]); break; // NR13 (freq low)
|
||||
case 0xFF14: ret = (byte)(Audio_Regs[NR14] | unused_bits[NR14]); break; // NR14 (freq hi)
|
||||
case 0xFF16: ret = (byte)(Audio_Regs[NR21] | unused_bits[NR21]); break; // NR21 (sound length / wave pattern duty %)
|
||||
case 0xFF17: ret = (byte)(Audio_Regs[NR22] | unused_bits[NR22]); break; // NR22 (envelope)
|
||||
case 0xFF18: ret = (byte)(Audio_Regs[NR23] | unused_bits[NR23]); break; // NR23 (freq low)
|
||||
case 0xFF19: ret = (byte)(Audio_Regs[NR24] | unused_bits[NR24]); break; // NR24 (freq hi)
|
||||
case 0xFF1A: ret = (byte)(Audio_Regs[NR30] | unused_bits[NR30]); break; // NR30 (on/off)
|
||||
case 0xFF1B: ret = (byte)(Audio_Regs[NR31] | unused_bits[NR31]); break; // NR31 (length)
|
||||
case 0xFF1C: ret = (byte)(Audio_Regs[NR32] | unused_bits[NR32]); break; // NR32 (level output)
|
||||
case 0xFF1D: ret = (byte)(Audio_Regs[NR33] | unused_bits[NR33]); break; // NR33 (freq low)
|
||||
case 0xFF1E: ret = (byte)(Audio_Regs[NR34] | unused_bits[NR34]); break; // NR34 (freq hi)
|
||||
case 0xFF20: ret = (byte)(Audio_Regs[NR41] | unused_bits[NR41]); break; // NR41 (sweep)
|
||||
case 0xFF21: ret = (byte)(Audio_Regs[NR42] | unused_bits[NR42]); break; // NR42 (sweep)
|
||||
case 0xFF22: ret = (byte)(Audio_Regs[NR43] | unused_bits[NR43]); break; // NR43 (sweep)
|
||||
case 0xFF23: ret = (byte)(Audio_Regs[NR44] | unused_bits[NR44]); break; // NR44 (sweep)
|
||||
case 0xFF24: ret = (byte)(Audio_Regs[NR50] | unused_bits[NR50]); break; // NR50 (sweep)
|
||||
case 0xFF25: ret = (byte)(Audio_Regs[NR51] | unused_bits[NR51]); break; // NR51 (sweep)
|
||||
case 0xFF26: ret = (byte)(Audio_Regs[NR52] | unused_bits[NR52]); break; // NR52 (sweep)
|
||||
|
||||
// wave ram table
|
||||
case 0xFF30:
|
||||
case 0xFF31:
|
||||
case 0xFF32:
|
||||
case 0xFF33:
|
||||
case 0xFF34:
|
||||
case 0xFF35:
|
||||
case 0xFF36:
|
||||
case 0xFF37:
|
||||
case 0xFF38:
|
||||
case 0xFF39:
|
||||
case 0xFF3A:
|
||||
case 0xFF3B:
|
||||
case 0xFF3C:
|
||||
case 0xFF3D:
|
||||
case 0xFF3E:
|
||||
case 0xFF3F:
|
||||
ret = Wave_RAM[addr & 0x0F];
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void WriteReg(int addr, byte value)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF10: Audio_Regs[NR10] = value; break; // NR10 (sweep)
|
||||
case 0xFF11: Audio_Regs[NR11] = value; break; // NR11 (sound length / wave pattern duty %)
|
||||
case 0xFF12: Audio_Regs[NR12] = value; break; // NR12 (envelope)
|
||||
case 0xFF13: Audio_Regs[NR13] = value; break; // NR13 (freq low)
|
||||
case 0xFF14: Audio_Regs[NR14] = value; break; // NR14 (freq hi)
|
||||
case 0xFF16: Audio_Regs[NR21] = value; break; // NR21 (sound length / wave pattern duty %)
|
||||
case 0xFF17: Audio_Regs[NR22] = value; break; // NR22 (envelope)
|
||||
case 0xFF18: Audio_Regs[NR23] = value; break; // NR23 (freq low)
|
||||
case 0xFF19: Audio_Regs[NR24] = value; break; // NR24 (freq hi)
|
||||
case 0xFF1A: Audio_Regs[NR30] = value; break; // NR30 (on/off)
|
||||
case 0xFF1B: Audio_Regs[NR31] = value; break; // NR31 (length)
|
||||
case 0xFF1C: Audio_Regs[NR32] = value; break; // NR32 (level output)
|
||||
case 0xFF1D: Audio_Regs[NR33] = value; break; // NR33 (freq low)
|
||||
case 0xFF1E: Audio_Regs[NR34] = value; break; // NR34 (freq hi)
|
||||
case 0xFF20: Audio_Regs[NR41] = value; break; // NR41 (sweep)
|
||||
case 0xFF21: Audio_Regs[NR42] = value; break; // NR42 (sweep)
|
||||
case 0xFF22: Audio_Regs[NR43] = value; break; // NR43 (sweep)
|
||||
case 0xFF23: Audio_Regs[NR44] = value; break; // NR44 (sweep)
|
||||
case 0xFF24: Audio_Regs[NR50] = value; break; // NR50 (sweep)
|
||||
case 0xFF25: Audio_Regs[NR51] = value; break; // NR51 (sweep)
|
||||
case 0xFF26: Audio_Regs[NR52] = value; break; // NR52 (sweep)
|
||||
|
||||
// wave ram table
|
||||
case 0xFF30:
|
||||
case 0xFF31:
|
||||
case 0xFF32:
|
||||
case 0xFF33:
|
||||
case 0xFF34:
|
||||
case 0xFF35:
|
||||
case 0xFF36:
|
||||
case 0xFF37:
|
||||
case 0xFF38:
|
||||
case 0xFF39:
|
||||
case 0xFF3A:
|
||||
case 0xFF3B:
|
||||
case 0xFF3C:
|
||||
case 0xFF3D:
|
||||
case 0xFF3E:
|
||||
case 0xFF3F:
|
||||
Wave_RAM[addr & 0x0F] = value;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void tick()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
Wave_RAM = new byte[16];
|
||||
|
||||
Audio_Regs = new byte[21];
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync("Audio_Regs", ref Audio_Regs, false);
|
||||
ser.Sync("Wave_Ram", ref Wave_RAM, false);
|
||||
|
||||
}
|
||||
|
||||
#region audio
|
||||
|
||||
public bool CanProvideAsync => false;
|
||||
|
||||
public int _spf;
|
||||
public int AudioClocks;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode != SyncSoundMode.Sync)
|
||||
{
|
||||
throw new InvalidOperationException("Only Sync mode is supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
short[] ret = new short[_spf * 2];
|
||||
nsamp = _spf;
|
||||
GetSamples(ret);
|
||||
samples = ret;
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new NotSupportedException("Async is not available");
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
AudioClocks = 0;
|
||||
}
|
||||
|
||||
// Exposing this as GetSamplesAsync would allow this to provide async sound
|
||||
// However, it does nothing special for async sound so I don't see a point
|
||||
private void GetSamples(short[] samples)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -112,11 +112,12 @@ namespace BizHawk.Client.Common
|
|||
case "SNES":
|
||||
return SystemInfo.SNES;
|
||||
case "GB":
|
||||
/*
|
||||
if ((Emulator as IGameboyCommon).IsCGBMode())
|
||||
{
|
||||
return SystemInfo.GBC;
|
||||
}
|
||||
|
||||
*/
|
||||
return SystemInfo.GB;
|
||||
case "A26":
|
||||
return SystemInfo.Atari2600;
|
||||
|
|
|
@ -13,6 +13,7 @@ using BizHawk.Emulation.Cores.Computers.AppleII;
|
|||
using BizHawk.Emulation.Cores.Computers.Commodore64;
|
||||
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
|
||||
using BizHawk.Emulation.Cores.Nintendo.Gameboy;
|
||||
using BizHawk.Emulation.Cores.Nintendo.GBHawk;
|
||||
using BizHawk.Emulation.Cores.Nintendo.SNES;
|
||||
using BizHawk.Emulation.Cores.PCEngine;
|
||||
using BizHawk.Emulation.Cores.Sega.Saturn;
|
||||
|
@ -938,7 +939,8 @@ namespace BizHawk.Client.Common
|
|||
case "GBC":
|
||||
if (!Global.Config.GB_AsSGB)
|
||||
{
|
||||
core = CoreInventory.Instance["GB", "Gambatte"];
|
||||
core = CoreInventory.Instance["GB", "GBHawk"];
|
||||
//core = CoreInventory.Instance["GB", "Gambatte"];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -581,6 +581,34 @@
|
|||
<DependentUpon>VBANext.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Nintendo\GBA\VBARegisterHelper.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Audio.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawk.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawk.IDebuggable.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawk.IEmulator.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawk.IInputPollable.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawk.IMemoryDomains.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawk.ISaveRam.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawk.ISettable.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawk.IStatable.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawkControllerDeck.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\GBHawkControllers.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\HW_Registers.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\MapperBase.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_Camera.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_Default.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_HuC1.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_HuC3.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_MBC1.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_MBC2.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_MBC3.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_MBC5.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_MBC6.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_MBC7.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_MMM01.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Mappers\Mapper_TAMA5.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\MemoryMap.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\PPU.cs" />
|
||||
<Compile Include="Consoles\Nintendo\GBHawk\Timer.cs" />
|
||||
<Compile Include="Consoles\Nintendo\N64\N64SyncSettings.GLideN64.cs" />
|
||||
<Compile Include="Consoles\Nintendo\N64\N64.IDebuggable.cs">
|
||||
<DependentUpon>N64.cs</DependentUpon>
|
||||
|
@ -1161,6 +1189,14 @@
|
|||
<Compile Include="CPUs\CP1610\CP1610.Disassembler.cs" />
|
||||
<Compile Include="CPUs\CP1610\CP1610.Execute.cs" />
|
||||
<Compile Include="CPUs\HuC6280\HuC6280_CDL.cs" />
|
||||
<Compile Include="CPUs\LR35902\Execute.cs" />
|
||||
<Compile Include="CPUs\LR35902\Interrupts.cs" />
|
||||
<Compile Include="CPUs\LR35902\LR35902.cs" />
|
||||
<Compile Include="CPUs\LR35902\NewDisassembler.cs" />
|
||||
<Compile Include="CPUs\LR35902\Operations.cs" />
|
||||
<Compile Include="CPUs\LR35902\Registers.cs" />
|
||||
<Compile Include="CPUs\LR35902\Tables_Direct.cs" />
|
||||
<Compile Include="CPUs\LR35902\Tables_Indirect.cs" />
|
||||
<Compile Include="CPUs\W65816\Disassembler.cs" />
|
||||
<Compile Include="CPUs\68000\Diassembler.cs" />
|
||||
<Compile Include="CPUs\68000\Instructions\BitArithemetic.cs" />
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
|||
}
|
||||
}
|
||||
|
||||
public IMemoryCallbackSystem MemoryCallbacks { get; } = new MemoryCallbackSystem();
|
||||
public IMemoryCallbackSystem MemoryCallbacks { get; } = new MemoryCallbackSystem(new[] { "System Bus" });
|
||||
|
||||
public bool CanStep(StepType type)
|
||||
{
|
||||
|
|
|
@ -136,7 +136,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
|||
|
||||
private void ExecFetch(ushort addr)
|
||||
{
|
||||
MemoryCallbacks.CallExecutes(addr);
|
||||
MemoryCallbacks.CallExecutes(addr, "System Bus");
|
||||
}
|
||||
|
||||
private void Setup_Mapper()
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
|||
{
|
||||
public byte ReadMemory(ushort addr)
|
||||
{
|
||||
MemoryCallbacks.CallReads(addr);
|
||||
MemoryCallbacks.CallReads(addr, "System Bus");
|
||||
|
||||
if (addr < 0x100)
|
||||
{
|
||||
|
@ -101,7 +101,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
|||
|
||||
public void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
MemoryCallbacks.CallWrites(addr);
|
||||
MemoryCallbacks.CallWrites(addr, "System Bus");
|
||||
|
||||
if (addr < 0x100)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk : IDebuggable
|
||||
{
|
||||
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
return new Dictionary<string, RegisterValue>
|
||||
{
|
||||
/*
|
||||
["A"] = cpu.A,
|
||||
["X"] = cpu.X,
|
||||
["Y"] = cpu.Y,
|
||||
["S"] = cpu.S,
|
||||
["PC"] = cpu.PC,
|
||||
["Flag C"] = cpu.FlagC,
|
||||
["Flag Z"] = cpu.FlagZ,
|
||||
["Flag I"] = cpu.FlagI,
|
||||
["Flag D"] = cpu.FlagD,
|
||||
["Flag B"] = cpu.FlagB,
|
||||
["Flag V"] = cpu.FlagV,
|
||||
["Flag N"] = cpu.FlagN,
|
||||
["Flag T"] = cpu.FlagT
|
||||
*/
|
||||
};
|
||||
}
|
||||
|
||||
public void SetCpuRegister(string register, int value)
|
||||
{
|
||||
switch (register)
|
||||
{
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
case "A":
|
||||
//cpu.A = (byte)value;
|
||||
break;
|
||||
case "X":
|
||||
//cpu.X = (byte)value;
|
||||
break;
|
||||
case "Y":
|
||||
//cpu.Y = (byte)value;
|
||||
break;
|
||||
case "S":
|
||||
//cpu.S = (byte)value;
|
||||
break;
|
||||
case "PC":
|
||||
//cpu.PC = (ushort)value;
|
||||
break;
|
||||
case "Flag I":
|
||||
//cpu.FlagI = value > 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IMemoryCallbackSystem MemoryCallbacks { get; } = new MemoryCallbackSystem();
|
||||
|
||||
public bool CanStep(StepType type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[FeatureNotImplemented]
|
||||
public void Step(StepType type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public int TotalExecutedCycles
|
||||
{
|
||||
get { return cpu.TotalExecutedCycles; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
using BizHawk.Common.NumberExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk : IEmulator, IVideoProvider
|
||||
{
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
|
||||
public ControllerDefinition ControllerDefinition => _controllerDeck.Definition;
|
||||
|
||||
public byte controller_state;
|
||||
public byte controller_state_old;
|
||||
public bool in_vblank_old;
|
||||
public bool in_vblank;
|
||||
public bool vblank_rise;
|
||||
|
||||
public void FrameAdvance(IController controller, bool render, bool rendersound)
|
||||
{
|
||||
//Console.WriteLine("-----------------------FRAME-----------------------");
|
||||
|
||||
if (_tracer.Enabled)
|
||||
{
|
||||
cpu.TraceCallback = s => _tracer.Put(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
cpu.TraceCallback = null;
|
||||
}
|
||||
|
||||
_frame++;
|
||||
|
||||
if (controller.IsPressed("Power"))
|
||||
{
|
||||
// it seems that theMachine.Reset() doesn't clear ram, etc
|
||||
// this should leave hsram intact but clear most other things
|
||||
HardReset();
|
||||
}
|
||||
|
||||
_islag = true;
|
||||
|
||||
GetControllerState(controller);
|
||||
|
||||
do_frame();
|
||||
|
||||
if (_islag)
|
||||
{
|
||||
_lagcount++;
|
||||
}
|
||||
}
|
||||
|
||||
public void do_frame()
|
||||
{
|
||||
// gameboy frames can be variable lengths
|
||||
// we want to end a frame when VBlank turns from false to true
|
||||
int ticker = 0;
|
||||
while (!vblank_rise && (ticker < 100000))
|
||||
{
|
||||
audio.tick();
|
||||
timer.tick_1();
|
||||
ppu.tick();
|
||||
|
||||
cpu.ExecuteOne(ref REG_FF0F, REG_FFFF);
|
||||
|
||||
timer.tick_2();
|
||||
|
||||
|
||||
if (in_vblank && !in_vblank_old)
|
||||
{
|
||||
vblank_rise = true;
|
||||
}
|
||||
ticker++;
|
||||
in_vblank_old = in_vblank;
|
||||
}
|
||||
|
||||
vblank_rise = false;
|
||||
}
|
||||
|
||||
public void RunCPUCycle()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void GetControllerState(IController controller)
|
||||
{
|
||||
InputCallbacks.Call();
|
||||
controller_state = _controllerDeck.ReadPort1(controller);
|
||||
|
||||
// set interrupt flag if a pin went from high to low
|
||||
if (controller_state < controller_state_old)
|
||||
{
|
||||
if (REG_FFFF.Bit(4)) { cpu.FlagI = true; }
|
||||
REG_FF0F |= 0x10;
|
||||
}
|
||||
|
||||
controller_state_old = controller_state;
|
||||
}
|
||||
|
||||
public void serial_transfer()
|
||||
{
|
||||
if (serial_control.Bit(7) && !serial_start_old)
|
||||
{
|
||||
serial_start_old = true;
|
||||
|
||||
// transfer out on byte of data
|
||||
// needs to be modelled
|
||||
}
|
||||
}
|
||||
|
||||
public int Frame => _frame;
|
||||
|
||||
public string SystemId => "GB";
|
||||
|
||||
public bool DeterministicEmulation { get; set; }
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
_frame = 0;
|
||||
_lagcount = 0;
|
||||
_islag = false;
|
||||
}
|
||||
|
||||
public CoreComm CoreComm { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
#region Video provider
|
||||
|
||||
public int _frameHz = 60;
|
||||
|
||||
public int[] _vidbuffer;
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
return _vidbuffer;
|
||||
}
|
||||
|
||||
public int VirtualWidth => 160;
|
||||
public int VirtualHeight => 144;
|
||||
public int BufferWidth => 160;
|
||||
public int BufferHeight => 144;
|
||||
public int BackgroundColor => unchecked((int)0xFF000000);
|
||||
public int VsyncNumerator => _frameHz;
|
||||
public int VsyncDenominator => 1;
|
||||
|
||||
public static readonly uint[] color_palette = { 0xFFFFFFFF , 0xFFAAAAAA, 0xFF555555, 0xFF000000 };
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk : IInputPollable
|
||||
{
|
||||
public int LagCount
|
||||
{
|
||||
get { return _lagcount; }
|
||||
set { _lagcount = value; }
|
||||
}
|
||||
|
||||
public bool IsLagFrame
|
||||
{
|
||||
get { return _islag; }
|
||||
set { _islag = value; }
|
||||
}
|
||||
|
||||
public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem();
|
||||
|
||||
public bool _islag = true;
|
||||
private int _lagcount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk
|
||||
{
|
||||
private IMemoryDomains MemoryDomains;
|
||||
|
||||
public void SetupMemoryDomains()
|
||||
{
|
||||
var domains = new List<MemoryDomain>
|
||||
{
|
||||
new MemoryDomainDelegate(
|
||||
"Main RAM",
|
||||
RAM.Length,
|
||||
MemoryDomain.Endian.Little,
|
||||
addr => RAM[addr],
|
||||
(addr, value) => RAM[addr] = value,
|
||||
1),
|
||||
new MemoryDomainDelegate(
|
||||
"Zero Page RAM",
|
||||
ZP_RAM.Length,
|
||||
MemoryDomain.Endian.Little,
|
||||
addr => ZP_RAM[addr],
|
||||
(addr, value) => ZP_RAM[addr] = value,
|
||||
1),
|
||||
new MemoryDomainDelegate(
|
||||
"System Bus",
|
||||
0X10000,
|
||||
MemoryDomain.Endian.Little,
|
||||
addr => PeekSystemBus(addr),
|
||||
(addr, value) => PokeSystemBus(addr, value),
|
||||
1)
|
||||
};
|
||||
|
||||
MemoryDomains = new MemoryDomainList(domains);
|
||||
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(MemoryDomains);
|
||||
}
|
||||
|
||||
private byte PeekSystemBus(long addr)
|
||||
{
|
||||
ushort addr2 = (ushort)(addr & 0xFFFF);
|
||||
return ReadMemory(addr2);
|
||||
}
|
||||
|
||||
private void PokeSystemBus(long addr, byte value)
|
||||
{
|
||||
ushort addr2 = (ushort)(addr & 0xFFFF);
|
||||
WriteMemory(addr2, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk : ISaveRam
|
||||
{
|
||||
public byte[] CloneSaveRam()
|
||||
{
|
||||
return (byte[])_sram.Clone();
|
||||
}
|
||||
|
||||
public void StoreSaveRam(byte[] data)
|
||||
{
|
||||
Buffer.BlockCopy(data, 0, _sram, 0, data.Length);
|
||||
}
|
||||
|
||||
public bool SaveRamModified
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk : IEmulator, IStatable, ISettable<GBHawk.GBSettings, GBHawk.GBSyncSettings>
|
||||
{
|
||||
public GBSettings GetSettings()
|
||||
{
|
||||
return _settings.Clone();
|
||||
}
|
||||
|
||||
public GBSyncSettings GetSyncSettings()
|
||||
{
|
||||
return _syncSettings.Clone();
|
||||
}
|
||||
|
||||
public bool PutSettings(GBSettings o)
|
||||
{
|
||||
_settings = o;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PutSyncSettings(GBSyncSettings o)
|
||||
{
|
||||
bool ret = GBSyncSettings.NeedsReboot(_syncSettings, o);
|
||||
_syncSettings = o;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private GBSettings _settings = new GBSettings();
|
||||
public GBSyncSettings _syncSettings = new GBSyncSettings();
|
||||
|
||||
public class GBSettings
|
||||
{
|
||||
public GBSettings Clone()
|
||||
{
|
||||
return (GBSettings)MemberwiseClone();
|
||||
}
|
||||
}
|
||||
|
||||
public class GBSyncSettings
|
||||
{
|
||||
private string _port1 = GBHawkControllerDeck.DefaultControllerName;
|
||||
|
||||
[JsonIgnore]
|
||||
public string Port1
|
||||
{
|
||||
get { return _port1; }
|
||||
set
|
||||
{
|
||||
if (!GBHawkControllerDeck.ValidControllerTypes.ContainsKey(value))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid controller type: " + value);
|
||||
}
|
||||
|
||||
_port1 = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GBSyncSettings Clone()
|
||||
{
|
||||
return (GBSyncSettings)MemberwiseClone();
|
||||
}
|
||||
|
||||
public static bool NeedsReboot(GBSyncSettings x, GBSyncSettings y)
|
||||
{
|
||||
return !DeepEquality.DeepEquals(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
using System.IO;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk : IStatable
|
||||
{
|
||||
public bool BinarySaveStatesPreferred => true;
|
||||
|
||||
public void SaveStateText(TextWriter writer)
|
||||
{
|
||||
SyncState(new Serializer(writer));
|
||||
}
|
||||
|
||||
public void LoadStateText(TextReader reader)
|
||||
{
|
||||
SyncState(new Serializer(reader));
|
||||
}
|
||||
|
||||
public void SaveStateBinary(BinaryWriter bw)
|
||||
{
|
||||
SyncState(new Serializer(bw));
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader br)
|
||||
{
|
||||
SyncState(new Serializer(br));
|
||||
}
|
||||
|
||||
public byte[] SaveStateBinary()
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
BinaryWriter bw = new BinaryWriter(ms);
|
||||
SaveStateBinary(bw);
|
||||
bw.Flush();
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private void SyncState(Serializer ser)
|
||||
{
|
||||
byte[] core = null;
|
||||
if (ser.IsWriter)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
ms.Close();
|
||||
core = ms.ToArray();
|
||||
}
|
||||
cpu.SyncState(ser);
|
||||
mapper.SyncState(ser);
|
||||
timer.SyncState(ser);
|
||||
ppu.SyncState(ser);
|
||||
audio.SyncState(ser);
|
||||
|
||||
ser.BeginSection("Gameboy");
|
||||
ser.Sync("core", ref core, false);
|
||||
ser.Sync("Lag", ref _lagcount);
|
||||
ser.Sync("Frame", ref _frame);
|
||||
ser.Sync("IsLag", ref _islag);
|
||||
_controllerDeck.SyncState(ser);
|
||||
|
||||
ser.Sync("controller_state", ref controller_state);
|
||||
ser.Sync("controller_state_old", ref controller_state_old);
|
||||
ser.Sync("in_vblank", ref in_vblank);
|
||||
ser.Sync("in_vblank_old", ref in_vblank_old);
|
||||
ser.Sync("vblank_rise", ref vblank_rise);
|
||||
ser.Sync("GB_bios_register", ref GB_bios_register);
|
||||
ser.Sync("input_register", ref input_register);
|
||||
|
||||
ser.Sync("serial_control", ref serial_control);
|
||||
ser.Sync("serial_data_out", ref serial_data_out);
|
||||
ser.Sync("serial_data_in", ref serial_data_in);
|
||||
ser.Sync("serial_start_old", ref serial_start_old);
|
||||
|
||||
ser.Sync("REG_FFFF", ref REG_FFFF);
|
||||
ser.Sync("REG_FF0F", ref REG_FF0F);
|
||||
ser.Sync("enable_VBL", ref enable_VBL);
|
||||
ser.Sync("enable_LCDC", ref enable_PRS);
|
||||
ser.Sync("enable_TIMO", ref enable_TIMO);
|
||||
ser.Sync("enable_SER", ref enable_SER);
|
||||
ser.Sync("enable_STAT", ref enable_STAT);
|
||||
|
||||
// memory domains
|
||||
ser.Sync("RAM", ref RAM, false);
|
||||
ser.Sync("ZP_RAM", ref ZP_RAM, false);
|
||||
ser.Sync("CHR_RAM", ref CHR_RAM, false);
|
||||
ser.Sync("BG_map_1", ref BG_map_1, false);
|
||||
ser.Sync("BG_map_2", ref BG_map_2, false);
|
||||
ser.Sync("OAM", ref OAM, false);
|
||||
// probably a better way to do this
|
||||
if (cart_RAM != null)
|
||||
{
|
||||
ser.Sync("cart_RAM", ref cart_RAM, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Common.BufferExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Common.Components.LR35902;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
[Core(
|
||||
"GBHawk",
|
||||
"",
|
||||
isPorted: false,
|
||||
isReleased: true)]
|
||||
[ServiceNotApplicable(typeof(ISettable<,>), typeof(IDriveLight))]
|
||||
public partial class GBHawk : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable,
|
||||
ISettable<GBHawk.GBSettings, GBHawk.GBSyncSettings>
|
||||
{
|
||||
// this register controls whether or not the GB BIOS is mapped into memory
|
||||
public byte GB_bios_register;
|
||||
|
||||
public byte input_register;
|
||||
|
||||
public byte serial_control;
|
||||
public byte serial_data_out;
|
||||
public byte serial_data_in;
|
||||
public bool serial_start_old;
|
||||
|
||||
// The unused bits in this register are still read/writable
|
||||
public byte REG_FFFF;
|
||||
// The unused bits in this register (interrupt flags) are always set
|
||||
public byte REG_FF0F = 0xE0;
|
||||
public bool enable_VBL;
|
||||
public bool enable_STAT;
|
||||
public bool enable_TIMO;
|
||||
public bool enable_SER;
|
||||
public bool enable_PRS;
|
||||
|
||||
|
||||
// memory domains
|
||||
public byte[] RAM = new byte[0x2000];
|
||||
public byte[] ZP_RAM = new byte[0x80];
|
||||
public byte[] CHR_RAM = new byte[0x1800];
|
||||
public byte[] BG_map_1 = new byte[0x400];
|
||||
public byte[] BG_map_2 = new byte[0x400];
|
||||
public byte[] OAM = new byte[0xA0];
|
||||
|
||||
public readonly byte[] _rom;
|
||||
public readonly byte[] _bios;
|
||||
public readonly byte[] _sram = new byte[2048];
|
||||
public readonly byte[] header = new byte[0x50];
|
||||
|
||||
public byte[] cart_RAM;
|
||||
|
||||
private int _frame = 0;
|
||||
|
||||
public MapperBase mapper;
|
||||
|
||||
private readonly ITraceable _tracer;
|
||||
|
||||
public LR35902 cpu;
|
||||
public PPU ppu;
|
||||
public Timer timer;
|
||||
public Audio audio;
|
||||
|
||||
[CoreConstructor("GB")]
|
||||
public GBHawk(CoreComm comm, GameInfo game, byte[] rom, /*string gameDbFn,*/ object settings, object syncSettings)
|
||||
{
|
||||
var ser = new BasicServiceProvider(this);
|
||||
|
||||
cpu = new LR35902
|
||||
{
|
||||
ReadMemory = ReadMemory,
|
||||
WriteMemory = WriteMemory,
|
||||
PeekMemory = ReadMemory,
|
||||
DummyReadMemory = ReadMemory,
|
||||
OnExecFetch = ExecFetch
|
||||
};
|
||||
ppu = new PPU();
|
||||
timer = new Timer();
|
||||
audio = new Audio();
|
||||
|
||||
CoreComm = comm;
|
||||
|
||||
_settings = (GBSettings)settings ?? new GBSettings();
|
||||
_syncSettings = (GBSyncSettings)syncSettings ?? new GBSyncSettings();
|
||||
_controllerDeck = new GBHawkControllerDeck(_syncSettings.Port1);
|
||||
|
||||
byte[] Bios = comm.CoreFileProvider.GetFirmware("GB", "World", false, "BIOS Not Found, Cannot Load");
|
||||
_bios = Bios;
|
||||
|
||||
Buffer.BlockCopy(rom, 0x100, header, 0, 0x50);
|
||||
|
||||
string hash_md5 = null;
|
||||
hash_md5 = "md5:" + rom.HashMD5(0, rom.Length);
|
||||
Console.WriteLine(hash_md5);
|
||||
|
||||
_rom = rom;
|
||||
Setup_Mapper();
|
||||
|
||||
_frameHz = 60;
|
||||
|
||||
timer.Core = this;
|
||||
audio.Core = this;
|
||||
ppu.Core = this;
|
||||
|
||||
ser.Register<IVideoProvider>(this);
|
||||
ser.Register<ISoundProvider>(audio);
|
||||
ServiceProvider = ser;
|
||||
|
||||
_tracer = new TraceBuffer { Header = cpu.TraceHeader };
|
||||
ser.Register<ITraceable>(_tracer);
|
||||
|
||||
SetupMemoryDomains();
|
||||
HardReset();
|
||||
}
|
||||
|
||||
public DisplayType Region => DisplayType.NTSC;
|
||||
|
||||
private readonly GBHawkControllerDeck _controllerDeck;
|
||||
|
||||
private void HardReset()
|
||||
{
|
||||
GB_bios_register = 0; // bios enable
|
||||
in_vblank = true; // we start off in vblank since the LCD is off
|
||||
in_vblank_old = true;
|
||||
|
||||
Register_Reset();
|
||||
timer.Reset();
|
||||
ppu.Reset();
|
||||
|
||||
cpu.SetCallbacks(ReadMemory, ReadMemory, ReadMemory, WriteMemory);
|
||||
|
||||
_vidbuffer = new int[VirtualWidth * VirtualHeight];
|
||||
}
|
||||
|
||||
private void ExecFetch(ushort addr)
|
||||
{
|
||||
MemoryCallbacks.CallExecutes(addr);
|
||||
}
|
||||
|
||||
private void Setup_Mapper()
|
||||
{
|
||||
// setup up mapper based on header entry
|
||||
switch (header[0x47])
|
||||
{
|
||||
case 0x0: mapper = new MapperDefault(); break;
|
||||
case 0x1: mapper = new MapperMBC1(); break;
|
||||
case 0x2: mapper = new MapperMBC1(); break;
|
||||
case 0x3: mapper = new MapperMBC1(); break;
|
||||
case 0x5: mapper = new MapperMBC2(); break;
|
||||
case 0x6: mapper = new MapperMBC2(); break;
|
||||
case 0x8: mapper = new MapperDefault(); break;
|
||||
case 0x9: mapper = new MapperDefault(); break;
|
||||
case 0xB: mapper = new MapperMMM01(); break;
|
||||
case 0xC: mapper = new MapperMMM01(); break;
|
||||
case 0xD: mapper = new MapperMMM01(); break;
|
||||
case 0xF: mapper = new MapperMBC3(); break;
|
||||
case 0x10: mapper = new MapperMBC3(); break;
|
||||
case 0x11: mapper = new MapperMBC3(); break;
|
||||
case 0x12: mapper = new MapperMBC3(); break;
|
||||
case 0x13: mapper = new MapperMBC3(); break;
|
||||
case 0x19: mapper = new MapperMBC5(); break;
|
||||
case 0x1A: mapper = new MapperMBC5(); break;
|
||||
case 0x1B: mapper = new MapperMBC5(); break;
|
||||
case 0x1C: mapper = new MapperMBC5(); break;
|
||||
case 0x1D: mapper = new MapperMBC5(); break;
|
||||
case 0x1E: mapper = new MapperMBC5(); break;
|
||||
case 0x20: mapper = new MapperMBC6(); break;
|
||||
case 0x22: mapper = new MapperMBC7(); break;
|
||||
case 0xFC: mapper = new MapperCamera(); break;
|
||||
case 0xFD: mapper = new MapperTAMA5(); break;
|
||||
case 0xFE: mapper = new MapperHuC3(); break;
|
||||
case 0xFF: mapper = new MapperHuC1(); break;
|
||||
|
||||
case 0x4:
|
||||
case 0x7:
|
||||
case 0xA:
|
||||
case 0xE:
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
case 0x16:
|
||||
case 0x17:
|
||||
case 0x18:
|
||||
case 0x1F:
|
||||
case 0x21:
|
||||
default:
|
||||
// mapper not implemented
|
||||
throw new Exception("Mapper not implemented");
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
Console.Write("Mapper: ");
|
||||
Console.WriteLine(header[0x47]);
|
||||
|
||||
cart_RAM = null;
|
||||
|
||||
switch (header[0x49])
|
||||
{
|
||||
case 1:
|
||||
cart_RAM = new byte[0x800];
|
||||
break;
|
||||
case 2:
|
||||
cart_RAM = new byte[0x2000];
|
||||
break;
|
||||
case 3:
|
||||
cart_RAM = new byte[0x8000];
|
||||
break;
|
||||
case 4:
|
||||
cart_RAM = new byte[0x20000];
|
||||
break;
|
||||
case 5:
|
||||
cart_RAM = new byte[0x10000];
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
mapper.Core = this;
|
||||
mapper.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.ReflectionExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public class GBHawkControllerDeck
|
||||
{
|
||||
public GBHawkControllerDeck(string controller1Name)
|
||||
{
|
||||
if (!ValidControllerTypes.ContainsKey(controller1Name))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid controller type: " + controller1Name);
|
||||
}
|
||||
|
||||
Port1 = (IPort)Activator.CreateInstance(ValidControllerTypes[controller1Name], 1);
|
||||
|
||||
Definition = new ControllerDefinition
|
||||
{
|
||||
Name = Port1.Definition.Name,
|
||||
BoolButtons = Port1.Definition.BoolButtons
|
||||
.Concat(new[]
|
||||
{
|
||||
"Power",
|
||||
"Reset",
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
|
||||
Definition.FloatControls.AddRange(Port1.Definition.FloatControls);
|
||||
|
||||
Definition.FloatRanges.AddRange(Port1.Definition.FloatRanges);
|
||||
}
|
||||
|
||||
public byte ReadPort1(IController c)
|
||||
{
|
||||
return Port1.Read(c);
|
||||
}
|
||||
|
||||
public ControllerDefinition Definition { get; }
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("Port1");
|
||||
Port1.SyncState(ser);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
private readonly IPort Port1;
|
||||
|
||||
private static Dictionary<string, Type> _controllerTypes;
|
||||
|
||||
public static Dictionary<string, Type> ValidControllerTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_controllerTypes == null)
|
||||
{
|
||||
_controllerTypes = typeof(GBHawkControllerDeck).Assembly
|
||||
.GetTypes()
|
||||
.Where(t => typeof(IPort).IsAssignableFrom(t))
|
||||
.Where(t => !t.IsAbstract && !t.IsInterface)
|
||||
.ToDictionary(tkey => tkey.DisplayName());
|
||||
}
|
||||
|
||||
return _controllerTypes;
|
||||
}
|
||||
}
|
||||
|
||||
public static string DefaultControllerName => typeof(StandardControls).DisplayName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a GB add on
|
||||
/// </summary>
|
||||
public interface IPort
|
||||
{
|
||||
byte Read(IController c);
|
||||
|
||||
ControllerDefinition Definition { get; }
|
||||
|
||||
void SyncState(Serializer ser);
|
||||
|
||||
int PortNum { get; }
|
||||
}
|
||||
|
||||
[DisplayName("Standard controls")]
|
||||
public class StandardControls : IPort
|
||||
{
|
||||
public StandardControls(int portNum)
|
||||
{
|
||||
PortNum = portNum;
|
||||
Definition = new ControllerDefinition
|
||||
{
|
||||
Name = "Game Boy",
|
||||
BoolButtons = BaseDefinition
|
||||
.Select(b => "P" + PortNum + " " + b)
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public int PortNum { get; }
|
||||
|
||||
public ControllerDefinition Definition { get; }
|
||||
|
||||
public byte Read(IController c)
|
||||
{
|
||||
byte result = 0xFF;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (c.IsPressed(Definition.BoolButtons[i]))
|
||||
{
|
||||
result -= (byte)(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static readonly string[] BaseDefinition =
|
||||
{
|
||||
"Right", "Left", "Up", "Down", "A", "B", "Select", "Start"
|
||||
};
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
//nothing
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
using System;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk
|
||||
{
|
||||
public byte Read_Registers(int addr)
|
||||
{
|
||||
byte ret = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
// Read Input
|
||||
case 0xFF00:
|
||||
_islag = false;
|
||||
|
||||
input_register &= 0xF0;
|
||||
if ((input_register & 0x30) == 0x20)
|
||||
{
|
||||
input_register |= (byte)(controller_state & 0xF);
|
||||
}
|
||||
else if ((input_register & 0x30) == 0x10)
|
||||
{
|
||||
input_register |= (byte)((controller_state & 0xF0) >> 4);
|
||||
}
|
||||
else if ((input_register & 0x30) == 0x30)
|
||||
{
|
||||
// if both polls are set, then a bit is zero if either or both pins are zero
|
||||
byte temp = (byte)((controller_state & 0xF) & ((controller_state & 0xF0) >> 4));
|
||||
input_register |= temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
input_register |= 0xF;
|
||||
}
|
||||
ret = input_register;
|
||||
break;
|
||||
|
||||
// Serial data port
|
||||
case 0xFF01:
|
||||
ret = serial_data_in;
|
||||
break;
|
||||
|
||||
// Serial port control
|
||||
case 0xFF02:
|
||||
ret = serial_control;
|
||||
break;
|
||||
|
||||
// Timer Registers
|
||||
case 0xFF04:
|
||||
case 0xFF05:
|
||||
case 0xFF06:
|
||||
case 0xFF07:
|
||||
ret = timer.ReadReg(addr);
|
||||
break;
|
||||
|
||||
// Interrupt flags
|
||||
case 0xFF0F:
|
||||
ret = REG_FF0F;
|
||||
break;
|
||||
|
||||
// audio regs
|
||||
case 0xFF10:
|
||||
case 0xFF11:
|
||||
case 0xFF12:
|
||||
case 0xFF13:
|
||||
case 0xFF14:
|
||||
case 0xFF16:
|
||||
case 0xFF17:
|
||||
case 0xFF18:
|
||||
case 0xFF19:
|
||||
case 0xFF1A:
|
||||
case 0xFF1B:
|
||||
case 0xFF1C:
|
||||
case 0xFF1D:
|
||||
case 0xFF1E:
|
||||
case 0xFF20:
|
||||
case 0xFF21:
|
||||
case 0xFF22:
|
||||
case 0xFF23:
|
||||
case 0xFF24:
|
||||
case 0xFF25:
|
||||
case 0xFF26:
|
||||
case 0xFF30:
|
||||
case 0xFF31:
|
||||
case 0xFF32:
|
||||
case 0xFF33:
|
||||
case 0xFF34:
|
||||
case 0xFF35:
|
||||
case 0xFF36:
|
||||
case 0xFF37:
|
||||
case 0xFF38:
|
||||
case 0xFF39:
|
||||
case 0xFF3A:
|
||||
case 0xFF3B:
|
||||
case 0xFF3C:
|
||||
case 0xFF3D:
|
||||
case 0xFF3E:
|
||||
case 0xFF3F:
|
||||
ret = audio.ReadReg(addr);
|
||||
break;
|
||||
|
||||
// PPU Regs
|
||||
case 0xFF40:
|
||||
case 0xFF41:
|
||||
case 0xFF42:
|
||||
case 0xFF43:
|
||||
case 0xFF44:
|
||||
case 0xFF45:
|
||||
case 0xFF46:
|
||||
case 0xFF47:
|
||||
case 0xFF48:
|
||||
case 0xFF49:
|
||||
case 0xFF4A:
|
||||
case 0xFF4B:
|
||||
ret = ppu.ReadReg(addr);
|
||||
break;
|
||||
|
||||
// Bios control register. Not sure if it is readable
|
||||
case 0xFF50:
|
||||
ret = 0xFF;
|
||||
break;
|
||||
|
||||
// interrupt control register
|
||||
case 0xFFFF:
|
||||
ret = REG_FFFF;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = 0xFF;
|
||||
break;
|
||||
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Write_Registers(int addr, byte value)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
// select input
|
||||
case 0xFF00:
|
||||
input_register = (byte)(0xC0 | (value & 0x3F)); // top 2 bits always 1
|
||||
break;
|
||||
|
||||
// Serial data port
|
||||
case 0xFF01:
|
||||
serial_data_out = value;
|
||||
serial_data_in = serial_data_out;
|
||||
break;
|
||||
|
||||
// Serial port control
|
||||
case 0xFF02:
|
||||
serial_control = (byte)(0x7E | (value & 0x81)); // middle six bits always 1
|
||||
break;
|
||||
|
||||
// Timer Registers
|
||||
case 0xFF04:
|
||||
case 0xFF05:
|
||||
case 0xFF06:
|
||||
case 0xFF07:
|
||||
timer.WriteReg(addr, value);
|
||||
break;
|
||||
|
||||
// Interrupt flags
|
||||
case 0xFF0F:
|
||||
REG_FF0F = (byte)(0xE0 | value);
|
||||
|
||||
// check if enabling any of the bits triggered an IRQ
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (REG_FFFF.Bit(i) && REG_FF0F.Bit(i))
|
||||
{
|
||||
cpu.FlagI = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no bits are in common between flags and enables, de-assert the IRQ
|
||||
if (((REG_FF0F & 0x1F) & REG_FFFF) == 0) { cpu.FlagI = false; }
|
||||
|
||||
break;
|
||||
|
||||
// audio regs
|
||||
case 0xFF10:
|
||||
case 0xFF11:
|
||||
case 0xFF12:
|
||||
case 0xFF13:
|
||||
case 0xFF14:
|
||||
case 0xFF16:
|
||||
case 0xFF17:
|
||||
case 0xFF18:
|
||||
case 0xFF19:
|
||||
case 0xFF1A:
|
||||
case 0xFF1B:
|
||||
case 0xFF1C:
|
||||
case 0xFF1D:
|
||||
case 0xFF1E:
|
||||
case 0xFF20:
|
||||
case 0xFF21:
|
||||
case 0xFF22:
|
||||
case 0xFF23:
|
||||
case 0xFF24:
|
||||
case 0xFF25:
|
||||
case 0xFF26:
|
||||
case 0xFF30:
|
||||
case 0xFF31:
|
||||
case 0xFF32:
|
||||
case 0xFF33:
|
||||
case 0xFF34:
|
||||
case 0xFF35:
|
||||
case 0xFF36:
|
||||
case 0xFF37:
|
||||
case 0xFF38:
|
||||
case 0xFF39:
|
||||
case 0xFF3A:
|
||||
case 0xFF3B:
|
||||
case 0xFF3C:
|
||||
case 0xFF3D:
|
||||
case 0xFF3E:
|
||||
case 0xFF3F:
|
||||
audio.WriteReg(addr, value);
|
||||
break;
|
||||
|
||||
// PPU Regs
|
||||
case 0xFF40:
|
||||
case 0xFF41:
|
||||
case 0xFF42:
|
||||
case 0xFF43:
|
||||
case 0xFF44:
|
||||
case 0xFF45:
|
||||
case 0xFF46:
|
||||
case 0xFF47:
|
||||
case 0xFF48:
|
||||
case 0xFF49:
|
||||
case 0xFF4A:
|
||||
case 0xFF4B:
|
||||
ppu.WriteReg(addr, value);
|
||||
break;
|
||||
|
||||
// Bios control register. Writing 1 permanently disables BIOS until a power cycle occurs
|
||||
case 0xFF50:
|
||||
//Console.WriteLine(value);
|
||||
if (GB_bios_register != 1)
|
||||
{
|
||||
GB_bios_register = value;
|
||||
}
|
||||
break;
|
||||
|
||||
// interrupt control register
|
||||
case 0xFFFF:
|
||||
REG_FFFF = value;
|
||||
enable_VBL = REG_FFFF.Bit(0);
|
||||
enable_STAT = REG_FFFF.Bit(1);
|
||||
enable_TIMO = REG_FFFF.Bit(2);
|
||||
enable_SER = REG_FFFF.Bit(3);
|
||||
enable_PRS = REG_FFFF.Bit(4);
|
||||
|
||||
// check if enabling any of the bits triggered an IRQ
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if (REG_FFFF.Bit(i) && REG_FF0F.Bit(i))
|
||||
{
|
||||
cpu.FlagI = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no bits are in common between flags and enables, de-assert the IRQ
|
||||
if (((REG_FF0F & 0x1F) & REG_FFFF) == 0) { cpu.FlagI = false; }
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Register_Reset()
|
||||
{
|
||||
input_register = 0xCF; // not reading any input
|
||||
serial_control = 0x7E;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using BizHawk.Common;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public class MapperBase
|
||||
{
|
||||
public GBHawk Core { get; set; }
|
||||
|
||||
public virtual byte ReadMemory(ushort addr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual byte PeekMemory(ushort addr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public virtual void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void SyncState(Serializer ser)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperCamera : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperDefault : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperHuC1 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperHuC3 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// MBC1 with bank switching and RAM
|
||||
public class MapperMBC1 : MapperBase
|
||||
{
|
||||
public int ROM_bank;
|
||||
public int RAM_bank;
|
||||
public bool RAM_enable;
|
||||
public bool sel_mode;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
ROM_bank = 1;
|
||||
RAM_bank = 0;
|
||||
RAM_enable = false;
|
||||
sel_mode = false;
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x4000)
|
||||
{
|
||||
// lowest bank is fixed
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[(addr - 0x4000) + ROM_bank * 0x4000];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
if (RAM_enable)
|
||||
{
|
||||
return Core.cart_RAM[(addr - 0xA000) + RAM_bank * 0x2000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
if (addr < 0x2000)
|
||||
{
|
||||
RAM_enable = ((value & 0xA) == 0xA) ? true : false;
|
||||
}
|
||||
else if (addr < 0x4000)
|
||||
{
|
||||
value &= 0x1F;
|
||||
|
||||
// writing zero gets translated to 1
|
||||
if (value == 0) { value = 1; }
|
||||
|
||||
ROM_bank &= 0xE0;
|
||||
ROM_bank |= value;
|
||||
}
|
||||
else if (addr < 0x6000)
|
||||
{
|
||||
if (sel_mode)
|
||||
{
|
||||
RAM_bank = value & 0x3;
|
||||
}
|
||||
else
|
||||
{
|
||||
ROM_bank &= 0x1F;
|
||||
ROM_bank |= ((value & 3) << 5);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sel_mode = (value & 1) > 0;
|
||||
|
||||
if (sel_mode)
|
||||
{
|
||||
ROM_bank &= 0x1F;
|
||||
}
|
||||
else
|
||||
{
|
||||
RAM_bank = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
if (RAM_enable)
|
||||
{
|
||||
Core.cart_RAM[(addr - 0xA000) + RAM_bank * 0x2000] = value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
|
||||
public override void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync("ROM_Bank", ref ROM_bank);
|
||||
ser.Sync("RAM_Bank", ref RAM_bank);
|
||||
ser.Sync("RAM_enable", ref RAM_enable);
|
||||
ser.Sync("sel_mode", ref sel_mode);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperMBC2 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperMBC3 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperMBC5 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperMBC6 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperMBC7 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperMMM01 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Default mapper with no bank switching
|
||||
public class MapperTAMA5 : MapperBase
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
// nothing to initialize
|
||||
}
|
||||
|
||||
public override byte ReadMemory(ushort addr)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
return Core._rom[addr];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
return Core.cart_RAM[addr - 0xA000];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override byte PeekMemory(ushort addr)
|
||||
{
|
||||
return ReadMemory(addr);
|
||||
}
|
||||
|
||||
public override void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x8000)
|
||||
{
|
||||
// no mapping hardware available
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Core.cart_RAM != null)
|
||||
{
|
||||
Core.cart_RAM[addr - 0xA000] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void PokeMemory(ushort addr, byte value)
|
||||
{
|
||||
WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
TODO:
|
||||
Official Mappers
|
||||
Unofficial Mappers
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Common.BufferExtensions;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
|
||||
/*
|
||||
$FFFF Interrupt Enable Flag
|
||||
$FF80-$FFFE Zero Page - 127 bytes
|
||||
$FF00-$FF7F Hardware I/O Registers
|
||||
$FEA0-$FEFF Unusable Memory
|
||||
$FE00-$FE9F OAM - Object Attribute Memory
|
||||
$E000-$FDFF Echo RAM - Reserved, Do Not Use
|
||||
$D000-$DFFF Internal RAM - Bank 1-7 (switchable - CGB only)
|
||||
$C000-$CFFF Internal RAM - Bank 0 (fixed)
|
||||
$A000-$BFFF Cartridge RAM (If Available)
|
||||
$9C00-$9FFF BG Map Data 2
|
||||
$9800-$9BFF BG Map Data 1
|
||||
$8000-$97FF Character RAM
|
||||
$4000-$7FFF Cartridge ROM - Switchable Banks 1-xx
|
||||
$0150-$3FFF Cartridge ROM - Bank 0 (fixed)
|
||||
$0100-$014F Cartridge Header Area
|
||||
$0000-$00FF Restart and Interrupt Vectors
|
||||
*/
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public partial class GBHawk
|
||||
{
|
||||
public byte ReadMemory(ushort addr)
|
||||
{
|
||||
MemoryCallbacks.CallReads(addr);
|
||||
|
||||
if (addr < 0x100)
|
||||
{
|
||||
// return Either BIOS ROM or Game ROM
|
||||
if ((GB_bios_register & 0x1) == 0)
|
||||
{
|
||||
return _bios[addr]; // Return BIOS
|
||||
}
|
||||
else
|
||||
{
|
||||
return mapper.ReadMemory(addr);
|
||||
}
|
||||
}
|
||||
else if (addr < 0x8000)
|
||||
{
|
||||
return mapper.ReadMemory(addr);
|
||||
}
|
||||
else if (addr < 0x9800)
|
||||
{
|
||||
return CHR_RAM[addr - 0x8000];
|
||||
}
|
||||
else if (addr < 0x9C00)
|
||||
{
|
||||
return BG_map_1[addr - 0x9800];
|
||||
}
|
||||
else if (addr < 0xA000)
|
||||
{
|
||||
return BG_map_2[addr - 0x9C00];
|
||||
}
|
||||
else if (addr < 0xC000)
|
||||
{
|
||||
return mapper.ReadMemory(addr);
|
||||
}
|
||||
else if (addr < 0xE000)
|
||||
{
|
||||
return RAM[addr - 0xC000];
|
||||
}
|
||||
else if (addr < 0xFE00)
|
||||
{
|
||||
return RAM[addr - 0xE000];
|
||||
}
|
||||
else if (addr < 0xFEA0 && ppu.OAM_access)
|
||||
{
|
||||
return OAM[addr - 0xFE00];
|
||||
}
|
||||
else if (addr < 0xFF00)
|
||||
{
|
||||
// unmapped memory, returns 0xFF
|
||||
return 0xFF;
|
||||
}
|
||||
else if (addr < 0xFF80)
|
||||
{
|
||||
return Read_Registers(addr);
|
||||
}
|
||||
else if (addr < 0xFFFF)
|
||||
{
|
||||
return ZP_RAM[addr - 0xFF80];
|
||||
}
|
||||
else
|
||||
{
|
||||
return Read_Registers(addr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
MemoryCallbacks.CallWrites(addr);
|
||||
|
||||
if (addr < 0x100)
|
||||
{
|
||||
// return Either BIOS ROM or Game ROM
|
||||
if ((GB_bios_register & 0x1) == 0)
|
||||
{
|
||||
// Can't write to BIOS region
|
||||
}
|
||||
else
|
||||
{
|
||||
mapper.WriteMemory(addr, value);
|
||||
}
|
||||
}
|
||||
else if (addr < 0x8000)
|
||||
{
|
||||
mapper.WriteMemory(addr, value);
|
||||
}
|
||||
else if (addr < 0x9800)
|
||||
{
|
||||
CHR_RAM[addr - 0x8000] = value;
|
||||
}
|
||||
else if (addr < 0x9C00)
|
||||
{
|
||||
BG_map_1[addr - 0x9800] = value;
|
||||
}
|
||||
else if (addr < 0xA000)
|
||||
{
|
||||
BG_map_2[addr - 0x9C00] = value;
|
||||
}
|
||||
else if (addr < 0xC000)
|
||||
{
|
||||
mapper.WriteMemory(addr, value);
|
||||
}
|
||||
else if (addr < 0xE000)
|
||||
{
|
||||
RAM[addr - 0xC000] = value;
|
||||
}
|
||||
else if (addr < 0xFE00)
|
||||
{
|
||||
RAM[addr - 0xE000] = value;
|
||||
}
|
||||
else if (addr < 0xFEA0 && ppu.OAM_access)
|
||||
{
|
||||
OAM[addr - 0xFE00] = value;
|
||||
}
|
||||
else if (addr < 0xFF00)
|
||||
{
|
||||
// unmapped, writing has no effect
|
||||
}
|
||||
else if (addr < 0xFF80)
|
||||
{
|
||||
Write_Registers(addr, value);
|
||||
}
|
||||
else if (addr < 0xFFFF)
|
||||
{
|
||||
ZP_RAM[addr - 0xFF80] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Write_Registers(addr, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,922 @@
|
|||
using System;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
public class PPU
|
||||
{
|
||||
public GBHawk Core { get; set; }
|
||||
|
||||
//public byte BGP_l;
|
||||
|
||||
// register variables
|
||||
public byte LCDC;
|
||||
public byte STAT;
|
||||
public byte scroll_y;
|
||||
public byte scroll_x;
|
||||
public byte LY;
|
||||
public byte LY_inc;
|
||||
public byte LYC;
|
||||
public byte DMA_addr;
|
||||
public byte BGP;
|
||||
public byte obj_pal_0;
|
||||
public byte obj_pal_1;
|
||||
public byte window_y;
|
||||
public byte window_x;
|
||||
public bool DMA_start;
|
||||
public int DMA_clock;
|
||||
public int DMA_inc;
|
||||
public byte DMA_byte;
|
||||
|
||||
// state variables
|
||||
public int cycle;
|
||||
public bool LYC_INT;
|
||||
public bool HBL_INT;
|
||||
public bool VBL_INT;
|
||||
public bool OAM_INT;
|
||||
public bool LCD_was_off;
|
||||
public bool stat_line;
|
||||
public bool stat_line_old;
|
||||
public bool hbl_set_once;
|
||||
// OAM scan
|
||||
public bool OAM_access;
|
||||
public int OAM_scan_index;
|
||||
public int SL_sprites_index;
|
||||
public int[] SL_sprites = new int[40];
|
||||
public int write_sprite;
|
||||
// render
|
||||
public bool VRAM_access;
|
||||
public int read_case;
|
||||
public int internal_cycle;
|
||||
public int y_tile;
|
||||
public int y_scroll_offset;
|
||||
public int x_tile;
|
||||
public int x_scroll_offset;
|
||||
public int tile_byte;
|
||||
public int sprite_fetch_cycles;
|
||||
public bool fetch_sprite;
|
||||
public int temp_fetch;
|
||||
public int tile_inc;
|
||||
public bool pre_render;
|
||||
public byte[] tile_data = new byte[2];
|
||||
public byte[] tile_data_latch = new byte[2];
|
||||
public int latch_counter;
|
||||
public bool latch_new_data;
|
||||
public int render_counter;
|
||||
public int render_offset;
|
||||
public int pixel_counter;
|
||||
public int pixel;
|
||||
public byte[] sprite_data = new byte[2];
|
||||
public byte[] sprite_sel = new byte[2];
|
||||
public int sl_use_index;
|
||||
public bool no_sprites;
|
||||
public int sprite_fetch_index;
|
||||
public int[] SL_sprites_ordered = new int[40]; // (x_end, data_low, data_high, attr)
|
||||
public int index_used;
|
||||
public int sprite_ordered_index;
|
||||
public int bottom_index;
|
||||
|
||||
public byte ReadReg(int addr)
|
||||
{
|
||||
byte ret = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF40: ret = LCDC; break; // LCDC
|
||||
case 0xFF41: ret = STAT; break; // STAT
|
||||
case 0xFF42: ret = scroll_y; break; // SCY
|
||||
case 0xFF43: ret = scroll_x; break; // SCX
|
||||
case 0xFF44: ret = LY; break; // LY
|
||||
case 0xFF45: ret = LYC; break; // LYC
|
||||
case 0xFF46: /*ret = DMA_addr; */ break; // DMA (not readable?)
|
||||
case 0xFF47: ret = BGP; break; // BGP
|
||||
case 0xFF48: ret = obj_pal_0; break; // OBP0
|
||||
case 0xFF49: ret = obj_pal_1; break; // OBP1
|
||||
case 0xFF4A: ret = window_y; break; // WY
|
||||
case 0xFF4B: ret = window_x; break; // WX
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void WriteReg(int addr, byte value)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF40: // LCDC
|
||||
LCDC = value;
|
||||
break;
|
||||
case 0xFF41: // STAT
|
||||
STAT = (byte)((value & 0xF8) | (STAT & 7) | 0x80);
|
||||
break;
|
||||
case 0xFF42: // SCY
|
||||
scroll_y = value;
|
||||
break;
|
||||
case 0xFF43: // SCX
|
||||
scroll_x = value;
|
||||
// calculate the column number of the tile to start with
|
||||
x_tile = (int)Math.Floor((float)(scroll_x) / 8);
|
||||
break;
|
||||
case 0xFF44: // LY
|
||||
LY = 0; /*reset*/
|
||||
break;
|
||||
case 0xFF45: // LYC
|
||||
LYC = value;
|
||||
if (LY != LYC) { STAT &= 0xFB; }
|
||||
break;
|
||||
case 0xFF46: // DMA
|
||||
DMA_addr = value;
|
||||
DMA_start = true;
|
||||
DMA_clock = 0;
|
||||
DMA_inc = 0;
|
||||
break;
|
||||
case 0xFF47: // BGP
|
||||
BGP = value;
|
||||
break;
|
||||
case 0xFF48: // OBP0
|
||||
obj_pal_0 = value;
|
||||
break;
|
||||
case 0xFF49: // OBP1
|
||||
obj_pal_1 = value;
|
||||
break;
|
||||
case 0xFF4A: // WY
|
||||
window_y = value;
|
||||
break;
|
||||
case 0xFF4B: // WX
|
||||
window_x = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void tick()
|
||||
{
|
||||
// tick DMA
|
||||
if (DMA_start)
|
||||
{
|
||||
if (DMA_clock >= 4)
|
||||
{
|
||||
OAM_access = false;
|
||||
if ((DMA_clock % 4) == 1)
|
||||
{
|
||||
// the cpu can't access memory during this time, but we still need the ppu to be able to.
|
||||
DMA_start = false;
|
||||
DMA_byte = Core.ReadMemory((ushort)((DMA_addr << 8) + DMA_inc));
|
||||
DMA_start = true;
|
||||
}
|
||||
else if ((DMA_clock % 4) == 3)
|
||||
{
|
||||
if ((DMA_inc % 4) == 3)
|
||||
{
|
||||
Core.OAM[DMA_inc] = DMA_byte;
|
||||
}
|
||||
else
|
||||
{
|
||||
Core.OAM[DMA_inc] = DMA_byte;
|
||||
}
|
||||
|
||||
if (DMA_inc < (0xA0 - 1)) { DMA_inc++; }
|
||||
}
|
||||
}
|
||||
|
||||
DMA_clock++;
|
||||
|
||||
if (DMA_clock==648)
|
||||
{
|
||||
DMA_start = false;
|
||||
OAM_access = true;
|
||||
}
|
||||
}
|
||||
|
||||
// the ppu only does anything if it is turned on via bit 7 of LCDC
|
||||
if (LCDC.Bit(7))
|
||||
{
|
||||
// exit vblank if LCD went from off to on
|
||||
if (LCD_was_off)
|
||||
{
|
||||
//VBL_INT = false;
|
||||
Core.in_vblank = false;
|
||||
LCD_was_off = false;
|
||||
|
||||
// we exit vblank into mode 0 for 4 cycles
|
||||
// but no hblank interrupt, presumably this only happens transitioning from mode 3 to 0
|
||||
STAT &= 0xFC;
|
||||
}
|
||||
|
||||
// the VBL stat is continuously asserted
|
||||
if ((LY >= 144))
|
||||
{
|
||||
if (STAT.Bit(4))
|
||||
{
|
||||
if ((cycle >= 4) && (LY == 144))
|
||||
{
|
||||
VBL_INT = true;
|
||||
}
|
||||
else if (LY > 144)
|
||||
{
|
||||
VBL_INT = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((cycle == 4) && (LY == 144)) {
|
||||
|
||||
HBL_INT = false;
|
||||
|
||||
// set STAT mode to 1 (VBlank) and interrupt flag if it is enabled
|
||||
STAT &= 0xFC;
|
||||
STAT |= 0x01;
|
||||
|
||||
if (Core.REG_FFFF.Bit(0)) { Core.cpu.FlagI = true; }
|
||||
Core.REG_FF0F |= 0x01;
|
||||
}
|
||||
|
||||
if ((LY >= 144) && (cycle == 4))
|
||||
{
|
||||
// a special case of OAM mode 2 IRQ assertion, even though PPU Mode still is 1
|
||||
if (STAT.Bit(5)) { OAM_INT = true; }
|
||||
}
|
||||
|
||||
if ((LY == 153) && (cycle == 8))
|
||||
{
|
||||
LY = 0;
|
||||
LY_inc = 0;
|
||||
Core.cpu.LY = LY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Core.in_vblank)
|
||||
{
|
||||
if (cycle == 4)
|
||||
{
|
||||
// here mode 2 will be set to true and interrupts fired if enabled
|
||||
STAT &= 0xFC;
|
||||
STAT |= 0x2;
|
||||
if (STAT.Bit(5)) { OAM_INT = true; }
|
||||
|
||||
HBL_INT = false;
|
||||
}
|
||||
|
||||
if (cycle >= 4 && cycle < 84)
|
||||
{
|
||||
// here OAM scanning is performed
|
||||
OAM_scan(cycle - 4);
|
||||
}
|
||||
else if (cycle >= 84 && LY < 144)
|
||||
{
|
||||
// render the screen and handle hblank
|
||||
render(cycle - 84);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((LY_inc == 0))
|
||||
{
|
||||
if (cycle == 12)
|
||||
{
|
||||
LYC_INT = false;
|
||||
STAT &= 0xFB;
|
||||
|
||||
// Special case of LY = LYC
|
||||
if (LY == LYC)
|
||||
{
|
||||
// set STAT coincidence FLAG and interrupt flag if it is enabled
|
||||
STAT |= 0x04;
|
||||
if (STAT.Bit(6)) { LYC_INT = true; }
|
||||
}
|
||||
|
||||
// also a special case of OAM mode 2 IRQ assertion, even though PPU Mode still is 1
|
||||
if (STAT.Bit(5)) { OAM_INT = true; }
|
||||
}
|
||||
|
||||
if (cycle == 92) { OAM_INT = false; }
|
||||
}
|
||||
|
||||
// here LY=LYC will be asserted
|
||||
if ((cycle == 4) && (LY != 0))
|
||||
{
|
||||
LYC_INT = false;
|
||||
STAT &= 0xFB;
|
||||
|
||||
if (LY == LYC)
|
||||
{
|
||||
// set STAT coincidence FLAG and interrupt flag if it is enabled
|
||||
STAT |= 0x04;
|
||||
if (STAT.Bit(6)) { LYC_INT = true; }
|
||||
}
|
||||
}
|
||||
|
||||
cycle++;
|
||||
|
||||
if (cycle==456)
|
||||
{
|
||||
cycle = 0;
|
||||
LY+=LY_inc;
|
||||
|
||||
if (LY==0 && LY_inc == 0)
|
||||
{
|
||||
LY_inc = 1;
|
||||
Core.in_vblank = false;
|
||||
VBL_INT = false;
|
||||
}
|
||||
|
||||
Core.cpu.LY = LY;
|
||||
|
||||
if (LY==144)
|
||||
{
|
||||
Core.in_vblank = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// screen disable sets STAT as though it were vblank, but there is no Stat IRQ asserted
|
||||
STAT &= 0xFC;
|
||||
STAT |= 0x01;
|
||||
|
||||
VBL_INT = LYC_INT = HBL_INT = OAM_INT = false;
|
||||
|
||||
Core.in_vblank = true;
|
||||
|
||||
LCD_was_off = true;
|
||||
|
||||
LY = 0;
|
||||
Core.cpu.LY = LY;
|
||||
|
||||
cycle = 0;
|
||||
}
|
||||
|
||||
// assert the STAT IRQ line if the line went from zero to 1
|
||||
stat_line = VBL_INT | LYC_INT | HBL_INT | OAM_INT;
|
||||
|
||||
if (stat_line && !stat_line_old)
|
||||
{
|
||||
if (Core.REG_FFFF.Bit(1)) { Core.cpu.FlagI = true; }
|
||||
Core.REG_FF0F |= 0x02;
|
||||
}
|
||||
|
||||
stat_line_old = stat_line;
|
||||
|
||||
// process latch delays
|
||||
//latch_delay();
|
||||
|
||||
}
|
||||
|
||||
// might be needed, not sure yet
|
||||
public void latch_delay()
|
||||
{
|
||||
//BGP_l = BGP;
|
||||
}
|
||||
|
||||
public void OAM_scan(int OAM_cycle)
|
||||
{
|
||||
// we are now in STAT mode 2
|
||||
// TODO: maybe stat mode 2 flags are set at cycle 0 on visible scanlines?
|
||||
if (OAM_cycle == 0)
|
||||
{
|
||||
OAM_access = false;
|
||||
OAM_scan_index = 0;
|
||||
SL_sprites_index = 0;
|
||||
write_sprite = 0;
|
||||
}
|
||||
|
||||
// the gameboy has 80 cycles to scan through 40 sprites, picking out the first 10 it finds to draw
|
||||
// the following is a guessed at implmenentation based on how NES does it, it's probably pretty close
|
||||
if (OAM_cycle < 10)
|
||||
{
|
||||
// start by clearing the sprite table (probably just clears X on hardware, but let's be safe here.)
|
||||
SL_sprites[OAM_cycle * 4] = 0;
|
||||
SL_sprites[OAM_cycle * 4 + 1] = 0;
|
||||
SL_sprites[OAM_cycle * 4 + 2] = 0;
|
||||
SL_sprites[OAM_cycle * 4 + 3] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (write_sprite == 0)
|
||||
{
|
||||
if (OAM_scan_index < 40)
|
||||
{
|
||||
// (sprite Y - 16) equals LY, we have a sprite
|
||||
if ((Core.OAM[OAM_scan_index * 4] - 16) <= LY &&
|
||||
((Core.OAM[OAM_scan_index * 4] - 16) + 8 + (LCDC.Bit(2) ? 8 : 0)) > LY)
|
||||
{
|
||||
// always pick the first 10 in range sprites
|
||||
if (SL_sprites_index < 10)
|
||||
{
|
||||
SL_sprites[SL_sprites_index * 4] = Core.OAM[OAM_scan_index * 4];
|
||||
|
||||
write_sprite = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we already have 10 sprites, there's nothing to do, increment the index
|
||||
OAM_scan_index++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OAM_scan_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SL_sprites[SL_sprites_index * 4 + write_sprite] = Core.OAM[OAM_scan_index * 4 + write_sprite];
|
||||
write_sprite++;
|
||||
|
||||
if (write_sprite == 4)
|
||||
{
|
||||
write_sprite = 0;
|
||||
SL_sprites_index++;
|
||||
OAM_scan_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void render(int render_cycle)
|
||||
{
|
||||
// we are now in STAT mode 3
|
||||
// NOTE: presumably the first necessary sprite is fetched at sprite evaulation
|
||||
// i.e. just keeping track of the lowest x-value sprite
|
||||
if (render_cycle == 0)
|
||||
{
|
||||
STAT &= 0xFC;
|
||||
STAT |= 0x03;
|
||||
OAM_INT = false;
|
||||
|
||||
OAM_access = false;
|
||||
VRAM_access = false;
|
||||
OAM_scan_index = 0;
|
||||
read_case = 0;
|
||||
internal_cycle = 0;
|
||||
pre_render = true;
|
||||
tile_inc = 0;
|
||||
pixel_counter = 0;
|
||||
sl_use_index = 0;
|
||||
index_used = 0;
|
||||
bottom_index = 0;
|
||||
sprite_ordered_index = 0;
|
||||
fetch_sprite = false;
|
||||
no_sprites = false;
|
||||
|
||||
// calculate the row number of the tiles to be fetched
|
||||
y_tile = ((int)Math.Floor((float)(scroll_y + LY) / 8)) % 32;
|
||||
|
||||
if (SL_sprites_index == 0)
|
||||
{
|
||||
no_sprites = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pre_render && !fetch_sprite)
|
||||
{
|
||||
// start by fetching all the sprites that need to be fetched
|
||||
if (!no_sprites)
|
||||
{
|
||||
for (int i = 0; i < SL_sprites_index; i++)
|
||||
{
|
||||
if ((pixel_counter >= (SL_sprites[i * 4 + 1] - 8)) &&
|
||||
(pixel_counter < SL_sprites[i * 4 + 1]) &&
|
||||
!index_used.Bit(i))
|
||||
{
|
||||
fetch_sprite = true;
|
||||
sprite_fetch_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!fetch_sprite)
|
||||
{
|
||||
// start shifting data into the LCD
|
||||
if (render_counter >= (render_offset + 8))
|
||||
{
|
||||
pixel = tile_data_latch[0].Bit(7 - (render_counter % 8)) ? 1 : 0;
|
||||
pixel |= tile_data_latch[1].Bit(7 - (render_counter % 8)) ? 2 : 0;
|
||||
pixel = (BGP >> (pixel * 2)) & 3;
|
||||
// now we have the BG pixel, we next need the sprite pixel
|
||||
if (!no_sprites)
|
||||
{
|
||||
bool have_sprite = false;
|
||||
int i = bottom_index;
|
||||
int s_pixel = 0;
|
||||
int sprite_attr = 0;
|
||||
|
||||
while (i < sprite_ordered_index)
|
||||
{
|
||||
if (SL_sprites_ordered[i * 4] == pixel_counter)
|
||||
{
|
||||
bottom_index++;
|
||||
if (bottom_index == SL_sprites_index) { no_sprites = true; }
|
||||
}
|
||||
else if (!have_sprite)
|
||||
{
|
||||
// we can use the current sprite, so pick out a pixel for it
|
||||
int t_index = pixel_counter - (SL_sprites_ordered[i * 4] - 8);
|
||||
|
||||
t_index = 7 - t_index;
|
||||
|
||||
sprite_data[0] = (byte)((SL_sprites_ordered[i * 4 + 1] >> t_index) & 1);
|
||||
sprite_data[1] = (byte)(((SL_sprites_ordered[i * 4 + 2] >> t_index) & 1) << 1);
|
||||
|
||||
s_pixel = sprite_data[0] + sprite_data[1];
|
||||
sprite_attr = SL_sprites_ordered[i * 4 + 3];
|
||||
|
||||
// pixel color of 0 is transparent, so if this is the case we dont have a pixel
|
||||
if (s_pixel != 0)
|
||||
{
|
||||
have_sprite = true;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (have_sprite)
|
||||
{
|
||||
bool use_sprite = false;
|
||||
if (LCDC.Bit(1))
|
||||
{
|
||||
if (!sprite_attr.Bit(7))
|
||||
{
|
||||
if (s_pixel != 0) { use_sprite = true; }
|
||||
}
|
||||
else if (pixel == 0)
|
||||
{
|
||||
use_sprite = true;
|
||||
}
|
||||
|
||||
if (!LCDC.Bit(0))
|
||||
{
|
||||
use_sprite = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_sprite)
|
||||
{
|
||||
if (sprite_attr.Bit(4))
|
||||
{
|
||||
pixel = (obj_pal_1 >> (s_pixel * 2)) & 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
pixel = (obj_pal_0 >> (s_pixel * 2)) & 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// based on sprite priority and pixel values, pick a final pixel color
|
||||
Core._vidbuffer[LY * 160 + pixel_counter] = (int)GBHawk.color_palette[pixel];
|
||||
pixel_counter++;
|
||||
|
||||
if (pixel_counter == 160)
|
||||
{
|
||||
read_case = 8;
|
||||
hbl_set_once = true;
|
||||
}
|
||||
}
|
||||
render_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fetch_sprite)
|
||||
{
|
||||
if (latch_new_data)
|
||||
{
|
||||
latch_new_data = false;
|
||||
tile_data_latch[0] = tile_data[0];
|
||||
tile_data_latch[1] = tile_data[1];
|
||||
}
|
||||
|
||||
switch (read_case)
|
||||
{
|
||||
case 0: // read a background tile
|
||||
if ((internal_cycle % 2) == 0)
|
||||
{
|
||||
|
||||
temp_fetch = y_tile * 32 + (x_tile + tile_inc) % 32;
|
||||
tile_byte = LCDC.Bit(3) ? Core.BG_map_2[temp_fetch] : Core.BG_map_1[temp_fetch];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!pre_render)
|
||||
{
|
||||
tile_inc++;
|
||||
}
|
||||
read_case = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // read from tile graphics (0)
|
||||
if ((internal_cycle % 2) == 0)
|
||||
{
|
||||
y_scroll_offset = (scroll_y + LY) % 8;
|
||||
|
||||
if (LCDC.Bit(4))
|
||||
{
|
||||
tile_data[0] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2];
|
||||
}
|
||||
else
|
||||
{
|
||||
// same as before except now tile byte represents a signed byte
|
||||
if (tile_byte.Bit(7))
|
||||
{
|
||||
tile_byte -= 256;
|
||||
}
|
||||
tile_data[0] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2];
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
read_case = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // read from tile graphics (1)
|
||||
if ((internal_cycle % 2) == 0)
|
||||
{
|
||||
y_scroll_offset = (scroll_y + LY) % 8;
|
||||
|
||||
if (LCDC.Bit(4))
|
||||
{
|
||||
// if LCDC somehow changed between the two reads, make sure we have a positive number
|
||||
if (tile_byte < 0)
|
||||
{
|
||||
tile_byte += 256;
|
||||
}
|
||||
|
||||
tile_data[1] = Core.CHR_RAM[tile_byte * 16 + y_scroll_offset * 2 + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// same as before except now tile byte represents a signed byte
|
||||
if (tile_byte.Bit(7) && tile_byte > 0)
|
||||
{
|
||||
tile_byte -= 256;
|
||||
}
|
||||
|
||||
tile_data[1] = Core.CHR_RAM[0x1000 + tile_byte * 16 + y_scroll_offset * 2 + 1];
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pre_render)
|
||||
{
|
||||
// here we set up rendering
|
||||
pre_render = false;
|
||||
render_offset = scroll_x % 8;
|
||||
render_counter = -1;
|
||||
latch_counter = 0;
|
||||
read_case = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
read_case = 3;
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // read from sprite data
|
||||
if ((internal_cycle % 2) == 0)
|
||||
{
|
||||
// nothing to do if not fetching
|
||||
}
|
||||
else
|
||||
{
|
||||
read_case = 0;
|
||||
latch_new_data = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // read from window data
|
||||
break;
|
||||
|
||||
case 6: // read from tile graphics (for the window)
|
||||
break;
|
||||
|
||||
case 7: // read from tile graphics (for the window)
|
||||
break;
|
||||
|
||||
case 8: // done reading, we are now in phase 0
|
||||
|
||||
OAM_access = true;
|
||||
VRAM_access = true;
|
||||
|
||||
STAT &= 0xFC;
|
||||
STAT |= 0x00;
|
||||
pre_render = true;
|
||||
if (hbl_set_once)
|
||||
{
|
||||
if (STAT.Bit(3)) { HBL_INT = true; }
|
||||
hbl_set_once = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
internal_cycle++;
|
||||
}
|
||||
|
||||
if (fetch_sprite)
|
||||
{
|
||||
if (sprite_fetch_index < SL_sprites_index)
|
||||
{
|
||||
if (pixel_counter != 0) {
|
||||
if ((pixel_counter == (SL_sprites[sprite_fetch_index * 4 + 1] - 8)) &&
|
||||
//(pixel_counter < SL_sprites[sprite_fetch_index * 4 + 1]) &&
|
||||
!index_used.Bit(sprite_fetch_index))
|
||||
{
|
||||
sl_use_index = sprite_fetch_index;
|
||||
process_sprite();
|
||||
SL_sprites_ordered[sprite_ordered_index * 4] = SL_sprites[sprite_fetch_index * 4 + 1];
|
||||
SL_sprites_ordered[sprite_ordered_index * 4 + 1] = sprite_sel[0];
|
||||
SL_sprites_ordered[sprite_ordered_index * 4 + 2] = sprite_sel[1];
|
||||
SL_sprites_ordered[sprite_ordered_index * 4 + 3] = SL_sprites[sprite_fetch_index * 4 + 3];
|
||||
sprite_ordered_index++;
|
||||
index_used |= (1 << sl_use_index);
|
||||
}
|
||||
sprite_fetch_index++;
|
||||
if (sprite_fetch_index == SL_sprites_index) { fetch_sprite = false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// whan pixel counter is 0, we want to scan all the points before 0 as well
|
||||
// certainly non-physical but good enough for now
|
||||
for (int j = -7; j < 1; j++)
|
||||
{
|
||||
for (int i = 0; i < SL_sprites_index; i++)
|
||||
{
|
||||
if ((j == (SL_sprites[i * 4 + 1] - 8)) &&
|
||||
!index_used.Bit(i))
|
||||
{
|
||||
sl_use_index = i;
|
||||
process_sprite();
|
||||
SL_sprites_ordered[sprite_ordered_index * 4] = SL_sprites[i * 4 + 1];
|
||||
SL_sprites_ordered[sprite_ordered_index * 4 + 1] = sprite_sel[0];
|
||||
SL_sprites_ordered[sprite_ordered_index * 4 + 2] = sprite_sel[1];
|
||||
SL_sprites_ordered[sprite_ordered_index * 4 + 3] = SL_sprites[i * 4 + 3];
|
||||
sprite_ordered_index++;
|
||||
index_used |= (1 << sl_use_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
fetch_sprite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
LCDC = 0;
|
||||
STAT = 0x80;
|
||||
scroll_y = 0;
|
||||
scroll_x = 0;
|
||||
LY = 0;
|
||||
LYC = 0;
|
||||
DMA_addr = 0;
|
||||
BGP = 0;
|
||||
obj_pal_0 = 0;
|
||||
obj_pal_1 = 0;
|
||||
window_y = 0;
|
||||
window_x = 0;
|
||||
LY_inc = 1;
|
||||
|
||||
cycle = 0;
|
||||
LYC_INT = false;
|
||||
HBL_INT = false;
|
||||
VBL_INT = false;
|
||||
OAM_INT = false;
|
||||
|
||||
stat_line = false;
|
||||
stat_line_old = false;
|
||||
}
|
||||
|
||||
public void process_sprite()
|
||||
{
|
||||
int y;
|
||||
|
||||
if (SL_sprites[sl_use_index * 4 + 3].Bit(6))
|
||||
{
|
||||
if (LCDC.Bit(2))
|
||||
{
|
||||
y = LY - (SL_sprites[sl_use_index * 4] - 16);
|
||||
y = 15 - y;
|
||||
sprite_sel[0] = Core.CHR_RAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
|
||||
sprite_sel[1] = Core.CHR_RAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
y = LY - (SL_sprites[sl_use_index * 4] - 16);
|
||||
y = 7 - y;
|
||||
sprite_sel[0] = Core.CHR_RAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
|
||||
sprite_sel[1] = Core.CHR_RAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LCDC.Bit(2))
|
||||
{
|
||||
y = LY - (SL_sprites[sl_use_index * 4] - 16);
|
||||
sprite_sel[0] = Core.CHR_RAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2];
|
||||
sprite_sel[1] = Core.CHR_RAM[(SL_sprites[sl_use_index * 4 + 2] & 0xFE) * 16 + y * 2 + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
y = LY - (SL_sprites[sl_use_index * 4] - 16);
|
||||
sprite_sel[0] = Core.CHR_RAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2];
|
||||
sprite_sel[1] = Core.CHR_RAM[SL_sprites[sl_use_index * 4 + 2] * 16 + y * 2 + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (SL_sprites[sl_use_index * 4 + 3].Bit(5))
|
||||
{
|
||||
int b0, b1, b2, b3, b4, b5, b6, b7 = 0;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
b0 = (sprite_sel[i] & 0x01) << 7;
|
||||
b1 = (sprite_sel[i] & 0x02) << 5;
|
||||
b2 = (sprite_sel[i] & 0x04) << 3;
|
||||
b3 = (sprite_sel[i] & 0x08) << 1;
|
||||
b4 = (sprite_sel[i] & 0x10) >> 1;
|
||||
b5 = (sprite_sel[i] & 0x20) >> 3;
|
||||
b6 = (sprite_sel[i] & 0x40) >> 5;
|
||||
b7 = (sprite_sel[i] & 0x80) >> 7;
|
||||
|
||||
sprite_sel[i] = (byte)(b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync("LCDC", ref LCDC);
|
||||
ser.Sync("STAT", ref STAT);
|
||||
ser.Sync("scroll_y", ref scroll_y);
|
||||
ser.Sync("scroll_x", ref scroll_x);
|
||||
ser.Sync("LY", ref LY);
|
||||
ser.Sync("LYinc", ref LY_inc);
|
||||
ser.Sync("LYC", ref LYC);
|
||||
ser.Sync("DMA_addr", ref DMA_addr);
|
||||
ser.Sync("BGP", ref BGP);
|
||||
ser.Sync("obj_pal_0", ref obj_pal_0);
|
||||
ser.Sync("obj_pal_1", ref obj_pal_1);
|
||||
ser.Sync("window_y", ref window_y);
|
||||
ser.Sync("window_x", ref window_x);
|
||||
ser.Sync("DMA_start", ref DMA_start);
|
||||
ser.Sync("DMA_clock", ref DMA_clock);
|
||||
ser.Sync("DMA_inc", ref DMA_inc);
|
||||
ser.Sync("DMA_byte", ref DMA_byte);
|
||||
|
||||
ser.Sync("LYC_INT", ref LYC_INT);
|
||||
ser.Sync("HBL_INT", ref HBL_INT);
|
||||
ser.Sync("VBL_INT", ref VBL_INT);
|
||||
ser.Sync("OAM_INT", ref OAM_INT);
|
||||
ser.Sync("stat_line", ref stat_line);
|
||||
ser.Sync("stat_line_old", ref stat_line_old);
|
||||
ser.Sync("hbl_set_once", ref hbl_set_once);
|
||||
ser.Sync("LCD_was_off", ref LCD_was_off);
|
||||
ser.Sync("OAM_access", ref OAM_access);
|
||||
ser.Sync("OAM_scan_index", ref OAM_scan_index);
|
||||
ser.Sync("SL_sprites_index", ref SL_sprites_index);
|
||||
ser.Sync("SL_sprites", ref SL_sprites, false);
|
||||
ser.Sync("write_sprite", ref write_sprite);
|
||||
|
||||
ser.Sync("VRAM_access", ref VRAM_access);
|
||||
ser.Sync("read_case", ref read_case);
|
||||
ser.Sync("internal_cycle", ref internal_cycle);
|
||||
ser.Sync("y_tile", ref y_tile);
|
||||
ser.Sync("y_scroll_offset", ref y_scroll_offset);
|
||||
ser.Sync("x_tile", ref x_tile);
|
||||
ser.Sync("x_scroll_offset", ref x_scroll_offset);
|
||||
ser.Sync("tile_byte", ref tile_byte);
|
||||
ser.Sync("sprite_fetch_cycles", ref sprite_fetch_cycles);
|
||||
ser.Sync("fetch_sprite", ref fetch_sprite);
|
||||
ser.Sync("temp_fetch", ref temp_fetch);
|
||||
ser.Sync("tile_inc", ref tile_inc);
|
||||
ser.Sync("pre_render", ref pre_render);
|
||||
ser.Sync("tile_data", ref tile_data, false);
|
||||
ser.Sync("tile_data_latch", ref tile_data_latch, false);
|
||||
ser.Sync("latch_counter", ref latch_counter);
|
||||
ser.Sync("latch_new_data", ref latch_new_data);
|
||||
ser.Sync("render_counter", ref render_counter);
|
||||
ser.Sync("render_offset", ref render_offset);
|
||||
ser.Sync("pixel_counter", ref pixel_counter);
|
||||
ser.Sync("pixel", ref pixel);
|
||||
ser.Sync("sprite_data", ref sprite_data, false);
|
||||
ser.Sync("sl_use_index", ref sl_use_index);
|
||||
ser.Sync("sprite_sel", ref sprite_sel, false);
|
||||
ser.Sync("no_sprites", ref no_sprites);
|
||||
ser.Sync("sprite_fetch_index", ref sprite_fetch_index);
|
||||
ser.Sync("SL_sprites_ordered", ref SL_sprites_ordered, false);
|
||||
ser.Sync("index_used", ref index_used);
|
||||
ser.Sync("sprite_ordered_index", ref sprite_ordered_index);
|
||||
ser.Sync("bottom_index", ref bottom_index);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
TODO:
|
|
@ -0,0 +1,175 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
||||
{
|
||||
// Timer Emulation
|
||||
public class Timer
|
||||
{
|
||||
public GBHawk Core { get; set; }
|
||||
|
||||
public ushort divider_reg;
|
||||
public byte timer_reload;
|
||||
public byte timer;
|
||||
public byte timer_old;
|
||||
public byte timer_control;
|
||||
public byte pending_reload;
|
||||
public byte write_ignore;
|
||||
public bool old_state;
|
||||
public bool state;
|
||||
public bool reload_block;
|
||||
public bool TMA_coincidence;
|
||||
|
||||
public byte ReadReg(int addr)
|
||||
{
|
||||
byte ret = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0xFF04: ret = (byte)(divider_reg >> 8); break; // DIV register
|
||||
case 0xFF05: ret = timer; break; // TIMA (Timer Counter)
|
||||
case 0xFF06: ret = timer_reload; break; // TMA (Timer Modulo)
|
||||
case 0xFF07: ret = timer_control; break; // TAC (Timer Control)
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void WriteReg(int addr, byte value)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
// DIV register
|
||||
case 0xFF04:
|
||||
divider_reg = 0;
|
||||
break;
|
||||
|
||||
// TIMA (Timer Counter)
|
||||
case 0xFF05:
|
||||
if (write_ignore == 0)
|
||||
{
|
||||
timer_old = timer;
|
||||
timer = value;
|
||||
reload_block = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// TMA (Timer Modulo)
|
||||
case 0xFF06:
|
||||
timer_reload = value;
|
||||
if (TMA_coincidence)
|
||||
{
|
||||
timer = timer_reload;
|
||||
timer_old = timer;
|
||||
}
|
||||
break;
|
||||
|
||||
// TAC (Timer Control)
|
||||
case 0xFF07:
|
||||
timer_control = (byte)((timer_control & 0xf8) | (value & 0x7)); // only bottom 3 bits function
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void tick_1()
|
||||
{
|
||||
if (write_ignore > 0)
|
||||
{
|
||||
write_ignore--;
|
||||
if (write_ignore==0)
|
||||
{
|
||||
TMA_coincidence = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pending_reload > 0)
|
||||
{
|
||||
pending_reload--;
|
||||
if (pending_reload == 0 && !reload_block)
|
||||
{
|
||||
timer = timer_reload;
|
||||
timer_old = timer;
|
||||
write_ignore = 4;
|
||||
TMA_coincidence = true;
|
||||
|
||||
// set interrupts
|
||||
if (Core.REG_FFFF.Bit(2)) { Core.cpu.FlagI = true; }
|
||||
Core.REG_FF0F |= 0x04;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void tick_2()
|
||||
{
|
||||
divider_reg++;
|
||||
|
||||
// pick a bit to test based on the current value of timer control
|
||||
switch (timer_control & 3)
|
||||
{
|
||||
case 0:
|
||||
state = divider_reg.Bit(9);
|
||||
break;
|
||||
case 1:
|
||||
state = divider_reg.Bit(3);
|
||||
break;
|
||||
case 2:
|
||||
state = divider_reg.Bit(5);
|
||||
break;
|
||||
case 3:
|
||||
state = divider_reg.Bit(7);
|
||||
break;
|
||||
}
|
||||
|
||||
// And it with the state of the timer on/off bit
|
||||
state &= timer_control.Bit(2);
|
||||
|
||||
// this procedure allows several glitchy timer ticks, since it only measures falling edge of the state
|
||||
// so things like turning the timer off and resetting the divider will tick the timer
|
||||
if (old_state && !state)
|
||||
{
|
||||
timer_old = timer;
|
||||
timer++;
|
||||
|
||||
// if overflow, set the interrupt flag and reload the timer (4 clocks later)
|
||||
if (timer < timer_old)
|
||||
{
|
||||
pending_reload = 4;
|
||||
reload_block = false;
|
||||
}
|
||||
}
|
||||
|
||||
old_state = state;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
divider_reg = 0;
|
||||
timer_reload = 0;
|
||||
timer = 0;
|
||||
timer_old = 0;
|
||||
timer_control = 0xF8;
|
||||
pending_reload = 0;
|
||||
write_ignore = 0;
|
||||
old_state = false;
|
||||
state = false;
|
||||
reload_block = false;
|
||||
TMA_coincidence = false;
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.Sync("divider_reg", ref divider_reg);
|
||||
ser.Sync("timer_reload", ref timer_reload);
|
||||
ser.Sync("timer", ref timer);
|
||||
ser.Sync("timer_old", ref timer_old);
|
||||
ser.Sync("timer_control", ref timer_control);
|
||||
ser.Sync("pending_reload", ref pending_reload);
|
||||
ser.Sync("write_ignore", ref write_ignore);
|
||||
ser.Sync("old_state", ref old_state);
|
||||
ser.Sync("state", ref state);
|
||||
ser.Sync("reload_block", ref reload_block);
|
||||
ser.Sync("TMA_coincidence", ref TMA_coincidence);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue