GBA.Unity/Assets/emulator/Emulator.cs
2024-08-16 14:51:15 +08:00

247 lines
6.9 KiB
C#

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<AudioSource>();
//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<TextAsset>("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<TextAsset>("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!");
}
}
}