diff --git a/Audio.cs b/Audio.cs new file mode 100644 index 0000000000..7fcaaca85f --- /dev/null +++ b/Audio.cs @@ -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 + } +} \ No newline at end of file diff --git a/BizHawk.Client.Common/Global.cs b/BizHawk.Client.Common/Global.cs index 42e9a75a89..b592f1696b 100644 --- a/BizHawk.Client.Common/Global.cs +++ b/BizHawk.Client.Common/Global.cs @@ -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; diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index d20e74a845..d89deccc64 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -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 { diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 0acda8f08e..b5a847cb21 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -581,6 +581,34 @@ VBANext.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + N64.cs @@ -1161,6 +1189,14 @@ + + + + + + + + diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IDebuggable.cs index d738c63fe4..778b17acfc 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.IDebuggable.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) { diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs index 356d4617eb..9015a4fe1d 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/GBHawk.cs @@ -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() diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/MemoryMap.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/MemoryMap.cs index da99274751..fe6bb017ce 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/MemoryMap.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/GBHawk/MemoryMap.cs @@ -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) { diff --git a/GBHawk.IDebuggable.cs b/GBHawk.IDebuggable.cs new file mode 100644 index 0000000000..543cecf962 --- /dev/null +++ b/GBHawk.IDebuggable.cs @@ -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 GetCpuFlagsAndRegisters() + { + return new Dictionary + { + /* + ["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; } + } + } +} diff --git a/GBHawk.IEmulator.cs b/GBHawk.IEmulator.cs new file mode 100644 index 0000000000..189feb6af7 --- /dev/null +++ b/GBHawk.IEmulator.cs @@ -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 + } +} diff --git a/GBHawk.IInputPollable.cs b/GBHawk.IInputPollable.cs new file mode 100644 index 0000000000..30af4780c6 --- /dev/null +++ b/GBHawk.IInputPollable.cs @@ -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; + } +} diff --git a/GBHawk.IMemoryDomains.cs b/GBHawk.IMemoryDomains.cs new file mode 100644 index 0000000000..aa9ecf8b2e --- /dev/null +++ b/GBHawk.IMemoryDomains.cs @@ -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 + { + 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(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); + } + } +} diff --git a/GBHawk.ISaveRam.cs b/GBHawk.ISaveRam.cs new file mode 100644 index 0000000000..5936f9543c --- /dev/null +++ b/GBHawk.ISaveRam.cs @@ -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; + } + } + } +} diff --git a/GBHawk.ISettable.cs b/GBHawk.ISettable.cs new file mode 100644 index 0000000000..75a267bf62 --- /dev/null +++ b/GBHawk.ISettable.cs @@ -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 + { + 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); + } + } + } +} diff --git a/GBHawk.IStatable.cs b/GBHawk.IStatable.cs new file mode 100644 index 0000000000..a50da0305a --- /dev/null +++ b/GBHawk.IStatable.cs @@ -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(); + } + } +} diff --git a/GBHawk.cs b/GBHawk.cs new file mode 100644 index 0000000000..8a4ff534bf --- /dev/null +++ b/GBHawk.cs @@ -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 + { + // 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(this); + ser.Register(audio); + ServiceProvider = ser; + + _tracer = new TraceBuffer { Header = cpu.TraceHeader }; + ser.Register(_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(); + } + } +} diff --git a/GBHawkControllerDeck.cs b/GBHawkControllerDeck.cs new file mode 100644 index 0000000000..464c4bfdcb --- /dev/null +++ b/GBHawkControllerDeck.cs @@ -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 _controllerTypes; + + public static Dictionary 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(); + } +} diff --git a/GBHawkControllers.cs b/GBHawkControllers.cs new file mode 100644 index 0000000000..af504834c7 --- /dev/null +++ b/GBHawkControllers.cs @@ -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 +{ + /// + /// Represents a GB add on + /// + 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 + } + } +} \ No newline at end of file diff --git a/HW_Registers.cs b/HW_Registers.cs new file mode 100644 index 0000000000..3e71185e35 --- /dev/null +++ b/HW_Registers.cs @@ -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; + } + } +} diff --git a/Mappers/MapperBase.cs b/Mappers/MapperBase.cs new file mode 100644 index 0000000000..f7aeb7ddca --- /dev/null +++ b/Mappers/MapperBase.cs @@ -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() + { + } + } +} diff --git a/Mappers/Mapper_Camera.cs b/Mappers/Mapper_Camera.cs new file mode 100644 index 0000000000..e247725259 --- /dev/null +++ b/Mappers/Mapper_Camera.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_Default.cs b/Mappers/Mapper_Default.cs new file mode 100644 index 0000000000..609a696040 --- /dev/null +++ b/Mappers/Mapper_Default.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_HuC1.cs b/Mappers/Mapper_HuC1.cs new file mode 100644 index 0000000000..0e33db1474 --- /dev/null +++ b/Mappers/Mapper_HuC1.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_HuC3.cs b/Mappers/Mapper_HuC3.cs new file mode 100644 index 0000000000..ff0e5cc6d1 --- /dev/null +++ b/Mappers/Mapper_HuC3.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_MBC1.cs b/Mappers/Mapper_MBC1.cs new file mode 100644 index 0000000000..2a8bed26be --- /dev/null +++ b/Mappers/Mapper_MBC1.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_MBC2.cs b/Mappers/Mapper_MBC2.cs new file mode 100644 index 0000000000..3130aaa469 --- /dev/null +++ b/Mappers/Mapper_MBC2.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_MBC3.cs b/Mappers/Mapper_MBC3.cs new file mode 100644 index 0000000000..961f1769e4 --- /dev/null +++ b/Mappers/Mapper_MBC3.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_MBC5.cs b/Mappers/Mapper_MBC5.cs new file mode 100644 index 0000000000..312c9dc108 --- /dev/null +++ b/Mappers/Mapper_MBC5.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_MBC6.cs b/Mappers/Mapper_MBC6.cs new file mode 100644 index 0000000000..7f9624adb9 --- /dev/null +++ b/Mappers/Mapper_MBC6.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_MBC7.cs b/Mappers/Mapper_MBC7.cs new file mode 100644 index 0000000000..40c9513b8b --- /dev/null +++ b/Mappers/Mapper_MBC7.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_MMM01.cs b/Mappers/Mapper_MMM01.cs new file mode 100644 index 0000000000..86c07e20ca --- /dev/null +++ b/Mappers/Mapper_MMM01.cs @@ -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); + } + } +} diff --git a/Mappers/Mapper_TAMA5.cs b/Mappers/Mapper_TAMA5.cs new file mode 100644 index 0000000000..eed5909336 --- /dev/null +++ b/Mappers/Mapper_TAMA5.cs @@ -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); + } + } +} diff --git a/Mappers/ReadMe.txt b/Mappers/ReadMe.txt new file mode 100644 index 0000000000..0c94c8272c --- /dev/null +++ b/Mappers/ReadMe.txt @@ -0,0 +1,3 @@ +TODO: +Official Mappers +Unofficial Mappers diff --git a/MemoryMap.cs b/MemoryMap.cs new file mode 100644 index 0000000000..8357ebe073 --- /dev/null +++ b/MemoryMap.cs @@ -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); + } + } + } +} diff --git a/PPU.cs b/PPU.cs new file mode 100644 index 0000000000..9170cf1128 --- /dev/null +++ b/PPU.cs @@ -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); + + } + } +} diff --git a/ReadMe.txt b/ReadMe.txt new file mode 100644 index 0000000000..bc60bf4b01 --- /dev/null +++ b/ReadMe.txt @@ -0,0 +1 @@ +TODO: diff --git a/Timer.cs b/Timer.cs new file mode 100644 index 0000000000..b243081d70 --- /dev/null +++ b/Timer.cs @@ -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); + } + } +} \ No newline at end of file