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 sealed partial class SMS : IEmulator, ISaveRam, IStatable, IInputPollable, IRegionable, IDebuggable, ISettable, 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"; 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.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 }; Vdp = new VDP(this, Cpu, IsGameGear ? VdpMode.GameGear : VdpMode.SMS, Region); (ServiceProvider as BasicServiceProvider).Register(Vdp); PSG = new SN76489(); YM2413 = new YM2413(); SoundMixer = new SoundMixer(YM2413, PSG); if (HasYM2413 && game["WhenFMDisablePSG"]) { SoundMixer.DisableSource(PSG); } ActiveSoundProvider = HasYM2413 ? (IAsyncSoundProvider)SoundMixer : PSG; _fakeSyncSound = new FakeSyncSound(ActiveSoundProvider, 735); (ServiceProvider as BasicServiceProvider).Register(_fakeSyncSound); 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.StereoPanning = 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") { 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(Tracer); serviceProvider.Register(Cpu); Vdp.ProcessOverscan(); Cpu.ReadMemory = ReadMemory; Cpu.WriteMemory = WriteMemory; } // Constants private const int BankSize = 16384; // ROM private byte[] RomData; private byte RomBank0, RomBank1, RomBank2, RomBank3; private byte RomBanks; private byte[] BiosRom; // Machine resources private Z80A Cpu; private byte[] SystemRam; public VDP Vdp; private SN76489 PSG; private YM2413 YM2413; public bool IsGameGear { get; set; } public bool IsSG1000 { get; set; } private bool HasYM2413 = false; private bool PortDEEnabled = false; private IController _controller; private int _frame = 0; private byte Port01 = 0xFF; private byte Port02 = 0xFF; private byte Port3E = 0xAF; private byte Port3F = 0xFF; private byte PortDE = 0x00; private byte ForceStereoByte = 0xAD; private bool IsGame3D = false; public DisplayType Region { get; set; } private ITraceable Tracer { get; set; } 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"; 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; } private byte ReadMemory(ushort addr) { MemoryCallbacks.CallReads(addr, "System Bus"); return ReadMemoryMapper(addr); } private void WriteMemory(ushort addr, byte value) { WriteMemoryMapper(addr, value); MemoryCallbacks.CallWrites(addr, "System Bus"); } private byte FetchMemory(ushort addr) { return ReadMemoryMapper(addr); } private void OnExecMemory(ushort addr) { MemoryCallbacks.CallExecutes(addr, "System Bus"); } /// /// The ReadMemory callback for the mapper /// private Func ReadMemoryMapper; /// /// The WriteMemory callback for the wrapper /// private Action WriteMemoryMapper; /// /// A dummy FetchMemory that simply reads the memory /// 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: 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 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: return ReadControls1(); case 0xC1: case 0xDD: return ReadControls2(); 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 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 { 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; } private string _region; private 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", "Auto" }; } }