Archived
1
0
forked from MeloNX/MeloNX

move unmanaged methods to separate file and expose more api

(cherry picked from commit 4a6b7ffbe55da614ca1a8b8dd693f60e17e99cb0)
This commit is contained in:
Emmanuel Hansen 2024-08-13 20:49:15 +00:00
parent 235a90aed8
commit c90b78499b
5 changed files with 454 additions and 212 deletions

View File

@ -12,36 +12,6 @@ namespace LibRyujinx
{
public static partial class LibRyujinx
{
[UnmanagedCallersOnly(EntryPoint = "device_initialize")]
public static bool InitializeDeviceNative(bool isHostMapped,
bool useHypervisor,
SystemLanguage systemLanguage,
RegionCode regionCode,
bool enableVsync,
bool enableDockedMode,
bool enablePtc,
bool enableInternetAccess,
IntPtr timeZone,
bool ignoreMissingServices)
{
return InitializeDevice(isHostMapped,
useHypervisor,
systemLanguage,
regionCode,
enableVsync,
enableDockedMode,
enablePtc,
enableInternetAccess,
Marshal.PtrToStringAnsi(timeZone),
ignoreMissingServices);
}
[UnmanagedCallersOnly(EntryPoint = "device_reloadFilesystem")]
public static void ReloadFileSystem()
{
SwitchDevice?.ReloadFileSystem();
}
public static bool InitializeDevice(bool isHostMapped,
bool useHypervisor,
SystemLanguage systemLanguage,
@ -70,34 +40,6 @@ namespace LibRyujinx
ignoreMissingServices);
}
[UnmanagedCallersOnly(EntryPoint = "device_load")]
public static bool LoadApplicationNative(IntPtr pathPtr)
{
if(SwitchDevice?.EmulationContext == null)
{
return false;
}
var path = Marshal.PtrToStringAnsi(pathPtr);
return LoadApplication(path);
}
[UnmanagedCallersOnly(EntryPoint = "device_install_firmware")]
public static void InstallFirmwareNative(int descriptor, bool isXci)
{
var stream = OpenFile(descriptor);
InstallFirmware(stream, isXci);
}
[UnmanagedCallersOnly(EntryPoint = "device_get_installed_firmware_version")]
public static IntPtr GetInstalledFirmwareVersionNative()
{
var result = GetInstalledFirmwareVersion();
return Marshal.StringToHGlobalAnsi(result);
}
public static void InstallFirmware(Stream stream, bool isXci)
{
SwitchDevice?.ContentManager.InstallFirmware(stream, isXci);

View File

@ -32,12 +32,6 @@ namespace LibRyujinx
public static IRenderer? Renderer { get; set; }
public static GraphicsConfiguration GraphicsConfiguration { get; private set; }
[UnmanagedCallersOnly(EntryPoint = "graphics_initialize")]
public static bool InitializeGraphicsNative(GraphicsConfiguration graphicsConfiguration)
{
return InitializeGraphics(graphicsConfiguration);
}
public static bool InitializeGraphics(GraphicsConfiguration graphicsConfiguration)
{
@ -55,30 +49,7 @@ namespace LibRyujinx
return true;
}
[UnmanagedCallersOnly(EntryPoint = "graphics_initialize_renderer")]
public unsafe static bool InitializeGraphicsRendererNative(GraphicsBackend graphicsBackend, NativeGraphicsInterop nativeGraphicsInterop)
{
_nativeGraphicsInterop = nativeGraphicsInterop;
if (Renderer != null)
{
return false;
}
List<string> extensions = new List<string>();
var size = Marshal.SizeOf<IntPtr>();
var extPtr = (IntPtr*)nativeGraphicsInterop.VkRequiredExtensions;
for (int i = 0; i < nativeGraphicsInterop.VkRequiredExtensionsCount; i++)
{
var ptr = extPtr[i];
extensions.Add(Marshal.PtrToStringAnsi(ptr) ?? string.Empty);
}
CreateSurface createSurfaceFunc = nativeGraphicsInterop.VkCreateSurface == IntPtr.Zero ? default : Marshal.GetDelegateForFunctionPointer<CreateSurface>(nativeGraphicsInterop.VkCreateSurface);
return InitializeGraphicsRenderer(graphicsBackend, createSurfaceFunc, extensions.ToArray());
}
public static bool InitializeGraphicsRenderer(GraphicsBackend graphicsBackend, CreateSurface createSurfaceFunc, string?[] requiredExtensions)
public static bool InitializeGraphicsRenderer(GraphicsBackend graphicsBackend, CreateSurface? createSurfaceFunc, string?[] requiredExtensions)
{
if (Renderer != null)
{
@ -91,7 +62,7 @@ namespace LibRyujinx
}
else if (graphicsBackend == GraphicsBackend.Vulkan)
{
Renderer = new VulkanRenderer(Vk.GetApi(), (instance, vk) => new SurfaceKHR((ulong?)createSurfaceFunc(instance.Handle)),
Renderer = new VulkanRenderer(Vk.GetApi(), (instance, vk) => new SurfaceKHR(createSurfaceFunc == null ? null : (ulong?)createSurfaceFunc(instance.Handle)),
() => requiredExtensions,
null);
}
@ -103,35 +74,11 @@ namespace LibRyujinx
return true;
}
[UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_size")]
public static void SetRendererSizeNative(int width, int height)
{
SetRendererSize(width, height);
}
public static void SetRendererSize(int width, int height)
{
Renderer?.Window?.SetSize(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "graphics_renderer_run_loop")]
public static void RunLoopNative()
{
if (Renderer is OpenGLRenderer)
{
var proc = Marshal.GetDelegateForFunctionPointer<GetProcAddress>(_nativeGraphicsInterop.GlGetProcAddress);
GL.LoadBindings(new OpenTKBindingsContext(x => proc!.Invoke(x)));
}
RunLoop();
}
[UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_vsync")]
public static void SetVsyncStateNative(bool enabled)
{
SetVsyncState(enabled);
}
public static void SetVsyncState(bool enabled)
{
var device = SwitchDevice!.EmulationContext!;
@ -248,12 +195,6 @@ namespace LibRyujinx
throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
}
}
[UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_swap_buffer_callback")]
public static void SetSwapBuffersCallbackNative(IntPtr swapBuffersCallback)
{
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
}
public static void SetSwapBuffersCallback(SwapBuffersCallback swapBuffersCallback)
{

View File

@ -191,76 +191,6 @@ namespace LibRyujinx
SwitchDevice!.EmulationContext?.Hid.Touchscreen.Update();
}
}
// Native Methods
[UnmanagedCallersOnly(EntryPoint = "input_initialize")]
public static void InitializeInputNative(int width, int height)
{
InitializeInput(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_client_size")]
public static void SetClientSizeNative(int width, int height)
{
SetClientSize(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_touch_point")]
public static void SetTouchPointNative(int x, int y)
{
SetTouchPoint(x, y);
}
[UnmanagedCallersOnly(EntryPoint = "input_release_touch_point")]
public static void ReleaseTouchPointNative()
{
ReleaseTouchPoint();
}
[UnmanagedCallersOnly(EntryPoint = "input_update")]
public static void UpdateInputNative()
{
UpdateInput();
}
[UnmanagedCallersOnly(EntryPoint = "input_set_button_pressed")]
public static void SetButtonPressedNative(GamepadButtonInputId button, int id)
{
SetButtonPressed(button, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_button_released")]
public static void SetButtonReleasedNative(GamepadButtonInputId button, int id)
{
SetButtonReleased(button, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_accelerometer_data")]
public static void SetAccelerometerDataNative(Vector3 accel, int id)
{
SetAccelerometerData(accel, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_gyro_data")]
public static void SetGryoDataNatuve(Vector3 gyro, int id)
{
SetGryoData(gyro, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_stick_axis")]
public static void SetStickAxisNative(StickInputId stick, Vector2 axes, int id)
{
SetStickAxis(stick, axes, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_connect_gamepad")]
public static IntPtr ConnectGamepadNative(int index)
{
return ConnectGamepad(index);
}
}
public class VirtualTouchScreen : IMouse

View File

@ -0,0 +1,450 @@
using LibRyujinx.Shared;
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibRyujinx
{
public static partial class LibRyujinx
{
private unsafe static IntPtr CreateStringArray(List<string> strings)
{
uint size = (uint)(Marshal.SizeOf<IntPtr>() * (strings.Count + 1));
var array = (char**)Marshal.AllocHGlobal((int)size);
Unsafe.InitBlockUnaligned(array, 0, size);
for (int i = 0; i < strings.Count; i++)
{
array[i] = (char*)Marshal.StringToHGlobalAnsi(strings[i]);
}
return (nint)array;
}
[UnmanagedCallersOnly(EntryPoint = "device_initialize")]
public static bool InitializeDeviceNative(bool isHostMapped,
bool useHypervisor,
SystemLanguage systemLanguage,
RegionCode regionCode,
bool enableVsync,
bool enableDockedMode,
bool enablePtc,
bool enableInternetAccess,
IntPtr timeZone,
bool ignoreMissingServices)
{
return InitializeDevice(isHostMapped,
useHypervisor,
systemLanguage,
regionCode,
enableVsync,
enableDockedMode,
enablePtc,
enableInternetAccess,
Marshal.PtrToStringAnsi(timeZone),
ignoreMissingServices);
}
[UnmanagedCallersOnly(EntryPoint = "device_reload_file_system")]
public static void ReloadFileSystemNative()
{
SwitchDevice?.ReloadFileSystem();
}
[UnmanagedCallersOnly(EntryPoint = "device_load")]
public static bool LoadApplicationNative(IntPtr pathPtr)
{
if (SwitchDevice?.EmulationContext == null)
{
return false;
}
var path = Marshal.PtrToStringAnsi(pathPtr);
return LoadApplication(path);
}
[UnmanagedCallersOnly(EntryPoint = "device_install_firmware")]
public static void InstallFirmwareNative(int descriptor, bool isXci)
{
var stream = OpenFile(descriptor);
InstallFirmware(stream, isXci);
}
[UnmanagedCallersOnly(EntryPoint = "device_get_installed_firmware_version")]
public static IntPtr GetInstalledFirmwareVersionNative()
{
var result = GetInstalledFirmwareVersion();
return Marshal.StringToHGlobalAnsi(result);
}
[UnmanagedCallersOnly(EntryPoint = "initialize")]
public static bool InitializeNative(IntPtr basePathPtr)
{
var path = Marshal.PtrToStringAnsi(basePathPtr);
var res = Initialize(path);
InitializeAudio();
return res;
}
[UnmanagedCallersOnly(EntryPoint = "graphics_initialize")]
public static bool InitializeGraphicsNative(GraphicsConfiguration graphicsConfiguration)
{
return InitializeGraphics(graphicsConfiguration);
}
[UnmanagedCallersOnly(EntryPoint = "graphics_initialize_renderer")]
public unsafe static bool InitializeGraphicsRendererNative(GraphicsBackend graphicsBackend, NativeGraphicsInterop nativeGraphicsInterop)
{
_nativeGraphicsInterop = nativeGraphicsInterop;
if (Renderer != null)
{
return false;
}
List<string> extensions = new List<string>();
var size = Marshal.SizeOf<IntPtr>();
var extPtr = (IntPtr*)nativeGraphicsInterop.VkRequiredExtensions;
for (int i = 0; i < nativeGraphicsInterop.VkRequiredExtensionsCount; i++)
{
var ptr = extPtr[i];
extensions.Add(Marshal.PtrToStringAnsi(ptr) ?? string.Empty);
}
CreateSurface? createSurfaceFunc = nativeGraphicsInterop.VkCreateSurface == IntPtr.Zero ? default : Marshal.GetDelegateForFunctionPointer<CreateSurface>(nativeGraphicsInterop.VkCreateSurface);
return InitializeGraphicsRenderer(graphicsBackend, createSurfaceFunc, extensions.ToArray());
}
[UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_size")]
public static void SetRendererSizeNative(int width, int height)
{
SetRendererSize(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "graphics_renderer_run_loop")]
public static void RunLoopNative()
{
if (Renderer is OpenGLRenderer)
{
var proc = Marshal.GetDelegateForFunctionPointer<GetProcAddress>(_nativeGraphicsInterop.GlGetProcAddress);
GL.LoadBindings(new OpenTKBindingsContext(x => proc!.Invoke(x)));
}
RunLoop();
}
[UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_vsync")]
public static void SetVsyncStateNative(bool enabled)
{
SetVsyncState(enabled);
}
[UnmanagedCallersOnly(EntryPoint = "graphics_renderer_set_swap_buffer_callback")]
public static void SetSwapBuffersCallbackNative(IntPtr swapBuffersCallback)
{
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
}
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
{
var extension = Marshal.PtrToStringAnsi(extensionPtr);
var stream = OpenFile(descriptor);
var gameInfo = GetGameInfo(stream, extension ?? "");
return gameInfo == null ? default : new GameInfoNative(gameInfo.FileSize, gameInfo.TitleName, gameInfo.TitleId, gameInfo.Developer, gameInfo.Version, gameInfo.Icon);
}
[UnmanagedCallersOnly(EntryPoint = "input_initialize")]
public static void InitializeInputNative(int width, int height)
{
InitializeInput(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_client_size")]
public static void SetClientSizeNative(int width, int height)
{
SetClientSize(width, height);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_touch_point")]
public static void SetTouchPointNative(int x, int y)
{
SetTouchPoint(x, y);
}
[UnmanagedCallersOnly(EntryPoint = "input_release_touch_point")]
public static void ReleaseTouchPointNative()
{
ReleaseTouchPoint();
}
[UnmanagedCallersOnly(EntryPoint = "input_update")]
public static void UpdateInputNative()
{
UpdateInput();
}
[UnmanagedCallersOnly(EntryPoint = "input_set_button_pressed")]
public static void SetButtonPressedNative(GamepadButtonInputId button, int id)
{
SetButtonPressed(button, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_button_released")]
public static void SetButtonReleasedNative(GamepadButtonInputId button, int id)
{
SetButtonReleased(button, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_accelerometer_data")]
public static void SetAccelerometerDataNative(Vector3 accel, int id)
{
SetAccelerometerData(accel, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_gyro_data")]
public static void SetGryoDataNative(Vector3 gyro, int id)
{
SetGryoData(gyro, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_set_stick_axis")]
public static void SetStickAxisNative(StickInputId stick, Vector2 axes, int id)
{
SetStickAxis(stick, axes, id);
}
[UnmanagedCallersOnly(EntryPoint = "input_connect_gamepad")]
public static IntPtr ConnectGamepadNative(int index)
{
return ConnectGamepad(index);
}
[UnmanagedCallersOnly(EntryPoint = "device_get_game_fifo")]
public static double GetGameInfoNative()
{
var stats = SwitchDevice?.EmulationContext?.Statistics.GetFifoPercent() ?? 0;
return stats;
}
[UnmanagedCallersOnly(EntryPoint = "device_get_game_frame_time")]
public static double GetGameFrameTimeNative()
{
var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameTime() ?? 0;
return stats;
}
[UnmanagedCallersOnly(EntryPoint = "device_get_game_frame_rate")]
public static double GetGameFrameRateNative()
{
var stats = SwitchDevice?.EmulationContext?.Statistics.GetGameFrameRate() ?? 0;
return stats;
}
[UnmanagedCallersOnly(EntryPoint = "device_launch_mii_editor")]
public static bool LaunchMiiEditAppletNative()
{
if (SwitchDevice?.EmulationContext == null)
{
return false;
}
return LaunchMiiEditApplet();
}
[UnmanagedCallersOnly(EntryPoint = "device_get_dlc_content_list")]
public static IntPtr GetDlcContentListNative(IntPtr pathPtr, long titleId)
{
var list = GetDlcContentList(Marshal.PtrToStringAnsi(pathPtr) ?? "", (ulong)titleId);
return CreateStringArray(list);
}
[UnmanagedCallersOnly(EntryPoint = "device_get_dlc_title_id")]
public static long GetDlcTitleIdNative(IntPtr pathPtr, IntPtr ncaPath)
{
return Marshal.StringToHGlobalAnsi(GetDlcTitleId(Marshal.PtrToStringAnsi(pathPtr) ?? "", Marshal.PtrToStringAnsi(ncaPath) ?? ""));
}
[UnmanagedCallersOnly(EntryPoint = "device_signal_emulation_close")]
public static void SignalEmulationCloseNative()
{
SignalEmulationClose();
}
[UnmanagedCallersOnly(EntryPoint = "device_close_emulation")]
public static void CloseEmulationNative()
{
CloseEmulation();
}
[UnmanagedCallersOnly(EntryPoint = "device_load_descriptor")]
public static bool LoadApplicationNative(int descriptor, int type, int updateDescriptor)
{
if (SwitchDevice?.EmulationContext == null)
{
return false;
}
var stream = OpenFile(descriptor);
var update = updateDescriptor == -1 ? null : OpenFile(updateDescriptor);
return LoadApplication(stream, (FileType)type, update);
}
[UnmanagedCallersOnly(EntryPoint = "device_verify_firmware")]
public static IntPtr VerifyFirmwareNative(int descriptor, bool isXci)
{
var stream = OpenFile(descriptor);
IntPtr stringHandle = 0;
string? version = "0.0";
try
{
version = VerifyFirmware(stream, isXci)?.VersionString;
}
catch (Exception _)
{
Logger.Error?.Print(LogClass.Service, $"Unable to verify firmware. Exception: {_}");
}
if (version != null)
{
stringHandle = Marshal.StringToHGlobalAnsi(version);
}
return stringHandle;
}
[UnmanagedCallersOnly(EntryPoint = "logging_set_enabled")]
public static void SetLoggingEnabledNative(int logLevel, bool enabled)
{
Logger.SetEnable((LogLevel)logLevel, enabled);
}
[UnmanagedCallersOnly(EntryPoint = "logging_enabled_graphics_log")]
public static void SetLoggingEnabledGraphicsLogNative(bool enabled)
{
_enableGraphicsLogging = enabled;
}
[UnmanagedCallersOnly(EntryPoint = "device_get_game_info")]
public unsafe static void GetGameInfoNative(int fileDescriptor, IntPtr extension, IntPtr infoPtr)
{
using var stream = OpenFile(fileDescriptor);
var ext = Marshal.PtrToStringAnsi(extension);
var info = GetGameInfo(stream, ext.ToLower()) ?? GetDefaultInfo(stream);
var i = (GameInfoNative*)infoPtr;
var n = new GameInfoNative(info);
i->TitleId = n.TitleId;
i->TitleName = n.TitleName;
i->Version = n.Version;
i->FileSize = n.FileSize;
i->Icon = n.Icon;
i->Version = n.Version;
i->Developer = n.Developer;
}
[UnmanagedCallersOnly(EntryPoint = "user_get_opened_user")]
public static IntPtr GetOpenedUserNative()
{
var userId = GetOpenedUser();
var ptr = Marshal.StringToHGlobalAnsi(userId);
return ptr;
}
[UnmanagedCallersOnly(EntryPoint = "user_get_user_picture")]
public static IntPtr GetUserPictureNative(IntPtr userIdPtr)
{
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
return Marshal.StringToHGlobalAnsi(GetUserPicture(userId));
}
[UnmanagedCallersOnly(EntryPoint = "user_set_user_picture")]
public static void SetUserPictureNative(IntPtr userIdPtr, IntPtr picturePtr)
{
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
SetUserPicture(userId, picture);
}
[UnmanagedCallersOnly(EntryPoint = "user_get_user_name")]
public static IntPtr GetUserNameNative(IntPtr userIdPtr)
{
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
return Marshal.StringToHGlobalAnsi(GetUserName(userId));
}
[UnmanagedCallersOnly(EntryPoint = "user_set_user_name")]
public static void SetUserNameNative(IntPtr userIdPtr, IntPtr userNamePtr)
{
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
SetUserName(userId, userName);
}
[UnmanagedCallersOnly(EntryPoint = "user_get_all_users")]
public static IntPtr GetAllUsersNative()
{
var users = GetAllUsers();
return CreateStringArray(users.ToList());
}
[UnmanagedCallersOnly(EntryPoint = "user_add_user")]
public static void AddUserNative(IntPtr userNamePtr, IntPtr picturePtr)
{
var userName = Marshal.PtrToStringAnsi(userNamePtr) ?? "";
var picture = Marshal.PtrToStringAnsi(picturePtr) ?? "";
AddUser(userName, picture);
}
[UnmanagedCallersOnly(EntryPoint = "user_delete_user")]
public static void DeleteUserNative(IntPtr userIdPtr)
{
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
DeleteUser(userId);
}
[UnmanagedCallersOnly(EntryPoint = "user_open_user")]
public static void OpenUserNative(IntPtr userIdPtr)
{
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
OpenUser(userId);
}
[UnmanagedCallersOnly(EntryPoint = "user_close_user")]
public static void CloseUserNative(IntPtr userIdPtr)
{
var userId = Marshal.PtrToStringAnsi(userIdPtr) ?? "";
CloseUser(userId);
}
}
}

View File

@ -44,18 +44,6 @@ namespace LibRyujinx
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public static SwitchDevice? SwitchDevice { get; set; }
[UnmanagedCallersOnly(EntryPoint = "initialize")]
public static bool Initialize(IntPtr basePathPtr)
{
var path = Marshal.PtrToStringAnsi(basePathPtr);
var res = Initialize(path);
InitializeAudio();
return res;
}
public static bool Initialize(string? basePath)
{
if (SwitchDevice != null)
@ -85,6 +73,8 @@ namespace LibRyujinx
return false;
}
Logger.SetEnable(LogLevel.Trace, false);
return true;
}
@ -123,17 +113,6 @@ namespace LibRyujinx
return GetGameInfo(stream, new FileInfo(file).Extension.Remove('.'));
}
[UnmanagedCallersOnly(EntryPoint = "get_game_info")]
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)
{
var extension = Marshal.PtrToStringAnsi(extensionPtr);
var stream = OpenFile(descriptor);
var gameInfo = GetGameInfo(stream, extension);
return new GameInfoNative(gameInfo.FileSize, gameInfo.TitleName, gameInfo.TitleId, gameInfo.Developer, gameInfo.Version, gameInfo.Icon);
}
public static GameInfo? GetGameInfo(Stream gameStream, string extension)
{
if (SwitchDevice == null)