From 56db34060c4feb929cb6ad5a1fa4a2529bd41094 Mon Sep 17 00:00:00 2001 From: Hokage3211 <54164358+Hokage3211@users.noreply.github.com> Date: Mon, 31 Aug 2020 08:33:56 -0700 Subject: [PATCH 1/4] Fixing sound, and adding save files using RAM saving as well as save states, compatible with retroarch's actual save files, for cross-loading. --- .../Plugins/RetroUnity/Scripts/GameManager.cs | 26 ++++ .../RetroUnity/Scripts/LibretroWrapper.cs | 139 +++++++++++++++--- 2 files changed, 142 insertions(+), 23 deletions(-) diff --git a/Assets/Plugins/RetroUnity/Scripts/GameManager.cs b/Assets/Plugins/RetroUnity/Scripts/GameManager.cs index 0b9b810..06ab7ba 100644 --- a/Assets/Plugins/RetroUnity/Scripts/GameManager.cs +++ b/Assets/Plugins/RetroUnity/Scripts/GameManager.cs @@ -7,6 +7,8 @@ public class GameManager : MonoBehaviour { [SerializeField] private string CoreName = "snes9x_libretro.dll"; [SerializeField] private string RomName = "Chrono Trigger (USA).sfc"; + private string RAMPath = ""; + private string STATEPath = ""; private LibretroWrapper.Wrapper wrapper; private float _frameTimer; @@ -27,6 +29,11 @@ private void Update() { wrapper.Update(); _frameTimer -= timePerFrame; } + + if (Input.GetKeyDown(KeyCode.G)) + saveState(); + else if (Input.GetKeyDown(KeyCode.H)) + loadState(); } if (LibretroWrapper.tex != null) { Display.material.mainTexture = LibretroWrapper.tex; @@ -34,6 +41,11 @@ private void Update() { } public void LoadRom(string path) { + + RAMPath = path.Substring(0, path.LastIndexOf('.')); + STATEPath = RAMPath + ".state"; + RAMPath += ".srm"; + #if !UNITY_ANDROID || UNITY_EDITOR // Doesn't work on Android because you can't do File.Exists in StreamingAssets folder. // Should figure out a different way to perform check later. @@ -49,9 +61,23 @@ public void LoadRom(string path) { wrapper.Init(); wrapper.LoadGame(path); + wrapper.LoadRAM(RAMPath); + + } + + public void saveState() + { + wrapper.SaveState(STATEPath); + } + + public void loadState() + { + wrapper.LoadState(STATEPath); } private void OnDestroy() { + wrapper.SaveRAM(RAMPath); + wrapper.DeInit(); WindowsDLLHandler.Instance.UnloadCore(); } } diff --git a/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs b/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs index 64bc7f9..b8cd713 100644 --- a/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs +++ b/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs @@ -191,6 +191,11 @@ public unsafe void Init() { Libretro.RetroInit(); } + public void DeInit() + { + Libretro.RetroDeInit(); + } + public bool Update() { Libretro.RetroRun(); return true; @@ -327,27 +332,24 @@ private unsafe void RetroVideoRefresh(void* data, uint width, uint height, uint private void RetroAudioSample(short left, short right) { // Unused. } - + + bool readAudio = false; + float readsSkipped = 0; private unsafe void RetroAudioSampleBatch(short* data, uint frames) { - //for (int i = 0; i < (int) frames; i++) { - // short chunk = Marshal.ReadInt16((IntPtr) data); - // data += sizeof (short); // Set pointer to next chunk. - // float value = chunk / 32768f; // Divide by Int16 max to get correct float value. - // value = Mathf.Clamp(value, -1.0f, 1.0f); // Unity's audio only takes values between -1 and 1. - - // AudioBatch[BatchPosition] = value; - // BatchPosition++; - - // // When the batch is filled send it to the speakers. - // if (BatchPosition >= AudioBatchSize - 1) { - // _speaker.UpdateAudio(AudioBatch); - // BatchPosition = 0; - // } - //} - for (int i = 0; i < frames * 2; ++i) { - float value = data[i] * 0.000030517578125f; - value = Mathf.Clamp(value, -1.0f, 1.0f); // Unity's audio only takes values between -1 and 1. - AudioBatch.Add(value); + if (!readAudio) + { + readsSkipped++; + if (readsSkipped > 1000) + readAudio = true; + } + else + { + for (int i = 0; i < frames * 2; ++i) + { + float value = data[i] * 0.000030517578125f; + value = Mathf.Clamp(value, -1.0f, 1.0f); // Unity's audio only takes values between -1 and 1. + AudioBatch.Add(value); + } } } @@ -483,6 +485,70 @@ public bool LoadGame(string gamePath) { Debug.Log("Sample rate " + _av.timing.sample_rate); return ret; } + + public void SaveRAM(string path) + { + unsafe + { + int size = Libretro.RetroGetMemorySize(0); //maybe look into getting the defined constant RETRO_MEMORY_SAVE_RAM + void* data = Libretro.RetroGetMemoryData(0); + + byte[] saveData = new byte[size]; + + for (int i = 0; i < size; i++) + saveData[i] = ((byte*)data)[i]; + + System.IO.File.WriteAllBytes(path, saveData); + } + } + + public void LoadRAM(string path) + { + unsafe + { + int size = Libretro.RetroGetMemorySize(0); //maybe look into getting the defined constant RETRO_MEMORY_SAVE_RAM + void* data = Libretro.RetroGetMemoryData(0); + + if (File.Exists(path)) + { + byte[] savedData = System.IO.File.ReadAllBytes(path); + + for (int i = 0; i < size; i++) + ((byte*)data)[i] = savedData[i]; + } + } + } + + public void SaveState(string path) + { + unsafe + { + int size = Libretro.RetroSerializeSize(); + byte[] toSaveData = new byte[size]; + fixed (byte* p = &toSaveData[0]) + { + Libretro.RetroSerialize(p, size); + System.IO.File.WriteAllBytes(path, toSaveData); + } + } + } + + public void LoadState(string path) + { + unsafe + { + if (File.Exists(path)) + { + int size = Libretro.RetroSerializeSize(); + byte[] toLoad = System.IO.File.ReadAllBytes(path); + fixed (byte* p = &toLoad[0]) + { + Libretro.RetroDeserialize(p, size); + } + } + } + } + } public unsafe class Libretro { @@ -538,8 +604,9 @@ public unsafe class Libretro { [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate bool RetroSetEnvironmentDelegate(RetroEnvironmentDelegate r); - public static RetroSetEnvironmentDelegate RetroSetEnvironment; + //typedef bool (*retro_environment_t)(unsigned cmd, void *data); + public delegate bool RetroEnvironmentDelegate(uint cmd, void* data); [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate void RetroRunDelegate(); @@ -566,8 +633,26 @@ public unsafe class Libretro { //typedef int16_t (*retro_input_state_t)(unsigned port, unsigned device, unsigned index, unsigned id); public delegate short RetroInputStateDelegate(uint port, uint device, uint index, uint id); - //typedef bool (*retro_environment_t)(unsigned cmd, void *data); - public delegate bool RetroEnvironmentDelegate(uint cmd, void* data); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate void* RetroGetMemoryDataDelegate(uint id); + public static RetroGetMemoryDataDelegate RetroGetMemoryData; + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate int RetroGetMemorySizeDelegate(uint id); + public static RetroGetMemorySizeDelegate RetroGetMemorySize; + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate int RetroSerializeSizeDeleagte(); + public static RetroSerializeSizeDeleagte RetroSerializeSize; + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate bool RetroSerializeDelegate(void* data, int size); + public static RetroSerializeDelegate RetroSerialize; + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate bool RetroDeserializeDelegate(void* data, int size); + public static RetroDeserializeDelegate RetroDeserialize; + public static void InitializeLibrary(string dllName) { IDLLHandler dllHandler = null; @@ -596,6 +681,14 @@ public static void InitializeLibrary(string dllName) { RetroSetEnvironment = dllHandler.GetMethod("retro_set_environment"); RetroRun = dllHandler.GetMethod("retro_run"); RetroDeInit = dllHandler.GetMethod("retro_deinit"); + + RetroGetMemoryData = dllHandler.GetMethod("retro_get_memory_data"); + RetroGetMemorySize = dllHandler.GetMethod("retro_get_memory_size"); + + RetroSerializeSize = dllHandler.GetMethod("retro_serialize_size"); + RetroSerialize = dllHandler.GetMethod("retro_serialize"); + RetroDeserialize = dllHandler.GetMethod("retro_unserialize"); + //Debug.Log("Got emthods"); } } } From 5911effd4f6a0506848cf3d195e35b2abd80e220 Mon Sep 17 00:00:00 2001 From: Hokage3211 <54164358+Hokage3211@users.noreply.github.com> Date: Sun, 6 Sep 2020 14:07:26 -0700 Subject: [PATCH 2/4] Important: Press page-up to choose what game and core to load, in that order. Updating code, works now with mGBA core, and sound fix seems to work better --- .../Plugins/RetroUnity/Scripts/GameManager.cs | 51 ++- .../RetroUnity/Scripts/LibretroWrapper.cs | 303 +++++++++++++----- Assets/Plugins/RetroUnity/Scripts/Speaker.cs | 19 +- 3 files changed, 276 insertions(+), 97 deletions(-) diff --git a/Assets/Plugins/RetroUnity/Scripts/GameManager.cs b/Assets/Plugins/RetroUnity/Scripts/GameManager.cs index 06ab7ba..e1e1bed 100644 --- a/Assets/Plugins/RetroUnity/Scripts/GameManager.cs +++ b/Assets/Plugins/RetroUnity/Scripts/GameManager.cs @@ -12,17 +12,35 @@ public class GameManager : MonoBehaviour { private LibretroWrapper.Wrapper wrapper; private float _frameTimer; + public float targetFPS = 50.99986f; public Renderer Display; + private bool gameLoaded = false; + private void Awake() { - LoadRom(Application.streamingAssetsPath + "/" + RomName); + //LoadGame(Application.streamingAssetsPath + "/" + RomName); + } + + public void loadGame() + { + string path = UnityEditor.EditorUtility.OpenFilePanel("Select Game ROM File", Application.streamingAssetsPath, ""); + string corePath = UnityEditor.EditorUtility.OpenFilePanel("Select Emulator Core File", Application.streamingAssetsPath, "dll"); + if (path != "" && corePath != "") + LoadRom(path, corePath); + } + + public void LoadGame(string path) + { + LoadRom(path); } private void Update() { - if (wrapper != null) { + if (gameLoaded) { _frameTimer += Time.deltaTime; - float timePerFrame = 1f / (float)wrapper.GetAVInfo().timing.fps; + float timePerFrame = 1 / targetFPS; + if (!double.IsNaN(wrapper.GetAVInfo().timing.fps)) + timePerFrame = 1f / (float)wrapper.GetAVInfo().timing.fps; while (_frameTimer >= timePerFrame) { @@ -38,9 +56,13 @@ private void Update() { if (LibretroWrapper.tex != null) { Display.material.mainTexture = LibretroWrapper.tex; } + if (Input.GetKeyDown(KeyCode.PageUp)) + { + loadGame(); + } } - public void LoadRom(string path) { + public void LoadRom(string path, string corePath = "") { RAMPath = path.Substring(0, path.LastIndexOf('.')); STATEPath = RAMPath + ".state"; @@ -57,12 +79,18 @@ public void LoadRom(string path) { #endif Display.material.color = Color.white; - wrapper = new LibretroWrapper.Wrapper(Application.streamingAssetsPath + "/" + CoreName); + if (corePath == "") + wrapper = new LibretroWrapper.Wrapper(Application.streamingAssetsPath + "/" + CoreName); + else + wrapper = new LibretroWrapper.Wrapper(corePath); wrapper.Init(); wrapper.LoadGame(path); - wrapper.LoadRAM(RAMPath); + if (RAMPath != null) + wrapper.LoadRAM(RAMPath); + gameLoaded = true; + wrapper.initialized = true; } public void saveState() @@ -76,9 +104,16 @@ public void loadState() } private void OnDestroy() { - wrapper.SaveRAM(RAMPath); - wrapper.DeInit(); + if (RAMPath != null && wrapper != null) + { + wrapper.SaveRAM(RAMPath); + wrapper.DeInit(); + wrapper.initialized = false; + } WindowsDLLHandler.Instance.UnloadCore(); + + gameLoaded = false; + } } } diff --git a/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs b/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs index b8cd713..d1fc5c8 100644 --- a/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs +++ b/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs @@ -21,8 +21,12 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; +using System.Threading; using RetroUnity.Utility; using UnityEngine; +using Unity.Collections; +using UnityEngine.Profiling; +using Unity.Jobs; namespace RetroUnity { public class LibretroWrapper : MonoBehaviour { @@ -112,6 +116,22 @@ public unsafe struct SystemInfo { public bool block_extract; } + [StructLayout(LayoutKind.Sequential)] + public unsafe struct retro_variable + { + /* Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + * If NULL, obtains the complete environment string if more + * complex parsing is necessary. + * The environment string is formatted as key-value pairs + * delimited by semicolons as so: + * "key1=value1;key2=value2;..." + */ + public readonly char* key; + + /* Value to be obtained. If key does not exist, it is set to NULL. */ + public readonly char* value; + }; + public class Environment { public const uint RetroEnvironmentSetRotation = 1; public const uint RetroEnvironmentGetOverscan = 2; @@ -125,6 +145,16 @@ public class Environment { public const uint RetroEnvironmentSetPixelFormat = 10; public const uint RetroEnvironmentSetInputDescriptors = 11; public const uint RetroEnvironmentSetKeyboardCallback = 12; + public const uint RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE = 13; + public const uint RETRO_ENVIRONMENT_SET_HW_RENDER = 14; + public const uint RETRO_ENVIRONMENT_GET_VARIABLE = 15; + public const uint RETRO_ENVIRONMENT_SET_VARIABLES = 16; + public const uint RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE = 17; + public const uint RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME = 18; + public const uint RETRO_ENVIRONMENT_GET_LIBRETRO_PATH = 19; + public const uint RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK = 21; + public const uint RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK = 22; + public const uint RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY = 31; } public class Wrapper { @@ -153,7 +183,9 @@ public Wrapper(string coreToLoad) { Libretro.InitializeLibrary(coreToLoad); } + public bool initialized = false; public unsafe void Init() { + int apiVersion = Libretro.RetroApiVersion(); SystemInfo info = new SystemInfo(); Libretro.RetroGetSystemInfo(ref info); @@ -224,7 +256,7 @@ private unsafe void RetroVideoRefresh(void* data, uint width, uint height, uint //IntPtr rowStart = pixels; //Get the size to move the pointer - //int size = 0; + int size = 24; uint i; uint j; @@ -263,33 +295,41 @@ private unsafe void RetroVideoRefresh(void* data, uint width, uint height, uint } break; case PixelFormat.RetroPixelFormatXRGB8888: - LibretroWrapper.w = Convert.ToInt32(width); LibretroWrapper.h = Convert.ToInt32(height); - if (tex == null) { - tex = new Texture2D(LibretroWrapper.w, LibretroWrapper.h, TextureFormat.RGB565, false); + if (tex == null || tex.height != LibretroWrapper.h || tex.width != LibretroWrapper.w) + { + tex = new Texture2D(LibretroWrapper.w, LibretroWrapper.h, TextureFormat.ARGB32, false); } LibretroWrapper.p = Convert.ToInt32(pitch); - //size = Marshal.SizeOf(typeof(int)); - for (i = 0; i < height; i++) { - for (j = 0; j < width; j++) { - int packed = Marshal.ReadInt32(pixels); - _frameBuffer[i * width + j] = new Pixel { - Alpha = 1, - Red = ((packed >> 16) & 0x00FF) / 255.0f, - Green = ((packed >> 8) & 0x00FF) / 255.0f, - Blue = (packed & 0x00FF) / 255.0f - }; - var color = new Color(((packed >> 16) & 0x00FF) / 255.0f, ((packed >> 8) & 0x00FF) / 255.0f, (packed & 0x00FF) / 255.0f, 1.0f); - tex.SetPixel((int)i, (int)j, color); - //pixels = (IntPtr)((int)pixels + size); - } - //pixels = (IntPtr)((int)rowStart + pitch); - //rowStart = pixels; + size = Marshal.SizeOf(typeof(int)); + //Get Pixel Array from Libretro + Int32[] pixelarr = new Int32[width * height]; + Marshal.Copy(pixels, pixelarr, 0, (int)(width * height)); + + //Create Color Array + Color32[] color32arr = new Color32[width * height]; + + //create job handles list + JobHandle jobHandle = new JobHandle(); + //create native pixel array and color array for returning from unity Job + var nativePixelArray = new NativeArray(pixelarr, Allocator.TempJob); + var nativeColorArray = new NativeArray(color32arr, Allocator.TempJob); + + var job = new RGB8888Job + { + pixelarray = nativePixelArray, + color32array = nativeColorArray + }; + jobHandle = (job.Schedule(pixelarr.Length, 1000)); + jobHandle.Complete(); + nativeColorArray.CopyTo(color32arr); + nativePixelArray.Dispose(); + nativeColorArray.Dispose(); - } + tex.SetPixels32(color32arr, 0); tex.filterMode = FilterMode.Trilinear; tex.Apply(); break; @@ -331,24 +371,47 @@ private unsafe void RetroVideoRefresh(void* data, uint width, uint height, uint private void RetroAudioSample(short left, short right) { // Unused. + if (initialized) + { + float value = left * -0.000030517578125f; + value = Mathf.Clamp(value, -1.0f, 1.0f); // Unity's audio only takes values between -1 and 1. + AudioBatch.Add(value); + + value = right * -0.000030517578125f; + value = Mathf.Clamp(value, -1.0f, 1.0f); // Unity's audio only takes values between -1 and 1. + AudioBatch.Add(value); + } } - bool readAudio = false; - float readsSkipped = 0; + bool prepAudio = false; + int tillReadAudio = 0; private unsafe void RetroAudioSampleBatch(short* data, uint frames) { - if (!readAudio) + if (initialized) { - readsSkipped++; - if (readsSkipped > 1000) - readAudio = true; - } - else - { - for (int i = 0; i < frames * 2; ++i) + if (!prepAudio) { - float value = data[i] * 0.000030517578125f; - value = Mathf.Clamp(value, -1.0f, 1.0f); // Unity's audio only takes values between -1 and 1. - AudioBatch.Add(value); + for (int i = 0; i < frames * 2 && !prepAudio; ++i) + { + if (data[i] * 0.000030517578125f >= 0.05) //wait till populated valid data is sent through + { + prepAudio = true; + _speaker.startAudio(); + } + } + } + if (prepAudio) + { + if (tillReadAudio <= 0) + { + for (int i = 0; i < frames * 2; ++i) + { + float value = data[i] * 0.000030517578125f; + value = Mathf.Clamp(value, -1.0f, 1.0f); // Unity's audio only takes values between -1 and 1. + AudioBatch.Add(value); + } + } + else + tillReadAudio--; } } } @@ -357,61 +420,115 @@ private void RetroInputPoll() { } public static short RetroInputState(uint port, uint device, uint index, uint id) { - switch (id) { - case 0: - return Input.GetKey(KeyCode.Z) || Input.GetButton("B") ? (short) 1 : (short) 0; // B - case 1: - return Input.GetKey(KeyCode.A) || Input.GetButton("Y") ? (short) 1 : (short) 0; // Y - case 2: - return Input.GetKey(KeyCode.Space) || Input.GetButton("SELECT") ? (short) 1 : (short) 0; // SELECT - case 3: - return Input.GetKey(KeyCode.Return) || Input.GetButton("START") ? (short) 1 : (short) 0; // START - case 4: - return Input.GetKey(KeyCode.UpArrow) || Input.GetAxisRaw("DpadX") >= 1.0f ? (short) 1 : (short) 0; // UP - case 5: - return Input.GetKey(KeyCode.DownArrow) || Input.GetAxisRaw("DpadX") <= -1.0f ? (short) 1 : (short) 0; // DOWN - case 6: - return Input.GetKey(KeyCode.LeftArrow) || Input.GetAxisRaw("DpadY") <= -1.0f ? (short) 1 : (short) 0; // LEFT - case 7: - return Input.GetKey(KeyCode.RightArrow) || Input.GetAxisRaw("DpadY") >= 1.0f ? (short) 1 : (short) 0; // RIGHT - case 8: - return Input.GetKey(KeyCode.X) || Input.GetButton("A") ? (short) 1 : (short) 0; // A - case 9: - return Input.GetKey(KeyCode.S) || Input.GetButton("X") ? (short) 1 : (short) 0; // X - case 10: - return Input.GetKey(KeyCode.Q) || Input.GetButton("L") ? (short) 1 : (short) 0; // L - case 11: - return Input.GetKey(KeyCode.W) || Input.GetButton("R") ? (short) 1 : (short) 0; // R - case 12: - return Input.GetKey(KeyCode.E) ? (short) 1 : (short) 0; - case 13: - return Input.GetKey(KeyCode.R) ? (short) 1 : (short) 0; - case 14: - return Input.GetKey(KeyCode.T) ? (short) 1 : (short) 0; - case 15: - return Input.GetKey(KeyCode.Y) ? (short) 1 : (short) 0; - default: - return 0; + switch (device) + { + case 1: //retro device joypad + { + switch (id) + { + case 0: + return Input.GetKey(KeyCode.Z) || Input.GetButton("B") ? (short)1 : (short)0; // B + case 1: + return Input.GetKey(KeyCode.A) || Input.GetButton("Y") ? (short)1 : (short)0; // Y + case 2: + return Input.GetKey(KeyCode.Space) || Input.GetButton("SELECT") ? (short)1 : (short)0; // SELECT + case 3: + return Input.GetKey(KeyCode.Return) || Input.GetButton("START") ? (short)1 : (short)0; // START + case 4: + return Input.GetKey(KeyCode.UpArrow) || Input.GetAxisRaw("DpadX") >= 1.0f ? (short)1 : (short)0; // UP + case 5: + return Input.GetKey(KeyCode.DownArrow) || Input.GetAxisRaw("DpadX") <= -1.0f ? (short)1 : (short)0; // DOWN + case 6: + return Input.GetKey(KeyCode.LeftArrow) || Input.GetAxisRaw("DpadY") <= -1.0f ? (short)1 : (short)0; // LEFT + case 7: + return Input.GetKey(KeyCode.RightArrow) || Input.GetAxisRaw("DpadY") >= 1.0f ? (short)1 : (short)0; // RIGHT + case 8: + return Input.GetKey(KeyCode.X) || Input.GetButton("A") ? (short)1 : (short)0; // A + case 9: + return Input.GetKey(KeyCode.S) || Input.GetButton("X") ? (short)1 : (short)0; // X + case 10: + return Input.GetKey(KeyCode.Q) || Input.GetButton("L") ? (short)1 : (short)0; // L || L1 + case 11: + return Input.GetKey(KeyCode.W) || Input.GetButton("R") ? (short)1 : (short)0; // R || R1 + case 12: + return Input.GetKey(KeyCode.E) ? (short)1 : (short)0; //L2? + case 13: + return Input.GetKey(KeyCode.R) ? (short)1 : (short)0; //R2? + case 14: + return Input.GetKey(KeyCode.T) ? (short)1 : (short)0; //L3? (Left stick press?) + case 15: + return Input.GetKey(KeyCode.Y) ? (short)1 : (short)0; //R3? (Right stick press?) + default: + return 0; + } + break; + } + + case 5: //retro device analog + // * axis values in the full analog range of [-0x7fff, 0x7fff], (-32767 to 32767) + // *although some devices may return -0x8000. + //* Positive X axis is right.Positive Y axis is down. + //* Buttons are returned in the range[0, 0x7fff]. (0 to 32767) + //#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 + //#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 + //#define RETRO_DEVICE_INDEX_ANALOG_BUTTON 2 + //#define RETRO_DEVICE_ID_ANALOG_X 0 + //#define RETRO_DEVICE_ID_ANALOG_Y 1 + switch (index) + { + case 0: //analog left (stick) + switch (id) + { + case 0: + return Input.GetKey(KeyCode.Y) ? (short)1 : (short)0; //L analog X + case 1: + return Input.GetKey(KeyCode.Y) ? (short)1 : (short)0; //L analog Y + default: return 0; + } + break; + case 1: //analog right (stick) + switch (id) + { + case 0: + return Input.GetKey(KeyCode.Y) ? (short)1 : (short)0; //R analog X + case 1: + return Input.GetKey(KeyCode.Y) ? (short)1 : (short)0; //R analog Y + default: return 0; + } + break; + case 2: //analog button? + return 0; + break; + default: return 0; + } + break; + default: return 0; } } private unsafe bool RetroEnvironment(uint cmd, void* data) { switch (cmd) { - case Environment.RetroEnvironmentGetOverscan: - break; - case Environment.RetroEnvironmentGetVariable: - break; - case Environment.RetroEnvironmentSetVariables: - break; - case Environment.RetroEnvironmentSetMessage: - break; - case Environment.RetroEnvironmentSetRotation: - break; - case Environment.RetroEnvironmentShutdown: - break; - case Environment.RetroEnvironmentSetPerformanceLevel: - break; + //case Environment.RetroEnvironmentGetOverscan: + // break; + //case Environment.RetroEnvironmentGetVariable: + // //retro_variable vr = *(retro_variable*)data; + // //Debug.Log("cmd 4: Asking for variable: " + Marshal.PtrToStringAnsi((IntPtr)vr.key)); + // break; + //case Environment.RetroEnvironmentSetVariables: + // break; + //case Environment.RetroEnvironmentSetMessage: + // break; + //case Environment.RetroEnvironmentSetRotation: + // break; + //case Environment.RetroEnvironmentShutdown: + // break; + //case Environment.RetroEnvironmentSetPerformanceLevel: + // break; case Environment.RetroEnvironmentGetSystemDirectory: + char** array = (char**)data; + string systemDirectory = Application.streamingAssetsPath + "/" + "System"; + *array = StringToChar(systemDirectory); + //Debug.Log("Polled system directory"); break; case Environment.RetroEnvironmentSetPixelFormat: _pixelFormat = *(PixelFormat*) data; @@ -426,9 +543,18 @@ private unsafe bool RetroEnvironment(uint cmd, void* data) { break; } break; - case Environment.RetroEnvironmentSetInputDescriptors: + //case Environment.RetroEnvironmentSetInputDescriptors: + // break; + //case Environment.RetroEnvironmentSetKeyboardCallback: + // break; + case Environment.RETRO_ENVIRONMENT_GET_VARIABLE: + //retro_variable v = *(retro_variable*)data; + //string keyName = Marshal.PtrToStringAnsi((IntPtr)v.key); + //Debug.Log("cmd 15 Asking for variable: " + keyName); + return false; //we still didn't do anything here so we return false like we didn't get command for now break; - case Environment.RetroEnvironmentSetKeyboardCallback: + case Environment.RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: + *(bool*)data = false; //say there has been no variable updates break; default: return false; @@ -513,8 +639,11 @@ public void LoadRAM(string path) { byte[] savedData = System.IO.File.ReadAllBytes(path); - for (int i = 0; i < size; i++) - ((byte*)data)[i] = savedData[i]; + if (savedData.Length == size) + { + for (int i = 0; i < size; i++) + ((byte*)data)[i] = savedData[i]; + } } } } diff --git a/Assets/Plugins/RetroUnity/Scripts/Speaker.cs b/Assets/Plugins/RetroUnity/Scripts/Speaker.cs index c26f45f..a50006f 100644 --- a/Assets/Plugins/RetroUnity/Scripts/Speaker.cs +++ b/Assets/Plugins/RetroUnity/Scripts/Speaker.cs @@ -7,7 +7,18 @@ public class Speaker : MonoBehaviour { private AudioSource _speaker; - private void Start() { + private bool started = false; + //private void Update() + //{ + // if (!started && LibretroWrapper.Wrapper.AudioBatch.Count != 0) + // { + // startAudio(); + // } + //} + + public void startAudio() + { + _speaker = GetComponent(); if (_speaker == null) return; //var audioConfig = AudioSettings.GetConfiguration(); @@ -20,6 +31,7 @@ private void Start() { //_speaker.loop = true; //Debug.Log("Unity sample rate: " + audioConfig.sampleRate); //Debug.Log("Unity buffer size: " + audioConfig.dspBufferSize); + started = true; } private void OnAudioFilterRead(float[] data, int channels) { @@ -30,7 +42,10 @@ private void OnAudioFilterRead(float[] data, int channels) { for (i = 0; i < data.Length; i++) data[i] = LibretroWrapper.Wrapper.AudioBatch[i]; // remove data from the beginning - LibretroWrapper.Wrapper.AudioBatch.RemoveRange(0, i); + if (LibretroWrapper.Wrapper.AudioBatch.Count < 10000) + LibretroWrapper.Wrapper.AudioBatch.RemoveRange(0, i); + else + LibretroWrapper.Wrapper.AudioBatch.RemoveRange(0, 10000); //clear out excess data, since my method method has no audio lag, but seems to provide trailing numbers } private void OnGUI() { From ed71be5a84291565476c0ebe41a55941e61f8d75 Mon Sep 17 00:00:00 2001 From: Hokage3211 <54164358+Hokage3211@users.noreply.github.com> Date: Thu, 10 Sep 2020 15:45:29 -0700 Subject: [PATCH 3/4] Implementing more commands to be recognized, and fixing sound more, ps1 gearsystem no longer outright crashes, but still doesn't load or play sound, simply displays 4 pixels. # Conflicts solved: # Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs # Assets/Plugins/RetroUnity/Scripts/Utility/RGB8888Job.cs --- .../RetroUnity/Scripts/LibretroWrapper.cs | 550 ++++++++++++++++-- 1 file changed, 505 insertions(+), 45 deletions(-) diff --git a/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs b/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs index d1fc5c8..abfb82d 100644 --- a/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs +++ b/Assets/Plugins/RetroUnity/Scripts/LibretroWrapper.cs @@ -102,6 +102,64 @@ public struct Timing { public double sample_rate; } + [StructLayout(LayoutKind.Sequential)] + public struct retro_frame_time_callback + { + public IntPtr callback; // retro_frame_time_callback_t + public long reference; + } + + private enum retro_hw_context_type + { + RETRO_HW_CONTEXT_NONE = 0, + RETRO_HW_CONTEXT_OPENGL = 1, + RETRO_HW_CONTEXT_OPENGLES2 = 2, + RETRO_HW_CONTEXT_OPENGL_CORE = 3, + RETRO_HW_CONTEXT_OPENGLES3 = 4, + RETRO_HW_CONTEXT_OPENGLES_VERSION = 5, + RETRO_HW_CONTEXT_VULKAN = 6, + RETRO_HW_CONTEXT_DIRECT3D = 7 + } + + [StructLayout(LayoutKind.Sequential)] + private struct retro_hw_render_callback + { + public retro_hw_context_type context_type; + public IntPtr context_reset; // retro_hw_context_reset_t + public IntPtr get_current_framebuffer; // retro_hw_get_current_framebuffer_t + public IntPtr get_proc_address; // retro_hw_get_proc_address_t + [MarshalAs(UnmanagedType.U1)] public bool depth; + [MarshalAs(UnmanagedType.U1)] public bool stencil; + [MarshalAs(UnmanagedType.U1)] public bool bottom_left_origin; + public uint version_major; + public uint version_minor; + [MarshalAs(UnmanagedType.U1)] public bool cache_context; + + public IntPtr context_destroy; // retro_hw_context_reset_t + + [MarshalAs(UnmanagedType.U1)] public bool debug_context; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct retro_memory_descriptor + { + public ulong flags; + public void* ptr; + public ulong offset; + public ulong start; + public ulong select; + public ulong disconnect; + public ulong len; + public char* addrspace; + } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct retro_memory_map + { + public retro_memory_descriptor* descriptors; + public uint num_descriptors; + } + [StructLayout(LayoutKind.Sequential)] public unsafe struct SystemInfo { @@ -116,6 +174,35 @@ public unsafe struct SystemInfo { public bool block_extract; } + [StructLayout(LayoutKind.Sequential)] + private unsafe struct retro_subsystem_memory_info + { + public char* extension; + public uint type; + } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct retro_subsystem_rom_info + { + public char* desc; + public char* valid_extensions; + [MarshalAs(UnmanagedType.U1)] public bool need_fullpath; + [MarshalAs(UnmanagedType.U1)] public bool block_extract; + [MarshalAs(UnmanagedType.U1)] public bool required; + public retro_subsystem_memory_info* memory; + public uint num_memory; + } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct retro_subsystem_info + { + public char* desc; + public char* ident; + public retro_subsystem_rom_info* roms; + public uint num_roms; + public uint id; + } + [StructLayout(LayoutKind.Sequential)] public unsafe struct retro_variable { @@ -129,15 +216,63 @@ public unsafe struct retro_variable public readonly char* key; /* Value to be obtained. If key does not exist, it is set to NULL. */ - public readonly char* value; + public char* value; }; + public class CoreOptions + { + public string CoreName = string.Empty; + public List Options = new List(); + } + + public class CoreOptionsList + { + public List Cores = new List(); + } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct retro_core_option_definition + { + public char* key; + public char* desc; + public char* info; + [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = RETRO_NUM_CORE_OPTION_VALUES_MAX)] + public retro_core_option_value[] values; // retro_core_option_value[RETRO_NUM_CORE_OPTION_VALUES_MAX] + public char* default_value; + } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct retro_core_options_intl + { + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct)] + public IntPtr us; // retro_core_option_definition* + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct)] + public IntPtr local; // retro_core_option_definition* + } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct retro_core_option_value + { + public char* value; + public char* label; + } + + public const int RETRO_NUM_CORE_OPTION_VALUES_MAX = 128; + public class Environment { + + public const int RETRO_API_VERSION = 1; + + private const int RETRO_ENVIRONMENT_EXPERIMENTAL = 0x10000; + private const int RETRO_ENVIRONMENT_PRIVATE = 0x20000; + + + public const uint RetroEnvironmentSetRotation = 1; public const uint RetroEnvironmentGetOverscan = 2; public const uint RetroEnvironmentGetCanDupe = 3; - public const uint RetroEnvironmentGetVariable = 4; - public const uint RetroEnvironmentSetVariables = 5; + //public const uint RetroEnvironmentGetVariable = 4; + //public const uint RetroEnvironmentSetVariables = 5; public const uint RetroEnvironmentSetMessage = 6; public const uint RetroEnvironmentShutdown = 7; public const uint RetroEnvironmentSetPerformanceLevel = 8; @@ -154,7 +289,15 @@ public class Environment { public const uint RETRO_ENVIRONMENT_GET_LIBRETRO_PATH = 19; public const uint RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK = 21; public const uint RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK = 22; + public const uint RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY = 30; public const uint RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY = 31; + public const uint RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO = 32; + public const uint RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO = 34; + public const uint RETRO_ENVIRONMENT_SET_MEMORY_MAPS = 36 | RETRO_ENVIRONMENT_EXPERIMENTAL; + public const uint RETRO_ENVIRONMENT_SET_GEOMETRY = 37; + public const uint RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE = 47 | RETRO_ENVIRONMENT_EXPERIMENTAL; + public const uint RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION = 52; + public const uint RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL = 54; } public class Wrapper { @@ -184,13 +327,16 @@ public Wrapper(string coreToLoad) { } public bool initialized = false; + string coreName; public unsafe void Init() { + LoadCoreOptionsFile(); + int apiVersion = Libretro.RetroApiVersion(); SystemInfo info = new SystemInfo(); Libretro.RetroGetSystemInfo(ref info); - string coreName = Marshal.PtrToStringAnsi((IntPtr)info.library_name); + coreName = Marshal.PtrToStringAnsi((IntPtr)info.library_name); string coreVersion = Marshal.PtrToStringAnsi((IntPtr)info.library_version); string validExtensions = Marshal.PtrToStringAnsi((IntPtr)info.valid_extensions); _requiresFullPath = info.need_fullpath; @@ -256,7 +402,7 @@ private unsafe void RetroVideoRefresh(void* data, uint width, uint height, uint //IntPtr rowStart = pixels; //Get the size to move the pointer - int size = 24; + //int size = 24; uint i; uint j; @@ -297,40 +443,24 @@ private unsafe void RetroVideoRefresh(void* data, uint width, uint height, uint case PixelFormat.RetroPixelFormatXRGB8888: LibretroWrapper.w = Convert.ToInt32(width); LibretroWrapper.h = Convert.ToInt32(height); - if (tex == null || tex.height != LibretroWrapper.h || tex.width != LibretroWrapper.w) + if (tex == null) { - tex = new Texture2D(LibretroWrapper.w, LibretroWrapper.h, TextureFormat.ARGB32, false); + tex = new Texture2D(LibretroWrapper.w, LibretroWrapper.h, TextureFormat.BGRA32, false); } - LibretroWrapper.p = Convert.ToInt32(pitch); - - size = Marshal.SizeOf(typeof(int)); - //Get Pixel Array from Libretro - Int32[] pixelarr = new Int32[width * height]; - Marshal.Copy(pixels, pixelarr, 0, (int)(width * height)); - - //Create Color Array - Color32[] color32arr = new Color32[width * height]; - - //create job handles list - JobHandle jobHandle = new JobHandle(); - //create native pixel array and color array for returning from unity Job - var nativePixelArray = new NativeArray(pixelarr, Allocator.TempJob); - var nativeColorArray = new NativeArray(color32arr, Allocator.TempJob); - - var job = new RGB8888Job + if (tex.format != TextureFormat.BGRA32 || tex.width != LibretroWrapper.w || tex.height != LibretroWrapper.h) { - pixelarray = nativePixelArray, - color32array = nativeColorArray - }; - jobHandle = (job.Schedule(pixelarr.Length, 1000)); - jobHandle.Complete(); - nativeColorArray.CopyTo(color32arr); - nativePixelArray.Dispose(); - nativeColorArray.Dispose(); + tex = new Texture2D(LibretroWrapper.w, LibretroWrapper.h, TextureFormat.BGRA32, false); + } + new ARGB8888Job + { + SourceData = (uint*)data, + Width = LibretroWrapper.w, + Height = LibretroWrapper.h, + PitchPixels = pitch, + TextureData = tex.GetRawTextureData() + }.Schedule().Complete(); - tex.SetPixels32(color32arr, 0); - tex.filterMode = FilterMode.Trilinear; tex.Apply(); break; @@ -362,6 +492,7 @@ private unsafe void RetroVideoRefresh(void* data, uint width, uint height, uint tex.Apply(); break; case PixelFormat.RetroPixelFormatUnknown: + Debug.LogWarning("Unknown Pixel Format!"); _frameBuffer = null; break; default: @@ -506,16 +637,31 @@ public static short RetroInputState(uint port, uint device, uint index, uint id) } } + public CoreOptions coreOptions; + private CoreOptionsList _coreOptionsList; + public retro_memory_descriptor[] descriptors; + private int rotation; + + private const uint SUBSYSTEM_MAX_SUBSYSTEMS = 20; + private const uint SUBSYSTEM_MAX_SUBSYSTEM_ROMS = 10; + + private readonly retro_subsystem_info[] subsystem_data = new retro_subsystem_info[SUBSYSTEM_MAX_SUBSYSTEMS]; + private readonly unsafe retro_subsystem_rom_info*[] subsystem_data_roms = new retro_subsystem_rom_info*[SUBSYSTEM_MAX_SUBSYSTEMS]; + private uint subsystem_current_count; + + private uint lastCommand = 0; private unsafe bool RetroEnvironment(uint cmd, void* data) { + int hey = 0; + lastCommand = cmd; switch (cmd) { - //case Environment.RetroEnvironmentGetOverscan: - // break; - //case Environment.RetroEnvironmentGetVariable: - // //retro_variable vr = *(retro_variable*)data; - // //Debug.Log("cmd 4: Asking for variable: " + Marshal.PtrToStringAnsi((IntPtr)vr.key)); - // break; - //case Environment.RetroEnvironmentSetVariables: - // break; + case Environment.RetroEnvironmentGetOverscan: + bool* outOverscan = (bool*)data; + *outOverscan = false; + break; + case Environment.RetroEnvironmentGetCanDupe: + bool* outCanDupe = (bool*)data; + *outCanDupe = true; + break; //case Environment.RetroEnvironmentSetMessage: // break; //case Environment.RetroEnvironmentSetRotation: @@ -543,30 +689,343 @@ private unsafe bool RetroEnvironment(uint cmd, void* data) { break; } break; - //case Environment.RetroEnvironmentSetInputDescriptors: - // break; //case Environment.RetroEnvironmentSetKeyboardCallback: // break; case Environment.RETRO_ENVIRONMENT_GET_VARIABLE: //retro_variable v = *(retro_variable*)data; //string keyName = Marshal.PtrToStringAnsi((IntPtr)v.key); //Debug.Log("cmd 15 Asking for variable: " + keyName); - return false; //we still didn't do anything here so we return false like we didn't get command for now + retro_variable* outVariable = (retro_variable*)data; + + string key = Marshal.PtrToStringAnsi((IntPtr)outVariable->key); + if (coreOptions != null) + { + string coreOption = coreOptions.Options.Find(x => x.StartsWith(key, StringComparison.OrdinalIgnoreCase)); + if (coreOption != null) + { + outVariable->value = StringToChar(coreOption.Split(';')[1]); + } + else + { + Debug.LogWarning($"Core option {key} not found!"); + return false; + } + } + else + { + Debug.LogWarning($"Core didn't set its options for key '{key}'."); + return false; + } + + //return false; //we still didn't do anything here so we return false like we didn't get command for now break; case Environment.RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: *(bool*)data = false; //say there has been no variable updates break; + case Environment.RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY: + char** assetsArray = (char**)data; + string sysDir = Application.streamingAssetsPath + "/" + "System"; + *assetsArray = StringToChar(sysDir); + break; + case Environment.RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY: + char** saveStr = (char**)data; + string savDir = Application.streamingAssetsPath + "/" + "SaveDirectory"; + *saveStr = StringToChar(savDir); + break; + case Environment.RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE: + int result = 0; + result |= 1; // if video enabled + result |= 2; // if audio enabled + + int* outAudioVideoEnabled = (int*)data; + *outAudioVideoEnabled = result; + break; + case Environment.RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: + uint* outVersion = (uint*)data; + *outVersion = Environment.RETRO_API_VERSION; + break; + + //considered "front-end" from here on out + + case Environment.RetroEnvironmentSetRotation: + // TODO: Rotate screen (counter-clockwise) + // Values: 0, 1, 2, 3 + // Result: 0, 90, 180, 270 degrees + uint* inRotation = (uint*)data; + rotation = (int)*inRotation; + break; + case Environment.RetroEnvironmentSetPerformanceLevel: + break; + case Environment.RetroEnvironmentSetInputDescriptors: + break; + case Environment.RETRO_ENVIRONMENT_SET_VARIABLES: + try + { + retro_variable* inVariable = (retro_variable*)data; + + coreOptions = _coreOptionsList.Cores.Find(x => x.CoreName.Equals(coreName, StringComparison.OrdinalIgnoreCase)); + if (coreOptions == null) + { + coreOptions = new CoreOptions { CoreName = coreName }; + _coreOptionsList.Cores.Add(coreOptions); + } + + while (inVariable->key != null) + { + string inKey = Marshal.PtrToStringAnsi((IntPtr)inVariable->key); + string coreOption = coreOptions.Options.Find(x => x.StartsWith(inKey, StringComparison.OrdinalIgnoreCase)); + if (coreOption == null) + { + string inValue = Marshal.PtrToStringAnsi((IntPtr)inVariable->value); + string[] descriptionAndValues = inValue.Split(';'); + string[] possibleValues = descriptionAndValues[1].Trim().Split('|'); + string defaultValue = possibleValues[0]; + string value = defaultValue; + coreOption = $"{inKey};{value};{string.Join("|", possibleValues)};"; + coreOptions.Options.Add(coreOption); + } + ++inVariable; + } + } + catch (Exception e) + { + Debug.LogError(e); + } + + SaveCoreOptionsFile(); + break; + case Environment.RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME: + *(bool*)data = false; + break; + case Environment.RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK: + + break; + case Environment.RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO: + SystemAVInfo* inSystemAVnfo = (SystemAVInfo*)data; + _av = *inSystemAVnfo; + break; + case Environment.RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO: + //retro_subsystem_info* subsytemInfo = (retro_subsystem_info*)data; + ////Debug.Log("Subsystem Info:"); + ////Debug.Log($"Description: {Marshal.PtrToStringAnsisubsytemInfo->desc)}"); + ////Debug.Log($"Ident: {Marshal.PtrToStringAnsisubsytemInfo->ident)}"); + //_game_type = subsytemInfo->id; + //_num_info = subsytemInfo->num_roms; + //while (subsytemInfo->roms != null) + //{ + // RetroSubsystemRomInfo* romInfo = subsytemInfo->roms; + // //Debug.Log("Rom Info:"); + // //Debug.Log($"Description: {Marshal.PtrToStringAnsiromInfo->desc)}"); + // //Debug.Log($"Extensions: {Marshal.PtrToStringAnsiromInfo->valid_extensions)}"); + // subsytemInfo++; + //} + + retro_subsystem_info* inSubsytemInfo = (retro_subsystem_info*)data; + // settings_t* settings = configuration_settings; + // unsigned log_level = settings->uints.frontend_log_level; + + subsystem_current_count = 0; + + uint size = 0; + + uint i = 0; + while (inSubsytemInfo[i].ident != null) + { + string subsystemDesc = CharsToString(inSubsytemInfo[i].desc); + string subsystemIdent = CharsToString(inSubsytemInfo[i].ident); + uint subsystemId = inSubsytemInfo[i].id; + for (uint j = 0; j < inSubsytemInfo[i].num_roms; j++) + { + string romDesc = CharsToString(inSubsytemInfo[i].roms[j].desc); + string required = inSubsytemInfo[i].roms[j].required ? "required" : "optional"; + } + i++; + } + + //if (log_level == RETRO_LOG_DEBUG) + size = i; + + //if (log_level == RETRO_LOG_DEBUG) + if (size > SUBSYSTEM_MAX_SUBSYSTEMS) + { + } + + if (subsystem_data != null) + { + for (uint k = 0; k < size && k < SUBSYSTEM_MAX_SUBSYSTEMS; k++) + { + ref retro_subsystem_info subdata = ref subsystem_data[k]; + + subdata.desc = inSubsytemInfo[k].desc; + subdata.ident = inSubsytemInfo[k].ident; + subdata.id = inSubsytemInfo[k].id; + subdata.num_roms = inSubsytemInfo[k].num_roms; + + //if (log_level == RETRO_LOG_DEBUG) + if (subdata.num_roms > SUBSYSTEM_MAX_SUBSYSTEM_ROMS) + { + } + + for (uint j = 0; j < subdata.num_roms && j < SUBSYSTEM_MAX_SUBSYSTEM_ROMS; j++) + { + while (subdata.roms != null) + { + retro_subsystem_rom_info* romInfo = subdata.roms; + romInfo->desc = inSubsytemInfo[k].roms[j].desc; + romInfo->valid_extensions = inSubsytemInfo[k].roms[j].valid_extensions; + romInfo->required = inSubsytemInfo[k].roms[j].required; + romInfo->block_extract = inSubsytemInfo[k].roms[j].block_extract; + romInfo->need_fullpath = inSubsytemInfo[k].roms[j].need_fullpath; + subdata.roms++; + } + } + + subdata.roms = subsystem_data_roms[k]; + } + + subsystem_current_count = (size <= SUBSYSTEM_MAX_SUBSYSTEMS) ? size : SUBSYSTEM_MAX_SUBSYSTEMS; + } + //return false; //TODO: Remove when implemented! + break; + case Environment.RETRO_ENVIRONMENT_SET_MEMORY_MAPS: + retro_memory_map* map = (retro_memory_map*)data; + descriptors = new retro_memory_descriptor[map->num_descriptors]; + for (uint j = 0; j < map->num_descriptors; j++) + { + descriptors[j].flags = map->descriptors[j].flags; + descriptors[j].ptr = map->descriptors[j].ptr; + descriptors[j].offset = map->descriptors[j].offset; + descriptors[j].start = map->descriptors[j].start; + descriptors[j].select = map->descriptors[j].select; + descriptors[j].disconnect = map->descriptors[j].disconnect; + descriptors[j].len = map->descriptors[j].len; + descriptors[j].addrspace = map->descriptors[j].addrspace; + //Debug.Log("Descriptor " + j + "= " + descriptors[j].start.ToString("X") + ", Length = " + descriptors[j].len.ToString("X")); + } + break; + case Environment.RETRO_ENVIRONMENT_SET_GEOMETRY: + if (initialized) + { + Geometry* inGeometry = (Geometry*)data; + if (_av.geometry.base_width != inGeometry->base_width + || _av.geometry.base_height != inGeometry->base_height + || _av.geometry.aspect_ratio != inGeometry->aspect_ratio) + { + _av.geometry = *inGeometry; + // TODO: Set video aspect ratio + } + } + break; + case Environment.RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL: + retro_core_options_intl inOptionsIntl = Marshal.PtrToStructure((IntPtr)data); + + coreOptions = _coreOptionsList.Cores.Find(x => x.CoreName.Equals(coreName, StringComparison.OrdinalIgnoreCase)); + if (coreOptions == null) + { + coreOptions = new CoreOptions { CoreName = coreName }; + _coreOptionsList.Cores.Add(coreOptions); + } + + for (int l = 0; l < RETRO_NUM_CORE_OPTION_VALUES_MAX; l++) + { + IntPtr ins = new IntPtr(inOptionsIntl.us.ToInt64() + l * Marshal.SizeOf()); + retro_core_option_definition defs = Marshal.PtrToStructure(ins); + if (defs.key == null) + { + break; + } + + string bkey = Marshal.PtrToStringAnsi((IntPtr)defs.key); + + string coreOption = coreOptions.Options.Find(x => x.StartsWith(bkey, StringComparison.OrdinalIgnoreCase)); + if (coreOption == null) + { + string defaultValue = CharsToString(defs.default_value); + + List possibleValues = new List(); + for (int j = 0; j < defs.values.Length; j++) + { + retro_core_option_value val = defs.values[j]; + if (val.value != null) + { + possibleValues.Add(CharsToString(val.value)); + } + } + + string value = string.Empty; + if (!string.IsNullOrEmpty(defaultValue)) + { + value = defaultValue; + } + else if (possibleValues.Count > 0) + { + value = possibleValues[0]; + } + + coreOption = $"{bkey};{value};{string.Join("|", possibleValues)}"; + + coreOptions.Options.Add(coreOption); + } + } + + SaveCoreOptionsFile(); + break; default: return false; } return true; } + private string CoreOptionsFile = Application.streamingAssetsPath + "/Options.txt"; + private void LoadCoreOptionsFile() + { + _coreOptionsList = DeserializeFromJson(CoreOptionsFile); + if (_coreOptionsList == null) {_coreOptionsList = new CoreOptionsList();} + } + + private void SaveCoreOptionsFile() + { + for (int i = 0; i < _coreOptionsList.Cores.Count; i++) + { + _coreOptionsList.Cores[i].Options.Sort(); + } + _ = SerializeToJson(_coreOptionsList, CoreOptionsFile); + } + + public static bool SerializeToJson(T sourceObject, string targetPath) + { + bool result = false; + + try{ + string jsonString = UnityEngine.JsonUtility.ToJson(sourceObject, true); + File.WriteAllText(targetPath, jsonString); + result = true; + } + catch (Exception e){Debug.LogError(e);} + return result; + } + + public static T DeserializeFromJson(string sourcePath) where T : class + { + T result = null; + try{ + string jsonString = File.ReadAllText(sourcePath); + result = UnityEngine.JsonUtility.FromJson(jsonString); + } + catch (Exception e){Debug.LogError(e);} + return result; + } + private static unsafe char* StringToChar(string s) { IntPtr p = Marshal.StringToHGlobalUni(s); return (char*) p.ToPointer(); } + private static unsafe string CharsToString(char* value) + { + return Marshal.PtrToStringAnsi((IntPtr)value); + } + private unsafe GameInfo LoadGameInfo(string file) { var gameInfo = new GameInfo(); @@ -609,6 +1068,7 @@ public bool LoadGame(string gamePath) { Debug.Log("Geometry:"); Debug.Log("Target fps: " + _av.timing.fps); Debug.Log("Sample rate " + _av.timing.sample_rate); + return ret; } From 625eb52c8b4edbb14797fb285a5afe60b01af31a Mon Sep 17 00:00:00 2001 From: Hokage3211 <54164358+Hokage3211@users.noreply.github.com> Date: Thu, 10 Sep 2020 15:49:32 -0700 Subject: [PATCH 4/4] Including options file needed by code used as a guide from here https://github.com/3DArcade/3DArcade/tree/master/Assets/Libretro/Scripts/Wrapper --- Assets/StreamingAssets/Options.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 Assets/StreamingAssets/Options.txt diff --git a/Assets/StreamingAssets/Options.txt b/Assets/StreamingAssets/Options.txt new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/Assets/StreamingAssets/Options.txt @@ -0,0 +1 @@ +{} \ No newline at end of file