using OptimeGBA; using System; using System.IO; using System.Threading; using UnityEngine; using UnityEngine.UI; public class Emulator : MonoBehaviour { public static Emulator instance; const int FrameCycles = 70224 * 4; const int ScanlineCycles = 1232; const float FrameRate = 59.7275f; static bool SyncToAudio = true; //public Renderer screenRenderer; public VideoProvider videoProvider; public AudioProvider audioProvider; public static System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); public bool ShowBackBuf = false; public bool RunEmulator; public bool EnableAudio; public bool BootBIOS = false; public bool RomLoaded { get; private set; } = false; public Gba gba; Thread EmulationThread; AutoResetEvent ThreadSync = new AutoResetEvent(false); private int _samplesAvailable; //private PipeStream _pipeStream; private byte[] _buffer; public float audioGain = 1.0f; // key delegate public delegate bool IsKeyPressed(GBAKeyCode keyCode); public IsKeyPressed KeyPressed; public Button btnStart; private void Awake() { instance = this; //BetterStreamingAssets.Initialize(); // must set it to 60 or it won't sync with audio or run too fast. Application.targetFrameRate = (int)FrameRate; //audioSource = GetComponent(); //AudioClip clip = AudioClip.Create("blank", GbaAudio.SampleRate * 2, 2, GbaAudio.SampleRate, true); //audioSource.clip = clip; //audioSource.playOnAwake = true; //audioSource.enabled = false; //screenRenderer.material.mainTexture = new Texture2D(240, 160, TextureFormat.RGBA32, false, false); // Get Unity Buffer size AudioSettings.GetDSPBufferSize(out int bufferLength, out _); _samplesAvailable = bufferLength; // Must be set to 32768 var audioConfig = AudioSettings.GetConfiguration(); audioConfig.sampleRate = GbaAudio.SampleRate; AudioSettings.Reset(audioConfig); // Prepare our buffer //_pipeStream = new PipeStream(); //_pipeStream.MaxBufferLength = _samplesAvailable * 2 * sizeof(float); _buffer = new byte[_samplesAvailable * 2 * sizeof(float)]; } void Start() { byte[] bios = Resources.Load("gba_bios.bin").bytes; //byte[] bios = BetterStreamingAssets.ReadAllBytes("gba_bios.bin"); Debug.Log(bios.Length); gba = new Gba(new ProviderGba(bios, new byte[0], "", audioProvider.AudioReady) { BootBios = BootBIOS }); EmulationThread = new Thread(EmulationThreadHandler); EmulationThread.Name = "Emulation Core"; EmulationThread.Start(); btnStart.onClick.AddListener( () => { byte[] romdata = Resources.Load("mario_world.gba").bytes; LoadRom(romdata, "mario_world.gba"); } ); } // Update is called once per frame void Update() { if (RomLoaded) { videoProvider.OnRenderFrame(); } OnUpdateFrame(); } public void LoadRom(byte[] rom, string name) { string savPath = Application.persistentDataPath + "/" + name.Substring(0, name.Length - 3) + "sav"; byte[] sav = new byte[0]; if (File.Exists(savPath)) { Debug.Log($"{savPath} exists, loading"); try { sav = File.ReadAllBytes(savPath); } catch { Debug.Log("Failed to load .sav file!"); } } else { Debug.Log(".sav not available"); } LoadRomAndSave(rom, sav, savPath); Debug.Log("Load Rom Success"); audioProvider.Initialize(); RomLoaded = true; RunEmulator = true; } public void LoadRomAndSave(byte[] rom, byte[] sav, string savPath) { var bios = gba.Provider.Bios; gba = new Gba(new ProviderGba(bios, rom, savPath, audioProvider.AudioReady) { BootBios = BootBIOS }); gba.Mem.SaveProvider.LoadSave(sav); } public void ResetGba() { byte[] save = gba.Mem.SaveProvider.GetSave(); ProviderGba p = gba.Provider; gba = new Gba(p); gba.Mem.SaveProvider.LoadSave(save); } public void EmulationThreadHandler() { while (true) { ThreadSync.WaitOne(); int cyclesLeft = 70224 * 4; while (cyclesLeft > 0 && !gba.Cpu.Errored) { cyclesLeft -= (int)gba.Step(); } while (!SyncToAudio && !gba.Cpu.Errored && RunEmulator) { gba.Step(); } } } public int GetOutputSampleRate() { return AudioSettings.outputSampleRate; } public int GetSamplesAvailable() { return _samplesAvailable; } //private void OnAudioFilterRead(float[] data, int channels) //{ // if (!EnableAudio) return; // int r = _pipeStream.Read(_buffer, 0, data.Length * sizeof(float)); // float[] pcm = CoreUtil.ByteToFloatArray(_buffer); // Array.Copy(pcm, data, data.Length); //} public void RunCycles(int cycles) { while (cycles > 0 && !gba.Cpu.Errored && RunEmulator) { cycles -= (int)gba.Step(); } } int CyclesLeft; public void RunFrame() { CyclesLeft += FrameCycles; while (CyclesLeft > 0 && !gba.Cpu.Errored) { CyclesLeft -= (int)gba.Step(); } } public void RunScanline() { CyclesLeft += ScanlineCycles; while (CyclesLeft > 0 && !gba.Cpu.Errored) { CyclesLeft -= (int)gba.Step(); } } public void OnUpdateFrame() { gba.Keypad.B = KeyPressed(GBAKeyCode.B); gba.Keypad.A = KeyPressed(GBAKeyCode.A); gba.Keypad.Left = KeyPressed(GBAKeyCode.Left); gba.Keypad.Up = KeyPressed(GBAKeyCode.Up); gba.Keypad.Right = KeyPressed(GBAKeyCode.Right); gba.Keypad.Down = KeyPressed(GBAKeyCode.Down); gba.Keypad.Start = KeyPressed(GBAKeyCode.Start); gba.Keypad.Select = KeyPressed(GBAKeyCode.Select); gba.Keypad.L = KeyPressed(GBAKeyCode.L); gba.Keypad.R = KeyPressed(GBAKeyCode.R); SyncToAudio = !(Input.GetKey(KeyCode.Tab) || Input.GetKey(KeyCode.Space)); if (RunEmulator) { ThreadSync.Set(); } if (gba.Mem.SaveProvider.Dirty) { DumpSav(); } } public void DumpSav() { try { //File.WriteAllBytesAsync(gba.Provider.SavPath, gba.Mem.SaveProvider.GetSave()); File.WriteAllBytes(gba.Provider.SavPath, gba.Mem.SaveProvider.GetSave()); } catch { Debug.Log("Failed to write .sav file!"); } } }