forked from MeloNX/MeloNX
libryujinx - fix branch
fix libryujinx content manager rebase disable trim warning suppression libryujinx - add graphics logging libryujinx - use pointers for game info struct libryujinx - update rd LibRyujinx: Fix path to Ryujinx.UI.Common project libryujinx - cleanup Start GameInfoNative Expand InitializeDeviceNative Signature libryujinx - Expose InstallFirmware libryujinx - Expose GetInstalledFirmwareVersion Don’t crash if no firmware is installed libryujinx - Expose Accelerometer & Gyro Functions libryujinx - add stream support libryujinx - add motion controls libryujinx - add openal reference, mii applet launch api rebase fix libryujinx - load firmware version at launch, add user manager api libryujinx - fix whitespace and remove unused usings libryuijinx - fix rd.xml libryujinx - some optimizations. apply current transform to native window instead of defaulting to Identity libryujinx - update libryujinx - Add more debug information when loading game files libryujinx - call swapbuffer callback libryujinx - update input add file logs add game stats helper libryujinx-update add basic touch and button input interface remove armeilleire reference in rd file libryujinx - disable shader cache remove redundant project reference add nativaot libryujinx project (cherry picked from commit 6288d793c6d9322c7ea188d689f524a9b73eaa9c)
This commit is contained in:
parent
45c4f56c1d
commit
0dbca88e08
1
.gitignore
vendored
1
.gitignore
vendored
@ -45,7 +45,6 @@ build/
|
|||||||
*.vssscc
|
*.vssscc
|
||||||
.builds
|
.builds
|
||||||
*.pidb
|
*.pidb
|
||||||
*.log
|
|
||||||
*.scc
|
*.scc
|
||||||
|
|
||||||
# Visual C++ cache files
|
# Visual C++ cache files
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
|
<PackageVersion Include="OpenTK" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||||
|
11
Ryujinx.sln
11
Ryujinx.sln
@ -89,6 +89,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibRyujinx", "src\LibRyujinx\LibRyujinx.csproj", "{5BBF478C-A520-41E7-9B88-890AD26766B8}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibRyujinx.NativeSample", "src\LibRyujinx.NativeSample\LibRyujinx.NativeSample.csproj", "{63D2C96B-5194-4592-BC91-30BEB11C06BD}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -255,6 +259,13 @@ Global
|
|||||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5BBF478C-A520-41E7-9B88-890AD26766B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5BBF478C-A520-41E7-9B88-890AD26766B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5BBF478C-A520-41E7-9B88-890AD26766B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5BBF478C-A520-41E7-9B88-890AD26766B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{63D2C96B-5194-4592-BC91-30BEB11C06BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{63D2C96B-5194-4592-BC91-30BEB11C06BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{63D2C96B-5194-4592-BC91-30BEB11C06BD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<clear />
|
||||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
<add key="nuget"
|
||||||
|
value="https://api.nuget.org/v3/index.json" />
|
||||||
</packageSources>
|
</packageSources>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
16
src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj
Normal file
16
src/LibRyujinx.NativeSample/LibRyujinx.NativeSample.csproj
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Platforms>AnyCPU</Platforms>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OpenTK" />
|
||||||
|
<PackageReference Include="Ryujinx.SDL2-CS" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
228
src/LibRyujinx.NativeSample/LibRyujinxInterop.cs
Normal file
228
src/LibRyujinx.NativeSample/LibRyujinxInterop.cs
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LibRyujinx.Sample
|
||||||
|
{
|
||||||
|
internal static class LibRyujinxInterop
|
||||||
|
{
|
||||||
|
private const string dll = "LibRyujinx.dll";
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "initialize")]
|
||||||
|
public extern static bool Initialize(IntPtr path);
|
||||||
|
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "graphics_initialize")]
|
||||||
|
public extern static bool InitializeGraphics(GraphicsConfiguration graphicsConfiguration);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "device_initialize")]
|
||||||
|
internal extern static bool InitializeDevice(bool isHostMapped,
|
||||||
|
bool useHypervisor,
|
||||||
|
SystemLanguage systemLanguage,
|
||||||
|
RegionCode regionCode,
|
||||||
|
bool enableVsync,
|
||||||
|
bool enableDockedMode,
|
||||||
|
bool enablePtc,
|
||||||
|
bool enableInternetAccess,
|
||||||
|
IntPtr timeZone,
|
||||||
|
bool ignoreMissingServices);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "graphics_initialize_renderer")]
|
||||||
|
internal extern static bool InitializeGraphicsRenderer(GraphicsBackend backend, NativeGraphicsInterop nativeGraphicsInterop);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "device_load")]
|
||||||
|
internal extern static bool LoadApplication(IntPtr pathPtr);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "graphics_renderer_run_loop")]
|
||||||
|
internal extern static void RunLoop();
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "graphics_renderer_set_size")]
|
||||||
|
internal extern static void SetRendererSize(int width, int height);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "graphics_renderer_set_swap_buffer_callback")]
|
||||||
|
internal extern static void SetSwapBuffersCallback(IntPtr swapBuffers);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "graphics_renderer_set_vsync")]
|
||||||
|
internal extern static void SetVsyncState(bool enabled);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_initialize")]
|
||||||
|
internal extern static void InitializeInput(int width, int height);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_set_client_size")]
|
||||||
|
internal extern static void SetClientSize(int width, int height);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_set_touch_point")]
|
||||||
|
internal extern static void SetTouchPoint(int x, int y);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_release_touch_point")]
|
||||||
|
internal extern static void ReleaseTouchPoint();
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_update")]
|
||||||
|
internal extern static void UpdateInput();
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_set_button_pressed")]
|
||||||
|
public extern static void SetButtonPressed(GamepadButtonInputId button, IntPtr idPtr);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_set_button_released")]
|
||||||
|
public extern static void SetButtonReleased(GamepadButtonInputId button, IntPtr idPtr);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_set_stick_axis")]
|
||||||
|
public extern static void SetStickAxis(StickInputId stick, Vector2 axes, IntPtr idPtr);
|
||||||
|
|
||||||
|
[DllImport(dll, EntryPoint = "input_connect_gamepad")]
|
||||||
|
public extern static IntPtr ConnectGamepad(int index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GraphicsBackend
|
||||||
|
{
|
||||||
|
Vulkan,
|
||||||
|
OpenGl
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BackendThreading
|
||||||
|
{
|
||||||
|
Auto,
|
||||||
|
Off,
|
||||||
|
On
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct GraphicsConfiguration
|
||||||
|
{
|
||||||
|
public float ResScale = 1f;
|
||||||
|
public float MaxAnisotropy = -1;
|
||||||
|
public bool FastGpuTime = true;
|
||||||
|
public bool Fast2DCopy = true;
|
||||||
|
public bool EnableMacroJit = false;
|
||||||
|
public bool EnableMacroHLE = true;
|
||||||
|
public bool EnableShaderCache = true;
|
||||||
|
public bool EnableTextureRecompression = false;
|
||||||
|
public BackendThreading BackendThreading = BackendThreading.Auto;
|
||||||
|
public AspectRatio AspectRatio = AspectRatio.Fixed16x9;
|
||||||
|
|
||||||
|
public GraphicsConfiguration()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public enum SystemLanguage
|
||||||
|
{
|
||||||
|
Japanese,
|
||||||
|
AmericanEnglish,
|
||||||
|
French,
|
||||||
|
German,
|
||||||
|
Italian,
|
||||||
|
Spanish,
|
||||||
|
Chinese,
|
||||||
|
Korean,
|
||||||
|
Dutch,
|
||||||
|
Portuguese,
|
||||||
|
Russian,
|
||||||
|
Taiwanese,
|
||||||
|
BritishEnglish,
|
||||||
|
CanadianFrench,
|
||||||
|
LatinAmericanSpanish,
|
||||||
|
SimplifiedChinese,
|
||||||
|
TraditionalChinese,
|
||||||
|
BrazilianPortuguese,
|
||||||
|
}
|
||||||
|
public enum RegionCode
|
||||||
|
{
|
||||||
|
Japan,
|
||||||
|
USA,
|
||||||
|
Europe,
|
||||||
|
Australia,
|
||||||
|
China,
|
||||||
|
Korea,
|
||||||
|
Taiwan,
|
||||||
|
|
||||||
|
Min = Japan,
|
||||||
|
Max = Taiwan,
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct NativeGraphicsInterop
|
||||||
|
{
|
||||||
|
public IntPtr GlGetProcAddress;
|
||||||
|
public IntPtr VkNativeContextLoader;
|
||||||
|
public IntPtr VkCreateSurface;
|
||||||
|
public IntPtr VkRequiredExtensions;
|
||||||
|
public int VkRequiredExtensionsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AspectRatio
|
||||||
|
{
|
||||||
|
Fixed4x3,
|
||||||
|
Fixed16x9,
|
||||||
|
Fixed16x10,
|
||||||
|
Fixed21x9,
|
||||||
|
Fixed32x9,
|
||||||
|
Stretched
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represent a button from a gamepad.
|
||||||
|
/// </summary>
|
||||||
|
public enum GamepadButtonInputId : byte
|
||||||
|
{
|
||||||
|
Unbound,
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
LeftStick,
|
||||||
|
RightStick,
|
||||||
|
LeftShoulder,
|
||||||
|
RightShoulder,
|
||||||
|
|
||||||
|
// Likely axis
|
||||||
|
LeftTrigger,
|
||||||
|
// Likely axis
|
||||||
|
RightTrigger,
|
||||||
|
|
||||||
|
DpadUp,
|
||||||
|
DpadDown,
|
||||||
|
DpadLeft,
|
||||||
|
DpadRight,
|
||||||
|
|
||||||
|
// Special buttons
|
||||||
|
|
||||||
|
Minus,
|
||||||
|
Plus,
|
||||||
|
|
||||||
|
Back = Minus,
|
||||||
|
Start = Plus,
|
||||||
|
|
||||||
|
Guide,
|
||||||
|
Misc1,
|
||||||
|
|
||||||
|
// Xbox Elite paddle
|
||||||
|
Paddle1,
|
||||||
|
Paddle2,
|
||||||
|
Paddle3,
|
||||||
|
Paddle4,
|
||||||
|
|
||||||
|
// PS5 touchpad button
|
||||||
|
Touchpad,
|
||||||
|
|
||||||
|
// Virtual buttons for single joycon
|
||||||
|
SingleLeftTrigger0,
|
||||||
|
SingleRightTrigger0,
|
||||||
|
|
||||||
|
SingleLeftTrigger1,
|
||||||
|
SingleRightTrigger1,
|
||||||
|
|
||||||
|
Count
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StickInputId : byte
|
||||||
|
{
|
||||||
|
Unbound,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
|
||||||
|
Count
|
||||||
|
}
|
||||||
|
}
|
260
src/LibRyujinx.NativeSample/NativeWindow.cs
Normal file
260
src/LibRyujinx.NativeSample/NativeWindow.cs
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
using LibRyujinx.Sample;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using OpenTK.Windowing.Common;
|
||||||
|
using OpenTK.Windowing.Desktop;
|
||||||
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace LibRyujinx.NativeSample
|
||||||
|
{
|
||||||
|
internal class NativeWindow : OpenTK.Windowing.Desktop.NativeWindow
|
||||||
|
{
|
||||||
|
private nint del;
|
||||||
|
public delegate void SwapBuffersCallback();
|
||||||
|
public delegate IntPtr GetProcAddress(string name);
|
||||||
|
public delegate IntPtr CreateSurface(IntPtr instance);
|
||||||
|
|
||||||
|
private bool _run;
|
||||||
|
private bool _isVulkan;
|
||||||
|
private Vector2 _lastPosition;
|
||||||
|
private bool _mousePressed;
|
||||||
|
private nint _gamepadIdPtr;
|
||||||
|
private string? _gamepadId;
|
||||||
|
|
||||||
|
public NativeWindow(NativeWindowSettings nativeWindowSettings) : base(nativeWindowSettings)
|
||||||
|
{
|
||||||
|
_isVulkan = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal unsafe void Start(string gamePath)
|
||||||
|
{
|
||||||
|
if (!_isVulkan)
|
||||||
|
{
|
||||||
|
MakeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
var getProcAddress = Marshal.GetFunctionPointerForDelegate<GetProcAddress>(x => GLFW.GetProcAddress(x));
|
||||||
|
var createSurface = Marshal.GetFunctionPointerForDelegate<CreateSurface>( x =>
|
||||||
|
{
|
||||||
|
VkHandle surface;
|
||||||
|
GLFW.CreateWindowSurface(new VkHandle(x) ,this.WindowPtr, null, out surface);
|
||||||
|
|
||||||
|
return surface.Handle;
|
||||||
|
});
|
||||||
|
var vkExtensions = GLFW.GetRequiredInstanceExtensions();
|
||||||
|
|
||||||
|
|
||||||
|
var pointers = new IntPtr[vkExtensions.Length];
|
||||||
|
for (int i = 0; i < vkExtensions.Length; i++)
|
||||||
|
{
|
||||||
|
pointers[i] = Marshal.StringToHGlobalAnsi(vkExtensions[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (IntPtr* ptr = pointers)
|
||||||
|
{
|
||||||
|
var nativeGraphicsInterop = new NativeGraphicsInterop()
|
||||||
|
{
|
||||||
|
GlGetProcAddress = getProcAddress,
|
||||||
|
VkRequiredExtensions = (nint)ptr,
|
||||||
|
VkRequiredExtensionsCount = pointers.Length,
|
||||||
|
VkCreateSurface = createSurface
|
||||||
|
};
|
||||||
|
var success = LibRyujinxInterop.InitializeGraphicsRenderer(_isVulkan ? GraphicsBackend.Vulkan : GraphicsBackend.OpenGl, nativeGraphicsInterop);
|
||||||
|
var timeZone = Marshal.StringToHGlobalAnsi("UTC");
|
||||||
|
success = LibRyujinxInterop.InitializeDevice(true,
|
||||||
|
false,
|
||||||
|
SystemLanguage.AmericanEnglish,
|
||||||
|
RegionCode.USA,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
timeZone,
|
||||||
|
false);
|
||||||
|
LibRyujinxInterop.InitializeInput(ClientSize.X, ClientSize.Y);
|
||||||
|
Marshal.FreeHGlobal(timeZone);
|
||||||
|
|
||||||
|
var path = Marshal.StringToHGlobalAnsi(gamePath);
|
||||||
|
var loaded = LibRyujinxInterop.LoadApplication(path);
|
||||||
|
LibRyujinxInterop.SetRendererSize(Size.X, Size.Y);
|
||||||
|
Marshal.FreeHGlobal(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
_gamepadIdPtr = LibRyujinxInterop.ConnectGamepad(0);
|
||||||
|
_gamepadId = Marshal.PtrToStringAnsi(_gamepadIdPtr);
|
||||||
|
|
||||||
|
if (!_isVulkan)
|
||||||
|
{
|
||||||
|
Context.MakeNoneCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
_run = true;
|
||||||
|
var thread = new Thread(new ThreadStart(RunLoop));
|
||||||
|
thread.Start();
|
||||||
|
|
||||||
|
UpdateLoop();
|
||||||
|
|
||||||
|
thread.Join();
|
||||||
|
|
||||||
|
foreach(var ptr in pointers)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(_gamepadIdPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunLoop()
|
||||||
|
{
|
||||||
|
del = Marshal.GetFunctionPointerForDelegate<SwapBuffersCallback>(SwapBuffers);
|
||||||
|
LibRyujinxInterop.SetSwapBuffersCallback(del);
|
||||||
|
|
||||||
|
if (!_isVulkan)
|
||||||
|
{
|
||||||
|
MakeCurrent();
|
||||||
|
|
||||||
|
Context.SwapInterval = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
LibRyujinxInterop.SetVsyncState(true);
|
||||||
|
});*/
|
||||||
|
|
||||||
|
LibRyujinxInterop.RunLoop();
|
||||||
|
|
||||||
|
_run = false;
|
||||||
|
|
||||||
|
if (!_isVulkan)
|
||||||
|
{
|
||||||
|
Context.MakeNoneCurrent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SwapBuffers()
|
||||||
|
{
|
||||||
|
if (!_isVulkan)
|
||||||
|
{
|
||||||
|
this.Context.SwapBuffers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseMove(MouseMoveEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseMove(e);
|
||||||
|
_lastPosition = e.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseDown(e);
|
||||||
|
if(e.Button == MouseButton.Left)
|
||||||
|
{
|
||||||
|
_mousePressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnResize(ResizeEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnResize(e);
|
||||||
|
|
||||||
|
if (_run)
|
||||||
|
{
|
||||||
|
LibRyujinxInterop.SetRendererSize(e.Width, e.Height);
|
||||||
|
LibRyujinxInterop.SetClientSize(e.Width, e.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
if (e.Button == MouseButton.Left)
|
||||||
|
{
|
||||||
|
_mousePressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyUp(KeyboardKeyEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnKeyUp(e);
|
||||||
|
|
||||||
|
if (_gamepadIdPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var key = GetKeyMapping(e.Key);
|
||||||
|
|
||||||
|
LibRyujinxInterop.SetButtonReleased(key, _gamepadIdPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyDown(KeyboardKeyEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnKeyDown(e);
|
||||||
|
|
||||||
|
if (_gamepadIdPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var key = GetKeyMapping(e.Key);
|
||||||
|
|
||||||
|
LibRyujinxInterop.SetButtonPressed(key, _gamepadIdPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateLoop()
|
||||||
|
{
|
||||||
|
while(_run)
|
||||||
|
{
|
||||||
|
ProcessWindowEvents(true);
|
||||||
|
NewInputFrame();
|
||||||
|
ProcessWindowEvents(IsEventDriven);
|
||||||
|
if (_mousePressed)
|
||||||
|
{
|
||||||
|
LibRyujinxInterop.SetTouchPoint((int)_lastPosition.X, (int)_lastPosition.Y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LibRyujinxInterop.ReleaseTouchPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
LibRyujinxInterop.UpdateInput();
|
||||||
|
|
||||||
|
Thread.Sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadButtonInputId GetKeyMapping(Keys key)
|
||||||
|
{
|
||||||
|
if(_keyMapping.TryGetValue(key, out var mapping))
|
||||||
|
{
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GamepadButtonInputId.Unbound;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<Keys, GamepadButtonInputId> _keyMapping = new Dictionary<Keys, GamepadButtonInputId>()
|
||||||
|
{
|
||||||
|
{Keys.A, GamepadButtonInputId.A },
|
||||||
|
{Keys.S, GamepadButtonInputId.B },
|
||||||
|
{Keys.Z, GamepadButtonInputId.X },
|
||||||
|
{Keys.X, GamepadButtonInputId.Y },
|
||||||
|
{Keys.Equal, GamepadButtonInputId.Plus },
|
||||||
|
{Keys.Minus, GamepadButtonInputId.Minus },
|
||||||
|
{Keys.Q, GamepadButtonInputId.LeftShoulder },
|
||||||
|
{Keys.D1, GamepadButtonInputId.LeftTrigger },
|
||||||
|
{Keys.W, GamepadButtonInputId.RightShoulder },
|
||||||
|
{Keys.D2, GamepadButtonInputId.RightTrigger },
|
||||||
|
{Keys.E, GamepadButtonInputId.LeftStick },
|
||||||
|
{Keys.R, GamepadButtonInputId.RightStick },
|
||||||
|
{Keys.Up, GamepadButtonInputId.DpadUp },
|
||||||
|
{Keys.Down, GamepadButtonInputId.DpadDown },
|
||||||
|
{Keys.Left, GamepadButtonInputId.DpadLeft },
|
||||||
|
{Keys.Right, GamepadButtonInputId.DpadRight },
|
||||||
|
{Keys.U, GamepadButtonInputId.SingleLeftTrigger0 },
|
||||||
|
{Keys.D7, GamepadButtonInputId.SingleLeftTrigger1 },
|
||||||
|
{Keys.O, GamepadButtonInputId.SingleRightTrigger0 },
|
||||||
|
{Keys.D9, GamepadButtonInputId.SingleRightTrigger1 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
33
src/LibRyujinx.NativeSample/Program.cs
Normal file
33
src/LibRyujinx.NativeSample/Program.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using LibRyujinx.Sample;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using OpenTK.Windowing.Common;
|
||||||
|
using OpenTK.Windowing.Desktop;
|
||||||
|
|
||||||
|
namespace LibRyujinx.NativeSample
|
||||||
|
{
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length > 0)
|
||||||
|
{
|
||||||
|
var success = LibRyujinxInterop.Initialize(IntPtr.Zero);
|
||||||
|
success = LibRyujinxInterop.InitializeGraphics(new GraphicsConfiguration());
|
||||||
|
var nativeWindowSettings = new NativeWindowSettings()
|
||||||
|
{
|
||||||
|
Size = new Vector2i(800, 600),
|
||||||
|
Title = "Ryujinx",
|
||||||
|
API = ContextAPI.NoAPI,
|
||||||
|
IsEventDriven = false,
|
||||||
|
// This is needed to run on macos
|
||||||
|
Flags = ContextFlags.ForwardCompatible,
|
||||||
|
};
|
||||||
|
|
||||||
|
using var window = new NativeWindow(nativeWindowSettings);
|
||||||
|
|
||||||
|
window.IsVisible = true;
|
||||||
|
window.Start(args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
293
src/LibRyujinx/LibRyujinx.Device.cs
Normal file
293
src/LibRyujinx/LibRyujinx.Device.cs
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Microsoft.Win32.SafeHandles;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
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,
|
||||||
|
RegionCode regionCode,
|
||||||
|
bool enableVsync,
|
||||||
|
bool enableDockedMode,
|
||||||
|
bool enablePtc,
|
||||||
|
bool enableInternetAccess,
|
||||||
|
string? timeZone,
|
||||||
|
bool ignoreMissingServices)
|
||||||
|
{
|
||||||
|
if (SwitchDevice == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SwitchDevice.InitializeContext(isHostMapped,
|
||||||
|
useHypervisor,
|
||||||
|
systemLanguage,
|
||||||
|
regionCode,
|
||||||
|
enableVsync,
|
||||||
|
enableDockedMode,
|
||||||
|
enablePtc,
|
||||||
|
enableInternetAccess,
|
||||||
|
timeZone,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetInstalledFirmwareVersion()
|
||||||
|
{
|
||||||
|
var version = SwitchDevice?.ContentManager.GetCurrentFirmwareVersion();
|
||||||
|
|
||||||
|
if (version != null)
|
||||||
|
{
|
||||||
|
return version.VersionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SystemVersion? VerifyFirmware(Stream stream, bool isXci)
|
||||||
|
{
|
||||||
|
return SwitchDevice?.ContentManager?.VerifyFirmwarePackage(stream, isXci) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool LoadApplication(Stream stream, FileType type, Stream? updateStream = null)
|
||||||
|
{
|
||||||
|
var emulationContext = SwitchDevice.EmulationContext;
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
FileType.None => false,
|
||||||
|
FileType.Nsp => emulationContext?.LoadNsp(stream, 0, updateStream) ?? false,
|
||||||
|
FileType.Xci => emulationContext?.LoadXci(stream, 0, updateStream) ?? false,
|
||||||
|
FileType.Nro => emulationContext?.LoadProgram(stream, true, "") ?? false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool LaunchMiiEditApplet()
|
||||||
|
{
|
||||||
|
string contentPath = SwitchDevice.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||||
|
|
||||||
|
return LoadApplication(contentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool LoadApplication(string? path)
|
||||||
|
{
|
||||||
|
var emulationContext = SwitchDevice.EmulationContext;
|
||||||
|
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
|
||||||
|
|
||||||
|
if (romFsFiles.Length == 0)
|
||||||
|
{
|
||||||
|
romFsFiles = Directory.GetFiles(path, "*.romfs");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (romFsFiles.Length > 0)
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
||||||
|
|
||||||
|
if (!emulationContext.LoadCart(path, romFsFiles[0]))
|
||||||
|
{
|
||||||
|
SwitchDevice.DisposeContext();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
||||||
|
|
||||||
|
if (!emulationContext.LoadCart(path))
|
||||||
|
{
|
||||||
|
SwitchDevice.DisposeContext();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (File.Exists(path))
|
||||||
|
{
|
||||||
|
switch (Path.GetExtension(path).ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case ".xci":
|
||||||
|
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||||
|
|
||||||
|
if (!emulationContext.LoadXci(path))
|
||||||
|
{
|
||||||
|
SwitchDevice.DisposeContext();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ".nca":
|
||||||
|
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||||
|
|
||||||
|
if (!emulationContext.LoadNca(path))
|
||||||
|
{
|
||||||
|
SwitchDevice.DisposeContext();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ".nsp":
|
||||||
|
case ".pfs0":
|
||||||
|
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||||
|
|
||||||
|
if (!emulationContext.LoadNsp(path))
|
||||||
|
{
|
||||||
|
SwitchDevice.DisposeContext();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!emulationContext.LoadProgram(path))
|
||||||
|
{
|
||||||
|
SwitchDevice.DisposeContext();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ArgumentOutOfRangeException)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
||||||
|
|
||||||
|
SwitchDevice.DisposeContext();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{path}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
||||||
|
|
||||||
|
SwitchDevice.DisposeContext();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SignalEmulationClose()
|
||||||
|
{
|
||||||
|
_isStopped = true;
|
||||||
|
_isActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CloseEmulation()
|
||||||
|
{
|
||||||
|
if (SwitchDevice == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_npadManager?.Dispose();
|
||||||
|
_npadManager = null;
|
||||||
|
|
||||||
|
_touchScreenManager?.Dispose();
|
||||||
|
_touchScreenManager = null;
|
||||||
|
|
||||||
|
SwitchDevice!.InputManager?.Dispose();
|
||||||
|
SwitchDevice!.InputManager = null;
|
||||||
|
_inputManager = null;
|
||||||
|
|
||||||
|
if (Renderer != null)
|
||||||
|
{
|
||||||
|
_gpuDoneEvent.WaitOne();
|
||||||
|
_gpuDoneEvent.Dispose();
|
||||||
|
_gpuDoneEvent = null;
|
||||||
|
SwitchDevice?.DisposeContext();
|
||||||
|
Renderer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileStream OpenFile(int descriptor)
|
||||||
|
{
|
||||||
|
var safeHandle = new SafeFileHandle(descriptor, false);
|
||||||
|
|
||||||
|
return new FileStream(safeHandle, FileAccess.ReadWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FileType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Nsp,
|
||||||
|
Xci,
|
||||||
|
Nro
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
291
src/LibRyujinx/LibRyujinx.Graphics.cs
Normal file
291
src/LibRyujinx/LibRyujinx.Graphics.cs
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
using LibRyujinx.Shared;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Cpu;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading;
|
||||||
|
using Ryujinx.Graphics.Gpu;
|
||||||
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
|
using Ryujinx.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace LibRyujinx
|
||||||
|
{
|
||||||
|
public static partial class LibRyujinx
|
||||||
|
{
|
||||||
|
private static bool _isActive;
|
||||||
|
private static bool _isStopped;
|
||||||
|
private static CancellationTokenSource _gpuCancellationTokenSource;
|
||||||
|
private static SwapBuffersCallback? _swapBuffersCallback;
|
||||||
|
private static NativeGraphicsInterop _nativeGraphicsInterop;
|
||||||
|
private static ManualResetEvent _gpuDoneEvent;
|
||||||
|
private static bool _enableGraphicsLogging;
|
||||||
|
|
||||||
|
public delegate void SwapBuffersCallback();
|
||||||
|
public delegate IntPtr GetProcAddress(string name);
|
||||||
|
public delegate IntPtr CreateSurface(IntPtr instance);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
GraphicsConfig.ResScale = graphicsConfiguration.ResScale;
|
||||||
|
GraphicsConfig.MaxAnisotropy = graphicsConfiguration.MaxAnisotropy;
|
||||||
|
GraphicsConfig.FastGpuTime = graphicsConfiguration.FastGpuTime;
|
||||||
|
GraphicsConfig.Fast2DCopy = graphicsConfiguration.Fast2DCopy;
|
||||||
|
GraphicsConfig.EnableMacroJit = graphicsConfiguration.EnableMacroJit;
|
||||||
|
GraphicsConfig.EnableMacroHLE = graphicsConfiguration.EnableMacroHLE;
|
||||||
|
GraphicsConfig.EnableShaderCache = graphicsConfiguration.EnableShaderCache;
|
||||||
|
GraphicsConfig.EnableTextureRecompression = graphicsConfiguration.EnableTextureRecompression;
|
||||||
|
|
||||||
|
GraphicsConfiguration = graphicsConfiguration;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (Renderer != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphicsBackend == GraphicsBackend.OpenGl)
|
||||||
|
{
|
||||||
|
Renderer = new OpenGLRenderer();
|
||||||
|
}
|
||||||
|
else if (graphicsBackend == GraphicsBackend.Vulkan)
|
||||||
|
{
|
||||||
|
Renderer = new VulkanRenderer(Vk.GetApi(), (instance, vk) => new SurfaceKHR((ulong?)createSurfaceFunc(instance.Handle)),
|
||||||
|
() => requiredExtensions,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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!;
|
||||||
|
device.EnableDeviceVsync = enabled;
|
||||||
|
device.Gpu.Renderer.Window.ChangeVSyncMode(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RunLoop()
|
||||||
|
{
|
||||||
|
if (Renderer == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var device = SwitchDevice!.EmulationContext!;
|
||||||
|
_gpuDoneEvent = new ManualResetEvent(true);
|
||||||
|
|
||||||
|
device.Gpu.Renderer.Initialize(_enableGraphicsLogging ? GraphicsDebugLevel.All : GraphicsDebugLevel.None);
|
||||||
|
|
||||||
|
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
device.Gpu.ShaderCacheStateChanged += LoadProgressStateChangedHandler;
|
||||||
|
device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += LoadProgressStateChangedHandler;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
device.Gpu.Renderer.RunLoop(() =>
|
||||||
|
{
|
||||||
|
_gpuDoneEvent.Reset();
|
||||||
|
device.Gpu.SetGpuThread();
|
||||||
|
device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||||
|
|
||||||
|
_isActive = true;
|
||||||
|
|
||||||
|
while (_isActive)
|
||||||
|
{
|
||||||
|
if (_isStopped)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.WaitFifo())
|
||||||
|
{
|
||||||
|
device.Statistics.RecordFifoStart();
|
||||||
|
device.ProcessFrame();
|
||||||
|
device.Statistics.RecordFifoEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (device.ConsumeFrameAvailable())
|
||||||
|
{
|
||||||
|
device.PresentFrame(() =>_swapBuffersCallback?.Invoke());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.Gpu.Renderer is ThreadedRenderer threaded)
|
||||||
|
{
|
||||||
|
threaded.FlushThreadedCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
_gpuDoneEvent.Set();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
device.Gpu.ShaderCacheStateChanged -= LoadProgressStateChangedHandler;
|
||||||
|
device.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= LoadProgressStateChangedHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LoadProgressStateChangedHandler<T>(T state, int current, int total) where T : Enum
|
||||||
|
{
|
||||||
|
void SetInfo(string status, float value)
|
||||||
|
{
|
||||||
|
var ptr = Marshal.StringToHGlobalAnsi(status);
|
||||||
|
// add setinfo callback
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(ptr);
|
||||||
|
}
|
||||||
|
var status = $"{current} / {total}";
|
||||||
|
var progress = current / (float)total;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case LoadState ptcState:
|
||||||
|
if (float.IsNaN((progress)))
|
||||||
|
progress = 0;
|
||||||
|
|
||||||
|
switch (ptcState)
|
||||||
|
{
|
||||||
|
case LoadState.Unloaded:
|
||||||
|
case LoadState.Loading:
|
||||||
|
SetInfo($"Loading PTC {status}", progress);
|
||||||
|
break;
|
||||||
|
case LoadState.Loaded:
|
||||||
|
SetInfo($"PTC Loaded", -1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ShaderCacheState shaderCacheState:
|
||||||
|
switch (shaderCacheState)
|
||||||
|
{
|
||||||
|
case ShaderCacheState.Start:
|
||||||
|
case ShaderCacheState.Loading:
|
||||||
|
SetInfo($"Compiling Shaders {status}", progress);
|
||||||
|
break;
|
||||||
|
case ShaderCacheState.Packaging:
|
||||||
|
SetInfo($"Packaging Shaders {status}", progress);
|
||||||
|
break;
|
||||||
|
case ShaderCacheState.Loaded:
|
||||||
|
SetInfo($"Shaders Loaded", -1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_swapBuffersCallback = swapBuffersCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct GraphicsConfiguration
|
||||||
|
{
|
||||||
|
public float ResScale = 1f;
|
||||||
|
public float MaxAnisotropy = -1;
|
||||||
|
public bool FastGpuTime = true;
|
||||||
|
public bool Fast2DCopy = true;
|
||||||
|
public bool EnableMacroJit = false;
|
||||||
|
public bool EnableMacroHLE = true;
|
||||||
|
public bool EnableShaderCache = true;
|
||||||
|
public bool EnableTextureRecompression = false;
|
||||||
|
public BackendThreading BackendThreading = BackendThreading.Auto;
|
||||||
|
public AspectRatio AspectRatio = AspectRatio.Fixed16x9;
|
||||||
|
|
||||||
|
public GraphicsConfiguration()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct NativeGraphicsInterop
|
||||||
|
{
|
||||||
|
public IntPtr GlGetProcAddress;
|
||||||
|
public IntPtr VkNativeContextLoader;
|
||||||
|
public IntPtr VkCreateSurface;
|
||||||
|
public IntPtr VkRequiredExtensions;
|
||||||
|
public int VkRequiredExtensionsCount;
|
||||||
|
}
|
||||||
|
}
|
595
src/LibRyujinx/LibRyujinx.Input.cs
Normal file
595
src/LibRyujinx/LibRyujinx.Input.cs
Normal file
@ -0,0 +1,595 @@
|
|||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||||
|
using Ryujinx.Input;
|
||||||
|
using Ryujinx.Input.HLE;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||||
|
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||||
|
using StickInputId = Ryujinx.Input.StickInputId;
|
||||||
|
|
||||||
|
namespace LibRyujinx
|
||||||
|
{
|
||||||
|
public static partial class LibRyujinx
|
||||||
|
{
|
||||||
|
private static VirtualGamepadDriver? _gamepadDriver;
|
||||||
|
private static VirtualTouchScreen? _virtualTouchScreen;
|
||||||
|
private static VirtualTouchScreenDriver? _touchScreenDriver;
|
||||||
|
private static TouchScreenManager? _touchScreenManager;
|
||||||
|
private static InputManager? _inputManager;
|
||||||
|
private static NpadManager? _npadManager;
|
||||||
|
private static InputConfig[] _configs;
|
||||||
|
|
||||||
|
public static void InitializeInput(int width, int height)
|
||||||
|
{
|
||||||
|
if(SwitchDevice!.InputManager != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Input is already initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
_gamepadDriver = new VirtualGamepadDriver(4);
|
||||||
|
_configs = new InputConfig[4];
|
||||||
|
_virtualTouchScreen = new VirtualTouchScreen();
|
||||||
|
_touchScreenDriver = new VirtualTouchScreenDriver(_virtualTouchScreen);
|
||||||
|
_inputManager = new InputManager(null, _gamepadDriver);
|
||||||
|
_inputManager.SetMouseDriver(_touchScreenDriver);
|
||||||
|
_npadManager = _inputManager.CreateNpadManager();
|
||||||
|
|
||||||
|
SwitchDevice!.InputManager = _inputManager;
|
||||||
|
|
||||||
|
_touchScreenManager = _inputManager.CreateTouchScreenManager();
|
||||||
|
_touchScreenManager.Initialize(SwitchDevice!.EmulationContext);
|
||||||
|
|
||||||
|
_npadManager.Initialize(SwitchDevice.EmulationContext, new List<InputConfig>(), false, false);
|
||||||
|
|
||||||
|
_virtualTouchScreen.ClientSize = new Size(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetClientSize(int width, int height)
|
||||||
|
{
|
||||||
|
_virtualTouchScreen!.ClientSize = new Size(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetTouchPoint(int x, int y)
|
||||||
|
{
|
||||||
|
_virtualTouchScreen?.SetPosition(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReleaseTouchPoint()
|
||||||
|
{
|
||||||
|
_virtualTouchScreen?.ReleaseTouch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetButtonPressed(GamepadButtonInputId button, int id)
|
||||||
|
{
|
||||||
|
_gamepadDriver?.SetButtonPressed(button, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetButtonReleased(GamepadButtonInputId button, int id)
|
||||||
|
{
|
||||||
|
_gamepadDriver?.SetButtonReleased(button, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetAccelerometerData(Vector3 accel, int id)
|
||||||
|
{
|
||||||
|
_gamepadDriver?.SetAccelerometerData(accel, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetGryoData(Vector3 gyro, int id)
|
||||||
|
{
|
||||||
|
_gamepadDriver?.SetGryoData(gyro, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetStickAxis(StickInputId stick, Vector2 axes, int deviceId)
|
||||||
|
{
|
||||||
|
_gamepadDriver?.SetStickAxis(stick, axes, deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ConnectGamepad(int index)
|
||||||
|
{
|
||||||
|
var gamepad = _gamepadDriver?.GetGamepad(index);
|
||||||
|
if (gamepad != null)
|
||||||
|
{
|
||||||
|
var config = CreateDefaultInputConfig();
|
||||||
|
|
||||||
|
config.Id = gamepad.Id;
|
||||||
|
config.PlayerIndex = (PlayerIndex)index;
|
||||||
|
|
||||||
|
_configs[index] = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
_npadManager?.ReloadConfiguration(_configs.Where(x => x != null).ToList(), false, false);
|
||||||
|
|
||||||
|
return int.TryParse(gamepad?.Id, out var idInt) ? idInt : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InputConfig CreateDefaultInputConfig()
|
||||||
|
{
|
||||||
|
return new StandardControllerInputConfig
|
||||||
|
{
|
||||||
|
Version = InputConfig.CurrentVersion,
|
||||||
|
Backend = InputBackendType.GamepadSDL2,
|
||||||
|
Id = null,
|
||||||
|
ControllerType = ControllerType.ProController,
|
||||||
|
DeadzoneLeft = 0.1f,
|
||||||
|
DeadzoneRight = 0.1f,
|
||||||
|
RangeLeft = 1.0f,
|
||||||
|
RangeRight = 1.0f,
|
||||||
|
TriggerThreshold = 0.5f,
|
||||||
|
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
|
||||||
|
{
|
||||||
|
DpadUp = ConfigGamepadInputId.DpadUp,
|
||||||
|
DpadDown = ConfigGamepadInputId.DpadDown,
|
||||||
|
DpadLeft = ConfigGamepadInputId.DpadLeft,
|
||||||
|
DpadRight = ConfigGamepadInputId.DpadRight,
|
||||||
|
ButtonMinus = ConfigGamepadInputId.Minus,
|
||||||
|
ButtonL = ConfigGamepadInputId.LeftShoulder,
|
||||||
|
ButtonZl = ConfigGamepadInputId.LeftTrigger,
|
||||||
|
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||||
|
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||||
|
},
|
||||||
|
|
||||||
|
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||||
|
{
|
||||||
|
Joystick = ConfigStickInputId.Left,
|
||||||
|
StickButton = ConfigGamepadInputId.LeftStick,
|
||||||
|
InvertStickX = false,
|
||||||
|
InvertStickY = false,
|
||||||
|
Rotate90CW = false,
|
||||||
|
},
|
||||||
|
|
||||||
|
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
|
||||||
|
{
|
||||||
|
ButtonA = ConfigGamepadInputId.A,
|
||||||
|
ButtonB = ConfigGamepadInputId.B,
|
||||||
|
ButtonX = ConfigGamepadInputId.X,
|
||||||
|
ButtonY = ConfigGamepadInputId.Y,
|
||||||
|
ButtonPlus = ConfigGamepadInputId.Plus,
|
||||||
|
ButtonR = ConfigGamepadInputId.RightShoulder,
|
||||||
|
ButtonZr = ConfigGamepadInputId.RightTrigger,
|
||||||
|
ButtonSl = ConfigGamepadInputId.Unbound,
|
||||||
|
ButtonSr = ConfigGamepadInputId.Unbound,
|
||||||
|
},
|
||||||
|
|
||||||
|
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
|
||||||
|
{
|
||||||
|
Joystick = ConfigStickInputId.Right,
|
||||||
|
StickButton = ConfigGamepadInputId.RightStick,
|
||||||
|
InvertStickX = false,
|
||||||
|
InvertStickY = false,
|
||||||
|
Rotate90CW = false,
|
||||||
|
},
|
||||||
|
|
||||||
|
Motion = new StandardMotionConfigController
|
||||||
|
{
|
||||||
|
MotionBackend = MotionInputBackendType.GamepadDriver,
|
||||||
|
EnableMotion = true,
|
||||||
|
Sensitivity = 100,
|
||||||
|
GyroDeadzone = 1,
|
||||||
|
},
|
||||||
|
Rumble = new RumbleConfigController
|
||||||
|
{
|
||||||
|
StrongRumble = 1f,
|
||||||
|
WeakRumble = 1f,
|
||||||
|
EnableRumble = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateInput()
|
||||||
|
{
|
||||||
|
_npadManager?.Update(GraphicsConfiguration.AspectRatio.ToFloat());
|
||||||
|
|
||||||
|
if(!_touchScreenManager!.Update(true, _virtualTouchScreen!.IsButtonPressed(MouseButton.Button1), GraphicsConfiguration.AspectRatio.ToFloat()))
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public Size ClientSize { get; set; }
|
||||||
|
|
||||||
|
public bool[] Buttons { get; }
|
||||||
|
|
||||||
|
public VirtualTouchScreen()
|
||||||
|
{
|
||||||
|
Buttons = new bool[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 CurrentPosition { get; private set; }
|
||||||
|
public Vector2 Scroll { get; private set; }
|
||||||
|
public string Id => "0";
|
||||||
|
public string Name => "AvaloniaMouse";
|
||||||
|
|
||||||
|
public bool IsConnected => true;
|
||||||
|
public GamepadFeaturesFlag Features => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPosition(int x, int y)
|
||||||
|
{
|
||||||
|
CurrentPosition = new Vector2(x, y);
|
||||||
|
|
||||||
|
Buttons[0] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReleaseTouch()
|
||||||
|
{
|
||||||
|
Buttons[0] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 GetPosition()
|
||||||
|
{
|
||||||
|
return CurrentPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 GetScroll()
|
||||||
|
{
|
||||||
|
return Scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetStateSnapshot()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public (float, float) GetStick(Ryujinx.Input.StickInputId inputId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsButtonPressed(MouseButton button)
|
||||||
|
{
|
||||||
|
return Buttons[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPressed(GamepadButtonInputId inputId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConfiguration(InputConfig configuration)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTriggerThreshold(float triggerThreshold)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VirtualTouchScreenDriver : IGamepadDriver
|
||||||
|
{
|
||||||
|
private readonly VirtualTouchScreen _virtualTouchScreen;
|
||||||
|
|
||||||
|
public VirtualTouchScreenDriver(VirtualTouchScreen virtualTouchScreen)
|
||||||
|
{
|
||||||
|
_virtualTouchScreen = virtualTouchScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DriverName => "VirtualTouchDriver";
|
||||||
|
|
||||||
|
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
|
||||||
|
|
||||||
|
|
||||||
|
public event Action<string> OnGamepadConnected
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<string> OnGamepadDisconnected
|
||||||
|
{
|
||||||
|
add { }
|
||||||
|
remove { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IGamepad GetGamepad(string id)
|
||||||
|
{
|
||||||
|
return _virtualTouchScreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VirtualGamepadDriver : IGamepadDriver
|
||||||
|
{
|
||||||
|
private readonly int _controllerCount;
|
||||||
|
|
||||||
|
public ReadOnlySpan<string> GamepadsIds => _gamePads.Keys.Select(x => x.ToString()).ToArray();
|
||||||
|
|
||||||
|
public string DriverName => "Virtual";
|
||||||
|
|
||||||
|
public event Action<string> OnGamepadConnected;
|
||||||
|
public event Action<string> OnGamepadDisconnected;
|
||||||
|
|
||||||
|
private Dictionary<int, VirtualGamepad> _gamePads;
|
||||||
|
|
||||||
|
public VirtualGamepadDriver(int controllerCount)
|
||||||
|
{
|
||||||
|
_gamePads = new Dictionary<int, VirtualGamepad>();
|
||||||
|
for (int joystickIndex = 0; joystickIndex < controllerCount; joystickIndex++)
|
||||||
|
{
|
||||||
|
HandleJoyStickConnected(joystickIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
_controllerCount = controllerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleJoyStickConnected(int joystickDeviceId)
|
||||||
|
{
|
||||||
|
_gamePads[joystickDeviceId] = new VirtualGamepad(this, joystickDeviceId);
|
||||||
|
OnGamepadConnected?.Invoke(joystickDeviceId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// Simulate a full disconnect when disposing
|
||||||
|
var ids = GamepadsIds;
|
||||||
|
foreach (string id in ids)
|
||||||
|
{
|
||||||
|
OnGamepadDisconnected?.Invoke(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
_gamePads.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IGamepad GetGamepad(string id)
|
||||||
|
{
|
||||||
|
return _gamePads[int.Parse(id)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IGamepad GetGamepad(int index)
|
||||||
|
{
|
||||||
|
return _gamePads[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStickAxis(StickInputId stick, Vector2 axes, int deviceId)
|
||||||
|
{
|
||||||
|
if(_gamePads.TryGetValue(deviceId, out var gamePad))
|
||||||
|
{
|
||||||
|
gamePad.StickInputs[(int)stick] = axes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetButtonPressed(GamepadButtonInputId button, int deviceId)
|
||||||
|
{
|
||||||
|
if (_gamePads.TryGetValue(deviceId, out var gamePad))
|
||||||
|
{
|
||||||
|
gamePad.ButtonInputs[(int)button] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetButtonReleased(GamepadButtonInputId button, int deviceId)
|
||||||
|
{
|
||||||
|
if (_gamePads.TryGetValue(deviceId, out var gamePad))
|
||||||
|
{
|
||||||
|
gamePad.ButtonInputs[(int)button] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAccelerometerData(Vector3 accel, int deviceId)
|
||||||
|
{
|
||||||
|
if (_gamePads.TryGetValue(deviceId, out var gamePad))
|
||||||
|
{
|
||||||
|
gamePad.Accelerometer = accel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGryoData(Vector3 gyro, int deviceId)
|
||||||
|
{
|
||||||
|
if (_gamePads.TryGetValue(deviceId, out var gamePad))
|
||||||
|
{
|
||||||
|
gamePad.Gyro = gyro;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VirtualGamepad : IGamepad
|
||||||
|
{
|
||||||
|
private readonly VirtualGamepadDriver _driver;
|
||||||
|
|
||||||
|
private bool[] _buttonInputs;
|
||||||
|
|
||||||
|
private Vector2[] _stickInputs;
|
||||||
|
|
||||||
|
public VirtualGamepad(VirtualGamepadDriver driver, int id)
|
||||||
|
{
|
||||||
|
_buttonInputs = new bool[(int)GamepadButtonInputId.Count];
|
||||||
|
_stickInputs = new Vector2[(int)StickInputId.Count];
|
||||||
|
_driver = driver;
|
||||||
|
Id = id.ToString();
|
||||||
|
IdInt = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
|
||||||
|
public GamepadFeaturesFlag Features { get; } = GamepadFeaturesFlag.Motion;
|
||||||
|
public string Id { get; }
|
||||||
|
|
||||||
|
internal readonly int IdInt;
|
||||||
|
|
||||||
|
public string Name => Id;
|
||||||
|
public bool IsConnected { get; }
|
||||||
|
public Vector2[] StickInputs { get => _stickInputs; set => _stickInputs = value; }
|
||||||
|
public bool[] ButtonInputs { get => _buttonInputs; set => _buttonInputs = value; }
|
||||||
|
public Vector3 Accelerometer { get; internal set; }
|
||||||
|
public Vector3 Gyro { get; internal set; }
|
||||||
|
|
||||||
|
public bool IsPressed(GamepadButtonInputId inputId)
|
||||||
|
{
|
||||||
|
return _buttonInputs[(int)inputId];
|
||||||
|
}
|
||||||
|
|
||||||
|
public (float, float) GetStick(StickInputId inputId)
|
||||||
|
{
|
||||||
|
var v = _stickInputs[(int)inputId];
|
||||||
|
|
||||||
|
return (v.X, v.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
|
{
|
||||||
|
if (inputId == MotionInputId.Accelerometer)
|
||||||
|
return Accelerometer;
|
||||||
|
else if (inputId == MotionInputId.Gyroscope)
|
||||||
|
return RadToDegree(Gyro);
|
||||||
|
return new Vector3();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 RadToDegree(Vector3 rad)
|
||||||
|
{
|
||||||
|
return rad * (180 / MathF.PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTriggerThreshold(float triggerThreshold)
|
||||||
|
{
|
||||||
|
//throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConfiguration(InputConfig configuration)
|
||||||
|
{
|
||||||
|
//throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
|
{
|
||||||
|
//throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||||
|
{
|
||||||
|
GamepadStateSnapshot result = default;
|
||||||
|
|
||||||
|
foreach (var button in Enum.GetValues<GamepadButtonInputId>())
|
||||||
|
{
|
||||||
|
// Do not touch state of button already pressed
|
||||||
|
if (button != GamepadButtonInputId.Count && !result.IsPressed(button))
|
||||||
|
{
|
||||||
|
result.SetPressed(button, IsPressed(button));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(float leftStickX, float leftStickY) = GetStick(StickInputId.Left);
|
||||||
|
(float rightStickX, float rightStickY) = GetStick(StickInputId.Right);
|
||||||
|
|
||||||
|
result.SetStick(StickInputId.Left, leftStickX, leftStickY);
|
||||||
|
result.SetStick(StickInputId.Right, rightStickX, rightStickY);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GamepadStateSnapshot GetStateSnapshot()
|
||||||
|
{
|
||||||
|
return new GamepadStateSnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
src/LibRyujinx/LibRyujinx.User.cs
Normal file
82
src/LibRyujinx/LibRyujinx.User.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LibRyujinx
|
||||||
|
{
|
||||||
|
public static partial class LibRyujinx
|
||||||
|
{
|
||||||
|
public static string GetOpenedUser()
|
||||||
|
{
|
||||||
|
var lastProfile = SwitchDevice?.AccountManager.LastOpenedUser;
|
||||||
|
|
||||||
|
return lastProfile?.UserId.ToString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetUserPicture(string userId)
|
||||||
|
{
|
||||||
|
var uid = new UserId(userId);
|
||||||
|
|
||||||
|
var user = SwitchDevice?.AccountManager.GetAllUsers().FirstOrDefault(x => x.UserId == uid);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var pic = user.Image;
|
||||||
|
|
||||||
|
return pic != null ? Convert.ToBase64String(pic) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetUserPicture(string userId, string picture)
|
||||||
|
{
|
||||||
|
var uid = new UserId(userId);
|
||||||
|
|
||||||
|
SwitchDevice?.AccountManager.SetUserImage(uid, Convert.FromBase64String(picture));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetUserName(string userId)
|
||||||
|
{
|
||||||
|
var uid = new UserId(userId);
|
||||||
|
|
||||||
|
var user = SwitchDevice?.AccountManager.GetAllUsers().FirstOrDefault(x => x.UserId == uid);
|
||||||
|
|
||||||
|
return user?.Name ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetUserName(string userId, string name)
|
||||||
|
{
|
||||||
|
var uid = new UserId(userId);
|
||||||
|
|
||||||
|
SwitchDevice?.AccountManager.SetUserName(uid, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string[] GetAllUsers()
|
||||||
|
{
|
||||||
|
return SwitchDevice?.AccountManager.GetAllUsers().Select(x => x.UserId.ToString()).ToArray() ??
|
||||||
|
Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddUser(string userName, string picture)
|
||||||
|
{
|
||||||
|
SwitchDevice?.AccountManager.AddUser(userName, Convert.FromBase64String(picture));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DeleteUser(string userId)
|
||||||
|
{
|
||||||
|
var uid = new UserId(userId);
|
||||||
|
SwitchDevice?.AccountManager.DeleteUser(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OpenUser(string userId)
|
||||||
|
{
|
||||||
|
var uid = new UserId(userId);
|
||||||
|
SwitchDevice?.AccountManager.OpenUser(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CloseUser(string userId)
|
||||||
|
{
|
||||||
|
var uid = new UserId(userId);
|
||||||
|
SwitchDevice?.AccountManager.CloseUser(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
849
src/LibRyujinx/LibRyujinx.cs
Normal file
849
src/LibRyujinx/LibRyujinx.cs
Normal file
@ -0,0 +1,849 @@
|
|||||||
|
// State class for the library
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.HLE.HOS;
|
||||||
|
using Ryujinx.Input.HLE;
|
||||||
|
using Ryujinx.HLE;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading;
|
||||||
|
using Ryujinx.Audio.Backends.Dummy;
|
||||||
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
|
using Ryujinx.UI.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Audio.Integration;
|
||||||
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
|
using System.IO;
|
||||||
|
using LibHac.Common.Keys;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Ns;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
using LibHac;
|
||||||
|
using Ryujinx.HLE.Loaders.Npdm;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Globalization;
|
||||||
|
using Ryujinx.UI.Common.Configuration.System;
|
||||||
|
using Ryujinx.Common.Logging.Targets;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Ryujinx.HLE.UI;
|
||||||
|
|
||||||
|
namespace LibRyujinx
|
||||||
|
{
|
||||||
|
public static partial class LibRyujinx
|
||||||
|
{
|
||||||
|
internal static IHardwareDeviceDriver AudioDriver { get; set; } = new DummyHardwareDeviceDriver();
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AppDataManager.Initialize(basePath);
|
||||||
|
|
||||||
|
ConfigurationState.Initialize();
|
||||||
|
LoggerModule.Initialize();
|
||||||
|
|
||||||
|
string logDir = Path.Combine(AppDataManager.BaseDirPath, "Logs");
|
||||||
|
FileStream logFile = FileLogTarget.PrepareLogFile(logDir);
|
||||||
|
Logger.AddTarget(new AsyncLogTargetWrapper(
|
||||||
|
new FileLogTarget("file", logFile),
|
||||||
|
1000,
|
||||||
|
AsyncLogTargetOverflowAction.Block
|
||||||
|
));
|
||||||
|
SwitchDevice = new SwitchDevice();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InitializeAudio()
|
||||||
|
{
|
||||||
|
AudioDriver = new SDL2HardwareDeviceDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameStats GetGameStats()
|
||||||
|
{
|
||||||
|
if (SwitchDevice?.EmulationContext == null)
|
||||||
|
return new GameStats();
|
||||||
|
|
||||||
|
var context = SwitchDevice.EmulationContext;
|
||||||
|
|
||||||
|
return new GameStats
|
||||||
|
{
|
||||||
|
Fifo = context.Statistics.GetFifoPercent(),
|
||||||
|
GameFps = context.Statistics.GetGameFrameRate(),
|
||||||
|
GameTime = context.Statistics.GetGameFrameTime()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static GameInfo? GetGameInfo(string? file)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(file))
|
||||||
|
{
|
||||||
|
return new GameInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Application, $"Getting game info for file: {file}");
|
||||||
|
|
||||||
|
using var stream = File.Open(file, FileMode.Open);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, "SwitchDevice is not initialized.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
GameInfo gameInfo = GetDefaultInfo(gameStream);
|
||||||
|
|
||||||
|
const Language TitleLanguage = Language.AmericanEnglish;
|
||||||
|
|
||||||
|
BlitStruct<ApplicationControlProperty> controlHolder = new(1);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (extension == "nsp" || extension == "pfs0" || extension == "xci")
|
||||||
|
{
|
||||||
|
IFileSystem pfs;
|
||||||
|
|
||||||
|
bool isExeFs = false;
|
||||||
|
|
||||||
|
if (extension == "xci")
|
||||||
|
{
|
||||||
|
Xci xci = new(SwitchDevice.VirtualFileSystem.KeySet, gameStream.AsStorage());
|
||||||
|
|
||||||
|
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pfsTemp = new PartitionFileSystem();
|
||||||
|
pfsTemp.Initialize(gameStream.AsStorage()).ThrowIfFailure();
|
||||||
|
pfs = pfsTemp;
|
||||||
|
|
||||||
|
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
|
||||||
|
bool hasMainNca = false;
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
|
||||||
|
{
|
||||||
|
using UniqueRef<IFile> ncaFile = new();
|
||||||
|
|
||||||
|
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new(SwitchDevice.VirtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||||
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
|
// Some main NCAs don't have a data partition, so check if the partition exists before opening it
|
||||||
|
if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
|
||||||
|
{
|
||||||
|
hasMainNca = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
|
||||||
|
{
|
||||||
|
isExeFs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMainNca && !isExeFs)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExeFs)
|
||||||
|
{
|
||||||
|
using UniqueRef<IFile> npdmFile = new();
|
||||||
|
|
||||||
|
Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
|
if (ResultFs.PathNotFound.Includes(result))
|
||||||
|
{
|
||||||
|
Npdm npdm = new(npdmFile.Get.AsStream());
|
||||||
|
|
||||||
|
gameInfo.TitleName = npdm.TitleName;
|
||||||
|
gameInfo.TitleId = npdm.Aci0.TitleId.ToString("x16");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GetControlFsAndTitleId(pfs, out IFileSystem? controlFs, out string? id);
|
||||||
|
|
||||||
|
gameInfo.TitleId = id;
|
||||||
|
|
||||||
|
if (controlFs == null)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"No control FS was returned. Unable to process game any further: {gameInfo.TitleName}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is an update available.
|
||||||
|
if (IsUpdateApplied(gameInfo.TitleId, out IFileSystem? updatedControlFs))
|
||||||
|
{
|
||||||
|
// Replace the original ControlFs by the updated one.
|
||||||
|
controlFs = updatedControlFs;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadControlData(controlFs, controlHolder.ByteSpan);
|
||||||
|
|
||||||
|
GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out _, out gameInfo.Developer, out gameInfo.Version);
|
||||||
|
|
||||||
|
// Read the icon from the ControlFS and store it as a byte array
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using UniqueRef<IFile> icon = new();
|
||||||
|
|
||||||
|
controlFs?.OpenFile(ref icon.Ref, $"/icon_{TitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
icon.Get.AsStream().CopyTo(stream);
|
||||||
|
gameInfo.Icon = stream.ToArray();
|
||||||
|
}
|
||||||
|
catch (HorizonResultException)
|
||||||
|
{
|
||||||
|
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
||||||
|
{
|
||||||
|
if (entry.Name == "control.nacp")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var icon = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
controlFs?.OpenFile(ref icon.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
icon.Get.AsStream().CopyTo(stream);
|
||||||
|
gameInfo.Icon = stream.ToArray();
|
||||||
|
|
||||||
|
if (gameInfo.Icon != null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (extension == "nro")
|
||||||
|
{
|
||||||
|
BinaryReader reader = new(gameStream);
|
||||||
|
|
||||||
|
byte[] Read(long position, int size)
|
||||||
|
{
|
||||||
|
gameStream.Seek(position, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
return reader.ReadBytes(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
gameStream.Seek(24, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
int assetOffset = reader.ReadInt32();
|
||||||
|
|
||||||
|
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
||||||
|
{
|
||||||
|
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
||||||
|
|
||||||
|
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
||||||
|
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||||
|
|
||||||
|
ulong nacpOffset = reader.ReadUInt64();
|
||||||
|
ulong nacpSize = reader.ReadUInt64();
|
||||||
|
|
||||||
|
// Reads and stores game icon as byte array
|
||||||
|
if (iconSize > 0)
|
||||||
|
{
|
||||||
|
gameInfo.Icon = Read(assetOffset + iconOffset, (int)iconSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the NACP data
|
||||||
|
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
|
||||||
|
|
||||||
|
GetGameInformation(ref controlHolder.Value, out gameInfo.TitleName, out _, out gameInfo.Developer, out gameInfo.Version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MissingKeyException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. {exception}");
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"The gameStream encountered was not of a valid type. Error: {exception}");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadControlData(IFileSystem? controlFs, Span<byte> outProperty)
|
||||||
|
{
|
||||||
|
using UniqueRef<IFile> controlFile = new();
|
||||||
|
|
||||||
|
controlFs?.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetGameInformation(ref ApplicationControlProperty controlData, out string? titleName, out string titleId, out string? publisher, out string? version)
|
||||||
|
{
|
||||||
|
_ = Enum.TryParse(TitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
||||||
|
|
||||||
|
if (controlData.Title.ItemsRo.Length > (int)desiredTitleLanguage)
|
||||||
|
{
|
||||||
|
titleName = controlData.Title[(int)desiredTitleLanguage].NameString.ToString();
|
||||||
|
publisher = controlData.Title[(int)desiredTitleLanguage].PublisherString.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
titleName = null;
|
||||||
|
publisher = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(titleName))
|
||||||
|
{
|
||||||
|
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
|
||||||
|
{
|
||||||
|
if (!controlTitle.NameString.IsEmpty())
|
||||||
|
{
|
||||||
|
titleName = controlTitle.NameString.ToString();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(publisher))
|
||||||
|
{
|
||||||
|
foreach (ref readonly var controlTitle in controlData.Title.ItemsRo)
|
||||||
|
{
|
||||||
|
if (!controlTitle.PublisherString.IsEmpty())
|
||||||
|
{
|
||||||
|
publisher = controlTitle.PublisherString.ToString();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlData.PresenceGroupId != 0)
|
||||||
|
{
|
||||||
|
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||||
|
}
|
||||||
|
else if (controlData.SaveDataOwnerId != 0)
|
||||||
|
{
|
||||||
|
titleId = controlData.SaveDataOwnerId.ToString();
|
||||||
|
}
|
||||||
|
else if (controlData.AddOnContentBaseId != 0)
|
||||||
|
{
|
||||||
|
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
titleId = "0000000000000000";
|
||||||
|
}
|
||||||
|
|
||||||
|
version = controlData.DisplayVersionString.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem? controlFs, out string? titleId)
|
||||||
|
{
|
||||||
|
if (SwitchDevice == null)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, "SwitchDevice is not initialized.");
|
||||||
|
|
||||||
|
controlFs = null;
|
||||||
|
titleId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(_, _, Nca? controlNca) = GetGameData(SwitchDevice.VirtualFileSystem, pfs, 0);
|
||||||
|
|
||||||
|
if (controlNca == null)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, "Control NCA is null. Unable to load control FS.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the ControlFS
|
||||||
|
controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
||||||
|
titleId = controlNca?.Header.TitleId.ToString("x16");
|
||||||
|
}
|
||||||
|
|
||||||
|
(Nca? mainNca, Nca? patchNca, Nca? controlNca) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex)
|
||||||
|
{
|
||||||
|
Nca? mainNca = null;
|
||||||
|
Nca? patchNca = null;
|
||||||
|
Nca? controlNca = null;
|
||||||
|
|
||||||
|
fileSystem.ImportTickets(pfs);
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||||
|
|
||||||
|
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||||
|
|
||||||
|
if (ncaProgramIndex != programIndex)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.Program)
|
||||||
|
{
|
||||||
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
|
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||||
|
{
|
||||||
|
patchNca = nca;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mainNca = nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||||
|
{
|
||||||
|
controlNca = nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mainNca, patchNca, controlNca);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsUpdateApplied(string? titleId, out IFileSystem? updatedControlFs)
|
||||||
|
{
|
||||||
|
updatedControlFs = null;
|
||||||
|
|
||||||
|
string? updatePath = "(unknown)";
|
||||||
|
|
||||||
|
if (SwitchDevice?.VirtualFileSystem == null)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, "SwitchDevice was not initialized.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
(Nca? patchNca, Nca? controlNca) = GetGameUpdateData(SwitchDevice.VirtualFileSystem, titleId, 0, out updatePath);
|
||||||
|
|
||||||
|
if (patchNca != null && controlNca != null)
|
||||||
|
{
|
||||||
|
updatedControlFs = controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InvalidDataException)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
|
||||||
|
}
|
||||||
|
catch (MissingKeyException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Nca? patch, Nca? control) GetGameUpdateData(VirtualFileSystem fileSystem, string? titleId, int programIndex, out string? updatePath)
|
||||||
|
{
|
||||||
|
updatePath = null;
|
||||||
|
|
||||||
|
if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
|
||||||
|
{
|
||||||
|
// Clear the program index part.
|
||||||
|
titleIdBase &= ~0xFUL;
|
||||||
|
|
||||||
|
// Load update information if exists.
|
||||||
|
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
|
||||||
|
|
||||||
|
if (File.Exists(titleUpdateMetadataPath))
|
||||||
|
{
|
||||||
|
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
|
||||||
|
|
||||||
|
if (File.Exists(updatePath))
|
||||||
|
{
|
||||||
|
FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
|
||||||
|
PartitionFileSystem nsp = new();
|
||||||
|
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||||
|
|
||||||
|
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
(Nca? patchNca, Nca? controlNca) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
|
||||||
|
{
|
||||||
|
Nca? patchNca = null;
|
||||||
|
Nca? controlNca = null;
|
||||||
|
|
||||||
|
fileSystem.ImportTickets(pfs);
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
||||||
|
|
||||||
|
int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
|
||||||
|
|
||||||
|
if (ncaProgramIndex != programIndex)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.Program)
|
||||||
|
{
|
||||||
|
patchNca = nca;
|
||||||
|
}
|
||||||
|
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||||
|
{
|
||||||
|
controlNca = nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (patchNca, controlNca);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gameInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameInfo GetDefaultInfo(Stream gameStream)
|
||||||
|
{
|
||||||
|
return new GameInfo
|
||||||
|
{
|
||||||
|
FileSize = gameStream.Length * 0.000000000931,
|
||||||
|
TitleName = "Unknown",
|
||||||
|
TitleId = "0000000000000000",
|
||||||
|
Developer = "Unknown",
|
||||||
|
Version = "0",
|
||||||
|
Icon = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetDlcTitleId(string path, string ncaPath)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new();
|
||||||
|
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||||
|
|
||||||
|
SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
using UniqueRef<IFile> ncaFile = new();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, ncaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), ncaPath);
|
||||||
|
if (nca != null)
|
||||||
|
{
|
||||||
|
return nca.Header.TitleId.ToString("X16");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Nca TryOpenNca(IStorage ncaStorage, string containerPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new Nca(SwitchDevice.VirtualFileSystem.KeySet, ncaStorage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> GetDlcContentList(string path, ulong titleId)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
return new List<string>();
|
||||||
|
|
||||||
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new();
|
||||||
|
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||||
|
|
||||||
|
SwitchDevice.VirtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
List<string> paths = new List<string>();
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
||||||
|
if (nca == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
|
{
|
||||||
|
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != titleId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
paths.Add(fileEntry.FullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SwitchDevice : IDisposable
|
||||||
|
{
|
||||||
|
private readonly SystemVersion _firmwareVersion;
|
||||||
|
public VirtualFileSystem VirtualFileSystem { get; set; }
|
||||||
|
public ContentManager ContentManager { get; set; }
|
||||||
|
public AccountManager AccountManager { get; set; }
|
||||||
|
public LibHacHorizonManager LibHacHorizonManager { get; set; }
|
||||||
|
public UserChannelPersistence UserChannelPersistence { get; set; }
|
||||||
|
public InputManager? InputManager { get; set; }
|
||||||
|
public Switch? EmulationContext { get; set; }
|
||||||
|
public IHostUIHandler? HostUiHandler { get; set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
|
||||||
|
VirtualFileSystem.Dispose();
|
||||||
|
InputManager?.Dispose();
|
||||||
|
EmulationContext?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SwitchDevice()
|
||||||
|
{
|
||||||
|
VirtualFileSystem = VirtualFileSystem.CreateInstance();
|
||||||
|
LibHacHorizonManager = new LibHacHorizonManager();
|
||||||
|
|
||||||
|
LibHacHorizonManager.InitializeFsServer(VirtualFileSystem);
|
||||||
|
LibHacHorizonManager.InitializeArpServer();
|
||||||
|
LibHacHorizonManager.InitializeBcatServer();
|
||||||
|
LibHacHorizonManager.InitializeSystemClients();
|
||||||
|
|
||||||
|
ContentManager = new ContentManager(VirtualFileSystem);
|
||||||
|
AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient);
|
||||||
|
UserChannelPersistence = new UserChannelPersistence();
|
||||||
|
|
||||||
|
_firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
||||||
|
|
||||||
|
if (_firmwareVersion != null)
|
||||||
|
{
|
||||||
|
Logger.Notice.Print(LogClass.Application, $"System Firmware Version: {_firmwareVersion.VersionString}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Notice.Print(LogClass.Application, $"System Firmware not installed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InitializeContext(bool isHostMapped,
|
||||||
|
bool useHypervisor,
|
||||||
|
SystemLanguage systemLanguage,
|
||||||
|
RegionCode regionCode,
|
||||||
|
bool enableVsync,
|
||||||
|
bool enableDockedMode,
|
||||||
|
bool enablePtc,
|
||||||
|
bool enableInternetAccess,
|
||||||
|
string? timeZone,
|
||||||
|
bool ignoreMissingServices)
|
||||||
|
{
|
||||||
|
if (LibRyujinx.Renderer == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var renderer = LibRyujinx.Renderer;
|
||||||
|
BackendThreading threadingMode = LibRyujinx.GraphicsConfiguration.BackendThreading;
|
||||||
|
|
||||||
|
bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
|
||||||
|
|
||||||
|
if (threadedGAL)
|
||||||
|
{
|
||||||
|
renderer = new ThreadedRenderer(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
HLEConfiguration configuration = new HLEConfiguration(VirtualFileSystem,
|
||||||
|
LibHacHorizonManager,
|
||||||
|
ContentManager,
|
||||||
|
AccountManager,
|
||||||
|
UserChannelPersistence,
|
||||||
|
renderer,
|
||||||
|
LibRyujinx.AudioDriver, //Audio
|
||||||
|
MemoryConfiguration.MemoryConfiguration4GiB,
|
||||||
|
HostUiHandler,
|
||||||
|
systemLanguage,
|
||||||
|
regionCode,
|
||||||
|
enableVsync,
|
||||||
|
enableDockedMode,
|
||||||
|
enablePtc,
|
||||||
|
enableInternetAccess,
|
||||||
|
IntegrityCheckLevel.None,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
timeZone,
|
||||||
|
isHostMapped ? MemoryManagerMode.HostMappedUnsafe : MemoryManagerMode.SoftwarePageTable,
|
||||||
|
ignoreMissingServices,
|
||||||
|
LibRyujinx.GraphicsConfiguration.AspectRatio,
|
||||||
|
100,
|
||||||
|
useHypervisor,
|
||||||
|
"",
|
||||||
|
Ryujinx.Common.Configuration.Multiplayer.MultiplayerMode.Disabled);
|
||||||
|
|
||||||
|
EmulationContext = new Switch(configuration);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ReloadFileSystem()
|
||||||
|
{
|
||||||
|
VirtualFileSystem.ReloadKeySet();
|
||||||
|
ContentManager = new ContentManager(VirtualFileSystem);
|
||||||
|
AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DisposeContext()
|
||||||
|
{
|
||||||
|
EmulationContext?.Dispose();
|
||||||
|
EmulationContext?.DisposeGpu();
|
||||||
|
EmulationContext = null;
|
||||||
|
LibRyujinx.Renderer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GameInfo
|
||||||
|
{
|
||||||
|
public double FileSize;
|
||||||
|
public string? TitleName;
|
||||||
|
public string? TitleId;
|
||||||
|
public string? Developer;
|
||||||
|
public string? Version;
|
||||||
|
public byte[]? Icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public unsafe struct GameInfoNative
|
||||||
|
{
|
||||||
|
public double FileSize;
|
||||||
|
public char* TitleName;
|
||||||
|
public char* TitleId;
|
||||||
|
public char* Developer;
|
||||||
|
public char* Version;
|
||||||
|
public char* Icon;
|
||||||
|
|
||||||
|
public GameInfoNative()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameInfoNative(double fileSize, string? titleName, string? titleId, string? developer, string? version, byte[]? icon)
|
||||||
|
{
|
||||||
|
FileSize = fileSize;
|
||||||
|
TitleId = (char*)Marshal.StringToHGlobalAnsi(titleId);
|
||||||
|
Version = (char*)Marshal.StringToHGlobalAnsi(version);
|
||||||
|
Developer = (char*)Marshal.StringToHGlobalAnsi(developer);
|
||||||
|
TitleName = (char*)Marshal.StringToHGlobalAnsi(titleName);
|
||||||
|
|
||||||
|
if (icon != null)
|
||||||
|
{
|
||||||
|
Icon = (char*)Marshal.StringToHGlobalAnsi(Convert.ToBase64String(icon));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Icon = (char*)0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameInfoNative(GameInfo info) : this(info.FileSize, info.TitleName, info.TitleId, info.Developer, info.Version, info.Icon){}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GameStats
|
||||||
|
{
|
||||||
|
public double Fifo;
|
||||||
|
public double GameFps;
|
||||||
|
public double GameTime;
|
||||||
|
}
|
||||||
|
}
|
36
src/LibRyujinx/LibRyujinx.csproj
Normal file
36
src/LibRyujinx/LibRyujinx.csproj
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<DefineConstants>$(DefineConstants);FORCE_EXTERNAL_BASE_DIR</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<PublishAot>true</PublishAot>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
|
||||||
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
|
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||||
|
<OptimizationPreference>Speed</OptimizationPreference>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
||||||
|
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<RdXmlFile Include="rd.xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
20
src/LibRyujinx/OpenTKBindingsContext.cs
Normal file
20
src/LibRyujinx/OpenTKBindingsContext.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using OpenTK;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LibRyujinx.Shared
|
||||||
|
{
|
||||||
|
public class OpenTKBindingsContext : IBindingsContext
|
||||||
|
{
|
||||||
|
private readonly Func<string, IntPtr> _getProcAddress;
|
||||||
|
|
||||||
|
public OpenTKBindingsContext(Func<string, IntPtr> getProcAddress)
|
||||||
|
{
|
||||||
|
_getProcAddress = getProcAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr GetProcAddress(string procName)
|
||||||
|
{
|
||||||
|
return _getProcAddress(procName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
534
src/LibRyujinx/rd.xml
Normal file
534
src/LibRyujinx/rd.xml
Normal file
@ -0,0 +1,534 @@
|
|||||||
|
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
|
||||||
|
<Application>
|
||||||
|
<Assembly Name="Silk.NET.Vulkan.Extensions.EXT">
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.EXT.ExtDebugUtils"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.EXT.ExtExternalMemoryHost"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.EXT.ExtConditionalRendering"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.EXT.ExtExtendedDynamicState"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.EXT.ExtTransformFeedback"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
</Assembly>
|
||||||
|
<Assembly Name="Silk.NET.Vulkan.Extensions.KHR">
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.KHR.KhrSurface"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.KHR.KhrPushDescriptor"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.KHR.KhrDrawIndirectCount"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
<Type Name="Silk.NET.Vulkan.Extensions.KHR.KhrSwapchain"
|
||||||
|
Dynamic="Required All"/>
|
||||||
|
</Assembly>
|
||||||
|
<Assembly Name="Ryujinx.HLE">
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.NvHostGpuDeviceFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.IFileSystemProxyForLoader"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pm.IInformationInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ngct.IService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.Tcap.IFactorySettingsServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Grc.IRemoteVideoTransfer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.AccountService.IProfileEditor"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostDbgGpu.NvHostDbgGpuDeviceFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy.ILibraryAppletSelfAccessor"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.AccountService.IProfile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.AccountService.IManagerForSystemService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ssl.SslService.SslManagedSocketConnection"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.INetworkInstallManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Apm.SessionServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Ethc.IEthInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.Nfp.IAmManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.Types.NvQueryEventNotImplementedException"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvMap.NvMapDeviceFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.Idle.IPolicyManagerSystem"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.IAccountServiceForSystemService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Apm.ManagerServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Bluetooth.IBluetoothUser"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.Irs.IIrSensorSystemServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.EphemeralNetworkSystemClockContextWriter"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.ProfilesJsonSerializerContext"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Apm.IManagerPrivileged"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.MouseDevice"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Bpc.IRtcManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Bgct.IStateControlService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.BluetoothManager.IBtmSystem"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.IStaticServiceForPsc"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fatal.IService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.SurfaceFlinger.ConsumerBase"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.StaticService.ISteadyClock"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.IStorage"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.HidServer.IAppletResource"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nifm.StaticService.IGeneralService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.IServiceGetterInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.IMultiCommitManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.IAudioController"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.Nfp.AmiiboJsonSerializerContext"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.StandardLocalSystemClockCore"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.NfcManager.INfc"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ptm.Fgm.IDebugger"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.IHidServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Eupld.IRequest"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.TickBasedSteadyClockCore"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.SurfaceFlinger.BufferItem"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.IShopServiceManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Erpt.IContext"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.IShopServiceAccessor"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.IFileSystem"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.ISystemDisplayService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Caps.IScreenshotService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.IGlobalStateController"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Settings.IFirmwareDebugSettingsServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.StandardNetworkSystemClockCore"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Olsc.IOlscServiceForApplication"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.IApplicationManagerInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.IProgramRegistry"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ldn.Lp2p.IServiceCreator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.IHidDebugServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.IAmManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sdb.Pdm.INotifyService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.IPowerStateRequestHandler"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.IDeviceOperator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.ISystemAppletProxy"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Caps.IAlbumAccessorService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sdb.Pl.ISharedFontManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Cec.ICecManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.Nfp.IUserManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.ServiceAttribute"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.News.IServiceCreator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.Irs.IIrSensorServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pctl.IParentalControlServiceFactory"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Bgct.ITaskService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.Omm.IOperationModeManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.KeyboardDevice"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fatal.IPrivateService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.BluetoothManager.IBtmDebug"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy.IProcessWindingController"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.IDirectory"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.ShopServiceAccessServer.ShopServiceAccessor.IShopServiceAsync"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.TouchDevice"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.NetworkSystemClockContextWriter"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nifm.StaticService.IRequest"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Settings.ISystemSettingsServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.ISelfController"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.ICommonStateGetter"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.Ntc.StaticService.IEnsureNetworkClockAvailabilityService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.IWindowController"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.DebugPadDevice"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ncm.Lr.LocationResolverManager.ILocationResolver"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Mii.IStaticService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.IHidbusServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Clkrst.IClkrstManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.IDevelopInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Mii.StaticService.DatabaseServiceImpl"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.IHidSystemServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.IAccountServiceForAdministrator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ectx.IWriterForSystem"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.NpadDevices"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Vi.IManagerRootService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.INvDrvServices"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.AccountService.IManagerForApplication"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.HidServer.IActiveApplicationDeviceList"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.IDebugFunctions"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.Tcap.IAvmService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.BluetoothManager.IBtmUser"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcie.IManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.IStorageAccessor"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator.ILibraryAppletAccessor"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.IAccountServiceForApplication"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Caps.IScreenShotApplicationService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.IBaasAccessTokenAccessor"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.IUserManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.IShopServiceAccessSystemInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.IApplicationProxy"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.BluetoothManager.IBtm"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ptm.Fgm.ISession"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.Host1xContext"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Arp.LibHacIReader"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Arp.LibHacArpServiceObject"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.IAlarmService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ngct.IServiceWithManagementApi"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Npns.INpnsSystem"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostProfGpu.NvHostProfGpuDeviceFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AppletFifo`1"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Bpc.IBoardPowerControlManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ldn.IUserServiceCreator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Spl.IRandomInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sdb.Pdm.IQueryService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ptm.Pcm.IManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.SurfaceFlinger.HOSBinderDriverServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ncm.IContentManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Vi.ISystemRootService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ptm.Fan.IManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.IApplicationFunctions"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.Nfp.INfp"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.IVulnerabilityManagerInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Caps.IAlbumApplicationService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.Tcap.IManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.SurfaceFlinger.BufferQueueProducer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.EphemeralNetworkSystemClockCore"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Bsd.ServerInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.ShopServiceAccessServerInterface.IShopServiceAccessServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.Aoc.IPurchaseEventManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ro.IRoInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Mig.IService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.Ntc.IStaticService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.StaticService.ITimeZoneServiceForPsc"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.Types.NvIoctlNotImplementedException"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ldn.ISystemServiceCreator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.ServerBase"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Nsd.IManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.LocalSystemClockContextWriter"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcie.ILogManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Dauth.IService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.INvGemControl"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Spl.IGeneralInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Clkrst.IArbitrationManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl.EventFileDescriptorPollManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Ethc.IEthInterfaceGroup"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Settings.ISettingsServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.INvDrvDebugFSServices"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.ISystemUpdateInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.ISystemManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.IPcvService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.IApplicationProxyService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Loader.IShellInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ssl.SslService.ISslConnection"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.SurfaceFlinger.BufferItemConsumer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl.NvHostCtrlDeviceFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory.IParentalControlService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.IAllSystemAppletProxiesService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.IFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.IReadOnlyApplicationControlDataInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Erpt.ISession"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ptm.Psm.IPsmServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nim.IShopServiceAccessServerInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Caps.IScreenShotControlService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.StandardSteadyClockCore"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Hid.ISystemServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.Mifare.IUserManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Clkrst.ClkrstManager.IClkrstSession"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.StaticService.ISystemClock"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pm.IDebugMonitorInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ldn.IMonitorServiceCreator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Grc.IGrcService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.IDisplayController"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Bsd.IClient"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.IApplicationCreator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.Aoc.IAddOnContentManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.ITimeServiceManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Bluetooth.IBluetoothDriver"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ns.Aoc.IContentsServiceManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.ILibraryAppletCreator"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.NvHostChannelDeviceFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.IStorage"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sm.IUserInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.NvHostCtrlGpuDeviceFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.CommandTipcAttribute"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Eupld.IControl"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Vi.RootService.IApplicationDisplayService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Loader.IDebugMonitorInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nfc.Nfp.ISystemManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.IAsyncNetworkServiceLicenseKindContext"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ectx.IWriterForApplication"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ncm.Lr.ILocationResolverManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.StaticService.ITimeZoneServiceForGlue"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Notification.INotificationServicesForSystem"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.Spsm.IPowerStateInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.DummyService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.IHomeMenuFunctions"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.NvHostAsGpuDeviceFile"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Apm.SystemManagerServer"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sm.IManagerInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.SurfaceFlinger.SurfaceFlinger"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.IResolver"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ptm.Psm.IPsmSession"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Account.Acc.IAsyncContext"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.IAppletCommonFunctions"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl.ManagedSocket"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pm.IBootModeInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Npns.INpnsUser"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Olsc.IOlscServiceForSystemService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService.IManagerDisplayService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ptm.Tc.IManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ectx.IReaderForSystem"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Loader.IProcessManagerInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Notification.INotificationServicesForApplication"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pm.IShellInterface"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.ILibraryAppletProxy"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ssl.ISslService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Mii.IImageDatabaseService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.Clock.StandardUserSystemClockCore"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Vi.IApplicationRootService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.BluetoothManager.BtmUser.IBtmUserCore"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.ISaveDataInfoReader"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ssl.SslService.ISslContext"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nv.INvGemCoreDump"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl.EventFileDescriptor"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Mnpp.IServiceForApplication"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Fs.IFileSystemProxy"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Caps.IAlbumControlService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.IUserLocalCommunicationService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Nifm.IStaticService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl.ManagedSocketPollManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Time.IStaticServiceForGlue"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.CommandCmifAttribute"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Rtc.IRtcManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Es.IETicketService"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
<Type Name="Ryujinx.HLE.HOS.Services.Pcv.Rgltr.IRegulatorManager"
|
||||||
|
Dynamic="Required All" />
|
||||||
|
</Assembly>
|
||||||
|
</Application>
|
||||||
|
</Directives>
|
@ -1,4 +1,5 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
namespace Ryujinx.Common
|
||||||
{
|
{
|
||||||
@ -26,6 +27,6 @@ namespace Ryujinx.Common
|
|||||||
|
|
||||||
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||||
|
|
||||||
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
public static string Version => IsValid ? BuildVersion : !RuntimeFeature.IsDynamicCodeCompiled ? "libryujinx" : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Silk.NET.Core;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using Silk.NET.Vulkan.Extensions.EXT;
|
using Silk.NET.Vulkan.Extensions.EXT;
|
||||||
using System;
|
using System;
|
||||||
@ -16,6 +17,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly ExtDebugUtils _debugUtils;
|
private readonly ExtDebugUtils _debugUtils;
|
||||||
private readonly DebugUtilsMessengerEXT? _debugUtilsMessenger;
|
private readonly DebugUtilsMessengerEXT? _debugUtilsMessenger;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
private unsafe delegate* unmanaged[Cdecl]<DebugUtilsMessageSeverityFlagsEXT, DebugUtilsMessageTypeFlagsEXT, DebugUtilsMessengerCallbackDataEXT*, void*, Bool32> _messageDelegate;
|
||||||
|
|
||||||
public VulkanDebugMessenger(Vk api, Instance instance, GraphicsDebugLevel logLevel)
|
public VulkanDebugMessenger(Vk api, Instance instance, GraphicsDebugLevel logLevel)
|
||||||
{
|
{
|
||||||
@ -71,7 +73,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
debugUtilsMessengerCreateInfo.PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(UserCallback);
|
_messageDelegate = (delegate* unmanaged[Cdecl]<DebugUtilsMessageSeverityFlagsEXT, DebugUtilsMessageTypeFlagsEXT, DebugUtilsMessengerCallbackDataEXT*, void*, Bool32>)Marshal.GetFunctionPointerForDelegate(UserCallback);
|
||||||
|
debugUtilsMessengerCreateInfo.PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(_messageDelegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugUtilsMessengerEXT messengerHandle = default;
|
DebugUtilsMessengerEXT messengerHandle = default;
|
||||||
@ -89,7 +92,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe static uint UserCallback(
|
private unsafe static Bool32 UserCallback(
|
||||||
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||||
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
||||||
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||||
|
@ -74,6 +74,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public IWindow Window => _window;
|
public IWindow Window => _window;
|
||||||
|
|
||||||
|
public SurfaceTransformFlagsKHR CurrentTransform => _window.CurrentTransform;
|
||||||
|
|
||||||
private readonly Func<Instance, Vk, SurfaceKHR> _getSurface;
|
private readonly Func<Instance, Vk, SurfaceKHR> _getSurface;
|
||||||
private readonly Func<string[]> _getRequiredExtensions;
|
private readonly Func<string[]> _getRequiredExtensions;
|
||||||
private readonly string _preferredGpuId;
|
private readonly string _preferredGpuId;
|
||||||
@ -978,6 +980,15 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return !(IsMoltenVk || IsQualcommProprietary);
|
return !(IsMoltenVk || IsQualcommProprietary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe void RecreateSurface()
|
||||||
|
{
|
||||||
|
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||||
|
|
||||||
|
_surface = _getSurface(_instance.Instance, Api);
|
||||||
|
|
||||||
|
(_window as Window)?.SetSurface(_surface);
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void Dispose()
|
public unsafe void Dispose()
|
||||||
{
|
{
|
||||||
if (!_initialized)
|
if (!_initialized)
|
||||||
|
@ -14,10 +14,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private const int SurfaceHeight = 720;
|
private const int SurfaceHeight = 720;
|
||||||
|
|
||||||
private readonly VulkanRenderer _gd;
|
private readonly VulkanRenderer _gd;
|
||||||
private readonly SurfaceKHR _surface;
|
|
||||||
private readonly PhysicalDevice _physicalDevice;
|
private readonly PhysicalDevice _physicalDevice;
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
private SwapchainKHR _swapchain;
|
private SwapchainKHR _swapchain;
|
||||||
|
private SurfaceKHR _surface;
|
||||||
|
|
||||||
private Image[] _swapchainImages;
|
private Image[] _swapchainImages;
|
||||||
private TextureView[] _swapchainImageViews;
|
private TextureView[] _swapchainImageViews;
|
||||||
@ -84,6 +84,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
CreateSwapchain();
|
CreateSwapchain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void SetSurface(SurfaceKHR surface)
|
||||||
|
{
|
||||||
|
_surface = surface;
|
||||||
|
RecreateSwapchain();
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe void CreateSwapchain()
|
private unsafe void CreateSwapchain()
|
||||||
{
|
{
|
||||||
_gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out var capabilities);
|
_gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out var capabilities);
|
||||||
@ -126,6 +132,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
var oldSwapchain = _swapchain;
|
var oldSwapchain = _swapchain;
|
||||||
|
|
||||||
|
CurrentTransform = capabilities.CurrentTransform;
|
||||||
|
|
||||||
var swapchainCreateInfo = new SwapchainCreateInfoKHR
|
var swapchainCreateInfo = new SwapchainCreateInfoKHR
|
||||||
{
|
{
|
||||||
SType = StructureType.SwapchainCreateInfoKhr,
|
SType = StructureType.SwapchainCreateInfoKhr,
|
||||||
@ -332,6 +340,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
RecreateSwapchain();
|
RecreateSwapchain();
|
||||||
semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length;
|
semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length;
|
||||||
}
|
}
|
||||||
|
else if(acquireResult == Result.ErrorSurfaceLostKhr)
|
||||||
|
{
|
||||||
|
_gd.RecreateSurface();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
acquireResult.ThrowOnError();
|
acquireResult.ThrowOnError();
|
||||||
@ -481,6 +493,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo);
|
_gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//While this does nothing in most cases, it's useful to notify the end of the frame.
|
||||||
|
swapBuffersCallback?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetAntiAliasing(AntiAliasing effect)
|
public override void SetAntiAliasing(AntiAliasing effect)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
@ -7,6 +8,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
public bool ScreenCaptureRequested { get; set; }
|
public bool ScreenCaptureRequested { get; set; }
|
||||||
|
|
||||||
|
public SurfaceTransformFlagsKHR CurrentTransform { get; set; }
|
||||||
|
|
||||||
public abstract void Dispose();
|
public abstract void Dispose();
|
||||||
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
|
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
|
||||||
public abstract void SetSize(int width, int height);
|
public abstract void SetSize(int width, int height);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user