2011-01-11 02:55:51 +00:00
|
|
|
|
using System;
|
2011-01-21 03:59:50 +00:00
|
|
|
|
using System.Collections.Generic;
|
2011-01-11 02:55:51 +00:00
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
2013-10-27 22:07:40 +00:00
|
|
|
|
|
|
|
|
|
using BizHawk.Common;
|
2013-11-04 01:06:36 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2013-11-14 19:33:13 +00:00
|
|
|
|
using BizHawk.Emulation.Common.Components;
|
2014-01-22 01:14:36 +00:00
|
|
|
|
using BizHawk.Emulation.Cores.Components.Z80;
|
2011-01-11 02:55:51 +00:00
|
|
|
|
|
|
|
|
|
/*****************************************************
|
|
|
|
|
|
2011-01-21 03:59:50 +00:00
|
|
|
|
TODO:
|
2011-01-11 02:55:51 +00:00
|
|
|
|
+ HCounter
|
|
|
|
|
+ Try to clean up the organization of the source code.
|
2011-02-16 04:45:59 +00:00
|
|
|
|
+ Lightgun/Paddle/etc if I get really bored
|
2014-03-13 04:15:05 +00:00
|
|
|
|
+ Mode 1 not implemented in VDP TMS modes. (I dont have a test case in SG1000 or Coleco)
|
2014-03-13 04:49:39 +00:00
|
|
|
|
+ Add Region to GameDB.
|
|
|
|
|
+ Still need a "disable bios for japan-only games when bios is enabled and region is export" functionality
|
|
|
|
|
+ Or a "force region to japan if game is only for japan" thing. Which one is better?
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
+ I confess, Mapper system needs some refactoring and love. But right now I want to get all games to work and THEN refactor it.
|
|
|
|
|
+ Savestate system.... maybe use a zeromus-like system. Except maintain the text-savestate compatibility. Might give up some speed for improved maintainability tho.
|
2014-03-17 03:00:07 +00:00
|
|
|
|
+ Make corecomm OSD notifier system
|
2012-11-26 01:44:17 +00:00
|
|
|
|
|
2011-01-11 02:55:51 +00:00
|
|
|
|
**********************************************************/
|
|
|
|
|
|
2013-11-13 23:36:21 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
2011-01-11 02:55:51 +00:00
|
|
|
|
{
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public sealed partial class SMS : IEmulator
|
|
|
|
|
{
|
|
|
|
|
// Constants
|
|
|
|
|
public const int BankSize = 16384;
|
|
|
|
|
|
|
|
|
|
// ROM
|
|
|
|
|
public byte[] RomData;
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
public byte RomBank0, RomBank1, RomBank2, RomBank3;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public byte RomBanks;
|
|
|
|
|
|
|
|
|
|
// SaveRAM
|
|
|
|
|
public byte[] SaveRAM = new byte[BankSize * 2];
|
|
|
|
|
public byte SaveRamBank;
|
|
|
|
|
|
2014-03-05 05:09:20 +00:00
|
|
|
|
public byte[] BiosRom;
|
|
|
|
|
|
2012-09-14 23:31:10 +00:00
|
|
|
|
public byte[] ReadSaveRam()
|
|
|
|
|
{
|
|
|
|
|
if (SaveRAM != null)
|
|
|
|
|
return (byte[])SaveRAM.Clone();
|
|
|
|
|
else
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
public void StoreSaveRam(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
if (SaveRAM != null)
|
|
|
|
|
Array.Copy(data, SaveRAM, data.Length);
|
|
|
|
|
}
|
|
|
|
|
public void ClearSaveRam()
|
|
|
|
|
{
|
|
|
|
|
if (SaveRAM != null)
|
|
|
|
|
SaveRAM = new byte[SaveRAM.Length];
|
|
|
|
|
}
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public bool SaveRamModified { get; set; }
|
|
|
|
|
|
|
|
|
|
// Machine resources
|
|
|
|
|
public Z80A Cpu;
|
|
|
|
|
public byte[] SystemRam;
|
|
|
|
|
public VDP Vdp;
|
|
|
|
|
public SN76489 PSG;
|
|
|
|
|
public YM2413 YM2413;
|
|
|
|
|
public SoundMixer SoundMixer;
|
|
|
|
|
public bool IsGameGear = false;
|
|
|
|
|
public bool HasYM2413 = false;
|
|
|
|
|
|
2011-09-05 17:45:01 +00:00
|
|
|
|
int _lagcount = 0;
|
|
|
|
|
bool lagged = true;
|
|
|
|
|
bool islag = false;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public int Frame { get; set; }
|
2014-03-17 03:00:07 +00:00
|
|
|
|
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public int LagCount { get { return _lagcount; } set { _lagcount = value; } }
|
|
|
|
|
public bool IsLagFrame { get { return islag; } }
|
2011-09-05 17:45:01 +00:00
|
|
|
|
byte Port01 = 0xFF;
|
|
|
|
|
byte Port02 = 0xFF;
|
|
|
|
|
byte Port3E = 0xAF;
|
|
|
|
|
byte Port3F = 0xFF;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
|
2014-02-27 00:28:05 +00:00
|
|
|
|
byte ForceStereoByte = 0xAD;
|
2014-03-05 02:20:22 +00:00
|
|
|
|
bool IsGame3D = false;
|
2014-02-27 00:28:05 +00:00
|
|
|
|
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public DisplayType DisplayType { get; set; }
|
2012-10-03 15:31:04 +00:00
|
|
|
|
public bool DeterministicEmulation { get { return true; } }
|
2011-01-11 02:55:51 +00:00
|
|
|
|
|
2014-03-06 04:43:36 +00:00
|
|
|
|
public SMS(CoreComm comm, GameInfo game, byte[] rom, object settings, object syncSettings)
|
2011-06-11 22:15:08 +00:00
|
|
|
|
{
|
2014-03-06 04:43:36 +00:00
|
|
|
|
Settings = (SMSSettings)settings ?? new SMSSettings();
|
|
|
|
|
SyncSettings = (SMSSyncSettings)syncSettings ?? new SMSSyncSettings();
|
2013-12-24 01:06:17 +00:00
|
|
|
|
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm = comm;
|
|
|
|
|
|
|
|
|
|
IsGameGear = game.System == "GG";
|
2011-08-04 03:20:54 +00:00
|
|
|
|
RomData = rom;
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.CpuTraceAvailable = true;
|
2012-10-06 18:09:41 +00:00
|
|
|
|
|
2011-08-04 03:20:54 +00:00
|
|
|
|
if (RomData.Length % BankSize != 0)
|
|
|
|
|
Array.Resize(ref RomData, ((RomData.Length / BankSize) + 1) * BankSize);
|
|
|
|
|
RomBanks = (byte)(RomData.Length / BankSize);
|
2012-07-15 07:15:31 +00:00
|
|
|
|
|
2014-03-13 04:49:39 +00:00
|
|
|
|
DisplayType = SyncSettings.UsePAL ? DisplayType.PAL : DisplayType.NTSC;
|
|
|
|
|
if (game["PAL"] && DisplayType != DisplayType.PAL)
|
|
|
|
|
{
|
|
|
|
|
DisplayType = DisplayType.PAL;
|
|
|
|
|
Console.WriteLine("Display was forced to PAL mode for game compatibility."); // TODO change to corecomm.notify when it exists
|
|
|
|
|
}
|
2014-03-17 03:00:07 +00:00
|
|
|
|
if (IsGameGear)
|
|
|
|
|
DisplayType = DisplayType.NTSC; // all game gears run at 60hz/NTSC mode
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm.VsyncNum = DisplayType == DisplayType.NTSC ? 60 : 50;
|
|
|
|
|
CoreComm.VsyncDen = 1;
|
2014-03-13 04:49:39 +00:00
|
|
|
|
|
|
|
|
|
Region = SyncSettings.ExportRegion ? "Export" : "Japan";
|
|
|
|
|
if (game["Japan"] && Region != "Japan")
|
|
|
|
|
{
|
|
|
|
|
Region = "Japan";
|
|
|
|
|
Console.WriteLine("Region was forced to Japan for game compatibility."); // TODO corecomm.notify
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (game.NotInDatabase || game["FM"] && SyncSettings.EnableFM && !IsGameGear)
|
2011-08-04 03:20:54 +00:00
|
|
|
|
HasYM2413 = true;
|
|
|
|
|
|
|
|
|
|
if (Controller == null)
|
|
|
|
|
Controller = NullController.GetNullController();
|
|
|
|
|
|
|
|
|
|
Cpu = new Z80A();
|
|
|
|
|
Cpu.RegisterSP = 0xDFF0;
|
|
|
|
|
Cpu.ReadHardware = ReadPort;
|
|
|
|
|
Cpu.WriteHardware = WritePort;
|
|
|
|
|
|
2012-03-11 16:15:20 +00:00
|
|
|
|
Vdp = new VDP(this, Cpu, IsGameGear ? VdpMode.GameGear : VdpMode.SMS, DisplayType);
|
2011-08-04 03:20:54 +00:00
|
|
|
|
PSG = new SN76489();
|
|
|
|
|
YM2413 = new YM2413();
|
|
|
|
|
SoundMixer = new SoundMixer(YM2413, PSG);
|
|
|
|
|
if (HasYM2413 && game["WhenFMDisablePSG"])
|
|
|
|
|
SoundMixer.DisableSource(PSG);
|
|
|
|
|
ActiveSoundProvider = HasYM2413 ? (ISoundProvider)SoundMixer : PSG;
|
|
|
|
|
|
|
|
|
|
SystemRam = new byte[0x2000];
|
|
|
|
|
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (game["CMMapper"])
|
|
|
|
|
InitCodeMastersMapper();
|
2014-03-18 02:23:10 +00:00
|
|
|
|
else if (game["CMMapperWithRam"])
|
|
|
|
|
InitCodeMastersMapperRam();
|
2014-03-06 04:43:36 +00:00
|
|
|
|
else if (game["ExtRam"])
|
|
|
|
|
InitExt2kMapper(int.Parse(game.OptionValue("ExtRam")));
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
else if (game["KoreaMapper"])
|
|
|
|
|
InitKoreaMapper();
|
|
|
|
|
else if (game["MSXMapper"])
|
|
|
|
|
InitMSXMapper();
|
|
|
|
|
else if (game["NemesisMapper"])
|
|
|
|
|
InitNemesisMapper();
|
2014-03-17 03:00:07 +00:00
|
|
|
|
else if (game["TerebiOekaki"])
|
|
|
|
|
InitTerebiOekaki();
|
2014-03-06 04:43:36 +00:00
|
|
|
|
else
|
|
|
|
|
InitSegaMapper();
|
|
|
|
|
|
|
|
|
|
if (Settings.ForceStereoSeparation && !IsGameGear)
|
2011-08-04 03:20:54 +00:00
|
|
|
|
{
|
|
|
|
|
if (game["StereoByte"])
|
|
|
|
|
{
|
2014-02-27 00:28:05 +00:00
|
|
|
|
ForceStereoByte = byte.Parse(game.OptionValue("StereoByte"));
|
2011-08-04 03:20:54 +00:00
|
|
|
|
}
|
2014-02-27 00:28:05 +00:00
|
|
|
|
PSG.StereoPanning = ForceStereoByte;
|
2011-08-04 03:20:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (SyncSettings.AllowOverlock && game["OverclockSafe"])
|
2011-08-04 03:20:54 +00:00
|
|
|
|
Vdp.IPeriod = 512;
|
|
|
|
|
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (Settings.SpriteLimit)
|
2011-09-19 00:39:28 +00:00
|
|
|
|
Vdp.SpriteLimit = true;
|
|
|
|
|
|
2014-03-05 02:20:22 +00:00
|
|
|
|
if (game["3D"])
|
|
|
|
|
IsGame3D = true;
|
|
|
|
|
|
2014-03-05 05:09:20 +00:00
|
|
|
|
if (game["BIOS"])
|
|
|
|
|
{
|
|
|
|
|
Port3E = 0xF7; // Disable cartridge, enable BIOS rom
|
|
|
|
|
InitBiosMapper();
|
|
|
|
|
}
|
2014-03-05 12:49:27 +00:00
|
|
|
|
else if (game.System == "SMS")
|
2014-03-05 05:09:20 +00:00
|
|
|
|
{
|
2014-03-13 04:49:39 +00:00
|
|
|
|
BiosRom = comm.CoreFileProvider.GetFirmware("SMS", Region, false);
|
2014-03-17 03:00:07 +00:00
|
|
|
|
if (BiosRom != null && (game["RequireBios"] || SyncSettings.UseBIOS))
|
2014-03-05 05:09:20 +00:00
|
|
|
|
Port3E = 0xF7;
|
|
|
|
|
|
2014-03-17 03:00:07 +00:00
|
|
|
|
if (BiosRom == null && game["RequireBios"])
|
|
|
|
|
Console.WriteLine("BIOS image not available. This game requires BIOS to function."); // TODO corecomm.notify
|
2014-03-13 04:49:39 +00:00
|
|
|
|
if (SyncSettings.UseBIOS && BiosRom == null)
|
|
|
|
|
Console.WriteLine("BIOS was selected, but rom image not available. BIOS not enabled."); // TODO corecomm.notify
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetupMemoryDomains();
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-03-17 03:00:07 +00:00
|
|
|
|
public void ResetCounters()
|
|
|
|
|
{
|
|
|
|
|
Frame = 0;
|
|
|
|
|
_lagcount = 0;
|
|
|
|
|
islag = false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public byte ReadPort(ushort port)
|
|
|
|
|
{
|
2014-03-17 03:56:42 +00:00
|
|
|
|
port &= 0xFF;
|
|
|
|
|
if (port < 0x40) // General IO ports
|
|
|
|
|
{
|
|
|
|
|
switch (port)
|
|
|
|
|
{
|
|
|
|
|
case 0x00: return ReadPort0();
|
|
|
|
|
case 0x01: return Port01;
|
|
|
|
|
case 0x02: return Port02;
|
|
|
|
|
case 0x03: return 0x00;
|
|
|
|
|
case 0x04: return 0xFF;
|
|
|
|
|
case 0x05: return 0x00;
|
|
|
|
|
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 0x50; // TODO Vdp.ReadHLineCounter();
|
|
|
|
|
}
|
|
|
|
|
if (port < 0xC0) // VDP data/control ports
|
|
|
|
|
{
|
|
|
|
|
if ((port & 1) == 0)
|
|
|
|
|
return Vdp.ReadData();
|
|
|
|
|
else
|
|
|
|
|
return Vdp.ReadVdpStatus();
|
|
|
|
|
}
|
|
|
|
|
switch (port)
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
|
|
|
|
case 0xC0:
|
|
|
|
|
case 0xDC: return ReadControls1();
|
|
|
|
|
case 0xC1:
|
|
|
|
|
case 0xDD: return ReadControls2();
|
|
|
|
|
case 0xF2: return HasYM2413 ? YM2413.DetectionValue : (byte)0xFF;
|
2014-03-17 03:56:42 +00:00
|
|
|
|
default: return 0xFF;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WritePort(ushort port, byte value)
|
|
|
|
|
{
|
2014-03-17 03:56:42 +00:00
|
|
|
|
port &= 0xFF;
|
|
|
|
|
if (port < 0x40) // general IO ports
|
|
|
|
|
{
|
|
|
|
|
switch (port & 0xFF)
|
|
|
|
|
{
|
|
|
|
|
case 0x01: Port01 = value; break;
|
|
|
|
|
case 0x02: Port02 = value; break;
|
|
|
|
|
case 0x06: PSG.StereoPanning = value; break;
|
|
|
|
|
case 0x3E: Port3E = value; break;
|
|
|
|
|
case 0x3F: Port3F = value; break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (port < 0x80) // PSG
|
|
|
|
|
PSG.WritePsgData(value, Cpu.TotalExecutedCycles);
|
|
|
|
|
else if (port < 0xC0) // VDP
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
2014-03-17 03:56:42 +00:00
|
|
|
|
if ((port & 1) == 0)
|
|
|
|
|
Vdp.WriteVdpData(value);
|
|
|
|
|
else
|
|
|
|
|
Vdp.WriteVdpControl(value);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
2014-03-17 03:56:42 +00:00
|
|
|
|
else if (port == 0xF0 && HasYM2413) YM2413.RegisterLatch = value;
|
|
|
|
|
else if (port == 0xF1 && HasYM2413) YM2413.Write(value);
|
|
|
|
|
else if (port == 0xF2 && HasYM2413) YM2413.DetectionValue = value;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-09-20 19:52:47 +00:00
|
|
|
|
public void FrameAdvance(bool render, bool rendersound)
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
|
|
|
|
lagged = true;
|
2013-12-07 00:53:06 +00:00
|
|
|
|
Frame++;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
PSG.BeginFrame(Cpu.TotalExecutedCycles);
|
2013-12-07 00:53:06 +00:00
|
|
|
|
Cpu.Debug = CoreComm.Tracer.Enabled;
|
2014-02-27 00:28:05 +00:00
|
|
|
|
if (!IsGameGear)
|
|
|
|
|
PSG.StereoPanning = Settings.ForceStereoSeparation ? ForceStereoByte : (byte) 0xFF;
|
|
|
|
|
|
2013-12-07 00:53:06 +00:00
|
|
|
|
if (Cpu.Debug && Cpu.Logger == null) // TODO, lets not do this on each frame. But lets refactor CoreComm/CoreComm first
|
|
|
|
|
Cpu.Logger = (s) => CoreComm.Tracer.Put(s);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
|
|
|
|
|
if (IsGameGear == false)
|
|
|
|
|
Cpu.NonMaskableInterrupt = Controller["Pause"];
|
|
|
|
|
|
2014-03-05 02:20:22 +00:00
|
|
|
|
if (IsGame3D && Settings.Fix3D)
|
|
|
|
|
Vdp.ExecFrame((Frame & 1) == 0);
|
|
|
|
|
else
|
|
|
|
|
Vdp.ExecFrame(render);
|
|
|
|
|
|
2011-06-27 01:24:26 +00:00
|
|
|
|
PSG.EndFrame(Cpu.TotalExecutedCycles);
|
|
|
|
|
if (lagged)
|
|
|
|
|
{
|
|
|
|
|
_lagcount++;
|
|
|
|
|
islag = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
islag = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SaveStateText(TextWriter writer)
|
|
|
|
|
{
|
|
|
|
|
writer.WriteLine("[SMS]\n");
|
|
|
|
|
Cpu.SaveStateText(writer);
|
|
|
|
|
PSG.SaveStateText(writer);
|
|
|
|
|
Vdp.SaveStateText(writer);
|
|
|
|
|
|
|
|
|
|
writer.WriteLine("Frame {0}", Frame);
|
|
|
|
|
writer.WriteLine("Lag {0}", _lagcount);
|
2012-11-24 14:19:59 +00:00
|
|
|
|
writer.WriteLine("IsLag {0}", islag);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
writer.WriteLine("Bank0 {0}", RomBank0);
|
|
|
|
|
writer.WriteLine("Bank1 {0}", RomBank1);
|
|
|
|
|
writer.WriteLine("Bank2 {0}", RomBank2);
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
writer.WriteLine("Bank3 {0}", RomBank3);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
writer.Write("RAM ");
|
|
|
|
|
SystemRam.SaveAsHex(writer);
|
|
|
|
|
writer.WriteLine("Port01 {0:X2}", Port01);
|
|
|
|
|
writer.WriteLine("Port02 {0:X2}", Port02);
|
2014-03-06 04:43:36 +00:00
|
|
|
|
writer.WriteLine("Port3E {0:X2}", Port3E);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
writer.WriteLine("Port3F {0:X2}", Port3F);
|
|
|
|
|
int SaveRamLen = Util.SaveRamBytesUsed(SaveRAM);
|
|
|
|
|
if (SaveRamLen > 0)
|
|
|
|
|
{
|
|
|
|
|
writer.Write("SaveRAM ");
|
|
|
|
|
SaveRAM.SaveAsHex(writer, SaveRamLen);
|
|
|
|
|
}
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (ExtRam != null)
|
|
|
|
|
{
|
|
|
|
|
writer.Write("ExtRAM ");
|
|
|
|
|
ExtRam.SaveAsHex(writer, ExtRam.Length);
|
|
|
|
|
}
|
2011-06-27 01:24:26 +00:00
|
|
|
|
if (HasYM2413)
|
|
|
|
|
{
|
|
|
|
|
writer.Write("FMRegs ");
|
|
|
|
|
YM2413.opll.reg.SaveAsHex(writer);
|
|
|
|
|
}
|
|
|
|
|
writer.WriteLine("[/SMS]");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateText(TextReader reader)
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
string[] args = reader.ReadLine().Split(' ');
|
|
|
|
|
if (args[0].Trim() == "") continue;
|
|
|
|
|
if (args[0] == "[SMS]") continue;
|
|
|
|
|
if (args[0] == "[/SMS]") break;
|
|
|
|
|
if (args[0] == "Bank0")
|
|
|
|
|
RomBank0 = byte.Parse(args[1]);
|
|
|
|
|
else if (args[0] == "Bank1")
|
|
|
|
|
RomBank1 = byte.Parse(args[1]);
|
|
|
|
|
else if (args[0] == "Bank2")
|
|
|
|
|
RomBank2 = byte.Parse(args[1]);
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
else if (args[0] == "Bank3")
|
|
|
|
|
RomBank3 = byte.Parse(args[1]);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
else if (args[0] == "Frame")
|
|
|
|
|
Frame = int.Parse(args[1]);
|
|
|
|
|
else if (args[0] == "Lag")
|
|
|
|
|
_lagcount = int.Parse(args[1]);
|
2012-11-24 14:19:59 +00:00
|
|
|
|
else if (args[0] == "IsLag")
|
2012-11-22 01:01:39 +00:00
|
|
|
|
islag = bool.Parse(args[1]);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
else if (args[0] == "RAM")
|
|
|
|
|
SystemRam.ReadFromHex(args[1]);
|
|
|
|
|
else if (args[0] == "SaveRAM")
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < SaveRAM.Length; i++) SaveRAM[i] = 0;
|
|
|
|
|
SaveRAM.ReadFromHex(args[1]);
|
|
|
|
|
}
|
2014-03-06 04:43:36 +00:00
|
|
|
|
else if (args[0] == "ExtRAM")
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < ExtRam.Length; i++) ExtRam[i] = 0;
|
|
|
|
|
ExtRam.ReadFromHex(args[1]);
|
|
|
|
|
}
|
2011-06-27 01:24:26 +00:00
|
|
|
|
else if (args[0] == "FMRegs")
|
|
|
|
|
{
|
|
|
|
|
byte[] regs = new byte[YM2413.opll.reg.Length];
|
|
|
|
|
regs.ReadFromHex(args[1]);
|
|
|
|
|
for (byte i = 0; i < regs.Length; i++)
|
|
|
|
|
YM2413.Write(i, regs[i]);
|
|
|
|
|
}
|
|
|
|
|
else if (args[0] == "Port01")
|
|
|
|
|
Port01 = byte.Parse(args[1], NumberStyles.HexNumber);
|
|
|
|
|
else if (args[0] == "Port02")
|
|
|
|
|
Port02 = byte.Parse(args[1], NumberStyles.HexNumber);
|
2014-03-06 04:43:36 +00:00
|
|
|
|
else if (args[0] == "Port3E")
|
|
|
|
|
Port3E = byte.Parse(args[1], NumberStyles.HexNumber);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
else if (args[0] == "Port3F")
|
|
|
|
|
Port3F = byte.Parse(args[1], NumberStyles.HexNumber);
|
|
|
|
|
else if (args[0] == "[Z80]")
|
|
|
|
|
Cpu.LoadStateText(reader);
|
|
|
|
|
else if (args[0] == "[PSG]")
|
|
|
|
|
PSG.LoadStateText(reader);
|
|
|
|
|
else if (args[0] == "[VDP]")
|
|
|
|
|
Vdp.LoadStateText(reader);
|
|
|
|
|
else
|
|
|
|
|
Console.WriteLine("Skipping unrecognized identifier " + args[0]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] SaveStateBinary()
|
|
|
|
|
{
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
int buflen = 24809 + 16384 + 16384;
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (ExtRam != null)
|
|
|
|
|
buflen += ExtRam.Length;
|
|
|
|
|
var buf = new byte[buflen];
|
2011-06-27 01:24:26 +00:00
|
|
|
|
var stream = new MemoryStream(buf);
|
|
|
|
|
var writer = new BinaryWriter(stream);
|
|
|
|
|
SaveStateBinary(writer);
|
2012-12-14 16:59:23 +00:00
|
|
|
|
if (stream.Length != buf.Length)
|
|
|
|
|
throw new Exception(string.Format("savestate buffer underrun: {0} < {1}", stream.Length, buf.Length));
|
2011-06-27 01:24:26 +00:00
|
|
|
|
writer.Close();
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
2013-05-06 20:51:28 +00:00
|
|
|
|
public bool BinarySaveStatesPreferred { get { return false; } }
|
|
|
|
|
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public void SaveStateBinary(BinaryWriter writer)
|
|
|
|
|
{
|
|
|
|
|
Cpu.SaveStateBinary(writer);
|
|
|
|
|
PSG.SaveStateBinary(writer);
|
|
|
|
|
Vdp.SaveStateBinary(writer);
|
|
|
|
|
|
|
|
|
|
writer.Write(Frame);
|
|
|
|
|
writer.Write(_lagcount);
|
2012-11-22 01:01:39 +00:00
|
|
|
|
writer.Write(islag);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
writer.Write(RomBank0);
|
|
|
|
|
writer.Write(RomBank1);
|
|
|
|
|
writer.Write(RomBank2);
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
writer.Write(RomBank3);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
writer.Write(SystemRam);
|
|
|
|
|
writer.Write(SaveRAM);
|
|
|
|
|
writer.Write(Port01);
|
|
|
|
|
writer.Write(Port02);
|
2014-03-06 04:43:36 +00:00
|
|
|
|
writer.Write(Port3E);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
writer.Write(Port3F);
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (ExtRam != null)
|
|
|
|
|
writer.Write(ExtRam);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
writer.Write(YM2413.opll.reg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void LoadStateBinary(BinaryReader reader)
|
|
|
|
|
{
|
|
|
|
|
Cpu.LoadStateBinary(reader);
|
|
|
|
|
PSG.LoadStateBinary(reader);
|
|
|
|
|
Vdp.LoadStateBinary(reader);
|
|
|
|
|
|
|
|
|
|
Frame = reader.ReadInt32();
|
|
|
|
|
_lagcount = reader.ReadInt32();
|
2012-11-22 01:01:39 +00:00
|
|
|
|
islag = reader.ReadBoolean();
|
2011-06-27 01:24:26 +00:00
|
|
|
|
RomBank0 = reader.ReadByte();
|
|
|
|
|
RomBank1 = reader.ReadByte();
|
|
|
|
|
RomBank2 = reader.ReadByte();
|
SMS: Korean mappers work. Fixes Cyborg Z, Dodgeball King, F1 Spirit, Jang Pang III, Knightmare II, Nemesis, Nemesis 2, Penguin Adventure, Sangokushi 3, Street Master, Wonsiin
2014-03-14 04:30:01 +00:00
|
|
|
|
RomBank3 = reader.ReadByte();
|
2011-06-27 01:24:26 +00:00
|
|
|
|
SystemRam = reader.ReadBytes(SystemRam.Length);
|
|
|
|
|
reader.Read(SaveRAM, 0, SaveRAM.Length);
|
|
|
|
|
Port01 = reader.ReadByte();
|
|
|
|
|
Port02 = reader.ReadByte();
|
2014-03-06 04:43:36 +00:00
|
|
|
|
Port3E = reader.ReadByte();
|
2011-06-27 01:24:26 +00:00
|
|
|
|
Port3F = reader.ReadByte();
|
2014-03-06 04:43:36 +00:00
|
|
|
|
if (ExtRam != null)
|
|
|
|
|
reader.Read(ExtRam, 0, ExtRam.Length);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
if (HasYM2413)
|
|
|
|
|
{
|
|
|
|
|
byte[] regs = new byte[YM2413.opll.reg.Length];
|
|
|
|
|
reader.Read(regs, 0, regs.Length);
|
|
|
|
|
for (byte i = 0; i < regs.Length; i++)
|
|
|
|
|
YM2413.Write(i, regs[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-03-17 03:00:07 +00:00
|
|
|
|
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public IVideoProvider VideoProvider { get { return Vdp; } }
|
2012-12-10 00:43:43 +00:00
|
|
|
|
public CoreComm CoreComm { get; private set; }
|
2011-01-11 02:55:51 +00:00
|
|
|
|
|
2011-09-05 17:45:01 +00:00
|
|
|
|
ISoundProvider ActiveSoundProvider;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public ISoundProvider SoundProvider { get { return ActiveSoundProvider; } }
|
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
|
|
|
|
public ISyncSoundProvider SyncSoundProvider { get { return new FakeSyncSound(ActiveSoundProvider, 735); } }
|
|
|
|
|
public bool StartAsyncSound() { return true; }
|
|
|
|
|
public void EndAsyncSound() { }
|
2011-06-27 01:24:26 +00:00
|
|
|
|
|
|
|
|
|
public string SystemId { get { return "SMS"; } }
|
|
|
|
|
|
2013-08-24 16:54:22 +00:00
|
|
|
|
public string BoardName { get { return null; } }
|
|
|
|
|
|
2014-03-13 04:49:39 +00:00
|
|
|
|
string region;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public string Region
|
|
|
|
|
{
|
|
|
|
|
get { return region; }
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (value.NotIn(validRegions))
|
|
|
|
|
throw new Exception("Passed value " + value + " is not a valid region!");
|
|
|
|
|
region = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-03-17 03:00:07 +00:00
|
|
|
|
|
2011-09-05 17:45:01 +00:00
|
|
|
|
readonly string[] validRegions = { "Export", "Japan" };
|
2011-06-27 01:24:26 +00:00
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
MemoryDomainList memoryDomains;
|
2011-06-27 01:24:26 +00:00
|
|
|
|
|
2011-09-05 17:45:01 +00:00
|
|
|
|
void SetupMemoryDomains()
|
2011-06-27 01:24:26 +00:00
|
|
|
|
{
|
|
|
|
|
var domains = new List<MemoryDomain>(3);
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var MainMemoryDomain = new MemoryDomain("Main RAM", SystemRam.Length, MemoryDomain.Endian.Little,
|
2014-02-26 20:18:48 +00:00
|
|
|
|
addr => SystemRam[addr],
|
|
|
|
|
(addr, value) => SystemRam[addr] = value);
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var VRamDomain = new MemoryDomain("Video RAM", Vdp.VRAM.Length, MemoryDomain.Endian.Little,
|
2014-02-26 20:18:48 +00:00
|
|
|
|
addr => Vdp.VRAM[addr],
|
|
|
|
|
(addr, value) => Vdp.VRAM[addr] = value);
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var SaveRamDomain = new MemoryDomain("Save RAM", SaveRAM.Length, MemoryDomain.Endian.Little,
|
2014-02-26 20:18:48 +00:00
|
|
|
|
addr => SaveRAM[addr],
|
|
|
|
|
(addr, value) => { SaveRAM[addr] = value; SaveRamModified = true; });
|
2013-11-04 02:11:40 +00:00
|
|
|
|
var SystemBusDomain = new MemoryDomain("System Bus", 0x10000, MemoryDomain.Endian.Little,
|
2014-02-26 20:18:48 +00:00
|
|
|
|
(addr) =>
|
|
|
|
|
{
|
|
|
|
|
if (addr < 0 || addr >= 65536)
|
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
|
return Cpu.ReadMemory((ushort)addr);
|
|
|
|
|
},
|
|
|
|
|
(addr, value) =>
|
|
|
|
|
{
|
|
|
|
|
if (addr < 0 || addr >= 65536)
|
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
|
Cpu.WriteMemory((ushort)addr, value);
|
|
|
|
|
});
|
2011-06-27 01:24:26 +00:00
|
|
|
|
|
|
|
|
|
domains.Add(MainMemoryDomain);
|
|
|
|
|
domains.Add(VRamDomain);
|
|
|
|
|
domains.Add(SaveRamDomain);
|
|
|
|
|
domains.Add(SystemBusDomain);
|
2013-11-06 02:15:29 +00:00
|
|
|
|
memoryDomains = new MemoryDomainList(domains);
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-06 02:15:29 +00:00
|
|
|
|
public MemoryDomainList MemoryDomains { get { return memoryDomains; } }
|
2011-06-27 01:24:26 +00:00
|
|
|
|
|
2014-03-17 03:00:07 +00:00
|
|
|
|
public List<KeyValuePair<string, int>> GetCpuFlagsAndRegisters()
|
|
|
|
|
{
|
|
|
|
|
return new List<KeyValuePair<string, int>>
|
|
|
|
|
{
|
|
|
|
|
new KeyValuePair<string, int>("A", Cpu.RegisterA),
|
|
|
|
|
new KeyValuePair<string, int>("AF", Cpu.RegisterAF),
|
|
|
|
|
new KeyValuePair<string, int>("B", Cpu.RegisterB),
|
|
|
|
|
new KeyValuePair<string, int>("BC", Cpu.RegisterBC),
|
|
|
|
|
new KeyValuePair<string, int>("C", Cpu.RegisterC),
|
|
|
|
|
new KeyValuePair<string, int>("D", Cpu.RegisterD),
|
|
|
|
|
new KeyValuePair<string, int>("DE", Cpu.RegisterDE),
|
|
|
|
|
new KeyValuePair<string, int>("E", Cpu.RegisterE),
|
|
|
|
|
new KeyValuePair<string, int>("F", Cpu.RegisterF),
|
|
|
|
|
new KeyValuePair<string, int>("H", Cpu.RegisterH),
|
|
|
|
|
new KeyValuePair<string, int>("HL", Cpu.RegisterHL),
|
|
|
|
|
new KeyValuePair<string, int>("I", Cpu.RegisterI),
|
|
|
|
|
new KeyValuePair<string, int>("IX", Cpu.RegisterIX),
|
|
|
|
|
new KeyValuePair<string, int>("IY", Cpu.RegisterIY),
|
|
|
|
|
new KeyValuePair<string, int>("L", Cpu.RegisterL),
|
|
|
|
|
new KeyValuePair<string, int>("PC", Cpu.RegisterPC),
|
|
|
|
|
new KeyValuePair<string, int>("R", Cpu.RegisterR),
|
|
|
|
|
new KeyValuePair<string, int>("Shadow AF", Cpu.RegisterShadowAF),
|
|
|
|
|
new KeyValuePair<string, int>("Shadow BC", Cpu.RegisterShadowBC),
|
|
|
|
|
new KeyValuePair<string, int>("Shadow DE", Cpu.RegisterShadowDE),
|
|
|
|
|
new KeyValuePair<string, int>("Shadow HL", Cpu.RegisterShadowHL),
|
|
|
|
|
new KeyValuePair<string, int>("SP", Cpu.RegisterSP),
|
|
|
|
|
new KeyValuePair<string, int>("Flag C", Cpu.RegisterF.Bit(0) ? 1 : 0),
|
|
|
|
|
new KeyValuePair<string, int>("Flag N", Cpu.RegisterF.Bit(1) ? 1 : 0),
|
|
|
|
|
new KeyValuePair<string, int>("Flag P/V", Cpu.RegisterF.Bit(2) ? 1 : 0),
|
|
|
|
|
new KeyValuePair<string, int>("Flag 3rd", Cpu.RegisterF.Bit(3) ? 1 : 0),
|
|
|
|
|
new KeyValuePair<string, int>("Flag H", Cpu.RegisterF.Bit(4) ? 1 : 0),
|
|
|
|
|
new KeyValuePair<string, int>("Flag 5th", Cpu.RegisterF.Bit(5) ? 1 : 0),
|
|
|
|
|
new KeyValuePair<string, int>("Flag Z", Cpu.RegisterF.Bit(6) ? 1 : 0),
|
|
|
|
|
new KeyValuePair<string, int>("Flag S", Cpu.RegisterF.Bit(7) ? 1 : 0),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-27 01:24:26 +00:00
|
|
|
|
public void Dispose() { }
|
2013-12-22 00:44:39 +00:00
|
|
|
|
|
2013-12-24 01:06:17 +00:00
|
|
|
|
public object GetSettings() { return Settings.Clone(); }
|
|
|
|
|
public object GetSyncSettings() { return SyncSettings.Clone(); }
|
|
|
|
|
public bool PutSettings(object o)
|
|
|
|
|
{
|
|
|
|
|
SMSSettings n = (SMSSettings)o;
|
|
|
|
|
bool ret = SMSSettings.RebootNeeded(Settings, n);
|
|
|
|
|
Settings = n;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
public bool PutSyncSettings(object o)
|
|
|
|
|
{
|
|
|
|
|
SMSSyncSettings n = (SMSSyncSettings)o;
|
|
|
|
|
bool ret = SMSSyncSettings.RebootNeeded(SyncSettings, n);
|
|
|
|
|
SyncSettings = n;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SMSSettings Settings;
|
|
|
|
|
public SMSSyncSettings SyncSettings;
|
|
|
|
|
|
|
|
|
|
public class SMSSettings
|
|
|
|
|
{
|
2014-03-13 04:15:05 +00:00
|
|
|
|
// Game settings
|
2013-12-24 01:06:17 +00:00
|
|
|
|
public bool ForceStereoSeparation = false;
|
|
|
|
|
public bool SpriteLimit = false;
|
2014-03-05 02:20:22 +00:00
|
|
|
|
public bool Fix3D = true;
|
2013-12-24 01:06:17 +00:00
|
|
|
|
// GG settings
|
|
|
|
|
public bool ShowClippedRegions = false;
|
|
|
|
|
public bool HighlightActiveDisplayRegion = false;
|
|
|
|
|
// graphics settings
|
|
|
|
|
public bool DispBG = true;
|
|
|
|
|
public bool DispOBJ = true;
|
|
|
|
|
|
|
|
|
|
public SMSSettings Clone()
|
|
|
|
|
{
|
|
|
|
|
return (SMSSettings)MemberwiseClone();
|
|
|
|
|
}
|
|
|
|
|
public static bool RebootNeeded(SMSSettings x, SMSSettings y)
|
|
|
|
|
{
|
2014-02-27 00:28:05 +00:00
|
|
|
|
return false;
|
2013-12-24 01:06:17 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class SMSSyncSettings
|
|
|
|
|
{
|
|
|
|
|
public bool EnableFM = true;
|
|
|
|
|
public bool AllowOverlock = false;
|
2014-03-13 04:15:05 +00:00
|
|
|
|
public bool UseBIOS = false;
|
|
|
|
|
public bool ExportRegion = true;
|
|
|
|
|
public bool UsePAL = false;
|
2013-12-24 01:06:17 +00:00
|
|
|
|
|
|
|
|
|
public SMSSyncSettings Clone()
|
|
|
|
|
{
|
|
|
|
|
return (SMSSyncSettings)MemberwiseClone();
|
|
|
|
|
}
|
|
|
|
|
public static bool RebootNeeded(SMSSyncSettings x, SMSSyncSettings y)
|
|
|
|
|
{
|
2014-03-13 04:15:05 +00:00
|
|
|
|
return
|
|
|
|
|
x.EnableFM != y.EnableFM ||
|
|
|
|
|
x.AllowOverlock != y.AllowOverlock ||
|
|
|
|
|
x.UseBIOS != y.UseBIOS ||
|
|
|
|
|
x.ExportRegion != y.ExportRegion ||
|
|
|
|
|
x.UsePAL != y.UsePAL;
|
2013-12-24 01:06:17 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-06-27 01:24:26 +00:00
|
|
|
|
}
|
2012-03-11 16:15:20 +00:00
|
|
|
|
}
|