2017-08-29 13:18:28 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
|
|
|
|
using BizHawk.Common.BufferExtensions;
|
|
|
|
|
using BizHawk.Emulation.Common;
|
|
|
|
|
using BizHawk.Emulation.Common.Components.LR35902;
|
|
|
|
|
|
2017-11-26 02:16:14 +00:00
|
|
|
|
using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.GBHawk
|
|
|
|
|
{
|
|
|
|
|
[Core(
|
|
|
|
|
"GBHawk",
|
|
|
|
|
"",
|
|
|
|
|
isPorted: false,
|
2018-06-14 15:05:48 +00:00
|
|
|
|
isReleased: true)]
|
2017-11-19 20:16:15 +00:00
|
|
|
|
[ServiceNotApplicable(typeof(IDriveLight))]
|
2017-11-26 02:16:14 +00:00
|
|
|
|
public partial class GBHawk : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable, IGameboyCommon,
|
2017-08-29 13:18:28 +00:00
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// 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;
|
2018-11-10 18:47:46 +00:00
|
|
|
|
// Updating reg FF0F seemsto be delayed by one cycle
|
|
|
|
|
// tests
|
|
|
|
|
public byte REG_FF0F_OLD = 0xE0;
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
|
|
|
|
// memory domains
|
2018-03-25 14:07:12 +00:00
|
|
|
|
public byte[] RAM = new byte[0x8000]; // only 0x2000 available to GB
|
2017-08-29 13:18:28 +00:00
|
|
|
|
public byte[] ZP_RAM = new byte[0x80];
|
2018-03-25 14:07:12 +00:00
|
|
|
|
/*
|
|
|
|
|
* VRAM is arranged as:
|
|
|
|
|
* 0x1800 Tiles
|
|
|
|
|
* 0x400 BG Map 1
|
|
|
|
|
* 0x400 BG Map 2
|
|
|
|
|
* 0x1800 Tiles
|
|
|
|
|
* 0x400 CA Map 1
|
|
|
|
|
* 0x400 CA Map 2
|
|
|
|
|
* Only the top set is available in GB (i.e. VRAM_Bank = 0)
|
|
|
|
|
*/
|
|
|
|
|
public byte[] VRAM = new byte[0x4000];
|
2017-08-29 13:18:28 +00:00
|
|
|
|
public byte[] OAM = new byte[0xA0];
|
|
|
|
|
|
2018-03-25 14:07:12 +00:00
|
|
|
|
public int RAM_Bank;
|
|
|
|
|
public byte VRAM_Bank;
|
|
|
|
|
public bool is_GBC;
|
2018-03-28 14:15:05 +00:00
|
|
|
|
public bool GBC_compat; // compatibility mode for GB games played on GBC
|
2018-03-25 14:07:12 +00:00
|
|
|
|
public bool double_speed;
|
|
|
|
|
public bool speed_switch;
|
2018-03-25 21:58:21 +00:00
|
|
|
|
public bool HDMA_transfer; // stalls CPU when in progress
|
2018-03-25 14:07:12 +00:00
|
|
|
|
|
2018-12-28 16:11:37 +00:00
|
|
|
|
// several undocumented GBC Registers
|
|
|
|
|
public byte undoc_6C, undoc_72, undoc_73, undoc_74, undoc_75, undoc_76, undoc_77;
|
|
|
|
|
|
2018-03-25 21:58:21 +00:00
|
|
|
|
public byte[] _bios;
|
|
|
|
|
public readonly byte[] _rom;
|
2017-08-29 13:18:28 +00:00
|
|
|
|
public readonly byte[] header = new byte[0x50];
|
|
|
|
|
|
|
|
|
|
public byte[] cart_RAM;
|
2018-03-18 20:06:48 +00:00
|
|
|
|
public bool has_bat;
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
|
|
|
|
private int _frame = 0;
|
|
|
|
|
|
2018-04-10 13:19:29 +00:00
|
|
|
|
public bool Use_MT;
|
|
|
|
|
public ushort addr_access;
|
2017-11-24 20:44:29 +00:00
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
public MapperBase mapper;
|
|
|
|
|
|
|
|
|
|
private readonly ITraceable _tracer;
|
|
|
|
|
|
|
|
|
|
public LR35902 cpu;
|
|
|
|
|
public PPU ppu;
|
|
|
|
|
public Timer timer;
|
|
|
|
|
public Audio audio;
|
2017-11-22 01:22:56 +00:00
|
|
|
|
public SerialPort serialport;
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
2018-03-25 21:58:21 +00:00
|
|
|
|
private static byte[] GBA_override = { 0xFF, 0x00, 0xCD, 0x03, 0x35, 0xAA, 0x31, 0x90, 0x94, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
|
|
2018-03-24 13:11:23 +00:00
|
|
|
|
[CoreConstructor("GB", "GBC")]
|
2017-08-29 13:18:28 +00:00
|
|
|
|
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,
|
2017-11-23 02:38:56 +00:00
|
|
|
|
PeekMemory = PeekMemory,
|
2017-08-29 13:18:28 +00:00
|
|
|
|
DummyReadMemory = ReadMemory,
|
2018-03-25 14:07:12 +00:00
|
|
|
|
OnExecFetch = ExecFetch,
|
|
|
|
|
SpeedFunc = SpeedFunc,
|
2017-08-29 13:18:28 +00:00
|
|
|
|
};
|
2018-03-24 13:11:23 +00:00
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
timer = new Timer();
|
|
|
|
|
audio = new Audio();
|
2017-11-22 01:22:56 +00:00
|
|
|
|
serialport = new SerialPort();
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
|
|
|
|
CoreComm = comm;
|
|
|
|
|
|
|
|
|
|
_settings = (GBSettings)settings ?? new GBSettings();
|
|
|
|
|
_syncSettings = (GBSyncSettings)syncSettings ?? new GBSyncSettings();
|
|
|
|
|
_controllerDeck = new GBHawkControllerDeck(_syncSettings.Port1);
|
|
|
|
|
|
2018-03-24 13:11:23 +00:00
|
|
|
|
byte[] Bios = null;
|
|
|
|
|
|
|
|
|
|
// Load up a BIOS and initialize the correct PPU
|
|
|
|
|
if (_syncSettings.ConsoleMode == GBSyncSettings.ConsoleModeType.Auto)
|
|
|
|
|
{
|
|
|
|
|
if (game.System == "GB")
|
|
|
|
|
{
|
2018-11-14 22:14:27 +00:00
|
|
|
|
Bios = comm.CoreFileProvider.GetFirmware("GB", "World", true, "BIOS Not Found, Cannot Load");
|
2018-03-24 13:11:23 +00:00
|
|
|
|
ppu = new GB_PPU();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-11-14 22:14:27 +00:00
|
|
|
|
Bios = comm.CoreFileProvider.GetFirmware("GBC", "World", true, "BIOS Not Found, Cannot Load");
|
2018-03-24 13:11:23 +00:00
|
|
|
|
ppu = new GBC_PPU();
|
2018-03-25 14:07:12 +00:00
|
|
|
|
is_GBC = true;
|
2018-03-24 13:11:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else if (_syncSettings.ConsoleMode == GBSyncSettings.ConsoleModeType.GB)
|
|
|
|
|
{
|
2018-11-14 22:14:27 +00:00
|
|
|
|
Bios = comm.CoreFileProvider.GetFirmware("GB", "World", true, "BIOS Not Found, Cannot Load");
|
2018-03-24 13:11:23 +00:00
|
|
|
|
ppu = new GB_PPU();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-11-14 22:14:27 +00:00
|
|
|
|
Bios = comm.CoreFileProvider.GetFirmware("GBC", "World", true, "BIOS Not Found, Cannot Load");
|
2018-03-24 13:11:23 +00:00
|
|
|
|
ppu = new GBC_PPU();
|
2018-03-25 14:07:12 +00:00
|
|
|
|
is_GBC = true;
|
2018-03-24 13:11:23 +00:00
|
|
|
|
}
|
2017-11-19 20:16:15 +00:00
|
|
|
|
|
|
|
|
|
if (Bios == null)
|
|
|
|
|
{
|
|
|
|
|
throw new MissingFirmwareException("Missing Gamboy Bios");
|
|
|
|
|
}
|
2018-03-24 13:11:23 +00:00
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
_bios = Bios;
|
|
|
|
|
|
2018-03-25 21:58:21 +00:00
|
|
|
|
// Here we modify the BIOS if GBA mode is set (credit to ExtraTricky)
|
|
|
|
|
if (is_GBC && _syncSettings.GBACGB)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < 13; i++)
|
|
|
|
|
{
|
|
|
|
|
_bios[i + 0xF3] = (byte)((GBA_override[i] + _bios[i + 0xF3]) & 0xFF);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CPU needs to know about GBC status too
|
|
|
|
|
cpu.is_GBC = is_GBC;
|
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
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;
|
2017-11-22 01:22:56 +00:00
|
|
|
|
serialport.Core = this;
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
|
|
|
|
ser.Register<IVideoProvider>(this);
|
|
|
|
|
ser.Register<ISoundProvider>(audio);
|
|
|
|
|
ServiceProvider = ser;
|
|
|
|
|
|
2017-11-19 20:16:15 +00:00
|
|
|
|
_settings = (GBSettings)settings ?? new GBSettings();
|
|
|
|
|
_syncSettings = (GBSyncSettings)syncSettings ?? new GBSyncSettings();
|
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
_tracer = new TraceBuffer { Header = cpu.TraceHeader };
|
|
|
|
|
ser.Register<ITraceable>(_tracer);
|
|
|
|
|
|
|
|
|
|
SetupMemoryDomains();
|
2017-11-26 02:16:14 +00:00
|
|
|
|
HardReset();
|
|
|
|
|
|
2018-03-25 14:07:12 +00:00
|
|
|
|
iptr0 = Marshal.AllocHGlobal(VRAM.Length + 1);
|
2017-11-26 02:16:14 +00:00
|
|
|
|
iptr1 = Marshal.AllocHGlobal(OAM.Length + 1);
|
2018-03-27 14:50:55 +00:00
|
|
|
|
iptr2 = Marshal.AllocHGlobal(color_palette.Length * 8 * 8 + 1);
|
|
|
|
|
iptr3 = Marshal.AllocHGlobal(color_palette.Length * 8 * 8 + 1);
|
2017-11-26 02:16:14 +00:00
|
|
|
|
|
|
|
|
|
_scanlineCallback = null;
|
2017-08-29 13:18:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-26 02:16:14 +00:00
|
|
|
|
#region GPUViewer
|
|
|
|
|
|
2018-03-27 14:50:55 +00:00
|
|
|
|
public bool IsCGBMode() => is_GBC;
|
2017-11-26 02:16:14 +00:00
|
|
|
|
|
|
|
|
|
public IntPtr iptr0 = IntPtr.Zero;
|
|
|
|
|
public IntPtr iptr1 = IntPtr.Zero;
|
|
|
|
|
public IntPtr iptr2 = IntPtr.Zero;
|
|
|
|
|
public IntPtr iptr3 = IntPtr.Zero;
|
|
|
|
|
|
|
|
|
|
private GPUMemoryAreas _gpuMemory
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2018-03-25 14:07:12 +00:00
|
|
|
|
Marshal.Copy(VRAM, 0, iptr0, VRAM.Length);
|
2017-11-26 02:16:14 +00:00
|
|
|
|
Marshal.Copy(OAM, 0, iptr1, OAM.Length);
|
|
|
|
|
|
2018-03-27 14:50:55 +00:00
|
|
|
|
if (is_GBC)
|
2017-11-26 02:55:37 +00:00
|
|
|
|
{
|
2018-03-27 14:50:55 +00:00
|
|
|
|
int[] cp2 = new int[32];
|
|
|
|
|
int[] cp = new int[32];
|
|
|
|
|
for (int i = 0; i < 32; i++)
|
|
|
|
|
{
|
|
|
|
|
cp2[i] = (int)ppu.OBJ_palette[i];
|
|
|
|
|
cp[i] = (int)ppu.BG_palette[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Marshal.Copy(cp2, 0, iptr2, ppu.OBJ_palette.Length);
|
|
|
|
|
Marshal.Copy(cp, 0, iptr3, ppu.BG_palette.Length);
|
2017-11-26 02:55:37 +00:00
|
|
|
|
}
|
2018-03-27 14:50:55 +00:00
|
|
|
|
else
|
2017-11-26 02:16:14 +00:00
|
|
|
|
{
|
2018-03-27 14:50:55 +00:00
|
|
|
|
int[] cp2 = new int[8];
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
{
|
|
|
|
|
cp2[i] = (int)color_palette[(ppu.obj_pal_0 >> (i * 2)) & 3];
|
|
|
|
|
cp2[i + 4] = (int)color_palette[(ppu.obj_pal_1 >> (i * 2)) & 3];
|
|
|
|
|
}
|
|
|
|
|
Marshal.Copy(cp2, 0, iptr2, cp2.Length);
|
|
|
|
|
|
|
|
|
|
int[] cp = new int[4];
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
{
|
|
|
|
|
cp[i] = (int)color_palette[(ppu.BGP >> (i * 2)) & 3];
|
|
|
|
|
}
|
|
|
|
|
Marshal.Copy(cp, 0, iptr3, cp.Length);
|
2017-11-26 02:16:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new GPUMemoryAreas(iptr0, iptr1, iptr2, iptr3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public GPUMemoryAreas GetGPU() => _gpuMemory;
|
|
|
|
|
|
|
|
|
|
public ScanlineCallback _scanlineCallback;
|
|
|
|
|
public int _scanlineCallbackLine = 0;
|
|
|
|
|
|
|
|
|
|
public void SetScanlineCallback(ScanlineCallback callback, int line)
|
|
|
|
|
{
|
|
|
|
|
_scanlineCallback = callback;
|
|
|
|
|
_scanlineCallbackLine = line;
|
|
|
|
|
|
|
|
|
|
if (line == -2)
|
|
|
|
|
{
|
|
|
|
|
GetGPU();
|
|
|
|
|
_scanlineCallback(ppu.LCDC);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private PrinterCallback _printerCallback = null;
|
|
|
|
|
|
|
|
|
|
public void SetPrinterCallback(PrinterCallback callback)
|
|
|
|
|
{
|
|
|
|
|
_printerCallback = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
public DisplayType Region => DisplayType.NTSC;
|
|
|
|
|
|
|
|
|
|
private readonly GBHawkControllerDeck _controllerDeck;
|
|
|
|
|
|
2019-01-03 18:10:53 +00:00
|
|
|
|
public void HardReset()
|
2017-08-29 13:18:28 +00:00
|
|
|
|
{
|
|
|
|
|
GB_bios_register = 0; // bios enable
|
2019-01-01 01:14:53 +00:00
|
|
|
|
GBC_compat = is_GBC;
|
2017-08-29 13:18:28 +00:00
|
|
|
|
in_vblank = true; // we start off in vblank since the LCD is off
|
|
|
|
|
in_vblank_old = true;
|
|
|
|
|
|
2018-03-25 14:07:12 +00:00
|
|
|
|
RAM_Bank = 1; // RAM bank always starts as 1 (even writing zero still sets 1)
|
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
Register_Reset();
|
|
|
|
|
timer.Reset();
|
|
|
|
|
ppu.Reset();
|
2017-11-14 22:52:35 +00:00
|
|
|
|
audio.Reset();
|
2017-11-22 01:22:56 +00:00
|
|
|
|
serialport.Reset();
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
2017-11-23 02:38:56 +00:00
|
|
|
|
cpu.SetCallbacks(ReadMemory, PeekMemory, PeekMemory, WriteMemory);
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
|
|
|
|
_vidbuffer = new int[VirtualWidth * VirtualHeight];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ExecFetch(ushort addr)
|
|
|
|
|
{
|
2017-11-09 14:51:39 +00:00
|
|
|
|
MemoryCallbacks.CallExecutes(addr, "System Bus");
|
2017-08-29 13:18:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Setup_Mapper()
|
|
|
|
|
{
|
|
|
|
|
// setup up mapper based on header entry
|
2017-11-14 19:43:58 +00:00
|
|
|
|
string mppr;
|
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
switch (header[0x47])
|
|
|
|
|
{
|
2018-03-18 20:06:48 +00:00
|
|
|
|
case 0x0: mapper = new MapperDefault(); mppr = "NROM"; break;
|
|
|
|
|
case 0x1: mapper = new MapperMBC1(); mppr = "MBC1"; break;
|
|
|
|
|
case 0x2: mapper = new MapperMBC1(); mppr = "MBC1"; break;
|
|
|
|
|
case 0x3: mapper = new MapperMBC1(); mppr = "MBC1"; has_bat = true; break;
|
|
|
|
|
case 0x5: mapper = new MapperMBC2(); mppr = "MBC2"; break;
|
|
|
|
|
case 0x6: mapper = new MapperMBC2(); mppr = "MBC2"; has_bat = true; break;
|
|
|
|
|
case 0x8: mapper = new MapperDefault(); mppr = "NROM"; break;
|
|
|
|
|
case 0x9: mapper = new MapperDefault(); mppr = "NROM"; has_bat = true; break;
|
|
|
|
|
case 0xB: mapper = new MapperMMM01(); mppr = "MMM01"; break;
|
|
|
|
|
case 0xC: mapper = new MapperMMM01(); mppr = "MMM01"; break;
|
|
|
|
|
case 0xD: mapper = new MapperMMM01(); mppr = "MMM01"; has_bat = true; break;
|
|
|
|
|
case 0xF: mapper = new MapperMBC3(); mppr = "MBC3"; has_bat = true; break;
|
|
|
|
|
case 0x10: mapper = new MapperMBC3(); mppr = "MBC3"; has_bat = true; break;
|
|
|
|
|
case 0x11: mapper = new MapperMBC3(); mppr = "MBC3"; break;
|
|
|
|
|
case 0x12: mapper = new MapperMBC3(); mppr = "MBC3"; break;
|
|
|
|
|
case 0x13: mapper = new MapperMBC3(); mppr = "MBC3"; has_bat = true; break;
|
|
|
|
|
case 0x19: mapper = new MapperMBC5(); mppr = "MBC5"; break;
|
|
|
|
|
case 0x1A: mapper = new MapperMBC5(); mppr = "MBC5"; has_bat = true; break;
|
|
|
|
|
case 0x1B: mapper = new MapperMBC5(); mppr = "MBC5"; break;
|
|
|
|
|
case 0x1C: mapper = new MapperMBC5(); mppr = "MBC5"; break;
|
|
|
|
|
case 0x1D: mapper = new MapperMBC5(); mppr = "MBC5"; break;
|
|
|
|
|
case 0x1E: mapper = new MapperMBC5(); mppr = "MBC5"; has_bat = true; break;
|
|
|
|
|
case 0x20: mapper = new MapperMBC6(); mppr = "MBC6"; break;
|
|
|
|
|
case 0x22: mapper = new MapperMBC7(); mppr = "MBC7"; has_bat = true; break;
|
|
|
|
|
case 0xFC: mapper = new MapperCamera(); mppr = "CAM"; break;
|
|
|
|
|
case 0xFD: mapper = new MapperTAMA5(); mppr = "TAMA5"; break;
|
|
|
|
|
case 0xFE: mapper = new MapperHuC3(); mppr = "HuC3"; break;
|
|
|
|
|
case 0xFF: mapper = new MapperHuC1(); mppr = "HuC1"; break;
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
2017-11-23 02:38:56 +00:00
|
|
|
|
// Bootleg mappers
|
|
|
|
|
// NOTE: Sachen mapper selection does not account for scrambling, so if another bootleg mapper
|
|
|
|
|
// identifies itself as 0x31, this will need to be modified
|
2018-03-18 20:06:48 +00:00
|
|
|
|
case 0x31: mapper = new MapperSachen2(); mppr = "Schn2"; break;
|
2017-11-23 02:38:56 +00:00
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
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
|
2017-11-23 02:38:56 +00:00
|
|
|
|
Console.WriteLine(header[0x47]);
|
2017-08-29 13:18:28 +00:00
|
|
|
|
throw new Exception("Mapper not implemented");
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-11 00:07:36 +00:00
|
|
|
|
// special case for multi cart mappers
|
2018-11-15 13:52:46 +00:00
|
|
|
|
if ((_rom.HashMD5(0, _rom.Length) == "97122B9B183AAB4079C8D36A4CE6E9C1") ||
|
2017-11-11 00:07:36 +00:00
|
|
|
|
(_rom.HashMD5(0, _rom.Length) == "9FB9C42CF52DCFDCFBAD5E61AE1B5777") ||
|
|
|
|
|
(_rom.HashMD5(0, _rom.Length) == "CF1F58AB72112716D3C615A553B2F481")
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Using Multi-Cart Mapper");
|
|
|
|
|
mapper = new MapperMBC1Multi();
|
|
|
|
|
}
|
2018-10-09 23:00:07 +00:00
|
|
|
|
|
2018-10-08 15:21:09 +00:00
|
|
|
|
// Wisdom Tree does not identify their mapper, so use hash instead
|
|
|
|
|
if ((_rom.HashMD5(0, _rom.Length) == "2C07CAEE51A1F0C91C72C7C6F380B0F6") || // Joshua
|
|
|
|
|
(_rom.HashMD5(0, _rom.Length) == "37E017C8D1A45BAB609FB5B43FB64337") || // Spiritual Warfare
|
|
|
|
|
(_rom.HashMD5(0, _rom.Length) == "AB1FA0ED0207B1D0D5F401F0CD17BEBF") || // Exodus
|
|
|
|
|
(_rom.HashMD5(0, _rom.Length) == "BA2AC3587B3E1B36DE52E740274071B0") || // Bible - KJV
|
|
|
|
|
(_rom.HashMD5(0, _rom.Length) == "8CDDB8B2DCD3EC1A3FDD770DF8BDA07C") // Bible - NIV
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Using Wisdom Tree Mapper");
|
|
|
|
|
mapper = new MapperWT();
|
2018-11-15 13:52:46 +00:00
|
|
|
|
mppr = "Wtree";
|
2018-10-08 15:21:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-31 01:56:32 +00:00
|
|
|
|
// special case for bootlegs
|
|
|
|
|
if ((_rom.HashMD5(0, _rom.Length) == "CAE0998A899DF2EE6ABA8E7695C2A096"))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Using RockMan 8 (Unlicensed) Mapper");
|
|
|
|
|
mapper = new MapperRM8();
|
|
|
|
|
}
|
2018-04-10 13:19:29 +00:00
|
|
|
|
if ((_rom.HashMD5(0, _rom.Length) == "D3C1924D847BC5D125BF54C2076BE27A"))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Using Sachen 1 (Unlicensed) Mapper");
|
|
|
|
|
mapper = new MapperSachen1();
|
|
|
|
|
mppr = "Schn1";
|
|
|
|
|
}
|
2018-03-31 01:56:32 +00:00
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
Console.Write("Mapper: ");
|
2017-11-14 19:43:58 +00:00
|
|
|
|
Console.WriteLine(mppr);
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
|
|
|
|
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;
|
2018-03-23 23:49:54 +00:00
|
|
|
|
case 0:
|
|
|
|
|
Console.WriteLine("Mapper Number indicates Battery Backed RAM but none present.");
|
|
|
|
|
Console.WriteLine("Disabling Battery Setting.");
|
|
|
|
|
has_bat = false;
|
|
|
|
|
break;
|
2017-11-14 19:43:58 +00:00
|
|
|
|
}
|
2017-08-29 13:18:28 +00:00
|
|
|
|
|
2017-11-23 02:38:56 +00:00
|
|
|
|
// Sachen maper not known to have RAM
|
|
|
|
|
if ((mppr == "Schn1") || (mppr == "Schn2"))
|
|
|
|
|
{
|
|
|
|
|
cart_RAM = null;
|
2018-04-10 13:19:29 +00:00
|
|
|
|
Use_MT = true;
|
2017-11-23 02:38:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-11-14 19:43:58 +00:00
|
|
|
|
// mbc2 carts have built in RAM
|
|
|
|
|
if (mppr == "MBC2")
|
|
|
|
|
{
|
|
|
|
|
cart_RAM = new byte[0x200];
|
2017-08-29 13:18:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-06 23:11:21 +00:00
|
|
|
|
// mbc7 has 256 bytes of RAM, regardless of any header info
|
|
|
|
|
if (mppr == "MBC7")
|
|
|
|
|
{
|
|
|
|
|
cart_RAM = new byte[0x100];
|
|
|
|
|
has_bat = true;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-29 13:18:28 +00:00
|
|
|
|
mapper.Core = this;
|
|
|
|
|
mapper.Initialize();
|
2017-11-24 20:44:29 +00:00
|
|
|
|
|
2018-04-06 23:11:21 +00:00
|
|
|
|
if (cart_RAM != null && (mppr != "MBC7"))
|
2017-11-25 16:04:07 +00:00
|
|
|
|
{
|
2017-11-25 16:10:59 +00:00
|
|
|
|
Console.Write("RAM: "); Console.WriteLine(cart_RAM.Length);
|
|
|
|
|
|
2018-03-18 20:06:48 +00:00
|
|
|
|
for (int i = 0; i < cart_RAM.Length; i++)
|
2017-11-25 16:04:07 +00:00
|
|
|
|
{
|
2018-03-18 20:06:48 +00:00
|
|
|
|
cart_RAM[i] = 0xFF;
|
2017-11-25 16:04:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-24 20:44:29 +00:00
|
|
|
|
// Extra RTC initialization for mbc3
|
|
|
|
|
if (mppr == "MBC3")
|
|
|
|
|
{
|
2018-04-10 13:19:29 +00:00
|
|
|
|
Use_MT = true;
|
2017-11-24 20:44:29 +00:00
|
|
|
|
int days = (int)Math.Floor(_syncSettings.RTCInitialTime / 86400.0);
|
|
|
|
|
|
|
|
|
|
int days_upper = ((days & 0x100) >> 8) | ((days & 0x200) >> 2);
|
|
|
|
|
|
|
|
|
|
mapper.RTC_Get((byte)days_upper, 4);
|
|
|
|
|
mapper.RTC_Get((byte)(days & 0xFF), 3);
|
|
|
|
|
|
|
|
|
|
int remaining = _syncSettings.RTCInitialTime - (days * 86400);
|
|
|
|
|
|
|
|
|
|
int hours = (int)Math.Floor(remaining / 3600.0);
|
|
|
|
|
|
|
|
|
|
mapper.RTC_Get((byte)(hours & 0xFF), 2);
|
|
|
|
|
|
|
|
|
|
remaining = remaining - (hours * 3600);
|
|
|
|
|
|
|
|
|
|
int minutes = (int)Math.Floor(remaining / 60.0);
|
|
|
|
|
|
|
|
|
|
mapper.RTC_Get((byte)(minutes & 0xFF), 1);
|
|
|
|
|
|
|
|
|
|
remaining = remaining - (minutes * 60);
|
|
|
|
|
|
|
|
|
|
mapper.RTC_Get((byte)(remaining & 0xFF), 0);
|
|
|
|
|
}
|
2018-12-22 15:10:27 +00:00
|
|
|
|
|
|
|
|
|
if (mppr == "HuC3")
|
|
|
|
|
{
|
|
|
|
|
Use_MT = true;
|
|
|
|
|
|
|
|
|
|
int years = (int)Math.Floor(_syncSettings.RTCInitialTime / 31536000.0);
|
|
|
|
|
|
|
|
|
|
mapper.RTC_Get((byte)years, 24);
|
|
|
|
|
|
|
|
|
|
int remaining = _syncSettings.RTCInitialTime - (years * 31536000);
|
|
|
|
|
|
|
|
|
|
int days = (int)Math.Floor(remaining / 86400.0);
|
|
|
|
|
int days_upper = (days >> 8) & 0xF;
|
|
|
|
|
|
|
|
|
|
mapper.RTC_Get((byte)days_upper, 20);
|
|
|
|
|
mapper.RTC_Get((byte)(days & 0xFF), 12);
|
|
|
|
|
|
|
|
|
|
remaining = remaining - (days * 86400);
|
|
|
|
|
|
|
|
|
|
int minutes = (int)Math.Floor(remaining / 60.0);
|
|
|
|
|
int minutes_upper = (minutes >> 8) & 0xF;
|
|
|
|
|
|
|
|
|
|
mapper.RTC_Get((byte)(minutes_upper), 8);
|
|
|
|
|
mapper.RTC_Get((byte)(remaining & 0xFF), 0);
|
|
|
|
|
}
|
2017-08-29 13:18:28 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|