using System; using System.Runtime.InteropServices; using System.IO; using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS { [Core("MelonDS", "Arisotura", false, false, null, null, true)] public unsafe partial class MelonDS : IEmulator { private readonly BasicServiceProvider _serviceProvider; public IEmulatorServiceProvider ServiceProvider => _serviceProvider; public ControllerDefinition ControllerDefinition { get; } public int Frame => GetFrameCount(); public string SystemId => "NDS"; public bool DeterministicEmulation => true; internal CoreComm CoreComm { get; } public void Dispose() { Deinit(); } public bool FrameAdvance(IController controller, bool render, bool renderSound = true) { int buttons = (controller.IsPressed("A") ? 1 : 0) | (controller.IsPressed("B") ? 2 : 0) | (controller.IsPressed("Select") ? 4 : 0) | (controller.IsPressed("Start") ? 8 : 0) | (controller.IsPressed("Right") ? 0x10 : 0) | (controller.IsPressed("Left") ? 0x20 : 0) | (controller.IsPressed("Up") ? 0x40 : 0) | (controller.IsPressed("Down") ? 0x80 : 0) | (controller.IsPressed("R") ? 0x100 : 0) | (controller.IsPressed("L") ? 0x200 : 0) | (controller.IsPressed("X") ? 0x400 : 0) | (controller.IsPressed("Y") ? 0x800 : 0) | (controller.IsPressed("Touch") ? 0x2000 : 0) | (controller.IsPressed("LidOpen") ? 0x4000 : 0) | (controller.IsPressed("LidClose") ? 0x8000 : 0) | (controller.IsPressed("Power") ? 0x10000 : 0); FrameAdvance((uint)buttons, (byte)controller.AxisValue("TouchX"), (byte)controller.AxisValue("TouchY")); _getNewBuffer = true; return true; } public void ResetCounters() { _ResetCounters(); } // debug path/build for easier testing //const string dllPath = "../../MelonDS/build/libmelonDS.dll"; const string dllPath = "dll/libmelonDS.dll"; [DllImport(dllPath)] private static extern bool Init(); [DllImport(dllPath)] private static extern void Deinit(); [DllImport(dllPath)] private static extern void LoadROM(byte* file, int fileSize); [DllImport(dllPath, EntryPoint = "ResetCounters")] private static extern void _ResetCounters(); [DllImport(dllPath)] private static extern int GetFrameCount(); [DllImport(dllPath)] private static extern void SetFrameCount(uint count); [DllImport(dllPath)] private static extern void FrameAdvance(uint buttons, byte touchX, byte touchY); [CoreConstructor("NDS")] public MelonDS(byte[] file, CoreComm comm, object settings, object syncSettings) { _serviceProvider = new BasicServiceProvider(this); ControllerDefinition = new ControllerDefinition { Name = "NDS Controller" }; ControllerDefinition.BoolButtons.Add("Left"); ControllerDefinition.BoolButtons.Add("Right"); ControllerDefinition.BoolButtons.Add("Up"); ControllerDefinition.BoolButtons.Add("Down"); ControllerDefinition.BoolButtons.Add("A"); ControllerDefinition.BoolButtons.Add("B"); ControllerDefinition.BoolButtons.Add("X"); ControllerDefinition.BoolButtons.Add("Y"); ControllerDefinition.BoolButtons.Add("L"); ControllerDefinition.BoolButtons.Add("R"); ControllerDefinition.BoolButtons.Add("Start"); ControllerDefinition.BoolButtons.Add("Select"); ControllerDefinition.BoolButtons.Add("LidOpen"); ControllerDefinition.BoolButtons.Add("LidClose"); ControllerDefinition.BoolButtons.Add("Power"); ControllerDefinition.BoolButtons.Add("Touch"); ControllerDefinition.AxisControls.Add("TouchX"); ControllerDefinition.AxisRanges.Add(new ControllerDefinition.AxisRange(0, 128, 255)); ControllerDefinition.AxisControls.Add("TouchY"); ControllerDefinition.AxisRanges.Add(new ControllerDefinition.AxisRange(0, 96, 191)); CoreComm = comm; _resampler = new SpeexResampler(SpeexResampler.Quality.QUALITY_DEFAULT, 32768, 44100, 32768, 44100); SetUpFiles(); PutSettings(settings as MelonSettings); PutSyncSettings(syncSettings as MelonSyncSettings); if (!Init()) throw new Exception("Failed to init NDS."); InitMemoryDomains(); fixed (byte* f = file) { LoadROM(f, file.Length); } } /// /// MelonDS expects bios and firmware files at a specific location. /// This should never be called without an accompanying call to PutSyncSettings. /// private void SetUpFiles() { Directory.CreateDirectory("melon"); byte[] fwBytes; bool missingAny = false; fwBytes = CoreComm.CoreFileProvider.GetFirmware("NDS", "bios7", false); if (fwBytes != null) File.WriteAllBytes("melon/bios7.bin", fwBytes); else { File.Delete("melon/bios7.bin"); missingAny = true; } fwBytes = CoreComm.CoreFileProvider.GetFirmware("NDS", "bios9", false); if (fwBytes != null) File.WriteAllBytes("melon/bios9.bin", fwBytes); else { File.Delete("melon/bios9.bin"); missingAny = true; } fwBytes = CoreComm.CoreFileProvider.GetFirmware("NDS", "firmware", false); if (fwBytes != null) File.WriteAllBytes("melon/firmware.bin", fwBytes); else { File.Delete("melon/firmware.bin"); missingAny = true; } if (missingAny) CoreComm.Notify("NDS bios and firmware files are recommended; at least one is missing."); } /// /// Creates a modified copy of the given firmware file, with the user settings erased. /// /// Returns a path to the new file. public static string CreateModifiedFirmware(string firmwarePath) { Directory.CreateDirectory("melon"); const string newPath = "melon/tohash.bin"; byte[] bytes = File.ReadAllBytes(firmwarePath); // There are two regions for user settings int settingsLength = GetUserSettingsLength(); for (int i = bytes.Length - 0x200; i < bytes.Length - 0x200 + settingsLength; i++) bytes[i] = 0xFF; for (int i = bytes.Length - 0x100; i < bytes.Length - 0x100 + settingsLength; i++) bytes[i] = 0xFF; File.WriteAllBytes(newPath, bytes); return newPath; } } }