430 lines
12 KiB
C#
430 lines
12 KiB
C#
using System;
|
|
|
|
using BizHawk.Common.StringExtensions;
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Emulation.Common.Components;
|
|
using BizHawk.Emulation.Cores.Components;
|
|
using BizHawk.Emulation.Cores.Components.Z80A;
|
|
|
|
/*****************************************************
|
|
TODO:
|
|
+ HCounter (Manually set for light phaser emulation... should be only case it's polled)
|
|
+ Try to clean up the organization of the source code.
|
|
+ Mode 1 not implemented in VDP TMS modes. (I dont have a test case in SG1000 or Coleco)
|
|
|
|
**********************************************************/
|
|
|
|
namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|
{
|
|
[Core(
|
|
"SMSHawk",
|
|
"Vecna",
|
|
isPorted: false,
|
|
isReleased: true)]
|
|
[ServiceNotApplicable(typeof(IDriveLight))]
|
|
public partial class SMS : IEmulator, ISaveRam, IStatable, IInputPollable, IRegionable,
|
|
IDebuggable, ISettable<SMS.SMSSettings, SMS.SMSSyncSettings>, ICodeDataLogger
|
|
{
|
|
[CoreConstructor("SMS", "SG", "GG")]
|
|
public SMS(CoreComm comm, GameInfo game, byte[] rom, object settings, object syncSettings)
|
|
{
|
|
ServiceProvider = new BasicServiceProvider(this);
|
|
Settings = (SMSSettings)settings ?? new SMSSettings();
|
|
SyncSettings = (SMSSyncSettings)syncSettings ?? new SMSSyncSettings();
|
|
CoreComm = comm;
|
|
MemoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
|
|
|
|
IsGameGear = game.System == "GG";
|
|
IsGameGear_C = game.System == "GG";
|
|
IsSG1000 = game.System == "SG";
|
|
RomData = rom;
|
|
|
|
if (RomData.Length % BankSize != 0)
|
|
{
|
|
Array.Resize(ref RomData, ((RomData.Length / BankSize) + 1) * BankSize);
|
|
}
|
|
|
|
RomBanks = (byte)(RomData.Length / BankSize);
|
|
|
|
Region = DetermineDisplayType(SyncSettings.DisplayType, game.Region);
|
|
if (game["PAL"] && Region != DisplayType.PAL)
|
|
{
|
|
Region = DisplayType.PAL;
|
|
CoreComm.Notify("Display was forced to PAL mode for game compatibility.");
|
|
}
|
|
|
|
if (IsGameGear)
|
|
{
|
|
Region = DisplayType.NTSC; // all game gears run at 60hz/NTSC mode
|
|
}
|
|
|
|
RegionStr = SyncSettings.ConsoleRegion;
|
|
if (RegionStr == "Auto")
|
|
{
|
|
RegionStr = DetermineRegion(game.Region);
|
|
}
|
|
|
|
if (game["Japan"] && RegionStr != "Japan")
|
|
{
|
|
RegionStr = "Japan";
|
|
CoreComm.Notify("Region was forced to Japan for game compatibility.");
|
|
}
|
|
|
|
if (game["Korea"] && RegionStr != "Korea")
|
|
{
|
|
RegionStr = "Korea";
|
|
CoreComm.Notify("Region was forced to Korea for game compatibility.");
|
|
}
|
|
|
|
if ((game.NotInDatabase || game["FM"]) && SyncSettings.EnableFM && !IsGameGear)
|
|
{
|
|
HasYM2413 = true;
|
|
}
|
|
|
|
Cpu = new Z80A()
|
|
{
|
|
ReadHardware = ReadPort,
|
|
WriteHardware = WritePort,
|
|
FetchMemory = FetchMemory,
|
|
ReadMemory = ReadMemory,
|
|
WriteMemory = WriteMemory,
|
|
MemoryCallbacks = MemoryCallbacks,
|
|
OnExecFetch = OnExecMemory
|
|
};
|
|
|
|
if (game["GG_in_SMS"])
|
|
{
|
|
// skip setting the BIOS because this is a game gear game that puts the system
|
|
// in SMS compatibility mode (it will fail the check sum if played on an actual SMS though.)
|
|
IsGameGear = false;
|
|
IsGameGear_C = true;
|
|
game.System = "GG";
|
|
Console.WriteLine("Using SMS Compatibility mode for Game Gear System");
|
|
}
|
|
|
|
Vdp = new VDP(this, Cpu, IsGameGear ? VdpMode.GameGear : VdpMode.SMS, Region);
|
|
(ServiceProvider as BasicServiceProvider).Register<IVideoProvider>(Vdp);
|
|
PSG = new SN76489sms();
|
|
YM2413 = new YM2413();
|
|
//SoundMixer = new SoundMixer(YM2413, PSG);
|
|
if (HasYM2413 && game["WhenFMDisablePSG"])
|
|
{
|
|
disablePSG = true;
|
|
}
|
|
|
|
blip_L.SetRates(3579545, 44100);
|
|
blip_R.SetRates(3579545, 44100);
|
|
|
|
(ServiceProvider as BasicServiceProvider).Register<ISoundProvider>(this);
|
|
|
|
SystemRam = new byte[0x2000];
|
|
|
|
if (game["CMMapper"])
|
|
InitCodeMastersMapper();
|
|
else if (game["CMMapperWithRam"])
|
|
InitCodeMastersMapperRam();
|
|
else if (game["ExtRam"])
|
|
InitExt2kMapper(int.Parse(game.OptionValue("ExtRam")));
|
|
else if (game["KoreaMapper"])
|
|
InitKoreaMapper();
|
|
else if (game["MSXMapper"])
|
|
InitMSXMapper();
|
|
else if (game["NemesisMapper"])
|
|
InitNemesisMapper();
|
|
else if (game["TerebiOekaki"])
|
|
InitTerebiOekaki();
|
|
else if (game["EEPROM"])
|
|
InitEEPROMMapper();
|
|
else
|
|
InitSegaMapper();
|
|
|
|
if (Settings.ForceStereoSeparation && !IsGameGear)
|
|
{
|
|
if (game["StereoByte"])
|
|
{
|
|
ForceStereoByte = byte.Parse(game.OptionValue("StereoByte"));
|
|
}
|
|
|
|
PSG.Set_Panning(ForceStereoByte);
|
|
}
|
|
|
|
if (SyncSettings.AllowOverlock && game["OverclockSafe"])
|
|
Vdp.IPeriod = 512;
|
|
|
|
if (Settings.SpriteLimit)
|
|
Vdp.SpriteLimit = true;
|
|
|
|
if (game["3D"])
|
|
IsGame3D = true;
|
|
|
|
if (game["BIOS"])
|
|
{
|
|
Port3E = 0xF7; // Disable cartridge, enable BIOS rom
|
|
InitBiosMapper();
|
|
}
|
|
else if ((game.System == "SMS") && !game["GG_in_SMS"])
|
|
{
|
|
BiosRom = comm.CoreFileProvider.GetFirmware("SMS", RegionStr, false);
|
|
|
|
if (BiosRom == null)
|
|
{
|
|
throw new MissingFirmwareException("No BIOS found");
|
|
}
|
|
else if (!game["RequireBios"] && !SyncSettings.UseBIOS)
|
|
{
|
|
// we are skipping the BIOS
|
|
// but only if it won't break the game
|
|
}
|
|
else
|
|
{
|
|
Port3E = 0xF7;
|
|
}
|
|
}
|
|
|
|
if (game["SRAM"])
|
|
{
|
|
SaveRAM = new byte[int.Parse(game.OptionValue("SRAM"))];
|
|
Console.WriteLine(SaveRAM.Length);
|
|
}
|
|
else if (game.NotInDatabase)
|
|
SaveRAM = new byte[0x8000];
|
|
|
|
SetupMemoryDomains();
|
|
|
|
//this manages the linkage between the cpu and mapper callbacks so it needs running before bootup is complete
|
|
((ICodeDataLogger)this).SetCDL(null);
|
|
|
|
InputCallbacks = new InputCallbackSystem();
|
|
|
|
Tracer = new TraceBuffer { Header = Cpu.TraceHeader };
|
|
|
|
var serviceProvider = ServiceProvider as BasicServiceProvider;
|
|
serviceProvider.Register<ITraceable>(Tracer);
|
|
serviceProvider.Register<IDisassemblable>(Cpu);
|
|
Vdp.ProcessOverscan();
|
|
|
|
Cpu.ReadMemory = ReadMemory;
|
|
Cpu.WriteMemory = WriteMemory;
|
|
|
|
// Z80 SP initialization
|
|
// stops a few SMS and GG games from crashing
|
|
Cpu.Regs[Cpu.SPl] = 0xF0;
|
|
Cpu.Regs[Cpu.SPh] = 0xDF;
|
|
}
|
|
|
|
public void HardReset()
|
|
{
|
|
|
|
}
|
|
|
|
// Constants
|
|
private const int BankSize = 16384;
|
|
|
|
// ROM
|
|
public byte[] RomData;
|
|
private byte RomBank0, RomBank1, RomBank2, RomBank3;
|
|
private byte Bios_bank;
|
|
private byte RomBanks;
|
|
private byte[] BiosRom;
|
|
|
|
// Machine resources
|
|
public Z80A Cpu;
|
|
public byte[] SystemRam;
|
|
public VDP Vdp;
|
|
public SN76489sms PSG;
|
|
private YM2413 YM2413;
|
|
public bool IsGameGear { get; set; }
|
|
public bool IsGameGear_C { get; set; }
|
|
public bool IsSG1000 { get; set; }
|
|
|
|
private bool HasYM2413 = false;
|
|
private bool disablePSG = false;
|
|
private bool PortDEEnabled = false;
|
|
private IController _controller = NullController.Instance;
|
|
|
|
private int _frame = 0;
|
|
|
|
private byte Port01 = 0xFF;
|
|
private byte Port02 = 0xFF;
|
|
private byte Port05 = 0x00;
|
|
private byte Port3E = 0xAF;
|
|
private byte Port3F = 0xFF;
|
|
private byte PortDE = 0x00;
|
|
|
|
private byte ForceStereoByte = 0xAD;
|
|
private bool IsGame3D = false;
|
|
|
|
// Linked Play Only
|
|
public bool start_pressed;
|
|
public byte cntr_rd_0;
|
|
public byte cntr_rd_1;
|
|
public byte cntr_rd_2;
|
|
public bool stand_alone = true;
|
|
|
|
public DisplayType Region { get; set; }
|
|
|
|
private readonly ITraceable Tracer;
|
|
|
|
string DetermineRegion(string gameRegion)
|
|
{
|
|
if (gameRegion == null)
|
|
return "Export";
|
|
if (gameRegion.IndexOf("USA") >= 0)
|
|
return "Export";
|
|
if (gameRegion.IndexOf("Europe") >= 0)
|
|
return "Export";
|
|
if (gameRegion.IndexOf("World") >= 0)
|
|
return "Export";
|
|
if (gameRegion.IndexOf("Brazil") >= 0)
|
|
return "Export";
|
|
if (gameRegion.IndexOf("Australia") >= 0)
|
|
return "Export";
|
|
if (gameRegion.IndexOf("Korea") >= 0)
|
|
return "Korea";
|
|
return "Japan";
|
|
}
|
|
|
|
private DisplayType DetermineDisplayType(string display, string region)
|
|
{
|
|
if (display == "NTSC") return DisplayType.NTSC;
|
|
if (display == "PAL") return DisplayType.PAL;
|
|
if (region != null && region == "Europe") return DisplayType.PAL;
|
|
return DisplayType.NTSC;
|
|
}
|
|
|
|
public byte ReadMemory(ushort addr)
|
|
{
|
|
MemoryCallbacks.CallReads(addr, "System Bus");
|
|
|
|
return ReadMemoryMapper(addr);
|
|
}
|
|
|
|
public void WriteMemory(ushort addr, byte value)
|
|
{
|
|
WriteMemoryMapper(addr, value);
|
|
|
|
MemoryCallbacks.CallWrites(addr, "System Bus");
|
|
}
|
|
|
|
public byte FetchMemory(ushort addr)
|
|
{
|
|
return ReadMemoryMapper(addr);
|
|
}
|
|
|
|
private void OnExecMemory(ushort addr)
|
|
{
|
|
MemoryCallbacks.CallExecutes(addr, "System Bus");
|
|
}
|
|
|
|
/// <summary>
|
|
/// The ReadMemory callback for the mapper
|
|
/// </summary>
|
|
private Func<ushort, byte> ReadMemoryMapper;
|
|
|
|
/// <summary>
|
|
/// The WriteMemory callback for the wrapper
|
|
/// </summary>
|
|
private Action<ushort, byte> WriteMemoryMapper;
|
|
|
|
/// <summary>
|
|
/// A dummy FetchMemory that simply reads the memory
|
|
/// </summary>
|
|
private byte FetchMemory_StubThunk(ushort address)
|
|
{
|
|
return ReadMemory(address);
|
|
}
|
|
|
|
private byte ReadPort(ushort port)
|
|
{
|
|
port &= 0xFF;
|
|
if (port < 0x40) // General IO ports
|
|
{
|
|
|
|
switch (port)
|
|
{
|
|
case 0x00: if (stand_alone) { return ReadPort0(); } else { _lagged = false; return cntr_rd_0; }
|
|
case 0x01: return Port01;
|
|
case 0x02: return Port02;
|
|
case 0x03: return 0x00;
|
|
case 0x04: return 0xFF;
|
|
case 0x05: return Port05;
|
|
case 0x06: return 0xFF;
|
|
case 0x3E: return Port3E;
|
|
default: return 0xFF;
|
|
}
|
|
}
|
|
if (port < 0x80) // VDP Vcounter/HCounter
|
|
{
|
|
if ((port & 1) == 0)
|
|
return Vdp.ReadVLineCounter();
|
|
else
|
|
return Vdp.ReadHLineCounter();
|
|
}
|
|
if (port < 0xC0) // VDP data/control ports
|
|
{
|
|
if ((port & 1) == 0)
|
|
return Vdp.ReadData();
|
|
else
|
|
return Vdp.ReadVdpStatus();
|
|
}
|
|
switch (port)
|
|
{
|
|
case 0xC0:
|
|
case 0xDC: if (stand_alone) { return ReadControls1(); } else { _lagged = false; return cntr_rd_1; }
|
|
case 0xC1:
|
|
case 0xDD: if (stand_alone) { return ReadControls2(); } else { _lagged = false; return cntr_rd_2; }
|
|
case 0xDE: return PortDEEnabled ? PortDE : (byte)0xFF;
|
|
case 0xF2: return HasYM2413 ? YM2413.DetectionValue : (byte)0xFF;
|
|
default: return 0xFF;
|
|
}
|
|
}
|
|
|
|
private void WritePort(ushort port, byte value)
|
|
{
|
|
port &= 0xFF;
|
|
if (port < 0x40) // general IO ports
|
|
{
|
|
switch (port & 0xFF)
|
|
{
|
|
case 0x01: Port01 = value; break;
|
|
case 0x02: Port02 = value; break;
|
|
case 0x05: Port05 = value; break;
|
|
case 0x06: PSG.Set_Panning(value); break;
|
|
case 0x3E: Port3E = value; break;
|
|
case 0x3F: Port3F = value; break;
|
|
}
|
|
}
|
|
else if (port < 0x80) // PSG
|
|
PSG.WriteReg(value);
|
|
else if (port < 0xC0) // VDP
|
|
{
|
|
if ((port & 1) == 0)
|
|
Vdp.WriteVdpData(value);
|
|
else
|
|
Vdp.WriteVdpControl(value);
|
|
}
|
|
else if (port == 0xDE && PortDEEnabled) PortDE = value;
|
|
else if (port == 0xF0 && HasYM2413) YM2413.RegisterLatch = value;
|
|
else if (port == 0xF1 && HasYM2413) YM2413.Write(value);
|
|
else if (port == 0xF2 && HasYM2413) YM2413.DetectionValue = value;
|
|
}
|
|
|
|
public string _region;
|
|
public string RegionStr
|
|
{
|
|
get { return _region; }
|
|
set
|
|
{
|
|
if (value.NotIn(validRegions))
|
|
{
|
|
throw new Exception("Passed value " + value + " is not a valid region!");
|
|
}
|
|
|
|
_region = value;
|
|
}
|
|
}
|
|
|
|
private readonly string[] validRegions = { "Export", "Japan", "Korea" , "Auto" };
|
|
}
|
|
}
|