247 lines
6.9 KiB
C#
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!");
|
|
}
|
|
}
|
|
|
|
}
|