Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
49be977588 | ||
|
c95be55091 | ||
|
63dedbda86 | ||
|
c532118d94 | ||
|
52d6f2e656 | ||
|
c9bc4eaf58 | ||
|
3249f8ff41 | ||
|
1b41b285ac | ||
|
f5a6f45b27 | ||
|
210557951b | ||
|
4c2d9ff3ff | ||
|
8198b99935 | ||
|
460f96967d | ||
|
7ca779a26d | ||
|
b5032b3c91 | ||
|
f0a3dff136 |
@@ -63,6 +63,10 @@ dotnet_code_quality_unused_parameters = all:suggestion
|
|||||||
|
|
||||||
#### C# Coding Conventions ####
|
#### C# Coding Conventions ####
|
||||||
|
|
||||||
|
# Namespace preferences
|
||||||
|
csharp_style_namespace_declarations = block_scoped:warning
|
||||||
|
resharper_csharp_namespace_body = block_scoped
|
||||||
|
|
||||||
# var preferences
|
# var preferences
|
||||||
csharp_style_var_elsewhere = false:silent
|
csharp_style_var_elsewhere = false:silent
|
||||||
csharp_style_var_for_built_in_types = false:silent
|
csharp_style_var_for_built_in_types = false:silent
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
namespace ARMeilleure.Decoders;
|
namespace ARMeilleure.Decoders
|
||||||
|
|
||||||
interface IOpCode32Exception
|
|
||||||
{
|
{
|
||||||
int Id { get; }
|
interface IOpCode32Exception
|
||||||
|
{
|
||||||
|
int Id { get; }
|
||||||
|
}
|
||||||
}
|
}
|
@@ -3,17 +3,17 @@
|
|||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="0.10.18" />
|
<PackageVersion Include="Avalonia" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||||
<PackageVersion Include="DynamicData" Version="7.12.11" />
|
<PackageVersion Include="DynamicData" Version="7.13.1" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||||
@@ -44,10 +44,10 @@
|
|||||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
|
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.28.1" />
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.6.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@@ -18,6 +18,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
private ulong _playedSampleCount;
|
private ulong _playedSampleCount;
|
||||||
private ManualResetEvent _updateRequiredEvent;
|
private ManualResetEvent _updateRequiredEvent;
|
||||||
private uint _outputStream;
|
private uint _outputStream;
|
||||||
|
private bool _hasSetupError;
|
||||||
private SDL_AudioCallback _callbackDelegate;
|
private SDL_AudioCallback _callbackDelegate;
|
||||||
private int _bytesPerFrame;
|
private int _bytesPerFrame;
|
||||||
private uint _sampleCount;
|
private uint _sampleCount;
|
||||||
@@ -42,7 +43,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||||
{
|
{
|
||||||
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||||
bool needAudioSetup = _outputStream == 0 ||
|
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
|
||||||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||||
|
|
||||||
if (needAudioSetup)
|
if (needAudioSetup)
|
||||||
@@ -51,12 +52,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
|
|
||||||
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||||
|
|
||||||
if (newOutputStream == 0)
|
_hasSetupError = newOutputStream == 0;
|
||||||
{
|
|
||||||
// No stream in place, this is unexpected.
|
if (!_hasSetupError)
|
||||||
throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\"");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
if (_outputStream != 0)
|
if (_outputStream != 0)
|
||||||
{
|
{
|
||||||
@@ -151,11 +149,20 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
{
|
{
|
||||||
EnsureAudioStreamSetup(buffer);
|
EnsureAudioStreamSetup(buffer);
|
||||||
|
|
||||||
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
if (_outputStream != 0)
|
||||||
|
{
|
||||||
|
SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||||
|
|
||||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||||
|
|
||||||
_queuedBuffers.Enqueue(driverBuffer);
|
_queuedBuffers.Enqueue(driverBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer));
|
||||||
|
|
||||||
|
_updateRequiredEvent.Set();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetVolume(float volume)
|
public override void SetVolume(float volume)
|
||||||
|
@@ -320,10 +320,14 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_viewModel.IsGameRunning = true;
|
_viewModel.IsGameRunning = true;
|
||||||
|
|
||||||
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) ? string.Empty : $" - {Device.Application.TitleName}";
|
var activeProcess = Device.Processes.ActiveApplication;
|
||||||
string titleVersionSection = string.IsNullOrWhiteSpace(Device.Application.DisplayVersion) ? string.Empty : $" v{Device.Application.DisplayVersion}";
|
var nacp = activeProcess.ApplicationControlProperties;
|
||||||
string titleIdSection = string.IsNullOrWhiteSpace(Device.Application.TitleIdText) ? string.Empty : $" ({Device.Application.TitleIdText.ToUpper()})";
|
int desiredLanguage = (int)Device.System.State.DesiredTitleLanguage;
|
||||||
string titleArchSection = Device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
|
|
||||||
|
string titleNameSection = string.IsNullOrWhiteSpace(nacp.Title[desiredLanguage].NameString.ToString()) ? string.Empty : $" - {nacp.Title[desiredLanguage].NameString.ToString()}";
|
||||||
|
string titleVersionSection = string.IsNullOrWhiteSpace(nacp.DisplayVersionString.ToString()) ? string.Empty : $" v{nacp.DisplayVersionString.ToString()}";
|
||||||
|
string titleIdSection = string.IsNullOrWhiteSpace(activeProcess.ProgramIdText) ? string.Empty : $" ({activeProcess.ProgramIdText.ToUpper()})";
|
||||||
|
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
@@ -423,9 +427,9 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private void Dispose()
|
private void Dispose()
|
||||||
{
|
{
|
||||||
if (Device.Application != null)
|
if (Device.Processes != null)
|
||||||
{
|
{
|
||||||
_viewModel.UpdateGameMetadata(Device.Application.TitleIdText);
|
_viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
|
||||||
@@ -539,7 +543,12 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
|
Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA).");
|
||||||
|
|
||||||
Device.LoadNca(ApplicationPath);
|
if (!Device.LoadNca(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (Directory.Exists(ApplicationPath))
|
else if (Directory.Exists(ApplicationPath))
|
||||||
{
|
{
|
||||||
@@ -554,13 +563,23 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
||||||
|
|
||||||
Device.LoadCart(ApplicationPath, romFsFiles[0]);
|
if (!Device.LoadCart(ApplicationPath, romFsFiles[0]))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
||||||
|
|
||||||
Device.LoadCart(ApplicationPath);
|
if (!Device.LoadCart(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (File.Exists(ApplicationPath))
|
else if (File.Exists(ApplicationPath))
|
||||||
@@ -571,7 +590,12 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||||
|
|
||||||
Device.LoadXci(ApplicationPath);
|
if (!Device.LoadXci(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -579,7 +603,12 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||||
|
|
||||||
Device.LoadNca(ApplicationPath);
|
if (!Device.LoadNca(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -588,7 +617,12 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||||
|
|
||||||
Device.LoadNsp(ApplicationPath);
|
if (!Device.LoadNsp(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -598,13 +632,18 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Device.LoadProgram(ApplicationPath);
|
if (!Device.LoadProgram(ApplicationPath))
|
||||||
|
{
|
||||||
|
Device.Dispose();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException)
|
catch (ArgumentOutOfRangeException)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
||||||
|
|
||||||
Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -617,14 +656,14 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
||||||
|
|
||||||
Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName);
|
DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name);
|
||||||
|
|
||||||
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
|
_viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
||||||
{
|
{
|
||||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||||
});
|
});
|
||||||
@@ -950,7 +989,7 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
|
||||||
{
|
{
|
||||||
Device.Application.DiskCacheLoadState?.Cancel();
|
Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1088,4 +1127,4 @@ namespace Ryujinx.Ava
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
@@ -227,7 +228,7 @@ namespace Ryujinx.Ava.Common
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
patchNca = updatePatchNca;
|
patchNca = updatePatchNca;
|
||||||
|
@@ -130,7 +130,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
{
|
{
|
||||||
var localeStrings = new Dictionary<LocaleKeys, string>();
|
var localeStrings = new Dictionary<LocaleKeys, string>();
|
||||||
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
||||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
|
||||||
|
|
||||||
foreach (var item in strings)
|
foreach (var item in strings)
|
||||||
{
|
{
|
||||||
|
@@ -4,13 +4,14 @@ using FluentAvalonia.UI.Controls;
|
|||||||
using ICSharpCode.SharpZipLib.GZip;
|
using ICSharpCode.SharpZipLib.GZip;
|
||||||
using ICSharpCode.SharpZipLib.Tar;
|
using ICSharpCode.SharpZipLib.Tar;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Ryujinx.Ava;
|
using Ryujinx.Ava;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
using Ryujinx.Ui.Common.Models.Github;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -31,6 +32,7 @@ namespace Ryujinx.Modules
|
|||||||
internal static class Updater
|
internal static class Updater
|
||||||
{
|
{
|
||||||
private const string GitHubApiURL = "https://api.github.com";
|
private const string GitHubApiURL = "https://api.github.com";
|
||||||
|
private static readonly GithubReleasesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||||
@@ -99,22 +101,16 @@ namespace Ryujinx.Modules
|
|||||||
|
|
||||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
||||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
|
||||||
JToken assets = jsonRoot["assets"];
|
_buildVer = fetched.Name;
|
||||||
|
|
||||||
_buildVer = (string)jsonRoot["name"];
|
foreach (var asset in fetched.Assets)
|
||||||
|
|
||||||
foreach (JToken asset in assets)
|
|
||||||
{
|
{
|
||||||
string assetName = (string)asset["name"];
|
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
|
||||||
string assetState = (string)asset["state"];
|
|
||||||
string downloadURL = (string)asset["browser_download_url"];
|
|
||||||
|
|
||||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
|
||||||
{
|
{
|
||||||
_buildUrl = downloadURL;
|
_buildUrl = asset.BrowserDownloadUrl;
|
||||||
|
|
||||||
if (assetState != "uploaded")
|
if (asset.State != "uploaded")
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
if (showVersionUpToDate)
|
||||||
{
|
{
|
||||||
|
@@ -1,72 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
|
||||||
{
|
|
||||||
public class Amiibo
|
|
||||||
{
|
|
||||||
public struct AmiiboJson
|
|
||||||
{
|
|
||||||
[JsonPropertyName("amiibo")] public List<AmiiboApi> Amiibo { get; set; }
|
|
||||||
[JsonPropertyName("lastUpdated")] public DateTime LastUpdated { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct AmiiboApi
|
|
||||||
{
|
|
||||||
[JsonPropertyName("name")] public string Name { get; set; }
|
|
||||||
[JsonPropertyName("head")] public string Head { get; set; }
|
|
||||||
[JsonPropertyName("tail")] public string Tail { get; set; }
|
|
||||||
[JsonPropertyName("image")] public string Image { get; set; }
|
|
||||||
[JsonPropertyName("amiiboSeries")] public string AmiiboSeries { get; set; }
|
|
||||||
[JsonPropertyName("character")] public string Character { get; set; }
|
|
||||||
[JsonPropertyName("gameSeries")] public string GameSeries { get; set; }
|
|
||||||
[JsonPropertyName("type")] public string Type { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("release")] public Dictionary<string, string> Release { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("gamesSwitch")] public List<AmiiboApiGamesSwitch> GamesSwitch { get; set; }
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetId()
|
|
||||||
{
|
|
||||||
return Head + Tail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
if (obj is AmiiboApi amiibo)
|
|
||||||
{
|
|
||||||
return amiibo.Head + amiibo.Tail == Head + Tail;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return base.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AmiiboApiGamesSwitch
|
|
||||||
{
|
|
||||||
[JsonPropertyName("amiiboUsage")] public List<AmiiboApiUsage> AmiiboUsage { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("gameID")] public List<string> GameId { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("gameName")] public string GameName { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AmiiboApiUsage
|
|
||||||
{
|
|
||||||
[JsonPropertyName("Usage")] public string Usage { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("write")] public bool Write { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
|
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
|
||||||
|
|
||||||
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString)) + "\n\n";
|
Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)) + "\n\n";
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@@ -4,11 +4,11 @@ using Avalonia.Media.Imaging;
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.Ui.Common.Models.Amiibo;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@@ -17,6 +17,7 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@@ -31,8 +32,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private readonly StyleableWindow _owner;
|
private readonly StyleableWindow _owner;
|
||||||
|
|
||||||
private Bitmap _amiiboImage;
|
private Bitmap _amiiboImage;
|
||||||
private List<Amiibo.AmiiboApi> _amiiboList;
|
private List<AmiiboApi> _amiiboList;
|
||||||
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
|
private AvaloniaList<AmiiboApi> _amiibos;
|
||||||
private ObservableCollection<string> _amiiboSeries;
|
private ObservableCollection<string> _amiiboSeries;
|
||||||
|
|
||||||
private int _amiiboSelectedIndex;
|
private int _amiiboSelectedIndex;
|
||||||
@@ -41,6 +42,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private bool _showAllAmiibo;
|
private bool _showAllAmiibo;
|
||||||
private bool _useRandomUuid;
|
private bool _useRandomUuid;
|
||||||
private string _usage;
|
private string _usage;
|
||||||
|
|
||||||
|
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
|
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
|
||||||
{
|
{
|
||||||
@@ -52,9 +55,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
|
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
|
||||||
|
|
||||||
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
|
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
|
||||||
_amiiboList = new List<Amiibo.AmiiboApi>();
|
_amiiboList = new List<AmiiboApi>();
|
||||||
_amiiboSeries = new ObservableCollection<string>();
|
_amiiboSeries = new ObservableCollection<string>();
|
||||||
_amiibos = new AvaloniaList<Amiibo.AmiiboApi>();
|
_amiibos = new AvaloniaList<AmiiboApi>();
|
||||||
|
|
||||||
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
|
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
|
||||||
|
|
||||||
@@ -94,7 +97,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AvaloniaList<Amiibo.AmiiboApi> AmiiboList
|
public AvaloniaList<AmiiboApi> AmiiboList
|
||||||
{
|
{
|
||||||
get => _amiibos;
|
get => _amiibos;
|
||||||
set
|
set
|
||||||
@@ -187,9 +190,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (File.Exists(_amiiboJsonPath))
|
if (File.Exists(_amiiboJsonPath))
|
||||||
{
|
{
|
||||||
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
|
||||||
|
|
||||||
if (await NeedsUpdate(JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
|
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
|
||||||
{
|
{
|
||||||
amiiboJsonString = await DownloadAmiiboJson();
|
amiiboJsonString = await DownloadAmiiboJson();
|
||||||
}
|
}
|
||||||
@@ -206,7 +209,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
|
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
|
||||||
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||||
|
|
||||||
ParseAmiiboData();
|
ParseAmiiboData();
|
||||||
@@ -223,7 +226,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (!ShowAllAmiibo)
|
if (!ShowAllAmiibo)
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (game != null)
|
if (game != null)
|
||||||
{
|
{
|
||||||
@@ -255,7 +258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void SelectLastScannedAmiibo()
|
private void SelectLastScannedAmiibo()
|
||||||
{
|
{
|
||||||
Amiibo.AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
|
AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
|
||||||
|
|
||||||
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
|
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
|
||||||
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
||||||
@@ -270,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList
|
List<AmiiboApi> amiiboSortedList = _amiiboList
|
||||||
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
||||||
.OrderBy(amiibo => amiibo.Name).ToList();
|
.OrderBy(amiibo => amiibo.Name).ToList();
|
||||||
|
|
||||||
@@ -280,7 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (!_showAllAmiibo)
|
if (!_showAllAmiibo)
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (game != null)
|
if (game != null)
|
||||||
{
|
{
|
||||||
@@ -314,7 +317,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||||
|
|
||||||
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
||||||
|
|
||||||
@@ -326,11 +329,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
bool writable = false;
|
bool writable = false;
|
||||||
|
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (item.GameId.Contains(TitleId))
|
if (item.GameId.Contains(TitleId))
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage)
|
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||||
{
|
{
|
||||||
usageString += Environment.NewLine +
|
usageString += Environment.NewLine +
|
||||||
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
||||||
|
@@ -51,6 +51,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private bool _isLoaded;
|
private bool _isLoaded;
|
||||||
private readonly UserControl _owner;
|
private readonly UserControl _owner;
|
||||||
|
|
||||||
|
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||||
public IGamepad SelectedGamepad { get; private set; }
|
public IGamepad SelectedGamepad { get; private set; }
|
||||||
|
|
||||||
@@ -706,10 +708,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (Stream stream = File.OpenRead(path))
|
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
|
||||||
{
|
|
||||||
config = JsonHelper.Deserialize<InputConfig>(stream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (JsonException) { }
|
catch (JsonException) { }
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
@@ -775,7 +774,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
config.ControllerType = Controllers[_controller].Type;
|
config.ControllerType = Controllers[_controller].Type;
|
||||||
|
|
||||||
string jsonString = JsonHelper.Serialize(config, true);
|
string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig);
|
||||||
|
|
||||||
await File.WriteAllTextAsync(path, jsonString);
|
await File.WriteAllTextAsync(path, jsonString);
|
||||||
|
|
||||||
|
@@ -21,7 +21,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
@@ -41,6 +40,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private ulong _titleId;
|
private ulong _titleId;
|
||||||
private string _titleName;
|
private string _titleName;
|
||||||
|
|
||||||
|
private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
||||||
{
|
{
|
||||||
get => _downloadableContents;
|
get => _downloadableContents;
|
||||||
@@ -100,7 +101,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
|
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -330,10 +331,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_downloadableContentContainerList.Add(container);
|
_downloadableContentContainerList.Add(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
|
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, SerializerContext.ListDownloadableContentContainer);
|
||||||
{
|
|
||||||
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1208,10 +1208,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void SetUIProgressHandlers(Switch emulationContext)
|
public void SetUIProgressHandlers(Switch emulationContext)
|
||||||
{
|
{
|
||||||
if (emulationContext.Application.DiskCacheLoadState != null)
|
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
|
||||||
{
|
{
|
||||||
emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
||||||
emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
|
emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
|
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
|
||||||
@@ -1705,8 +1705,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleName))
|
if (string.IsNullOrWhiteSpace(titleName))
|
||||||
{
|
{
|
||||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName);
|
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
|
||||||
TitleName = AppHost.Device.Application.TitleName;
|
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
SwitchToRenderer(startFullscreen);
|
SwitchToRenderer(startFullscreen);
|
||||||
|
@@ -17,234 +17,236 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels;
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
public class TitleUpdateViewModel : BaseModel
|
|
||||||
{
|
{
|
||||||
public TitleUpdateMetadata _titleUpdateWindowData;
|
public class TitleUpdateViewModel : BaseModel
|
||||||
public readonly string _titleUpdateJsonPath;
|
|
||||||
private VirtualFileSystem _virtualFileSystem { get; }
|
|
||||||
private ulong _titleId { get; }
|
|
||||||
private string _titleName { get; }
|
|
||||||
|
|
||||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
|
||||||
private AvaloniaList<object> _views = new();
|
|
||||||
private object _selectedUpdate;
|
|
||||||
|
|
||||||
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
|
||||||
{
|
{
|
||||||
get => _titleUpdates;
|
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
set
|
public readonly string _titleUpdateJsonPath;
|
||||||
|
private VirtualFileSystem _virtualFileSystem { get; }
|
||||||
|
private ulong _titleId { get; }
|
||||||
|
private string _titleName { get; }
|
||||||
|
|
||||||
|
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||||
|
private AvaloniaList<object> _views = new();
|
||||||
|
private object _selectedUpdate;
|
||||||
|
|
||||||
|
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
|
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||||
{
|
{
|
||||||
_titleUpdates = value;
|
get => _titleUpdates;
|
||||||
OnPropertyChanged();
|
set
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvaloniaList<object> Views
|
|
||||||
{
|
|
||||||
get => _views;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_views = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public object SelectedUpdate
|
|
||||||
{
|
|
||||||
get => _selectedUpdate;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_selectedUpdate = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
|
||||||
{
|
|
||||||
_virtualFileSystem = virtualFileSystem;
|
|
||||||
|
|
||||||
_titleId = titleId;
|
|
||||||
_titleName = titleName;
|
|
||||||
|
|
||||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
|
||||||
|
|
||||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
|
||||||
{
|
{
|
||||||
Selected = "",
|
_titleUpdates = value;
|
||||||
Paths = new List<string>()
|
OnPropertyChanged();
|
||||||
};
|
|
||||||
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadUpdates()
|
|
||||||
{
|
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
|
||||||
{
|
|
||||||
AddUpdate(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
|
||||||
|
|
||||||
SelectedUpdate = selected;
|
|
||||||
|
|
||||||
// NOTE: Save the list again to remove leftovers.
|
|
||||||
Save();
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SortUpdates()
|
|
||||||
{
|
|
||||||
var list = TitleUpdates.ToList();
|
|
||||||
|
|
||||||
list.Sort((first, second) =>
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
Views.Clear();
|
|
||||||
Views.Add(new BaseModel());
|
|
||||||
Views.AddRange(list);
|
|
||||||
|
|
||||||
if (SelectedUpdate == null)
|
|
||||||
{
|
|
||||||
SelectedUpdate = Views[0];
|
|
||||||
}
|
|
||||||
else if (!TitleUpdates.Contains(SelectedUpdate))
|
|
||||||
{
|
|
||||||
if (Views.Count > 1)
|
|
||||||
{
|
|
||||||
SelectedUpdate = Views[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SelectedUpdate = Views[0];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
public AvaloniaList<object> Views
|
||||||
{
|
|
||||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
|
||||||
{
|
{
|
||||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
get => _views;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_views = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object SelectedUpdate
|
||||||
|
{
|
||||||
|
get => _selectedUpdate;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedUpdate = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
|
{
|
||||||
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
|
_titleId = titleId;
|
||||||
|
_titleName = titleName;
|
||||||
|
|
||||||
|
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
_titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||||
{
|
{
|
||||||
ApplicationControlProperty controlData = new();
|
Selected = "",
|
||||||
|
Paths = new List<string>()
|
||||||
|
};
|
||||||
|
|
||||||
using UniqueRef<IFile> nacpFile = new();
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
LoadUpdates();
|
||||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
}
|
||||||
|
|
||||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
private void LoadUpdates()
|
||||||
|
{
|
||||||
|
foreach (string path in _titleUpdateWindowData.Paths)
|
||||||
|
{
|
||||||
|
AddUpdate(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||||
|
|
||||||
|
SelectedUpdate = selected;
|
||||||
|
|
||||||
|
// NOTE: Save the list again to remove leftovers.
|
||||||
|
Save();
|
||||||
|
SortUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SortUpdates()
|
||||||
|
{
|
||||||
|
var list = TitleUpdates.ToList();
|
||||||
|
|
||||||
|
list.Sort((first, second) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
Views.Clear();
|
||||||
|
Views.Add(new BaseModel());
|
||||||
|
Views.AddRange(list);
|
||||||
|
|
||||||
|
if (SelectedUpdate == null)
|
||||||
|
{
|
||||||
|
SelectedUpdate = Views[0];
|
||||||
|
}
|
||||||
|
else if (!TitleUpdates.Contains(SelectedUpdate))
|
||||||
|
{
|
||||||
|
if (Views.Count > 1)
|
||||||
|
{
|
||||||
|
SelectedUpdate = Views[1];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
SelectedUpdate = Views[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddUpdate(string path)
|
||||||
|
{
|
||||||
|
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||||
|
{
|
||||||
|
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||||
|
|
||||||
|
if (controlNca != null && patchNca != null)
|
||||||
|
{
|
||||||
|
ApplicationControlProperty controlData = new();
|
||||||
|
|
||||||
|
using UniqueRef<IFile> nacpFile = new();
|
||||||
|
|
||||||
|
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||||
|
|
||||||
|
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveUpdate(TitleUpdateModel update)
|
public void RemoveUpdate(TitleUpdateModel update)
|
||||||
{
|
|
||||||
TitleUpdates.Remove(update);
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Add()
|
|
||||||
{
|
|
||||||
OpenFileDialog dialog = new()
|
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
TitleUpdates.Remove(update);
|
||||||
AllowMultiple = true
|
|
||||||
};
|
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
SortUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Add()
|
||||||
{
|
{
|
||||||
Name = "NSP",
|
OpenFileDialog dialog = new()
|
||||||
Extensions = { "nsp" }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
||||||
{
|
|
||||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
|
||||||
|
|
||||||
if (files != null)
|
|
||||||
{
|
{
|
||||||
foreach (string file in files)
|
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||||
|
AllowMultiple = true
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.Filters.Add(new FileDialogFilter
|
||||||
|
{
|
||||||
|
Name = "NSP",
|
||||||
|
Extensions = { "nsp" }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||||
|
|
||||||
|
if (files != null)
|
||||||
{
|
{
|
||||||
AddUpdate(file);
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
AddUpdate(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SortUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
SortUpdates();
|
public void Save()
|
||||||
}
|
|
||||||
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData.Paths.Clear();
|
|
||||||
_titleUpdateWindowData.Selected = "";
|
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in TitleUpdates)
|
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
_titleUpdateWindowData.Paths.Clear();
|
||||||
|
_titleUpdateWindowData.Selected = "";
|
||||||
|
|
||||||
if (update == SelectedUpdate)
|
foreach (TitleUpdateModel update in TitleUpdates)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Selected = update.Path;
|
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
if (update == SelectedUpdate)
|
||||||
|
{
|
||||||
|
_titleUpdateWindowData.Selected = update.Path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -42,7 +42,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
{
|
{
|
||||||
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
|
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
|
||||||
string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}");
|
string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}");
|
||||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
|
||||||
|
|
||||||
if (!strings.TryGetValue("Language", out string languageName))
|
if (!strings.TryGetValue("Language", out string languageName))
|
||||||
{
|
{
|
||||||
@@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
|
|
||||||
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
||||||
{
|
{
|
||||||
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper();
|
string titleId = ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
|
||||||
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
|
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
|
||||||
|
|
||||||
await window.ShowDialog(Window);
|
await window.ShowDialog(Window);
|
||||||
@@ -148,13 +148,11 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationLoader application = ViewModel.AppHost.Device.Application;
|
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||||
if (application != null)
|
|
||||||
{
|
|
||||||
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
|
|
||||||
|
|
||||||
ViewModel.AppHost.Device.EnableCheats();
|
await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
|
||||||
}
|
|
||||||
|
ViewModel.AppHost.Device.EnableCheats();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Models;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.Ui.Common.Models.Amiibo;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
@@ -35,14 +35,14 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsScanned { get; set; }
|
public bool IsScanned { get; set; }
|
||||||
public Amiibo.AmiiboApi ScannedAmiibo { get; set; }
|
public AmiiboApi ScannedAmiibo { get; set; }
|
||||||
public AmiiboWindowViewModel ViewModel { get; set; }
|
public AmiiboWindowViewModel ViewModel { get; set; }
|
||||||
|
|
||||||
private void ScanButton_Click(object sender, RoutedEventArgs e)
|
private void ScanButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (ViewModel.AmiiboSelectedIndex > -1)
|
if (ViewModel.AmiiboSelectedIndex > -1)
|
||||||
{
|
{
|
||||||
Amiibo.AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
||||||
ScannedAmiibo = amiibo;
|
ScannedAmiibo = amiibo;
|
||||||
IsScanned = true;
|
IsScanned = true;
|
||||||
Close();
|
Close();
|
||||||
|
@@ -6,11 +6,8 @@ using Ryujinx.Ava.Common.Locale;
|
|||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Button = Avalonia.Controls.Button;
|
using Button = Avalonia.Controls.Button;
|
||||||
|
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<AntiAliasing>))]
|
||||||
public enum AntiAliasing
|
public enum AntiAliasing
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
@@ -9,4 +13,4 @@
|
|||||||
SmaaHigh,
|
SmaaHigh,
|
||||||
SmaaUltra
|
SmaaUltra
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
|
||||||
public enum AspectRatio
|
public enum AspectRatio
|
||||||
{
|
{
|
||||||
Fixed4x3,
|
Fixed4x3,
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
|
||||||
public enum BackendThreading
|
public enum BackendThreading
|
||||||
{
|
{
|
||||||
Auto,
|
Auto,
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(List<DownloadableContentContainer>))]
|
||||||
|
public partial class DownloadableContentJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||||
public enum GraphicsBackend
|
public enum GraphicsBackend
|
||||||
{
|
{
|
||||||
Vulkan,
|
Vulkan,
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
|
||||||
public enum GraphicsDebugLevel
|
public enum GraphicsDebugLevel
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<GamepadInputId>))]
|
||||||
public enum GamepadInputId : byte
|
public enum GamepadInputId : byte
|
||||||
{
|
{
|
||||||
Unbound,
|
Unbound,
|
||||||
@@ -51,4 +55,4 @@
|
|||||||
|
|
||||||
Count
|
Count
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
@@ -6,6 +7,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
|||||||
{
|
{
|
||||||
class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController>
|
class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController>
|
||||||
{
|
{
|
||||||
|
private static readonly MotionConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader)
|
private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader)
|
||||||
{
|
{
|
||||||
// Temporary reader to get the backend type
|
// Temporary reader to get the backend type
|
||||||
@@ -52,8 +55,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
|||||||
|
|
||||||
return motionBackendType switch
|
return motionBackendType switch
|
||||||
{
|
{
|
||||||
MotionInputBackendType.GamepadDriver => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(StandardMotionConfigController), options),
|
MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardMotionConfigController),
|
||||||
MotionInputBackendType.CemuHook => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(CemuHookMotionConfigController), options),
|
MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, SerializerContext.CemuHookMotionConfigController),
|
||||||
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
|
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -63,10 +66,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
|||||||
switch (value.MotionBackend)
|
switch (value.MotionBackend)
|
||||||
{
|
{
|
||||||
case MotionInputBackendType.GamepadDriver:
|
case MotionInputBackendType.GamepadDriver:
|
||||||
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, options);
|
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, SerializerContext.StandardMotionConfigController);
|
||||||
break;
|
break;
|
||||||
case MotionInputBackendType.CemuHook:
|
case MotionInputBackendType.CemuHook:
|
||||||
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, options);
|
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, SerializerContext.CemuHookMotionConfigController);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");
|
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(JsonMotionConfigControllerConverter))]
|
||||||
public class MotionConfigController
|
public class MotionConfigController
|
||||||
{
|
{
|
||||||
public MotionInputBackendType MotionBackend { get; set; }
|
public MotionInputBackendType MotionBackend { get; set; }
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(MotionConfigController))]
|
||||||
|
[JsonSerializable(typeof(CemuHookMotionConfigController))]
|
||||||
|
[JsonSerializable(typeof(StandardMotionConfigController))]
|
||||||
|
public partial class MotionConfigJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
|
||||||
public enum MotionInputBackendType : byte
|
public enum MotionInputBackendType : byte
|
||||||
{
|
{
|
||||||
Invalid,
|
Invalid,
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<StickInputId>))]
|
||||||
public enum StickInputId : byte
|
public enum StickInputId : byte
|
||||||
{
|
{
|
||||||
Unbound,
|
Unbound,
|
||||||
@@ -8,4 +12,4 @@
|
|||||||
|
|
||||||
Count
|
Count
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,9 +1,12 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
[Flags]
|
|
||||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||||
|
[Flags]
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
|
||||||
public enum ControllerType : int
|
public enum ControllerType : int
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
|
||||||
public enum InputBackendType
|
public enum InputBackendType
|
||||||
{
|
{
|
||||||
Invalid,
|
Invalid,
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(JsonInputConfigConverter))]
|
||||||
public class InputConfig : INotifyPropertyChanged
|
public class InputConfig : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(InputConfig))]
|
||||||
|
[JsonSerializable(typeof(StandardKeyboardInputConfig))]
|
||||||
|
[JsonSerializable(typeof(StandardControllerInputConfig))]
|
||||||
|
public partial class InputConfigJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +1,16 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
class JsonInputConfigConverter : JsonConverter<InputConfig>
|
public class JsonInputConfigConverter : JsonConverter<InputConfig>
|
||||||
{
|
{
|
||||||
|
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader)
|
private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader)
|
||||||
{
|
{
|
||||||
// Temporary reader to get the backend type
|
// Temporary reader to get the backend type
|
||||||
@@ -54,8 +57,8 @@ namespace Ryujinx.Common.Configuration.Hid
|
|||||||
|
|
||||||
return backendType switch
|
return backendType switch
|
||||||
{
|
{
|
||||||
InputBackendType.WindowKeyboard => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardKeyboardInputConfig), options),
|
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig),
|
||||||
InputBackendType.GamepadSDL2 => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardControllerInputConfig), options),
|
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig),
|
||||||
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
|
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -65,10 +68,10 @@ namespace Ryujinx.Common.Configuration.Hid
|
|||||||
switch (value.Backend)
|
switch (value.Backend)
|
||||||
{
|
{
|
||||||
case InputBackendType.WindowKeyboard:
|
case InputBackendType.WindowKeyboard:
|
||||||
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, options);
|
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig);
|
||||||
break;
|
break;
|
||||||
case InputBackendType.GamepadSDL2:
|
case InputBackendType.GamepadSDL2:
|
||||||
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, options);
|
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Unknown backend type {value.Backend}");
|
throw new ArgumentException($"Unknown backend type {value.Backend}");
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<Key>))]
|
||||||
public enum Key
|
public enum Key
|
||||||
{
|
{
|
||||||
Unknown,
|
Unknown,
|
||||||
@@ -136,4 +140,4 @@
|
|||||||
|
|
||||||
Count
|
Count
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
namespace Ryujinx.Common.Configuration.Hid
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
|
// NOTE: Please don't change this to struct.
|
||||||
|
// This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys.
|
||||||
public class KeyboardHotkeys
|
public class KeyboardHotkeys
|
||||||
{
|
{
|
||||||
public Key ToggleVsync { get; set; }
|
public Key ToggleVsync { get; set; }
|
||||||
@@ -12,4 +14,4 @@
|
|||||||
public Key VolumeUp { get; set; }
|
public Key VolumeUp { get; set; }
|
||||||
public Key VolumeDown { get; set; }
|
public Key VolumeDown { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,10 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
|
||||||
public enum PlayerIndex : int
|
public enum PlayerIndex : int
|
||||||
{
|
{
|
||||||
Player1 = 0,
|
Player1 = 0,
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
|
||||||
public enum MemoryManagerMode : byte
|
public enum MemoryManagerMode : byte
|
||||||
{
|
{
|
||||||
SoftwarePageTable,
|
SoftwarePageTable,
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
|
||||||
public enum ScalingFilter
|
public enum ScalingFilter
|
||||||
{
|
{
|
||||||
Bilinear,
|
Bilinear,
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(TitleUpdateMetadata))]
|
||||||
|
public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,22 +1,20 @@
|
|||||||
using System;
|
using System.Text;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.Logging
|
namespace Ryujinx.Common.Logging
|
||||||
{
|
{
|
||||||
internal class DefaultLogFormatter : ILogFormatter
|
internal class DefaultLogFormatter : ILogFormatter
|
||||||
{
|
{
|
||||||
private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
|
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||||
|
|
||||||
public string Format(LogEventArgs args)
|
public string Format(LogEventArgs args)
|
||||||
{
|
{
|
||||||
StringBuilder sb = _stringBuilderPool.Allocate();
|
StringBuilder sb = StringBuilderPool.Allocate();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sb.Clear();
|
sb.Clear();
|
||||||
|
|
||||||
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time);
|
sb.Append($@"{args.Time:hh\:mm\:ss\.fff}");
|
||||||
sb.Append($" |{args.Level.ToString()[0]}| ");
|
sb.Append($" |{args.Level.ToString()[0]}| ");
|
||||||
|
|
||||||
if (args.ThreadName != null)
|
if (args.ThreadName != null)
|
||||||
@@ -27,53 +25,17 @@ namespace Ryujinx.Common.Logging
|
|||||||
|
|
||||||
sb.Append(args.Message);
|
sb.Append(args.Message);
|
||||||
|
|
||||||
if (args.Data != null)
|
if (args.Data is not null)
|
||||||
{
|
{
|
||||||
PropertyInfo[] props = args.Data.GetType().GetProperties();
|
sb.Append(' ');
|
||||||
|
DynamicObjectFormatter.Format(sb, args.Data);
|
||||||
sb.Append(" {");
|
|
||||||
|
|
||||||
foreach (var prop in props)
|
|
||||||
{
|
|
||||||
sb.Append(prop.Name);
|
|
||||||
sb.Append(": ");
|
|
||||||
|
|
||||||
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
|
|
||||||
{
|
|
||||||
Array array = (Array)prop.GetValue(args.Data);
|
|
||||||
foreach (var item in array)
|
|
||||||
{
|
|
||||||
sb.Append(item.ToString());
|
|
||||||
sb.Append(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (array.Length > 0)
|
|
||||||
{
|
|
||||||
sb.Remove(sb.Length - 2, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Append(prop.GetValue(args.Data));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(" ; ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// We remove the final ';' from the string
|
|
||||||
if (props.Length > 0)
|
|
||||||
{
|
|
||||||
sb.Remove(sb.Length - 3, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append('}');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_stringBuilderPool.Release(sb);
|
StringBuilderPool.Release(sb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
84
Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs
Normal file
84
Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Logging
|
||||||
|
{
|
||||||
|
internal class DynamicObjectFormatter
|
||||||
|
{
|
||||||
|
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||||
|
|
||||||
|
public static string? Format(object? dynamicObject)
|
||||||
|
{
|
||||||
|
if (dynamicObject is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = StringBuilderPool.Allocate();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Format(sb, dynamicObject);
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
StringBuilderPool.Release(sb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Format(StringBuilder sb, object? dynamicObject)
|
||||||
|
{
|
||||||
|
if (dynamicObject is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyInfo[] props = dynamicObject.GetType().GetProperties();
|
||||||
|
|
||||||
|
sb.Append('{');
|
||||||
|
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
sb.Append(prop.Name);
|
||||||
|
sb.Append(": ");
|
||||||
|
|
||||||
|
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
|
||||||
|
{
|
||||||
|
Array? array = (Array?) prop.GetValue(dynamicObject);
|
||||||
|
|
||||||
|
if (array is not null)
|
||||||
|
{
|
||||||
|
foreach (var item in array)
|
||||||
|
{
|
||||||
|
sb.Append(item);
|
||||||
|
sb.Append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array.Length > 0)
|
||||||
|
{
|
||||||
|
sb.Remove(sb.Length - 2, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(prop.GetValue(dynamicObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(" ; ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We remove the final ';' from the string
|
||||||
|
if (props.Length > 0)
|
||||||
|
{
|
||||||
|
sb.Remove(sb.Length - 3, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,9 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Logging
|
namespace Ryujinx.Common.Logging
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
|
||||||
public enum LogClass
|
public enum LogClass
|
||||||
{
|
{
|
||||||
Application,
|
Application,
|
||||||
|
@@ -11,15 +11,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
public readonly string Message;
|
public readonly string Message;
|
||||||
public readonly object Data;
|
public readonly object Data;
|
||||||
|
|
||||||
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message)
|
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null)
|
||||||
{
|
|
||||||
Level = level;
|
|
||||||
Time = time;
|
|
||||||
ThreadName = threadName;
|
|
||||||
Message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data)
|
|
||||||
{
|
{
|
||||||
Level = level;
|
Level = level;
|
||||||
Time = time;
|
Time = time;
|
||||||
|
30
Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
30
Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Logging
|
||||||
|
{
|
||||||
|
internal class LogEventArgsJson
|
||||||
|
{
|
||||||
|
public LogLevel Level { get; }
|
||||||
|
public TimeSpan Time { get; }
|
||||||
|
public string ThreadName { get; }
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
public string Data { get; }
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public LogEventArgsJson(LogLevel level, TimeSpan time, string threadName, string message, string data = null)
|
||||||
|
{
|
||||||
|
Level = level;
|
||||||
|
Time = time;
|
||||||
|
ThreadName = threadName;
|
||||||
|
Message = message;
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogEventArgsJson FromLogEventArgs(LogEventArgs args)
|
||||||
|
{
|
||||||
|
return new LogEventArgsJson(args.Level, args.Time, args.ThreadName, args.Message, DynamicObjectFormatter.Format(args.Data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs
Normal file
9
Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Logging
|
||||||
|
{
|
||||||
|
[JsonSerializable(typeof(LogEventArgsJson))]
|
||||||
|
internal partial class LogEventJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,9 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Logging
|
namespace Ryujinx.Common.Logging
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
|
||||||
public enum LogLevel
|
public enum LogLevel
|
||||||
{
|
{
|
||||||
Debug,
|
Debug,
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
using System.IO;
|
using Ryujinx.Common.Utilities;
|
||||||
using System.Text.Json;
|
using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Logging
|
namespace Ryujinx.Common.Logging
|
||||||
{
|
{
|
||||||
@@ -25,12 +25,8 @@ namespace Ryujinx.Common.Logging
|
|||||||
|
|
||||||
public void Log(object sender, LogEventArgs e)
|
public void Log(object sender, LogEventArgs e)
|
||||||
{
|
{
|
||||||
string text = JsonSerializer.Serialize(e);
|
var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
|
||||||
|
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
|
||||||
using (BinaryWriter writer = new BinaryWriter(_stream))
|
|
||||||
{
|
|
||||||
writer.Write(text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@@ -1,18 +1,21 @@
|
|||||||
|
using Microsoft.Win32.SafeHandles;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ryujinx.Common.SystemInterop
|
namespace Ryujinx.Common.SystemInterop
|
||||||
{
|
{
|
||||||
public partial class StdErrAdapter : IDisposable
|
public partial class StdErrAdapter : IDisposable
|
||||||
{
|
{
|
||||||
private bool _disposable = false;
|
private bool _disposable = false;
|
||||||
private UnixStream _pipeReader;
|
private Stream _pipeReader;
|
||||||
private UnixStream _pipeWriter;
|
private Stream _pipeWriter;
|
||||||
private Thread _worker;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private Task _worker;
|
||||||
|
|
||||||
public StdErrAdapter()
|
public StdErrAdapter()
|
||||||
{
|
{
|
||||||
@@ -31,37 +34,39 @@ namespace Ryujinx.Common.SystemInterop
|
|||||||
(int readFd, int writeFd) = MakePipe();
|
(int readFd, int writeFd) = MakePipe();
|
||||||
dup2(writeFd, stdErrFileno);
|
dup2(writeFd, stdErrFileno);
|
||||||
|
|
||||||
_pipeReader = new UnixStream(readFd);
|
_pipeReader = CreateFileDescriptorStream(readFd);
|
||||||
_pipeWriter = new UnixStream(writeFd);
|
_pipeWriter = CreateFileDescriptorStream(writeFd);
|
||||||
|
|
||||||
_worker = new Thread(EventWorker);
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
_worker = Task.Run(async () => await EventWorkerAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
|
||||||
_disposable = true;
|
_disposable = true;
|
||||||
_worker.Start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
[SupportedOSPlatform("linux")]
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
private void EventWorker()
|
private async Task EventWorkerAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
TextReader reader = new StreamReader(_pipeReader);
|
using TextReader reader = new StreamReader(_pipeReader, leaveOpen: true);
|
||||||
string line;
|
string line;
|
||||||
while ((line = reader.ReadLine()) != null)
|
while (cancellationToken.IsCancellationRequested == false && (line = await reader.ReadLineAsync(cancellationToken)) != null)
|
||||||
{
|
{
|
||||||
Logger.Error?.PrintRawMsg(line);
|
Logger.Error?.PrintRawMsg(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
private void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposable)
|
if (_disposable)
|
||||||
{
|
{
|
||||||
|
_disposable = false;
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
|
_cancellationTokenSource.Cancel();
|
||||||
|
_worker.Wait(0);
|
||||||
_pipeReader?.Close();
|
_pipeReader?.Close();
|
||||||
_pipeWriter?.Close();
|
_pipeWriter?.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposable = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +79,11 @@ namespace Ryujinx.Common.SystemInterop
|
|||||||
private static partial int dup2(int fd, int fd2);
|
private static partial int dup2(int fd, int fd2);
|
||||||
|
|
||||||
[LibraryImport("libc", SetLastError = true)]
|
[LibraryImport("libc", SetLastError = true)]
|
||||||
private static unsafe partial int pipe(int* pipefd);
|
private static partial int pipe(Span<int> pipefd);
|
||||||
|
|
||||||
private static unsafe (int, int) MakePipe()
|
private static (int, int) MakePipe()
|
||||||
{
|
{
|
||||||
int *pipefd = stackalloc int[2];
|
Span<int> pipefd = stackalloc int[2];
|
||||||
|
|
||||||
if (pipe(pipefd) == 0)
|
if (pipe(pipefd) == 0)
|
||||||
{
|
{
|
||||||
@@ -89,5 +94,16 @@ namespace Ryujinx.Common.SystemInterop
|
|||||||
throw new();
|
throw new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
private static Stream CreateFileDescriptorStream(int fd)
|
||||||
|
{
|
||||||
|
return new FileStream(
|
||||||
|
new SafeFileHandle((IntPtr)fd, ownsHandle: true),
|
||||||
|
FileAccess.ReadWrite
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,155 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common.SystemInterop
|
|
||||||
{
|
|
||||||
[SupportedOSPlatform("linux")]
|
|
||||||
[SupportedOSPlatform("macos")]
|
|
||||||
public partial class UnixStream : Stream, IDisposable
|
|
||||||
{
|
|
||||||
private const int InvalidFd = -1;
|
|
||||||
|
|
||||||
private int _fd;
|
|
||||||
|
|
||||||
[LibraryImport("libc", SetLastError = true)]
|
|
||||||
private static partial long read(int fd, IntPtr buf, ulong count);
|
|
||||||
|
|
||||||
[LibraryImport("libc", SetLastError = true)]
|
|
||||||
private static partial long write(int fd, IntPtr buf, ulong count);
|
|
||||||
|
|
||||||
[LibraryImport("libc", SetLastError = true)]
|
|
||||||
private static partial int close(int fd);
|
|
||||||
|
|
||||||
public UnixStream(int fd)
|
|
||||||
{
|
|
||||||
if (InvalidFd == fd)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Invalid file descriptor");
|
|
||||||
}
|
|
||||||
|
|
||||||
_fd = fd;
|
|
||||||
|
|
||||||
CanRead = read(fd, IntPtr.Zero, 0) != -1;
|
|
||||||
CanWrite = write(fd, IntPtr.Zero, 0) != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
~UnixStream()
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanRead { get; }
|
|
||||||
public override bool CanWrite { get; }
|
|
||||||
public override bool CanSeek => false;
|
|
||||||
|
|
||||||
public override long Length => throw new NotSupportedException();
|
|
||||||
|
|
||||||
public override long Position
|
|
||||||
{
|
|
||||||
get => throw new NotSupportedException();
|
|
||||||
set => throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Flush()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override unsafe int Read([In, Out] byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer.Length == 0)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
long r = 0;
|
|
||||||
fixed (byte* buf = &buffer[offset])
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
r = read(_fd, (IntPtr)buf, (ulong)count);
|
|
||||||
} while (ShouldRetry(r));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)r;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override unsafe void Write(byte[] buffer, int offset, int count)
|
|
||||||
{
|
|
||||||
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer.Length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fixed (byte* buf = &buffer[offset])
|
|
||||||
{
|
|
||||||
long r = 0;
|
|
||||||
do {
|
|
||||||
r = write(_fd, (IntPtr)buf, (ulong)count);
|
|
||||||
} while (ShouldRetry(r));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override long Seek(long offset, SeekOrigin origin)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetLength(long value)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
if (_fd == InvalidFd)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Flush();
|
|
||||||
|
|
||||||
int r;
|
|
||||||
do {
|
|
||||||
r = close(_fd);
|
|
||||||
} while (ShouldRetry(r));
|
|
||||||
|
|
||||||
_fd = InvalidFd;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IDisposable.Dispose()
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ShouldRetry(long r)
|
|
||||||
{
|
|
||||||
if (r == -1)
|
|
||||||
{
|
|
||||||
const int eintr = 4;
|
|
||||||
|
|
||||||
int errno = Marshal.GetLastPInvokeError();
|
|
||||||
|
|
||||||
if (errno == eintr)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SystemException($"Operation failed with error 0x{errno:X}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
11
Ryujinx.Common/Utilities/CommonJsonContext.cs
Normal file
11
Ryujinx.Common/Utilities/CommonJsonContext.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Utilities
|
||||||
|
{
|
||||||
|
[JsonSerializable(typeof(string[]), TypeInfoPropertyName = "StringArray")]
|
||||||
|
[JsonSerializable(typeof(Dictionary<string, string>), TypeInfoPropertyName = "StringDictionary")]
|
||||||
|
public partial class CommonJsonContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,15 +1,62 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using System.IO;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Utilities
|
namespace Ryujinx.Common.Utilities
|
||||||
{
|
{
|
||||||
public class JsonHelper
|
public class JsonHelper
|
||||||
{
|
{
|
||||||
public static JsonNamingPolicy SnakeCase { get; }
|
private static readonly JsonNamingPolicy SnakeCasePolicy = new SnakeCaseNamingPolicy();
|
||||||
|
private const int DefaultFileWriteBufferSize = 4096;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates new serializer options with default settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It is REQUIRED for you to save returned options statically or as a part of static serializer context
|
||||||
|
/// in order to avoid performance issues. You can safely modify returned options for your case before storing.
|
||||||
|
/// </remarks>
|
||||||
|
public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true)
|
||||||
|
{
|
||||||
|
JsonSerializerOptions options = new()
|
||||||
|
{
|
||||||
|
DictionaryKeyPolicy = SnakeCasePolicy,
|
||||||
|
PropertyNamingPolicy = SnakeCasePolicy,
|
||||||
|
WriteIndented = indented,
|
||||||
|
AllowTrailingCommas = true,
|
||||||
|
ReadCommentHandling = JsonCommentHandling.Skip
|
||||||
|
};
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Serialize<T>(T value, JsonTypeInfo<T> typeInfo)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(value, typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T Deserialize<T>(string value, JsonTypeInfo<T> typeInfo)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize(value, typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SerializeToFile<T>(string filePath, T value, JsonTypeInfo<T> typeInfo)
|
||||||
|
{
|
||||||
|
using FileStream file = File.Create(filePath, DefaultFileWriteBufferSize, FileOptions.WriteThrough);
|
||||||
|
JsonSerializer.Serialize(file, value, typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T DeserializeFromFile<T>(string filePath, JsonTypeInfo<T> typeInfo)
|
||||||
|
{
|
||||||
|
using FileStream file = File.OpenRead(filePath);
|
||||||
|
return JsonSerializer.Deserialize(file, typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(stream, value, typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
private class SnakeCaseNamingPolicy : JsonNamingPolicy
|
private class SnakeCaseNamingPolicy : JsonNamingPolicy
|
||||||
{
|
{
|
||||||
@@ -20,7 +67,7 @@ namespace Ryujinx.Common.Utilities
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new();
|
||||||
|
|
||||||
for (int i = 0; i < name.Length; i++)
|
for (int i = 0; i < name.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -34,7 +81,7 @@ namespace Ryujinx.Common.Utilities
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append("_");
|
builder.Append('_');
|
||||||
builder.Append(char.ToLowerInvariant(c));
|
builder.Append(char.ToLowerInvariant(c));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,64 +94,5 @@ namespace Ryujinx.Common.Utilities
|
|||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static JsonHelper()
|
|
||||||
{
|
|
||||||
SnakeCase = new SnakeCaseNamingPolicy();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static JsonSerializerOptions GetDefaultSerializerOptions(bool prettyPrint = false)
|
|
||||||
{
|
|
||||||
JsonSerializerOptions options = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
DictionaryKeyPolicy = SnakeCase,
|
|
||||||
PropertyNamingPolicy = SnakeCase,
|
|
||||||
WriteIndented = prettyPrint,
|
|
||||||
AllowTrailingCommas = true,
|
|
||||||
ReadCommentHandling = JsonCommentHandling.Skip
|
|
||||||
};
|
|
||||||
|
|
||||||
options.Converters.Add(new JsonStringEnumConverter());
|
|
||||||
options.Converters.Add(new JsonInputConfigConverter());
|
|
||||||
options.Converters.Add(new JsonMotionConfigControllerConverter());
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T Deserialize<T>(Stream stream)
|
|
||||||
{
|
|
||||||
using (BinaryReader reader = new BinaryReader(stream))
|
|
||||||
{
|
|
||||||
return JsonSerializer.Deserialize<T>(reader.ReadBytes((int)(stream.Length - stream.Position)), GetDefaultSerializerOptions());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T DeserializeFromFile<T>(string path)
|
|
||||||
{
|
|
||||||
return Deserialize<T>(File.ReadAllText(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T Deserialize<T>(string json)
|
|
||||||
{
|
|
||||||
return JsonSerializer.Deserialize<T>(json, GetDefaultSerializerOptions());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Serialize<TValue>(Stream stream, TValue obj, bool prettyPrint = false)
|
|
||||||
{
|
|
||||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
|
||||||
{
|
|
||||||
writer.Write(SerializeToUtf8Bytes(obj, prettyPrint));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Serialize<TValue>(TValue obj, bool prettyPrint = false)
|
|
||||||
{
|
|
||||||
return JsonSerializer.Serialize(obj, GetDefaultSerializerOptions(prettyPrint));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] SerializeToUtf8Bytes<T>(T obj, bool prettyPrint = false)
|
|
||||||
{
|
|
||||||
return JsonSerializer.SerializeToUtf8Bytes(obj, GetDefaultSerializerOptions(prettyPrint));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
34
Ryujinx.Common/Utilities/TypedStringEnumConverter.cs
Normal file
34
Ryujinx.Common/Utilities/TypedStringEnumConverter.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Utilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies that value of <see cref="TEnum"/> will be serialized as string in JSONs
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Trimming friendly alternative to <see cref="JsonStringEnumConverter"/>.
|
||||||
|
/// Get rid of this converter if dotnet supports similar functionality out of the box.
|
||||||
|
/// </remarks>
|
||||||
|
/// <typeparam name="TEnum">Type of enum to serialize</typeparam>
|
||||||
|
public sealed class TypedStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
|
||||||
|
{
|
||||||
|
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var enumValue = reader.GetString();
|
||||||
|
if (string.IsNullOrEmpty(enumValue))
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Enum.Parse<TEnum>(enumValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -38,4 +38,25 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
Src1AlphaGl = 0xc902,
|
Src1AlphaGl = 0xc902,
|
||||||
OneMinusSrc1AlphaGl = 0xc903
|
OneMinusSrc1AlphaGl = 0xc903
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class BlendFactorExtensions
|
||||||
|
{
|
||||||
|
public static bool IsDualSource(this BlendFactor factor)
|
||||||
|
{
|
||||||
|
switch (factor)
|
||||||
|
{
|
||||||
|
case BlendFactor.Src1Color:
|
||||||
|
case BlendFactor.Src1ColorGl:
|
||||||
|
case BlendFactor.Src1Alpha:
|
||||||
|
case BlendFactor.Src1AlphaGl:
|
||||||
|
case BlendFactor.OneMinusSrc1Color:
|
||||||
|
case BlendFactor.OneMinusSrc1ColorGl:
|
||||||
|
case BlendFactor.OneMinusSrc1Alpha:
|
||||||
|
case BlendFactor.OneMinusSrc1AlphaGl:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -328,5 +328,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
Signal();
|
Signal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the dual-source blend enabled state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="enabled">True if blending is enabled and using dual-source blend</param>
|
||||||
|
public void SetDualSourceBlendEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled != _graphics.DualSourceBlendEnable)
|
||||||
|
{
|
||||||
|
_graphics.DualSourceBlendEnable = enabled;
|
||||||
|
|
||||||
|
Signal();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1183,6 +1183,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
bool blendIndependent = _state.State.BlendIndependent;
|
bool blendIndependent = _state.State.BlendIndependent;
|
||||||
ColorF blendConstant = _state.State.BlendConstant;
|
ColorF blendConstant = _state.State.BlendConstant;
|
||||||
|
|
||||||
|
bool dualSourceBlendEnabled = false;
|
||||||
|
|
||||||
if (blendIndependent)
|
if (blendIndependent)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||||
@@ -1200,6 +1202,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
FilterBlendFactor(blend.AlphaSrcFactor, index),
|
FilterBlendFactor(blend.AlphaSrcFactor, index),
|
||||||
FilterBlendFactor(blend.AlphaDstFactor, index));
|
FilterBlendFactor(blend.AlphaDstFactor, index));
|
||||||
|
|
||||||
|
if (enable &&
|
||||||
|
(blend.ColorSrcFactor.IsDualSource() ||
|
||||||
|
blend.ColorDstFactor.IsDualSource() ||
|
||||||
|
blend.AlphaSrcFactor.IsDualSource() ||
|
||||||
|
blend.AlphaDstFactor.IsDualSource()))
|
||||||
|
{
|
||||||
|
dualSourceBlendEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
_pipeline.BlendDescriptors[index] = descriptor;
|
_pipeline.BlendDescriptors[index] = descriptor;
|
||||||
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
||||||
}
|
}
|
||||||
@@ -1219,12 +1230,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
FilterBlendFactor(blend.AlphaSrcFactor, 0),
|
FilterBlendFactor(blend.AlphaSrcFactor, 0),
|
||||||
FilterBlendFactor(blend.AlphaDstFactor, 0));
|
FilterBlendFactor(blend.AlphaDstFactor, 0));
|
||||||
|
|
||||||
|
if (enable &&
|
||||||
|
(blend.ColorSrcFactor.IsDualSource() ||
|
||||||
|
blend.ColorDstFactor.IsDualSource() ||
|
||||||
|
blend.AlphaSrcFactor.IsDualSource() ||
|
||||||
|
blend.AlphaDstFactor.IsDualSource()))
|
||||||
|
{
|
||||||
|
dualSourceBlendEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||||
{
|
{
|
||||||
_pipeline.BlendDescriptors[index] = descriptor;
|
_pipeline.BlendDescriptors[index] = descriptor;
|
||||||
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_currentSpecState.SetDualSourceBlendEnabled(dualSourceBlendEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -141,6 +141,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
|
return _oldSpecState.GraphicsState.HasConstantBufferDrawParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool QueryDualSourceBlendEnable()
|
||||||
|
{
|
||||||
|
return _oldSpecState.GraphicsState.DualSourceBlendEnable;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public InputTopology QueryPrimitiveTopology()
|
public InputTopology QueryPrimitiveTopology()
|
||||||
{
|
{
|
||||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 4368;
|
private const uint CodeGenVersion = 4404;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
@@ -157,6 +157,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
|
return _state.GraphicsState.HasUnalignedStorageBuffer || _state.ComputeState.HasUnalignedStorageBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool QueryDualSourceBlendEnable()
|
||||||
|
{
|
||||||
|
return _state.GraphicsState.DualSourceBlendEnable;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public InputTopology QueryPrimitiveTopology()
|
public InputTopology QueryPrimitiveTopology()
|
||||||
{
|
{
|
||||||
|
@@ -92,6 +92,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Array8<AttributeType> FragmentOutputTypes;
|
public Array8<AttributeType> FragmentOutputTypes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether dual source blend is enabled.
|
||||||
|
/// </summary>
|
||||||
|
public bool DualSourceBlendEnable;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new GPU graphics state.
|
/// Creates a new GPU graphics state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -111,6 +116,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
|
/// <param name="hasConstantBufferDrawParameters">Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0</param>
|
||||||
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
|
/// <param name="hasUnalignedStorageBuffer">Indicates that any storage buffer use is unaligned</param>
|
||||||
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
|
/// <param name="fragmentOutputTypes">Type of the fragment shader outputs</param>
|
||||||
|
/// <param name="dualSourceBlendEnable">Type of the vertex attributes consumed by the shader</param>
|
||||||
public GpuChannelGraphicsState(
|
public GpuChannelGraphicsState(
|
||||||
bool earlyZForce,
|
bool earlyZForce,
|
||||||
PrimitiveTopology topology,
|
PrimitiveTopology topology,
|
||||||
@@ -127,7 +133,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
ref Array32<AttributeType> attributeTypes,
|
ref Array32<AttributeType> attributeTypes,
|
||||||
bool hasConstantBufferDrawParameters,
|
bool hasConstantBufferDrawParameters,
|
||||||
bool hasUnalignedStorageBuffer,
|
bool hasUnalignedStorageBuffer,
|
||||||
ref Array8<AttributeType> fragmentOutputTypes)
|
ref Array8<AttributeType> fragmentOutputTypes,
|
||||||
|
bool dualSourceBlendEnable)
|
||||||
{
|
{
|
||||||
EarlyZForce = earlyZForce;
|
EarlyZForce = earlyZForce;
|
||||||
Topology = topology;
|
Topology = topology;
|
||||||
@@ -145,6 +152,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
|
HasConstantBufferDrawParameters = hasConstantBufferDrawParameters;
|
||||||
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
HasUnalignedStorageBuffer = hasUnalignedStorageBuffer;
|
||||||
FragmentOutputTypes = fragmentOutputTypes;
|
FragmentOutputTypes = fragmentOutputTypes;
|
||||||
|
DualSourceBlendEnable = dualSourceBlendEnable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -535,6 +535,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (graphicsState.DualSourceBlendEnable != GraphicsState.DualSourceBlendEnable)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
return Matches(channel, ref poolState, checkTextures, isCompute: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -833,31 +833,13 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
(BlendingFactorSrc)blend.AlphaSrcFactor.Convert(),
|
(BlendingFactorSrc)blend.AlphaSrcFactor.Convert(),
|
||||||
(BlendingFactorDest)blend.AlphaDstFactor.Convert());
|
(BlendingFactorDest)blend.AlphaDstFactor.Convert());
|
||||||
|
|
||||||
static bool IsDualSource(BlendFactor factor)
|
|
||||||
{
|
|
||||||
switch (factor)
|
|
||||||
{
|
|
||||||
case BlendFactor.Src1Color:
|
|
||||||
case BlendFactor.Src1ColorGl:
|
|
||||||
case BlendFactor.Src1Alpha:
|
|
||||||
case BlendFactor.Src1AlphaGl:
|
|
||||||
case BlendFactor.OneMinusSrc1Color:
|
|
||||||
case BlendFactor.OneMinusSrc1ColorGl:
|
|
||||||
case BlendFactor.OneMinusSrc1Alpha:
|
|
||||||
case BlendFactor.OneMinusSrc1AlphaGl:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureFramebuffer();
|
EnsureFramebuffer();
|
||||||
|
|
||||||
_framebuffer.SetDualSourceBlend(
|
_framebuffer.SetDualSourceBlend(
|
||||||
IsDualSource(blend.ColorSrcFactor) ||
|
blend.ColorSrcFactor.IsDualSource() ||
|
||||||
IsDualSource(blend.ColorDstFactor) ||
|
blend.ColorDstFactor.IsDualSource() ||
|
||||||
IsDualSource(blend.AlphaSrcFactor) ||
|
blend.AlphaSrcFactor.IsDualSource() ||
|
||||||
IsDualSource(blend.AlphaDstFactor));
|
blend.AlphaDstFactor.IsDualSource());
|
||||||
|
|
||||||
if (_blendConstant != blend.BlendConstant)
|
if (_blendConstant != blend.BlendConstant)
|
||||||
{
|
{
|
||||||
|
@@ -612,6 +612,19 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int usedAttributes = context.Config.UsedOutputAttributes;
|
int usedAttributes = context.Config.UsedOutputAttributes;
|
||||||
|
|
||||||
|
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
|
||||||
|
{
|
||||||
|
int firstOutput = BitOperations.TrailingZeroCount(usedAttributes);
|
||||||
|
int mask = 3 << firstOutput;
|
||||||
|
|
||||||
|
if ((usedAttributes & mask) == mask)
|
||||||
|
{
|
||||||
|
usedAttributes &= ~mask;
|
||||||
|
DeclareOutputDualSourceBlendAttribute(context, firstOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (usedAttributes != 0)
|
while (usedAttributes != 0)
|
||||||
{
|
{
|
||||||
int index = BitOperations.TrailingZeroCount(usedAttributes);
|
int index = BitOperations.TrailingZeroCount(usedAttributes);
|
||||||
@@ -690,6 +703,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DeclareOutputDualSourceBlendAttribute(CodeGenContext context, int attr)
|
||||||
|
{
|
||||||
|
string name = $"{DefaultNames.OAttributePrefix}{attr}";
|
||||||
|
string name2 = $"{DefaultNames.OAttributePrefix}{(attr + 1)}";
|
||||||
|
|
||||||
|
context.AppendLine($"layout (location = {attr}, index = 0) out vec4 {name};");
|
||||||
|
context.AppendLine($"layout (location = {attr}, index = 1) out vec4 {name2};");
|
||||||
|
}
|
||||||
|
|
||||||
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
|
private static void DeclareUsedOutputAttributesPerPatch(CodeGenContext context, HashSet<int> attrs)
|
||||||
{
|
{
|
||||||
foreach (int attr in attrs.Order())
|
foreach (int attr in attrs.Order())
|
||||||
|
@@ -6,6 +6,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
using static Spv.Specification;
|
using static Spv.Specification;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
@@ -622,7 +623,27 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd)
|
else if (attr >= AttributeConsts.FragmentOutputColorBase && attr < AttributeConsts.FragmentOutputColorEnd)
|
||||||
{
|
{
|
||||||
int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16;
|
int location = (attr - AttributeConsts.FragmentOutputColorBase) / 16;
|
||||||
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
|
|
||||||
|
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryDualSourceBlendEnable())
|
||||||
|
{
|
||||||
|
int firstLocation = BitOperations.TrailingZeroCount(context.Config.UsedOutputAttributes);
|
||||||
|
int index = location - firstLocation;
|
||||||
|
int mask = 3 << firstLocation;
|
||||||
|
|
||||||
|
if ((uint)index < 2 && (context.Config.UsedOutputAttributes & mask) == mask)
|
||||||
|
{
|
||||||
|
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)firstLocation);
|
||||||
|
context.Decorate(spvVar, Decoration.Index, (LiteralInteger)index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Decorate(spvVar, Decoration.Location, (LiteralInteger)location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isOutAttr)
|
if (!isOutAttr)
|
||||||
|
@@ -205,6 +205,15 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries dual source blend state.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if blending is enabled with a dual source blend equation, false otherwise</returns>
|
||||||
|
bool QueryDualSourceBlendEnable()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
/// Queries host about the presence of the FrontFacing built-in variable bug.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -9,21 +9,18 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private ulong MaxDeviceMemoryUsageEstimate = 16UL * 1024 * 1024 * 1024;
|
private ulong MaxDeviceMemoryUsageEstimate = 16UL * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
private readonly Vk _api;
|
private readonly Vk _api;
|
||||||
private readonly PhysicalDevice _physicalDevice;
|
private readonly VulkanPhysicalDevice _physicalDevice;
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
private readonly List<MemoryAllocatorBlockList> _blockLists;
|
private readonly List<MemoryAllocatorBlockList> _blockLists;
|
||||||
private readonly int _blockAlignment;
|
private readonly int _blockAlignment;
|
||||||
private readonly PhysicalDeviceMemoryProperties _physicalDeviceMemoryProperties;
|
|
||||||
|
|
||||||
public MemoryAllocator(Vk api, PhysicalDevice physicalDevice, Device device, uint maxMemoryAllocationCount)
|
public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device)
|
||||||
{
|
{
|
||||||
_api = api;
|
_api = api;
|
||||||
_physicalDevice = physicalDevice;
|
_physicalDevice = physicalDevice;
|
||||||
_device = device;
|
_device = device;
|
||||||
_blockLists = new List<MemoryAllocatorBlockList>();
|
_blockLists = new List<MemoryAllocatorBlockList>();
|
||||||
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / (ulong)maxMemoryAllocationCount);
|
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / (ulong)_physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount);
|
||||||
|
|
||||||
_api.GetPhysicalDeviceMemoryProperties(_physicalDevice, out _physicalDeviceMemoryProperties);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemoryAllocation AllocateDeviceMemory(
|
public MemoryAllocation AllocateDeviceMemory(
|
||||||
@@ -64,9 +61,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
uint memoryTypeBits,
|
uint memoryTypeBits,
|
||||||
MemoryPropertyFlags flags)
|
MemoryPropertyFlags flags)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _physicalDeviceMemoryProperties.MemoryTypeCount; i++)
|
for (int i = 0; i < _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypeCount; i++)
|
||||||
{
|
{
|
||||||
var type = _physicalDeviceMemoryProperties.MemoryTypes[i];
|
var type = _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypes[i];
|
||||||
|
|
||||||
if ((memoryTypeBits & (1 << i)) != 0)
|
if ((memoryTypeBits & (1 << i)) != 0)
|
||||||
{
|
{
|
||||||
@@ -80,15 +77,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsDeviceMemoryShared(Vk api, PhysicalDevice physicalDevice)
|
public static bool IsDeviceMemoryShared(VulkanPhysicalDevice physicalDevice)
|
||||||
{
|
{
|
||||||
// The device is regarded as having shared memory if all heaps have the device local bit.
|
for (int i = 0; i < physicalDevice.PhysicalDeviceMemoryProperties.MemoryHeapCount; i++)
|
||||||
|
|
||||||
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
|
|
||||||
|
|
||||||
for (int i = 0; i < properties.MemoryHeapCount; i++)
|
|
||||||
{
|
{
|
||||||
if (!properties.MemoryHeaps[i].Flags.HasFlag(MemoryHeapFlags.DeviceLocalBit))
|
if (!physicalDevice.PhysicalDeviceMemoryProperties.MemoryHeaps[i].Flags.HasFlag(MemoryHeapFlags.DeviceLocalBit))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
153
Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs
Normal file
153
Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.EXT;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
class VulkanDebugMessenger : IDisposable
|
||||||
|
{
|
||||||
|
private static string[] _excludedMessages = new string[]
|
||||||
|
{
|
||||||
|
// NOTE: Done on purpose right now.
|
||||||
|
"UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
|
||||||
|
// TODO: Figure out if fixable
|
||||||
|
"VUID-vkCmdDrawIndexed-None-04584",
|
||||||
|
// TODO: Might be worth looking into making this happy to possibly optimize copies.
|
||||||
|
"UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout",
|
||||||
|
// TODO: Fix this, it's causing too much noise right now.
|
||||||
|
"VUID-VkSubpassDependency-srcSubpass-00867"
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly Vk _api;
|
||||||
|
private readonly Instance _instance;
|
||||||
|
private readonly GraphicsDebugLevel _logLevel;
|
||||||
|
private readonly ExtDebugUtils _debugUtils;
|
||||||
|
private readonly DebugUtilsMessengerEXT? _debugUtilsMessenger;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public VulkanDebugMessenger(Vk api, Instance instance, GraphicsDebugLevel logLevel)
|
||||||
|
{
|
||||||
|
_api = api;
|
||||||
|
_instance = instance;
|
||||||
|
_logLevel = logLevel;
|
||||||
|
|
||||||
|
_api.TryGetInstanceExtension(instance, out _debugUtils);
|
||||||
|
|
||||||
|
Result result = TryInitialize(out _debugUtilsMessenger);
|
||||||
|
|
||||||
|
if (result != Result.Success)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Gpu, $"Vulkan debug messenger initialization failed with error {result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result TryInitialize(out DebugUtilsMessengerEXT? debugUtilsMessengerHandle)
|
||||||
|
{
|
||||||
|
debugUtilsMessengerHandle = null;
|
||||||
|
|
||||||
|
if (_debugUtils != null && _logLevel != GraphicsDebugLevel.None)
|
||||||
|
{
|
||||||
|
var messageType = _logLevel switch
|
||||||
|
{
|
||||||
|
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt,
|
||||||
|
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||||
|
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||||
|
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
|
||||||
|
DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||||
|
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||||
|
_ => throw new ArgumentException($"Invalid log level \"{_logLevel}\".")
|
||||||
|
};
|
||||||
|
|
||||||
|
var messageSeverity = _logLevel switch
|
||||||
|
{
|
||||||
|
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||||
|
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt |
|
||||||
|
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
|
||||||
|
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.InfoBitExt |
|
||||||
|
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
|
||||||
|
DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
|
||||||
|
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||||
|
_ => throw new ArgumentException($"Invalid log level \"{_logLevel}\".")
|
||||||
|
};
|
||||||
|
|
||||||
|
var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT()
|
||||||
|
{
|
||||||
|
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
||||||
|
MessageType = messageType,
|
||||||
|
MessageSeverity = messageSeverity
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
debugUtilsMessengerCreateInfo.PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(UserCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugUtilsMessengerEXT messengerHandle = default;
|
||||||
|
|
||||||
|
Result result = _debugUtils.CreateDebugUtilsMessenger(_instance, SpanHelpers.AsReadOnlySpan(ref debugUtilsMessengerCreateInfo), ReadOnlySpan<AllocationCallbacks>.Empty, SpanHelpers.AsSpan(ref messengerHandle));
|
||||||
|
|
||||||
|
if (result == Result.Success)
|
||||||
|
{
|
||||||
|
debugUtilsMessengerHandle = messengerHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe static uint UserCallback(
|
||||||
|
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
||||||
|
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
||||||
|
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||||
|
void* pUserData)
|
||||||
|
{
|
||||||
|
var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage);
|
||||||
|
|
||||||
|
foreach (string excludedMessagePart in _excludedMessages)
|
||||||
|
{
|
||||||
|
if (msg.Contains(excludedMessagePart))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt))
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Gpu, msg);
|
||||||
|
}
|
||||||
|
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt))
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu, msg);
|
||||||
|
}
|
||||||
|
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.InfoBitExt))
|
||||||
|
{
|
||||||
|
Logger.Info?.Print(LogClass.Gpu, msg);
|
||||||
|
}
|
||||||
|
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt))
|
||||||
|
{
|
||||||
|
Logger.Debug?.Print(LogClass.Gpu, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (_debugUtilsMessenger.HasValue)
|
||||||
|
{
|
||||||
|
_debugUtils.DestroyDebugUtilsMessenger(_instance, _debugUtilsMessenger.Value, Span<AllocationCallbacks>.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
||||||
private const int QueuesCount = 2;
|
private const int QueuesCount = 2;
|
||||||
|
|
||||||
public static string[] DesirableExtensions { get; } = new string[]
|
private static readonly string[] _desirableExtensions = new string[]
|
||||||
{
|
{
|
||||||
ExtConditionalRendering.ExtensionName,
|
ExtConditionalRendering.ExtensionName,
|
||||||
ExtExtendedDynamicState.ExtensionName,
|
ExtExtendedDynamicState.ExtensionName,
|
||||||
@@ -42,52 +42,28 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
"VK_KHR_portability_subset", // By spec, we should enable this if present.
|
"VK_KHR_portability_subset", // By spec, we should enable this if present.
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string[] RequiredExtensions { get; } = new string[]
|
private static readonly string[] _requiredExtensions = new string[]
|
||||||
{
|
{
|
||||||
KhrSwapchain.ExtensionName
|
KhrSwapchain.ExtensionName
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string[] _excludedMessages = new string[]
|
internal static VulkanInstance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions)
|
||||||
{
|
|
||||||
// NOTE: Done on purpose right now.
|
|
||||||
"UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
|
|
||||||
// TODO: Figure out if fixable
|
|
||||||
"VUID-vkCmdDrawIndexed-None-04584",
|
|
||||||
// TODO: Might be worth looking into making this happy to possibly optimize copies.
|
|
||||||
"UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout",
|
|
||||||
// TODO: Fix this, it's causing too much noise right now.
|
|
||||||
"VUID-VkSubpassDependency-srcSubpass-00867"
|
|
||||||
};
|
|
||||||
|
|
||||||
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugUtils debugUtils, out DebugUtilsMessengerEXT debugUtilsMessenger)
|
|
||||||
{
|
{
|
||||||
var enabledLayers = new List<string>();
|
var enabledLayers = new List<string>();
|
||||||
|
|
||||||
|
var instanceExtensions = VulkanInstance.GetInstanceExtensions(api);
|
||||||
|
var instanceLayers = VulkanInstance.GetInstanceLayers(api);
|
||||||
|
|
||||||
void AddAvailableLayer(string layerName)
|
void AddAvailableLayer(string layerName)
|
||||||
{
|
{
|
||||||
uint layerPropertiesCount;
|
if (instanceLayers.Contains(layerName))
|
||||||
|
|
||||||
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
|
|
||||||
|
|
||||||
LayerProperties[] layerProperties = new LayerProperties[layerPropertiesCount];
|
|
||||||
|
|
||||||
fixed (LayerProperties* pLayerProperties = layerProperties)
|
|
||||||
{
|
{
|
||||||
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
|
enabledLayers.Add(layerName);
|
||||||
|
}
|
||||||
for (int i = 0; i < layerPropertiesCount; i++)
|
else
|
||||||
{
|
{
|
||||||
string currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
|
Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}");
|
||||||
|
|
||||||
if (currentLayerName == layerName)
|
|
||||||
{
|
|
||||||
enabledLayers.Add(layerName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logLevel != GraphicsDebugLevel.None)
|
if (logLevel != GraphicsDebugLevel.None)
|
||||||
@@ -95,7 +71,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
AddAvailableLayer("VK_LAYER_KHRONOS_validation");
|
AddAvailableLayer("VK_LAYER_KHRONOS_validation");
|
||||||
}
|
}
|
||||||
|
|
||||||
var enabledExtensions = requiredExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
|
var enabledExtensions = requiredExtensions;
|
||||||
|
|
||||||
|
if (instanceExtensions.Contains("VK_EXT_debug_utils"))
|
||||||
|
{
|
||||||
|
enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
var appName = Marshal.StringToHGlobalAnsi(AppName);
|
var appName = Marshal.StringToHGlobalAnsi(AppName);
|
||||||
|
|
||||||
@@ -131,7 +112,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
EnabledLayerCount = (uint)enabledLayers.Count
|
EnabledLayerCount = (uint)enabledLayers.Count
|
||||||
};
|
};
|
||||||
|
|
||||||
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
|
Result result = VulkanInstance.Create(api, ref instanceCreateInfo, out var instance);
|
||||||
|
|
||||||
Marshal.FreeHGlobal(appName);
|
Marshal.FreeHGlobal(appName);
|
||||||
|
|
||||||
@@ -145,59 +126,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateDebugMessenger(api, logLevel, instance, out debugUtils, out debugUtilsMessenger);
|
result.ThrowOnError();
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe static uint DebugMessenger(
|
internal static VulkanPhysicalDevice FindSuitablePhysicalDevice(Vk api, VulkanInstance instance, SurfaceKHR surface, string preferredGpuId)
|
||||||
DebugUtilsMessageSeverityFlagsEXT messageSeverity,
|
|
||||||
DebugUtilsMessageTypeFlagsEXT messageTypes,
|
|
||||||
DebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
|
||||||
void* pUserData)
|
|
||||||
{
|
{
|
||||||
var msg = Marshal.PtrToStringAnsi((IntPtr)pCallbackData->PMessage);
|
instance.EnumeratePhysicalDevices(out var physicalDevices).ThrowOnError();
|
||||||
|
|
||||||
foreach (string excludedMessagePart in _excludedMessages)
|
|
||||||
{
|
|
||||||
if (msg.Contains(excludedMessagePart))
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt))
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Gpu, msg);
|
|
||||||
}
|
|
||||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.WarningBitExt))
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Gpu, msg);
|
|
||||||
}
|
|
||||||
else if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.InfoBitExt))
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.Gpu, msg);
|
|
||||||
}
|
|
||||||
else // if (messageSeverity.HasFlag(DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt))
|
|
||||||
{
|
|
||||||
Logger.Debug?.Print(LogClass.Gpu, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static PhysicalDevice FindSuitablePhysicalDevice(Vk api, Instance instance, SurfaceKHR surface, string preferredGpuId)
|
|
||||||
{
|
|
||||||
uint physicalDeviceCount;
|
|
||||||
|
|
||||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
|
|
||||||
|
|
||||||
PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
|
|
||||||
|
|
||||||
fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
|
|
||||||
{
|
|
||||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
|
|
||||||
}
|
|
||||||
|
|
||||||
// First we try to pick the the user preferred GPU.
|
// First we try to pick the the user preferred GPU.
|
||||||
for (int i = 0; i < physicalDevices.Length; i++)
|
for (int i = 0; i < physicalDevices.Length; i++)
|
||||||
@@ -243,76 +179,41 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
EnabledLayerCount = 0
|
EnabledLayerCount = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
|
Result result = VulkanInstance.Create(api, ref instanceCreateInfo, out var rawInstance);
|
||||||
|
|
||||||
// We ensure that vkEnumerateInstanceVersion is present (added in 1.1).
|
|
||||||
// If the instance doesn't support it, no device is going to be 1.1 compatible.
|
|
||||||
if (api.GetInstanceProcAddr(instance, "vkEnumerateInstanceVersion") == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
api.DestroyInstance(instance, null);
|
|
||||||
|
|
||||||
return Array.Empty<DeviceInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We currently assume that the instance is compatible with Vulkan 1.2
|
|
||||||
// TODO: Remove this once we relax our initialization codepaths.
|
|
||||||
uint instanceApiVerison = 0;
|
|
||||||
api.EnumerateInstanceVersion(ref instanceApiVerison).ThrowOnError();
|
|
||||||
|
|
||||||
if (instanceApiVerison < MinimalInstanceVulkanVersion)
|
|
||||||
{
|
|
||||||
api.DestroyInstance(instance, null);
|
|
||||||
|
|
||||||
return Array.Empty<DeviceInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal(appName);
|
Marshal.FreeHGlobal(appName);
|
||||||
|
|
||||||
uint physicalDeviceCount;
|
result.ThrowOnError();
|
||||||
|
|
||||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
|
using VulkanInstance instance = rawInstance;
|
||||||
|
|
||||||
PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
|
// We currently assume that the instance is compatible with Vulkan 1.2
|
||||||
|
// TODO: Remove this once we relax our initialization codepaths.
|
||||||
fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
|
if (instance.InstanceVersion < MinimalInstanceVulkanVersion)
|
||||||
{
|
{
|
||||||
api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
|
return Array.Empty<DeviceInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceInfo[] devices = new DeviceInfo[physicalDevices.Length];
|
instance.EnumeratePhysicalDevices(out VulkanPhysicalDevice[] physicalDevices).ThrowOnError();
|
||||||
|
|
||||||
for (int i = 0; i < physicalDevices.Length; i++)
|
List<DeviceInfo> deviceInfos = new List<DeviceInfo>();
|
||||||
|
|
||||||
|
foreach (VulkanPhysicalDevice physicalDevice in physicalDevices)
|
||||||
{
|
{
|
||||||
var physicalDevice = physicalDevices[i];
|
if (physicalDevice.PhysicalDeviceProperties.ApiVersion < MinimalVulkanVersion)
|
||||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
|
||||||
|
|
||||||
if (properties.ApiVersion < MinimalVulkanVersion)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
devices[i] = new DeviceInfo(
|
deviceInfos.Add(physicalDevice.ToDeviceInfo());
|
||||||
StringFromIdPair(properties.VendorID, properties.DeviceID),
|
|
||||||
VendorUtils.GetNameFromId(properties.VendorID),
|
|
||||||
Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName),
|
|
||||||
properties.DeviceType == PhysicalDeviceType.DiscreteGpu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api.DestroyInstance(instance, null);
|
return deviceInfos.ToArray();
|
||||||
|
|
||||||
return devices;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string StringFromIdPair(uint vendorId, uint deviceId)
|
private static bool IsPreferredAndSuitableDevice(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId)
|
||||||
{
|
{
|
||||||
return $"0x{vendorId:X}_0x{deviceId:X}";
|
if (physicalDevice.Id != preferredGpuId)
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsPreferredAndSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId)
|
|
||||||
{
|
|
||||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
|
||||||
|
|
||||||
if (StringFromIdPair(properties.VendorID, properties.DeviceID) != preferredGpuId)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -320,68 +221,47 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return IsSuitableDevice(api, physicalDevice, surface);
|
return IsSuitableDevice(api, physicalDevice, surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface)
|
private static bool IsSuitableDevice(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface)
|
||||||
{
|
{
|
||||||
int extensionMatches = 0;
|
int extensionMatches = 0;
|
||||||
uint propertiesCount;
|
|
||||||
|
|
||||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
|
foreach (string requiredExtension in _requiredExtensions)
|
||||||
|
|
||||||
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
|
||||||
|
|
||||||
fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
|
|
||||||
{
|
{
|
||||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
|
if (physicalDevice.IsDeviceExtensionPresent(requiredExtension))
|
||||||
|
|
||||||
for (int i = 0; i < propertiesCount; i++)
|
|
||||||
{
|
{
|
||||||
string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
|
extensionMatches++;
|
||||||
|
|
||||||
if (RequiredExtensions.Contains(extensionName))
|
|
||||||
{
|
|
||||||
extensionMatches++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensionMatches == RequiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
return extensionMatches == _requiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
|
internal static uint FindSuitableQueueFamily(Vk api, VulkanPhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
|
||||||
{
|
{
|
||||||
const QueueFlags RequiredFlags = QueueFlags.GraphicsBit | QueueFlags.ComputeBit;
|
const QueueFlags RequiredFlags = QueueFlags.GraphicsBit | QueueFlags.ComputeBit;
|
||||||
|
|
||||||
var khrSurface = new KhrSurface(api.Context);
|
var khrSurface = new KhrSurface(api.Context);
|
||||||
|
|
||||||
uint propertiesCount;
|
for (uint index = 0; index < physicalDevice.QueueFamilyProperties.Length; index++)
|
||||||
|
|
||||||
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, null);
|
|
||||||
|
|
||||||
QueueFamilyProperties[] properties = new QueueFamilyProperties[propertiesCount];
|
|
||||||
|
|
||||||
fixed (QueueFamilyProperties* pProperties = properties)
|
|
||||||
{
|
{
|
||||||
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, pProperties);
|
ref QueueFamilyProperties property = ref physicalDevice.QueueFamilyProperties[index];
|
||||||
}
|
|
||||||
|
|
||||||
for (uint index = 0; index < propertiesCount; index++)
|
khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice.PhysicalDevice, index, surface, out var surfaceSupported).ThrowOnError();
|
||||||
{
|
|
||||||
var queueFlags = properties[index].QueueFlags;
|
|
||||||
|
|
||||||
khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice, index, surface, out var surfaceSupported).ThrowOnError();
|
if (property.QueueFlags.HasFlag(RequiredFlags) && surfaceSupported)
|
||||||
|
|
||||||
if (queueFlags.HasFlag(RequiredFlags) && surfaceSupported)
|
|
||||||
{
|
{
|
||||||
queueCount = properties[index].QueueCount;
|
queueCount = property.QueueCount;
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queueCount = 0;
|
queueCount = 0;
|
||||||
|
|
||||||
return InvalidIndex;
|
return InvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Device CreateDevice(Vk api, PhysicalDevice physicalDevice, uint queueFamilyIndex, string[] supportedExtensions, uint queueCount)
|
internal static Device CreateDevice(Vk api, VulkanPhysicalDevice physicalDevice, uint queueFamilyIndex, uint queueCount)
|
||||||
{
|
{
|
||||||
if (queueCount > QueuesCount)
|
if (queueCount > QueuesCount)
|
||||||
{
|
{
|
||||||
@@ -403,8 +283,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PQueuePriorities = queuePriorities
|
PQueuePriorities = queuePriorities
|
||||||
};
|
};
|
||||||
|
|
||||||
api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
bool useRobustBufferAccess = VendorUtils.FromId(physicalDevice.PhysicalDeviceProperties.VendorID) == Vendor.Nvidia;
|
||||||
bool useRobustBufferAccess = VendorUtils.FromId(properties.VendorID) == Vendor.Nvidia;
|
|
||||||
|
|
||||||
PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
|
PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
|
||||||
{
|
{
|
||||||
@@ -425,7 +304,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PNext = features2.PNext
|
PNext = features2.PNext
|
||||||
};
|
};
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_custom_border_color"))
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color"))
|
||||||
{
|
{
|
||||||
features2.PNext = &supportedFeaturesCustomBorderColor;
|
features2.PNext = &supportedFeaturesCustomBorderColor;
|
||||||
}
|
}
|
||||||
@@ -436,7 +315,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PNext = features2.PNext
|
PNext = features2.PNext
|
||||||
};
|
};
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_primitive_topology_list_restart"))
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
|
||||||
{
|
{
|
||||||
features2.PNext = &supportedFeaturesPrimitiveTopologyListRestart;
|
features2.PNext = &supportedFeaturesPrimitiveTopologyListRestart;
|
||||||
}
|
}
|
||||||
@@ -447,7 +326,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PNext = features2.PNext
|
PNext = features2.PNext
|
||||||
};
|
};
|
||||||
|
|
||||||
if (supportedExtensions.Contains(ExtTransformFeedback.ExtensionName))
|
if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName))
|
||||||
{
|
{
|
||||||
features2.PNext = &supportedFeaturesTransformFeedback;
|
features2.PNext = &supportedFeaturesTransformFeedback;
|
||||||
}
|
}
|
||||||
@@ -457,14 +336,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt
|
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt
|
||||||
};
|
};
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
|
||||||
{
|
{
|
||||||
supportedFeaturesRobustness2.PNext = features2.PNext;
|
supportedFeaturesRobustness2.PNext = features2.PNext;
|
||||||
|
|
||||||
features2.PNext = &supportedFeaturesRobustness2;
|
features2.PNext = &supportedFeaturesRobustness2;
|
||||||
}
|
}
|
||||||
|
|
||||||
api.GetPhysicalDeviceFeatures2(physicalDevice, &features2);
|
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
|
||||||
|
|
||||||
var supportedFeatures = features2.Features;
|
var supportedFeatures = features2.Features;
|
||||||
|
|
||||||
@@ -497,7 +376,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback;
|
PhysicalDeviceTransformFeedbackFeaturesEXT featuresTransformFeedback;
|
||||||
|
|
||||||
if (supportedExtensions.Contains(ExtTransformFeedback.ExtensionName))
|
if (physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName))
|
||||||
{
|
{
|
||||||
featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT()
|
featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT()
|
||||||
{
|
{
|
||||||
@@ -511,7 +390,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart;
|
PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT featuresPrimitiveTopologyListRestart;
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_primitive_topology_list_restart"))
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
|
||||||
{
|
{
|
||||||
featuresPrimitiveTopologyListRestart = new PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT()
|
featuresPrimitiveTopologyListRestart = new PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT()
|
||||||
{
|
{
|
||||||
@@ -526,7 +405,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2;
|
PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2;
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
|
||||||
{
|
{
|
||||||
featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
|
featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
|
||||||
{
|
{
|
||||||
@@ -542,7 +421,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt,
|
SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt,
|
||||||
PNext = pExtendedFeatures,
|
PNext = pExtendedFeatures,
|
||||||
ExtendedDynamicState = supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName)
|
ExtendedDynamicState = physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName)
|
||||||
};
|
};
|
||||||
|
|
||||||
pExtendedFeatures = &featuresExtendedDynamicState;
|
pExtendedFeatures = &featuresExtendedDynamicState;
|
||||||
@@ -560,16 +439,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||||
PNext = pExtendedFeatures,
|
PNext = pExtendedFeatures,
|
||||||
DescriptorIndexing = supportedExtensions.Contains("VK_EXT_descriptor_indexing"),
|
DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"),
|
||||||
DrawIndirectCount = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName),
|
DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
|
||||||
UniformBufferStandardLayout = supportedExtensions.Contains("VK_KHR_uniform_buffer_standard_layout")
|
UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout")
|
||||||
};
|
};
|
||||||
|
|
||||||
pExtendedFeatures = &featuresVk12;
|
pExtendedFeatures = &featuresVk12;
|
||||||
|
|
||||||
PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8;
|
PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8;
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_index_type_uint8"))
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"))
|
||||||
{
|
{
|
||||||
featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT()
|
featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT()
|
||||||
{
|
{
|
||||||
@@ -583,7 +462,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock;
|
PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock;
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"))
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"))
|
||||||
{
|
{
|
||||||
featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT()
|
featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT()
|
||||||
{
|
{
|
||||||
@@ -597,7 +476,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl;
|
PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl;
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_subgroup_size_control"))
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_subgroup_size_control"))
|
||||||
{
|
{
|
||||||
featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT()
|
featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT()
|
||||||
{
|
{
|
||||||
@@ -611,7 +490,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
|
PhysicalDeviceCustomBorderColorFeaturesEXT featuresCustomBorderColor;
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_custom_border_color") &&
|
if (physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&
|
||||||
supportedFeaturesCustomBorderColor.CustomBorderColors &&
|
supportedFeaturesCustomBorderColor.CustomBorderColors &&
|
||||||
supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat)
|
supportedFeaturesCustomBorderColor.CustomBorderColorWithoutFormat)
|
||||||
{
|
{
|
||||||
@@ -626,7 +505,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
pExtendedFeatures = &featuresCustomBorderColor;
|
pExtendedFeatures = &featuresCustomBorderColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
var enabledExtensions = RequiredExtensions.Union(DesirableExtensions.Intersect(supportedExtensions)).ToArray();
|
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(physicalDevice.DeviceExtensions)).ToArray();
|
||||||
|
|
||||||
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
||||||
|
|
||||||
@@ -646,7 +525,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PEnabledFeatures = &features
|
PEnabledFeatures = &features
|
||||||
};
|
};
|
||||||
|
|
||||||
api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
|
api.CreateDevice(physicalDevice.PhysicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
|
||||||
|
|
||||||
for (int i = 0; i < enabledExtensions.Length; i++)
|
for (int i = 0; i < enabledExtensions.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -655,82 +534,5 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string[] GetSupportedExtensions(Vk api, PhysicalDevice physicalDevice)
|
|
||||||
{
|
|
||||||
uint propertiesCount;
|
|
||||||
|
|
||||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
|
|
||||||
|
|
||||||
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
|
||||||
|
|
||||||
fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
|
|
||||||
{
|
|
||||||
api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex)
|
|
||||||
{
|
|
||||||
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal unsafe static void CreateDebugMessenger(
|
|
||||||
Vk api,
|
|
||||||
GraphicsDebugLevel logLevel,
|
|
||||||
Instance instance,
|
|
||||||
out ExtDebugUtils debugUtils,
|
|
||||||
out DebugUtilsMessengerEXT debugUtilsMessenger)
|
|
||||||
{
|
|
||||||
debugUtils = default;
|
|
||||||
|
|
||||||
if (logLevel != GraphicsDebugLevel.None)
|
|
||||||
{
|
|
||||||
if (!api.TryGetInstanceExtension(instance, out debugUtils))
|
|
||||||
{
|
|
||||||
debugUtilsMessenger = default;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterLogType = logLevel switch
|
|
||||||
{
|
|
||||||
GraphicsDebugLevel.Error => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt,
|
|
||||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
|
||||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
|
||||||
GraphicsDebugLevel.All => DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
|
|
||||||
DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
|
||||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
|
||||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
|
||||||
};
|
|
||||||
|
|
||||||
var filterLogSeverity = logLevel switch
|
|
||||||
{
|
|
||||||
GraphicsDebugLevel.Error => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
|
||||||
GraphicsDebugLevel.Slowdowns => DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt |
|
|
||||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
|
|
||||||
GraphicsDebugLevel.All => DebugUtilsMessageSeverityFlagsEXT.InfoBitExt |
|
|
||||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
|
|
||||||
DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
|
|
||||||
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
|
||||||
_ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
|
|
||||||
};
|
|
||||||
|
|
||||||
var debugUtilsMessengerCreateInfo = new DebugUtilsMessengerCreateInfoEXT()
|
|
||||||
{
|
|
||||||
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
|
||||||
MessageType = filterLogType,
|
|
||||||
MessageSeverity = filterLogSeverity,
|
|
||||||
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugMessenger)
|
|
||||||
};
|
|
||||||
|
|
||||||
debugUtils.CreateDebugUtilsMessenger(instance, in debugUtilsMessengerCreateInfo, null, out debugUtilsMessenger).ThrowOnError();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
debugUtilsMessenger = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
127
Ryujinx.Graphics.Vulkan/VulkanInstance.cs
Normal file
127
Ryujinx.Graphics.Vulkan/VulkanInstance.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Silk.NET.Core;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
class VulkanInstance : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Vk _api;
|
||||||
|
public readonly Instance Instance;
|
||||||
|
public readonly Version32 InstanceVersion;
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
private VulkanInstance(Vk api, Instance instance)
|
||||||
|
{
|
||||||
|
_api = api;
|
||||||
|
Instance = instance;
|
||||||
|
|
||||||
|
if (api.GetInstanceProcAddr(instance, "vkEnumerateInstanceVersion") == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
InstanceVersion = Vk.Version10;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint rawInstanceVersion = 0;
|
||||||
|
|
||||||
|
if (api.EnumerateInstanceVersion(ref rawInstanceVersion) != Result.Success)
|
||||||
|
{
|
||||||
|
rawInstanceVersion = Vk.Version11.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceVersion = (Version32)rawInstanceVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result Create(Vk api, ref InstanceCreateInfo createInfo, out VulkanInstance instance)
|
||||||
|
{
|
||||||
|
instance = null;
|
||||||
|
|
||||||
|
Instance rawInstance = default;
|
||||||
|
|
||||||
|
Result result = api.CreateInstance(SpanHelpers.AsReadOnlySpan(ref createInfo), ReadOnlySpan<AllocationCallbacks>.Empty, SpanHelpers.AsSpan(ref rawInstance));
|
||||||
|
|
||||||
|
if (result == Result.Success)
|
||||||
|
{
|
||||||
|
instance = new VulkanInstance(api, rawInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result EnumeratePhysicalDevices(out VulkanPhysicalDevice[] physicalDevices)
|
||||||
|
{
|
||||||
|
physicalDevices = null;
|
||||||
|
|
||||||
|
uint physicalDeviceCount = 0;
|
||||||
|
|
||||||
|
Result result = _api.EnumeratePhysicalDevices(Instance, SpanHelpers.AsSpan(ref physicalDeviceCount), Span<PhysicalDevice>.Empty);
|
||||||
|
|
||||||
|
if (result != Result.Success)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicalDevice[] rawPhysicalDevices = new PhysicalDevice[physicalDeviceCount];
|
||||||
|
|
||||||
|
result = _api.EnumeratePhysicalDevices(Instance, SpanHelpers.AsSpan(ref physicalDeviceCount), rawPhysicalDevices);
|
||||||
|
|
||||||
|
if (result != Result.Success)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
physicalDevices = rawPhysicalDevices.Select(x => new VulkanPhysicalDevice(_api, x)).ToArray();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlySet<string> GetInstanceExtensions(Vk api)
|
||||||
|
{
|
||||||
|
uint propertiesCount = 0;
|
||||||
|
|
||||||
|
api.EnumerateInstanceExtensionProperties(ReadOnlySpan<byte>.Empty, SpanHelpers.AsSpan(ref propertiesCount), Span<ExtensionProperties>.Empty).ThrowOnError();
|
||||||
|
|
||||||
|
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
||||||
|
|
||||||
|
api.EnumerateInstanceExtensionProperties(ReadOnlySpan<byte>.Empty, SpanHelpers.AsSpan(ref propertiesCount), extensionProperties).ThrowOnError();
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToImmutableHashSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlySet<string> GetInstanceLayers(Vk api)
|
||||||
|
{
|
||||||
|
uint propertiesCount = 0;
|
||||||
|
|
||||||
|
api.EnumerateInstanceLayerProperties(SpanHelpers.AsSpan(ref propertiesCount), Span<LayerProperties>.Empty).ThrowOnError();
|
||||||
|
|
||||||
|
LayerProperties[] layerProperties = new LayerProperties[propertiesCount];
|
||||||
|
|
||||||
|
api.EnumerateInstanceLayerProperties(SpanHelpers.AsSpan(ref propertiesCount), layerProperties).ThrowOnError();
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
return layerProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.LayerName)).ToImmutableHashSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
_api.DestroyInstance(Instance, ReadOnlySpan<AllocationCallbacks>.Empty);
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs
Normal file
70
Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
readonly struct VulkanPhysicalDevice
|
||||||
|
{
|
||||||
|
public readonly PhysicalDevice PhysicalDevice;
|
||||||
|
public readonly PhysicalDeviceFeatures PhysicalDeviceFeatures;
|
||||||
|
public readonly PhysicalDeviceProperties PhysicalDeviceProperties;
|
||||||
|
public readonly PhysicalDeviceMemoryProperties PhysicalDeviceMemoryProperties;
|
||||||
|
public readonly QueueFamilyProperties[] QueueFamilyProperties;
|
||||||
|
public readonly string DeviceName;
|
||||||
|
public readonly IReadOnlySet<string> DeviceExtensions;
|
||||||
|
|
||||||
|
public VulkanPhysicalDevice(Vk api, PhysicalDevice physicalDevice)
|
||||||
|
{
|
||||||
|
PhysicalDevice = physicalDevice;
|
||||||
|
PhysicalDeviceFeatures = api.GetPhysicalDeviceFeature(PhysicalDevice);
|
||||||
|
|
||||||
|
api.GetPhysicalDeviceProperties(PhysicalDevice, out var physicalDeviceProperties);
|
||||||
|
PhysicalDeviceProperties = physicalDeviceProperties;
|
||||||
|
|
||||||
|
api.GetPhysicalDeviceMemoryProperties(PhysicalDevice, out PhysicalDeviceMemoryProperties);
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
DeviceName = Marshal.PtrToStringAnsi((IntPtr)physicalDeviceProperties.DeviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint propertiesCount = 0;
|
||||||
|
|
||||||
|
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, SpanHelpers.AsSpan(ref propertiesCount), Span<QueueFamilyProperties>.Empty);
|
||||||
|
|
||||||
|
QueueFamilyProperties = new QueueFamilyProperties[propertiesCount];
|
||||||
|
|
||||||
|
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, SpanHelpers.AsSpan(ref propertiesCount), QueueFamilyProperties);
|
||||||
|
|
||||||
|
api.EnumerateDeviceExtensionProperties(PhysicalDevice, Span<byte>.Empty, SpanHelpers.AsSpan(ref propertiesCount), Span<ExtensionProperties>.Empty).ThrowOnError();
|
||||||
|
|
||||||
|
ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
|
||||||
|
|
||||||
|
api.EnumerateDeviceExtensionProperties(PhysicalDevice, Span<byte>.Empty, SpanHelpers.AsSpan(ref propertiesCount), extensionProperties).ThrowOnError();
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
DeviceExtensions = extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToImmutableHashSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id => $"0x{PhysicalDeviceProperties.VendorID:X}_0x{PhysicalDeviceProperties.DeviceID:X}";
|
||||||
|
|
||||||
|
public bool IsDeviceExtensionPresent(string extension) => DeviceExtensions.Contains(extension);
|
||||||
|
|
||||||
|
public DeviceInfo ToDeviceInfo()
|
||||||
|
{
|
||||||
|
return new DeviceInfo(
|
||||||
|
Id,
|
||||||
|
VendorUtils.GetNameFromId(PhysicalDeviceProperties.VendorID),
|
||||||
|
DeviceName,
|
||||||
|
PhysicalDeviceProperties.DeviceType == PhysicalDeviceType.DiscreteGpu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,9 +17,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
public sealed class VulkanRenderer : IRenderer
|
public sealed class VulkanRenderer : IRenderer
|
||||||
{
|
{
|
||||||
private Instance _instance;
|
private VulkanInstance _instance;
|
||||||
private SurfaceKHR _surface;
|
private SurfaceKHR _surface;
|
||||||
private PhysicalDevice _physicalDevice;
|
private VulkanPhysicalDevice _physicalDevice;
|
||||||
private Device _device;
|
private Device _device;
|
||||||
private WindowBase _window;
|
private WindowBase _window;
|
||||||
|
|
||||||
@@ -36,7 +36,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
internal KhrPushDescriptor PushDescriptorApi { get; private set; }
|
||||||
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
|
||||||
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
|
||||||
internal ExtDebugUtils DebugUtilsApi { get; private set; }
|
|
||||||
|
|
||||||
internal uint QueueFamilyIndex { get; private set; }
|
internal uint QueueFamilyIndex { get; private set; }
|
||||||
internal Queue Queue { get; private set; }
|
internal Queue Queue { get; private set; }
|
||||||
@@ -57,11 +56,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
internal HashSet<ITexture> Textures { get; }
|
internal HashSet<ITexture> Textures { get; }
|
||||||
internal HashSet<SamplerHolder> Samplers { get; }
|
internal HashSet<SamplerHolder> Samplers { get; }
|
||||||
|
|
||||||
|
private VulkanDebugMessenger _debugMessenger;
|
||||||
private Counters _counters;
|
private Counters _counters;
|
||||||
private SyncManager _syncManager;
|
private SyncManager _syncManager;
|
||||||
|
|
||||||
private PipelineFull _pipeline;
|
private PipelineFull _pipeline;
|
||||||
private DebugUtilsMessengerEXT _debugUtilsMessenger;
|
|
||||||
|
|
||||||
internal HelperShader HelperShader { get; private set; }
|
internal HelperShader HelperShader { get; private set; }
|
||||||
internal PipelineFull PipelineInternal => _pipeline;
|
internal PipelineFull PipelineInternal => _pipeline;
|
||||||
@@ -107,33 +106,31 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
|
private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex)
|
||||||
{
|
{
|
||||||
FormatCapabilities = new FormatCapabilities(Api, _physicalDevice);
|
FormatCapabilities = new FormatCapabilities(Api, _physicalDevice.PhysicalDevice);
|
||||||
|
|
||||||
var supportedFeatures = Api.GetPhysicalDeviceFeature(_physicalDevice);
|
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtConditionalRendering conditionalRenderingApi))
|
||||||
|
|
||||||
if (Api.TryGetDeviceExtension(_instance, _device, out ExtConditionalRendering conditionalRenderingApi))
|
|
||||||
{
|
{
|
||||||
ConditionalRenderingApi = conditionalRenderingApi;
|
ConditionalRenderingApi = conditionalRenderingApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Api.TryGetDeviceExtension(_instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi))
|
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi))
|
||||||
{
|
{
|
||||||
ExtendedDynamicStateApi = extendedDynamicStateApi;
|
ExtendedDynamicStateApi = extendedDynamicStateApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Api.TryGetDeviceExtension(_instance, _device, out KhrPushDescriptor pushDescriptorApi))
|
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrPushDescriptor pushDescriptorApi))
|
||||||
{
|
{
|
||||||
PushDescriptorApi = pushDescriptorApi;
|
PushDescriptorApi = pushDescriptorApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Api.TryGetDeviceExtension(_instance, _device, out ExtTransformFeedback transformFeedbackApi))
|
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtTransformFeedback transformFeedbackApi))
|
||||||
{
|
{
|
||||||
TransformFeedbackApi = transformFeedbackApi;
|
TransformFeedbackApi = transformFeedbackApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Api.TryGetDeviceExtension(_instance, _device, out KhrDrawIndirectCount drawIndirectCountApi))
|
if (Api.TryGetDeviceExtension(_instance.Instance, _device, out KhrDrawIndirectCount drawIndirectCountApi))
|
||||||
{
|
{
|
||||||
DrawIndirectCountApi = drawIndirectCountApi;
|
DrawIndirectCountApi = drawIndirectCountApi;
|
||||||
}
|
}
|
||||||
@@ -155,7 +152,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SType = StructureType.PhysicalDeviceBlendOperationAdvancedPropertiesExt
|
SType = StructureType.PhysicalDeviceBlendOperationAdvancedPropertiesExt
|
||||||
};
|
};
|
||||||
|
|
||||||
bool supportsBlendOperationAdvanced = supportedExtensions.Contains("VK_EXT_blend_operation_advanced");
|
bool supportsBlendOperationAdvanced = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_blend_operation_advanced");
|
||||||
|
|
||||||
if (supportsBlendOperationAdvanced)
|
if (supportsBlendOperationAdvanced)
|
||||||
{
|
{
|
||||||
@@ -168,14 +165,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SType = StructureType.PhysicalDeviceSubgroupSizeControlPropertiesExt
|
SType = StructureType.PhysicalDeviceSubgroupSizeControlPropertiesExt
|
||||||
};
|
};
|
||||||
|
|
||||||
bool supportsSubgroupSizeControl = supportedExtensions.Contains("VK_EXT_subgroup_size_control");
|
bool supportsSubgroupSizeControl = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_subgroup_size_control");
|
||||||
|
|
||||||
if (supportsSubgroupSizeControl)
|
if (supportsSubgroupSizeControl)
|
||||||
{
|
{
|
||||||
properties2.PNext = &propertiesSubgroupSizeControl;
|
properties2.PNext = &propertiesSubgroupSizeControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool supportsTransformFeedback = supportedExtensions.Contains(ExtTransformFeedback.ExtensionName);
|
bool supportsTransformFeedback = _physicalDevice.IsDeviceExtensionPresent(ExtTransformFeedback.ExtensionName);
|
||||||
|
|
||||||
PhysicalDeviceTransformFeedbackPropertiesEXT propertiesTransformFeedback = new PhysicalDeviceTransformFeedbackPropertiesEXT()
|
PhysicalDeviceTransformFeedbackPropertiesEXT propertiesTransformFeedback = new PhysicalDeviceTransformFeedbackPropertiesEXT()
|
||||||
{
|
{
|
||||||
@@ -223,30 +220,30 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
|
SType = StructureType.PhysicalDevicePortabilitySubsetFeaturesKhr
|
||||||
};
|
};
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_primitive_topology_list_restart"))
|
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_primitive_topology_list_restart"))
|
||||||
{
|
{
|
||||||
features2.PNext = &featuresPrimitiveTopologyListRestart;
|
features2.PNext = &featuresPrimitiveTopologyListRestart;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_robustness2"))
|
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_robustness2"))
|
||||||
{
|
{
|
||||||
featuresRobustness2.PNext = features2.PNext;
|
featuresRobustness2.PNext = features2.PNext;
|
||||||
features2.PNext = &featuresRobustness2;
|
features2.PNext = &featuresRobustness2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_KHR_shader_float16_int8"))
|
if (_physicalDevice.IsDeviceExtensionPresent("VK_KHR_shader_float16_int8"))
|
||||||
{
|
{
|
||||||
featuresShaderInt8.PNext = features2.PNext;
|
featuresShaderInt8.PNext = features2.PNext;
|
||||||
features2.PNext = &featuresShaderInt8;
|
features2.PNext = &featuresShaderInt8;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportedExtensions.Contains("VK_EXT_custom_border_color"))
|
if (_physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color"))
|
||||||
{
|
{
|
||||||
featuresCustomBorderColor.PNext = features2.PNext;
|
featuresCustomBorderColor.PNext = features2.PNext;
|
||||||
features2.PNext = &featuresCustomBorderColor;
|
features2.PNext = &featuresCustomBorderColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool usePortability = supportedExtensions.Contains("VK_KHR_portability_subset");
|
bool usePortability = _physicalDevice.IsDeviceExtensionPresent("VK_KHR_portability_subset");
|
||||||
|
|
||||||
if (usePortability)
|
if (usePortability)
|
||||||
{
|
{
|
||||||
@@ -257,8 +254,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
features2.PNext = &featuresPortabilitySubset;
|
features2.PNext = &featuresPortabilitySubset;
|
||||||
}
|
}
|
||||||
|
|
||||||
Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2);
|
Api.GetPhysicalDeviceProperties2(_physicalDevice.PhysicalDevice, &properties2);
|
||||||
Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
|
Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2);
|
||||||
|
|
||||||
var portabilityFlags = PortabilitySubsetFlags.None;
|
var portabilityFlags = PortabilitySubsetFlags.None;
|
||||||
uint vertexBufferAlignment = 1;
|
uint vertexBufferAlignment = 1;
|
||||||
@@ -273,7 +270,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
portabilityFlags |= featuresPortabilitySubset.SamplerMipLodBias ? 0 : PortabilitySubsetFlags.NoLodBias;
|
portabilityFlags |= featuresPortabilitySubset.SamplerMipLodBias ? 0 : PortabilitySubsetFlags.NoLodBias;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool supportsCustomBorderColor = supportedExtensions.Contains("VK_EXT_custom_border_color") &&
|
bool supportsCustomBorderColor = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_custom_border_color") &&
|
||||||
featuresCustomBorderColor.CustomBorderColors &&
|
featuresCustomBorderColor.CustomBorderColors &&
|
||||||
featuresCustomBorderColor.CustomBorderColorWithoutFormat;
|
featuresCustomBorderColor.CustomBorderColorWithoutFormat;
|
||||||
|
|
||||||
@@ -285,30 +282,30 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
properties.Limits.FramebufferStencilSampleCounts;
|
properties.Limits.FramebufferStencilSampleCounts;
|
||||||
|
|
||||||
Capabilities = new HardwareCapabilities(
|
Capabilities = new HardwareCapabilities(
|
||||||
supportedExtensions.Contains("VK_EXT_index_type_uint8"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_index_type_uint8"),
|
||||||
supportsCustomBorderColor,
|
supportsCustomBorderColor,
|
||||||
supportsBlendOperationAdvanced,
|
supportsBlendOperationAdvanced,
|
||||||
propertiesBlendOperationAdvanced.AdvancedBlendCorrelatedOverlap,
|
propertiesBlendOperationAdvanced.AdvancedBlendCorrelatedOverlap,
|
||||||
propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedSrcColor,
|
propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedSrcColor,
|
||||||
propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedDstColor,
|
propertiesBlendOperationAdvanced.AdvancedBlendNonPremultipliedDstColor,
|
||||||
supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
|
||||||
supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"),
|
||||||
supportedExtensions.Contains("VK_NV_geometry_shader_passthrough"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"),
|
||||||
supportsSubgroupSizeControl,
|
supportsSubgroupSizeControl,
|
||||||
featuresShaderInt8.ShaderInt8,
|
featuresShaderInt8.ShaderInt8,
|
||||||
supportedExtensions.Contains("VK_EXT_shader_stencil_export"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
|
||||||
supportedExtensions.Contains(ExtConditionalRendering.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtConditionalRendering.ExtensionName),
|
||||||
supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
|
||||||
features2.Features.MultiViewport,
|
features2.Features.MultiViewport,
|
||||||
featuresRobustness2.NullDescriptor || IsMoltenVk,
|
featuresRobustness2.NullDescriptor || IsMoltenVk,
|
||||||
supportedExtensions.Contains(KhrPushDescriptor.ExtensionName),
|
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
|
||||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
|
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
|
||||||
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
|
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
|
||||||
supportsTransformFeedback,
|
supportsTransformFeedback,
|
||||||
propertiesTransformFeedback.TransformFeedbackQueries,
|
propertiesTransformFeedback.TransformFeedbackQueries,
|
||||||
features2.Features.OcclusionQueryPrecise,
|
features2.Features.OcclusionQueryPrecise,
|
||||||
supportedFeatures.PipelineStatisticsQuery,
|
_physicalDevice.PhysicalDeviceFeatures.PipelineStatisticsQuery,
|
||||||
supportedFeatures.GeometryShader,
|
_physicalDevice.PhysicalDeviceFeatures.GeometryShader,
|
||||||
propertiesSubgroupSizeControl.MinSubgroupSize,
|
propertiesSubgroupSizeControl.MinSubgroupSize,
|
||||||
propertiesSubgroupSizeControl.MaxSubgroupSize,
|
propertiesSubgroupSizeControl.MaxSubgroupSize,
|
||||||
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
|
propertiesSubgroupSizeControl.RequiredSubgroupSizeStages,
|
||||||
@@ -316,11 +313,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
portabilityFlags,
|
portabilityFlags,
|
||||||
vertexBufferAlignment);
|
vertexBufferAlignment);
|
||||||
|
|
||||||
IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(Api, _physicalDevice);
|
IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(_physicalDevice);
|
||||||
|
|
||||||
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device, properties.Limits.MaxMemoryAllocationCount);
|
MemoryAllocator = new MemoryAllocator(Api, _physicalDevice, _device);
|
||||||
|
|
||||||
CommandBufferPool = VulkanInitialization.CreateCommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
||||||
|
|
||||||
DescriptorSetManager = new DescriptorSetManager(_device);
|
DescriptorSetManager = new DescriptorSetManager(_device);
|
||||||
|
|
||||||
@@ -345,24 +342,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
Api = api;
|
Api = api;
|
||||||
|
|
||||||
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugUtils debugUtils, out _debugUtilsMessenger);
|
_instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions());
|
||||||
|
_debugMessenger = new VulkanDebugMessenger(api, _instance.Instance, logLevel);
|
||||||
|
|
||||||
DebugUtilsApi = debugUtils;
|
if (api.TryGetInstanceExtension(_instance.Instance, out KhrSurface surfaceApi))
|
||||||
|
|
||||||
if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
|
|
||||||
{
|
{
|
||||||
SurfaceApi = surfaceApi;
|
SurfaceApi = surfaceApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
_surface = _getSurface(_instance, api);
|
_surface = _getSurface(_instance.Instance, api);
|
||||||
_physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(api, _instance, _surface, _preferredGpuId);
|
_physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(api, _instance, _surface, _preferredGpuId);
|
||||||
|
|
||||||
var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(api, _physicalDevice, _surface, out uint maxQueueCount);
|
var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(api, _physicalDevice, _surface, out uint maxQueueCount);
|
||||||
var supportedExtensions = VulkanInitialization.GetSupportedExtensions(api, _physicalDevice);
|
|
||||||
|
|
||||||
_device = VulkanInitialization.CreateDevice(api, _physicalDevice, queueFamilyIndex, supportedExtensions, maxQueueCount);
|
_device = VulkanInitialization.CreateDevice(api, _physicalDevice, queueFamilyIndex, maxQueueCount);
|
||||||
|
|
||||||
if (api.TryGetDeviceExtension(_instance, _device, out KhrSwapchain swapchainApi))
|
if (api.TryGetDeviceExtension(_instance.Instance, _device, out KhrSwapchain swapchainApi))
|
||||||
{
|
{
|
||||||
SwapchainApi = swapchainApi;
|
SwapchainApi = swapchainApi;
|
||||||
}
|
}
|
||||||
@@ -371,9 +366,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Queue = queue;
|
Queue = queue;
|
||||||
QueueLock = new object();
|
QueueLock = new object();
|
||||||
|
|
||||||
LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex);
|
LoadFeatures(maxQueueCount, queueFamilyIndex);
|
||||||
|
|
||||||
_window = new Window(this, _surface, _physicalDevice, _device);
|
_window = new Window(this, _surface, _physicalDevice.PhysicalDevice, _device);
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
@@ -538,10 +533,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PNext = &featuresVk12
|
PNext = &featuresVk12
|
||||||
};
|
};
|
||||||
|
|
||||||
Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
|
Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2);
|
||||||
Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties);
|
|
||||||
|
|
||||||
var limits = properties.Limits;
|
var limits = _physicalDevice.PhysicalDeviceProperties.Limits;
|
||||||
|
|
||||||
return new Capabilities(
|
return new Capabilities(
|
||||||
api: TargetApi.Vulkan,
|
api: TargetApi.Vulkan,
|
||||||
@@ -625,7 +619,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private unsafe void PrintGpuInformation()
|
private unsafe void PrintGpuInformation()
|
||||||
{
|
{
|
||||||
Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties);
|
var properties = _physicalDevice.PhysicalDeviceProperties;
|
||||||
|
|
||||||
string vendorName = VendorUtils.GetNameFromId(properties.VendorID);
|
string vendorName = VendorUtils.GetNameFromId(properties.VendorID);
|
||||||
|
|
||||||
@@ -794,11 +788,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
MemoryAllocator.Dispose();
|
MemoryAllocator.Dispose();
|
||||||
|
|
||||||
if (_debugUtilsMessenger.Handle != 0)
|
|
||||||
{
|
|
||||||
DebugUtilsApi.DestroyDebugUtilsMessenger(_instance, _debugUtilsMessenger, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var shader in Shaders)
|
foreach (var shader in Shaders)
|
||||||
{
|
{
|
||||||
shader.Dispose();
|
shader.Dispose();
|
||||||
@@ -814,12 +803,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
sampler.Dispose();
|
sampler.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
SurfaceApi.DestroySurface(_instance, _surface, null);
|
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||||
|
|
||||||
Api.DestroyDevice(_device, null);
|
Api.DestroyDevice(_device, null);
|
||||||
|
|
||||||
|
_debugMessenger.Dispose();
|
||||||
|
|
||||||
// Last step destroy the instance
|
// Last step destroy the instance
|
||||||
Api.DestroyInstance(_instance, null);
|
_instance.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -20,7 +20,6 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
using RightsId = LibHac.Fs.RightsId;
|
using RightsId = LibHac.Fs.RightsId;
|
||||||
|
|
||||||
@@ -146,6 +145,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
return $"{basePath}:/{fileName}";
|
return $"{basePath}:/{fileName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
||||||
var fsServer = new FileSystemServer(fsServerClient);
|
var fsServer = new FileSystemServer(fsServerClient);
|
||||||
|
|
||||||
RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer);
|
RandomDataGenerator randomGenerator = Random.Shared.NextBytes;
|
||||||
|
|
||||||
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
|
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
if (result.IsSuccess())
|
if (result.IsSuccess())
|
||||||
{
|
{
|
||||||
Ticket ticket = new Ticket(ticketFile.Get.AsStream());
|
Ticket ticket = new(ticketFile.Get.AsStream());
|
||||||
var titleKey = ticket.GetTitleKey(KeySet);
|
var titleKey = ticket.GetTitleKey(KeySet);
|
||||||
|
|
||||||
if (titleKey != null)
|
if (titleKey != null)
|
||||||
|
@@ -1,908 +0,0 @@
|
|||||||
using LibHac;
|
|
||||||
using LibHac.Account;
|
|
||||||
using LibHac.Common;
|
|
||||||
using LibHac.Fs;
|
|
||||||
using LibHac.Fs.Fsa;
|
|
||||||
using LibHac.Fs.Shim;
|
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Loader;
|
|
||||||
using LibHac.Ncm;
|
|
||||||
using LibHac.Ns;
|
|
||||||
using LibHac.Tools.Fs;
|
|
||||||
using LibHac.Tools.FsSystem;
|
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Cpu;
|
|
||||||
using Ryujinx.HLE.FileSystem;
|
|
||||||
using Ryujinx.HLE.Loaders.Executables;
|
|
||||||
using Ryujinx.Memory;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using static Ryujinx.HLE.HOS.ModLoader;
|
|
||||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
|
||||||
using Path = System.IO.Path;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS
|
|
||||||
{
|
|
||||||
using JsonHelper = Common.Utilities.JsonHelper;
|
|
||||||
|
|
||||||
public class ApplicationLoader
|
|
||||||
{
|
|
||||||
// Binaries from exefs are loaded into mem in this order. Do not change.
|
|
||||||
internal static readonly string[] ExeFsPrefixes =
|
|
||||||
{
|
|
||||||
"rtld",
|
|
||||||
"main",
|
|
||||||
"subsdk0",
|
|
||||||
"subsdk1",
|
|
||||||
"subsdk2",
|
|
||||||
"subsdk3",
|
|
||||||
"subsdk4",
|
|
||||||
"subsdk5",
|
|
||||||
"subsdk6",
|
|
||||||
"subsdk7",
|
|
||||||
"subsdk8",
|
|
||||||
"subsdk9",
|
|
||||||
"sdk"
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly Switch _device;
|
|
||||||
private string _titleName;
|
|
||||||
private string _displayVersion;
|
|
||||||
private BlitStruct<ApplicationControlProperty> _controlData;
|
|
||||||
|
|
||||||
public BlitStruct<ApplicationControlProperty> ControlData => _controlData;
|
|
||||||
public string TitleName => _titleName;
|
|
||||||
public string DisplayVersion => _displayVersion;
|
|
||||||
|
|
||||||
public ulong TitleId { get; private set; }
|
|
||||||
public bool TitleIs64Bit { get; private set; }
|
|
||||||
|
|
||||||
public string TitleIdText => TitleId.ToString("x16");
|
|
||||||
|
|
||||||
public IDiskCacheLoadState DiskCacheLoadState { get; private set; }
|
|
||||||
|
|
||||||
public ApplicationLoader(Switch device)
|
|
||||||
{
|
|
||||||
_device = device;
|
|
||||||
_controlData = new BlitStruct<ApplicationControlProperty>(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
|
||||||
{
|
|
||||||
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
|
|
||||||
|
|
||||||
MetaLoader metaData = ReadNpdm(codeFs);
|
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
|
||||||
new[] { TitleId },
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
|
||||||
|
|
||||||
if (TitleId != 0)
|
|
||||||
{
|
|
||||||
EnsureSaveData(new ApplicationId(TitleId));
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong pid = LoadExeFs(codeFs, string.Empty, metaData);
|
|
||||||
|
|
||||||
if (romFsFile != null)
|
|
||||||
{
|
|
||||||
_device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem 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 Nca(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (Nca patch, Nca control) 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 Nca(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static (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 &= 0xFFFFFFFFFFFFFFF0;
|
|
||||||
|
|
||||||
// Load update informations if existing.
|
|
||||||
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
|
|
||||||
|
|
||||||
if (File.Exists(titleUpdateMetadataPath))
|
|
||||||
{
|
|
||||||
updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
|
|
||||||
|
|
||||||
if (File.Exists(updatePath))
|
|
||||||
{
|
|
||||||
FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
|
|
||||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
|
||||||
|
|
||||||
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadXci(string xciFile)
|
|
||||||
{
|
|
||||||
FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
|
|
||||||
Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage());
|
|
||||||
|
|
||||||
if (!xci.HasPartition(XciPartitionType.Secure))
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
|
|
||||||
|
|
||||||
Nca mainNca;
|
|
||||||
Nca patchNca;
|
|
||||||
Nca controlNca;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
|
|
||||||
|
|
||||||
RegisterProgramMapInfo(securePartition).ThrowIfFailure();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainNca == null)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
|
||||||
_device.Configuration.ContentManager.ClearAocData();
|
|
||||||
_device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
|
|
||||||
|
|
||||||
LoadNca(mainNca, patchNca, controlNca);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNsp(string nspFile)
|
|
||||||
{
|
|
||||||
FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
|
|
||||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
|
||||||
|
|
||||||
Nca mainNca;
|
|
||||||
Nca patchNca;
|
|
||||||
Nca controlNca;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
|
|
||||||
|
|
||||||
RegisterProgramMapInfo(nsp).ThrowIfFailure();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainNca != null)
|
|
||||||
{
|
|
||||||
_device.Configuration.ContentManager.ClearAocData();
|
|
||||||
_device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel);
|
|
||||||
|
|
||||||
LoadNca(mainNca, patchNca, controlNca);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is not a normal NSP, it's actually a ExeFS as a NSP
|
|
||||||
LoadExeFs(nsp, null, isHomebrew: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNca(string ncaFile)
|
|
||||||
{
|
|
||||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
|
||||||
Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
|
||||||
|
|
||||||
LoadNca(nca, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadServiceNca(string ncaFile)
|
|
||||||
{
|
|
||||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
|
||||||
Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
|
|
||||||
|
|
||||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IFileSystem codeFs = null;
|
|
||||||
|
|
||||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
|
||||||
{
|
|
||||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codeFs == null)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var npdmFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
Result result = codeFs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
MetaLoader metaData;
|
|
||||||
|
|
||||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
|
||||||
|
|
||||||
var npdmBuffer = new byte[fileSize];
|
|
||||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
|
||||||
|
|
||||||
metaData = new MetaLoader();
|
|
||||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
|
||||||
|
|
||||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
|
||||||
|
|
||||||
for (int i = 0; i < nsos.Length; i++)
|
|
||||||
{
|
|
||||||
string name = ExeFsPrefixes[i];
|
|
||||||
|
|
||||||
if (!codeFs.FileExists($"/{name}"))
|
|
||||||
{
|
|
||||||
continue; // File doesn't exist, skip.
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
|
||||||
|
|
||||||
using var nsoFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect the nsos, ignoring ones that aren't used.
|
|
||||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
|
||||||
|
|
||||||
string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString;
|
|
||||||
bool usePtc = _device.System.EnablePtc;
|
|
||||||
|
|
||||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
|
||||||
ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false);
|
|
||||||
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
|
|
||||||
|
|
||||||
string titleIdText = npdm.Aci.ProgramId.Value.ToString("x16");
|
|
||||||
bool titleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
|
||||||
|
|
||||||
string programName = Encoding.ASCII.GetString(npdm.Meta.ProgramName).TrimEnd('\0');
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
|
|
||||||
{
|
|
||||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IStorage dataStorage = null;
|
|
||||||
IFileSystem codeFs = null;
|
|
||||||
|
|
||||||
(Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _);
|
|
||||||
|
|
||||||
if (updatePatchNca != null)
|
|
||||||
{
|
|
||||||
patchNca = updatePatchNca;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateControlNca != null)
|
|
||||||
{
|
|
||||||
controlNca = updateControlNca;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load program 0 control NCA as we are going to need it for display version.
|
|
||||||
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
|
||||||
|
|
||||||
// Load Aoc
|
|
||||||
string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
|
||||||
|
|
||||||
if (File.Exists(titleAocMetadataPath))
|
|
||||||
{
|
|
||||||
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath);
|
|
||||||
|
|
||||||
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
|
|
||||||
{
|
|
||||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
|
||||||
{
|
|
||||||
if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
|
|
||||||
{
|
|
||||||
_device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patchNca == null)
|
|
||||||
{
|
|
||||||
if (mainNca.CanOpenSection(NcaSectionType.Data))
|
|
||||||
{
|
|
||||||
dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
|
||||||
{
|
|
||||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (patchNca.CanOpenSection(NcaSectionType.Data))
|
|
||||||
{
|
|
||||||
dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patchNca.CanOpenSection(NcaSectionType.Code))
|
|
||||||
{
|
|
||||||
codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codeFs == null)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MetaLoader metaData = ReadNpdm(codeFs);
|
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(
|
|
||||||
_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId),
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
|
|
||||||
|
|
||||||
string displayVersion = string.Empty;
|
|
||||||
|
|
||||||
if (controlNca != null)
|
|
||||||
{
|
|
||||||
ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ControlData.ByteSpan.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
|
|
||||||
// BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program.
|
|
||||||
if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0)
|
|
||||||
{
|
|
||||||
string dummyTitleName = "";
|
|
||||||
BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1);
|
|
||||||
|
|
||||||
ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
_displayVersion = displayVersion;
|
|
||||||
|
|
||||||
ulong pid = LoadExeFs(codeFs, displayVersion, metaData);
|
|
||||||
|
|
||||||
if (dataStorage == null)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
|
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't create save data for system programs.
|
|
||||||
if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value))
|
|
||||||
{
|
|
||||||
// Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
|
|
||||||
// We'll know if this changes in the future because stuff will get errors when trying to mount the correct save.
|
|
||||||
EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets TitleId, so be sure to call before using it
|
|
||||||
private MetaLoader ReadNpdm(IFileSystem fs)
|
|
||||||
{
|
|
||||||
using var npdmFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
Result result = fs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
MetaLoader metaData;
|
|
||||||
|
|
||||||
if (ResultFs.PathNotFound.Includes(result))
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
|
|
||||||
|
|
||||||
metaData = GetDefaultNpdm();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure();
|
|
||||||
|
|
||||||
var npdmBuffer = new byte[fileSize];
|
|
||||||
npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure();
|
|
||||||
|
|
||||||
metaData = new MetaLoader();
|
|
||||||
metaData.Load(npdmBuffer).ThrowIfFailure();
|
|
||||||
}
|
|
||||||
|
|
||||||
metaData.GetNpdm(out var npdm).ThrowIfFailure();
|
|
||||||
|
|
||||||
TitleId = npdm.Aci.ProgramId.Value;
|
|
||||||
TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
|
||||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
|
||||||
|
|
||||||
return metaData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
|
|
||||||
{
|
|
||||||
using var controlFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
|
|
||||||
Result result = controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
|
|
||||||
|
|
||||||
if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
|
|
||||||
{
|
|
||||||
titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(titleName))
|
|
||||||
{
|
|
||||||
titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
displayVersion = controlData.Value.DisplayVersionString.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
controlData.ByteSpan.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false)
|
|
||||||
{
|
|
||||||
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
|
|
||||||
{
|
|
||||||
metaData = null; // TODO: Check if we should retain old npdm.
|
|
||||||
}
|
|
||||||
|
|
||||||
metaData ??= ReadNpdm(codeFs);
|
|
||||||
|
|
||||||
NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
|
|
||||||
|
|
||||||
for (int i = 0; i < nsos.Length; i++)
|
|
||||||
{
|
|
||||||
string name = ExeFsPrefixes[i];
|
|
||||||
|
|
||||||
if (!codeFs.FileExists($"/{name}"))
|
|
||||||
{
|
|
||||||
continue; // File doesn't exist, skip.
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
|
|
||||||
|
|
||||||
using var nsoFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExeFs file replacements.
|
|
||||||
ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
|
|
||||||
|
|
||||||
// Collect the nsos, ignoring ones that aren't used.
|
|
||||||
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
|
|
||||||
|
|
||||||
// Take the npdm from mods if present.
|
|
||||||
if (modLoadResult.Npdm != null)
|
|
||||||
{
|
|
||||||
metaData = modLoadResult.Npdm;
|
|
||||||
}
|
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
|
|
||||||
|
|
||||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
|
||||||
|
|
||||||
bool usePtc = _device.System.EnablePtc;
|
|
||||||
|
|
||||||
// Don't use PPTC if ExeFs files have been replaced.
|
|
||||||
usePtc &= !modLoadResult.Modified;
|
|
||||||
|
|
||||||
if (_device.System.EnablePtc && !usePtc)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
|
|
||||||
_device.Gpu.HostInitalized.Set();
|
|
||||||
|
|
||||||
MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
|
|
||||||
|
|
||||||
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
|
|
||||||
{
|
|
||||||
memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We allow it for nx-hbloader because it can be used to launch homebrew.
|
|
||||||
bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew;
|
|
||||||
|
|
||||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
|
||||||
ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit);
|
|
||||||
ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
|
|
||||||
|
|
||||||
DiskCacheLoadState = result.DiskCacheLoadState;
|
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
|
||||||
|
|
||||||
return result.ProcessId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadProgram(string filePath)
|
|
||||||
{
|
|
||||||
MetaLoader metaData = GetDefaultNpdm();
|
|
||||||
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
|
|
||||||
ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true);
|
|
||||||
|
|
||||||
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
|
||||||
|
|
||||||
IExecutable executable;
|
|
||||||
Stream romfsStream = null;
|
|
||||||
|
|
||||||
if (isNro)
|
|
||||||
{
|
|
||||||
FileStream input = new FileStream(filePath, FileMode.Open);
|
|
||||||
NroExecutable obj = new NroExecutable(input.AsStorage());
|
|
||||||
|
|
||||||
executable = obj;
|
|
||||||
|
|
||||||
// Homebrew NRO can actually have some data after the actual NRO.
|
|
||||||
if (input.Length > obj.FileSize)
|
|
||||||
{
|
|
||||||
input.Position = obj.FileSize;
|
|
||||||
|
|
||||||
BinaryReader reader = new BinaryReader(input);
|
|
||||||
|
|
||||||
uint asetMagic = reader.ReadUInt32();
|
|
||||||
if (asetMagic == 0x54455341)
|
|
||||||
{
|
|
||||||
uint asetVersion = reader.ReadUInt32();
|
|
||||||
if (asetVersion == 0)
|
|
||||||
{
|
|
||||||
ulong iconOffset = reader.ReadUInt64();
|
|
||||||
ulong iconSize = reader.ReadUInt64();
|
|
||||||
|
|
||||||
ulong nacpOffset = reader.ReadUInt64();
|
|
||||||
ulong nacpSize = reader.ReadUInt64();
|
|
||||||
|
|
||||||
ulong romfsOffset = reader.ReadUInt64();
|
|
||||||
ulong romfsSize = reader.ReadUInt64();
|
|
||||||
|
|
||||||
if (romfsSize != 0)
|
|
||||||
{
|
|
||||||
romfsStream = new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nacpSize != 0)
|
|
||||||
{
|
|
||||||
input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
reader.Read(ControlData.ByteSpan);
|
|
||||||
|
|
||||||
ref ApplicationControlProperty nacp = ref ControlData.Value;
|
|
||||||
|
|
||||||
programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(programInfo.Name))
|
|
||||||
{
|
|
||||||
programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nacp.PresenceGroupId != 0)
|
|
||||||
{
|
|
||||||
programInfo.ProgramId = nacp.PresenceGroupId;
|
|
||||||
}
|
|
||||||
else if (nacp.SaveDataOwnerId != 0)
|
|
||||||
{
|
|
||||||
programInfo.ProgramId = nacp.SaveDataOwnerId;
|
|
||||||
}
|
|
||||||
else if (nacp.AddOnContentBaseId != 0)
|
|
||||||
{
|
|
||||||
programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
programInfo.ProgramId = 0000000000000000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
_device.Configuration.ContentManager.LoadEntries(_device);
|
|
||||||
|
|
||||||
_titleName = programInfo.Name;
|
|
||||||
TitleId = programInfo.ProgramId;
|
|
||||||
TitleIs64Bit = (npdm.Meta.Flags & 1) != 0;
|
|
||||||
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
|
|
||||||
|
|
||||||
// Explicitly null titleid to disable the shader cache.
|
|
||||||
Graphics.Gpu.GraphicsConfig.TitleId = null;
|
|
||||||
_device.Gpu.HostInitalized.Set();
|
|
||||||
|
|
||||||
ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable);
|
|
||||||
|
|
||||||
if (romfsStream != null)
|
|
||||||
{
|
|
||||||
_device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiskCacheLoadState = result.DiskCacheLoadState;
|
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MetaLoader GetDefaultNpdm()
|
|
||||||
{
|
|
||||||
Assembly asm = Assembly.GetCallingAssembly();
|
|
||||||
|
|
||||||
using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
|
|
||||||
{
|
|
||||||
var npdmBuffer = new byte[npdmStream.Length];
|
|
||||||
npdmStream.Read(npdmBuffer);
|
|
||||||
|
|
||||||
var metaLoader = new MetaLoader();
|
|
||||||
metaLoader.Load(npdmBuffer).ThrowIfFailure();
|
|
||||||
|
|
||||||
return metaLoader;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs)
|
|
||||||
{
|
|
||||||
ulong mainProgramId = 0;
|
|
||||||
Span<bool> hasIndex = stackalloc bool[0x10];
|
|
||||||
|
|
||||||
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 Nca(fileSystem.KeySet, ncaFile.Release().AsStorage());
|
|
||||||
|
|
||||||
if (nca.Header.ContentType != NcaContentType.Program)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
|
||||||
|
|
||||||
if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong currentProgramId = nca.Header.TitleId;
|
|
||||||
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
|
|
||||||
|
|
||||||
if (mainProgramId == 0 && currentMainProgramId != 0)
|
|
||||||
{
|
|
||||||
mainProgramId = currentMainProgramId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainProgramId != currentMainProgramId)
|
|
||||||
{
|
|
||||||
// As far as I know there aren't any multi-application game cards containing multi-program applications,
|
|
||||||
// so because multi-application game cards are the only way we should run into multiple applications
|
|
||||||
// we'll just return that there's a single program.
|
|
||||||
return (mainProgramId, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
hasIndex[(int)(currentProgramId & 0xF)] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int programCount = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
|
|
||||||
{
|
|
||||||
programCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (mainProgramId, programCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Result RegisterProgramMapInfo(PartitionFileSystem pfs)
|
|
||||||
{
|
|
||||||
(ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs);
|
|
||||||
|
|
||||||
if (programCount <= 0)
|
|
||||||
return Result.Success;
|
|
||||||
|
|
||||||
Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
|
|
||||||
|
|
||||||
for (int i = 0; i < programCount; i++)
|
|
||||||
{
|
|
||||||
mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
|
|
||||||
mapInfo[i].MainProgramId = new ApplicationId(applicationId);
|
|
||||||
mapInfo[i].ProgramIndex = (byte)i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Result EnsureSaveData(ApplicationId applicationId)
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
|
|
||||||
|
|
||||||
Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid();
|
|
||||||
|
|
||||||
ref ApplicationControlProperty control = ref ControlData.Value;
|
|
||||||
|
|
||||||
if (LibHac.Common.Utilities.IsZeros(ControlData.ByteSpan))
|
|
||||||
{
|
|
||||||
// If the current application doesn't have a loaded control property, create a dummy one
|
|
||||||
// and set the savedata sizes so a user savedata will be created.
|
|
||||||
control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
|
|
||||||
|
|
||||||
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
|
|
||||||
control.UserAccountSaveDataSize = 0x4000;
|
|
||||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
|
||||||
control.SaveDataOwnerId = applicationId.Value;
|
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Application,
|
|
||||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
|
||||||
}
|
|
||||||
|
|
||||||
HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
|
|
||||||
Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control);
|
|
||||||
|
|
||||||
if (resultCode.IsFailure())
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
|
|
||||||
|
|
||||||
return resultCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user);
|
|
||||||
|
|
||||||
if (resultCode.IsFailure())
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -35,6 +35,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
|||||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.Loaders.Executables;
|
using Ryujinx.HLE.Loaders.Executables;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes;
|
||||||
using Ryujinx.Horizon;
|
using Ryujinx.Horizon;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -358,11 +359,11 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadKip(string kipPath)
|
public bool LoadKip(string kipPath)
|
||||||
{
|
{
|
||||||
using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
|
using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read));
|
||||||
|
|
||||||
ProgramLoader.LoadKip(KernelContext, new KipExecutable(in kipFile));
|
return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeDockedModeState(bool newState)
|
public void ChangeDockedModeState(bool newState)
|
||||||
|
@@ -2,7 +2,7 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
{
|
{
|
||||||
internal class ProcessTamperInfo
|
class ProcessTamperInfo
|
||||||
{
|
{
|
||||||
public KProcess Process { get; }
|
public KProcess Process { get; }
|
||||||
public IEnumerable<string> BuildIds { get; }
|
public IEnumerable<string> BuildIds { get; }
|
||||||
|
@@ -10,6 +10,7 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
using Ryujinx.HLE.Loaders.Executables;
|
using Ryujinx.HLE.Loaders.Executables;
|
||||||
using Ryujinx.HLE.Loaders.Mods;
|
using Ryujinx.HLE.Loaders.Mods;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
@@ -547,7 +548,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
return modLoadResult;
|
return modLoadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length)
|
if (nsos.Length != ProcessConst.ExeFsPrefixes.Length)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException("NSO Count is incorrect");
|
throw new ArgumentOutOfRangeException("NSO Count is incorrect");
|
||||||
}
|
}
|
||||||
@@ -556,9 +557,9 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
foreach (var mod in exeMods)
|
foreach (var mod in exeMods)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i)
|
for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i)
|
||||||
{
|
{
|
||||||
var nsoName = ApplicationLoader.ExeFsPrefixes[i];
|
var nsoName = ProcessConst.ExeFsPrefixes[i];
|
||||||
|
|
||||||
FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
|
FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
|
||||||
if (nsoFile.Exists)
|
if (nsoFile.Exists)
|
||||||
@@ -596,7 +597,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i)
|
for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
|
if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
|
||||||
{
|
{
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
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 Ryujinx.HLE.HOS.Services.Account.Acc.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
{
|
{
|
||||||
@@ -13,29 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
{
|
{
|
||||||
private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
|
private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
|
||||||
|
|
||||||
private struct ProfilesJson
|
private static readonly ProfilesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
{
|
|
||||||
[JsonPropertyName("profiles")]
|
|
||||||
public List<UserProfileJson> Profiles { get; set; }
|
|
||||||
[JsonPropertyName("last_opened")]
|
|
||||||
public string LastOpened { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct UserProfileJson
|
|
||||||
{
|
|
||||||
[JsonPropertyName("user_id")]
|
|
||||||
public string UserId { get; set; }
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public string Name { get; set; }
|
|
||||||
[JsonPropertyName("account_state")]
|
|
||||||
public AccountState AccountState { get; set; }
|
|
||||||
[JsonPropertyName("online_play_state")]
|
|
||||||
public AccountState OnlinePlayState { get; set; }
|
|
||||||
[JsonPropertyName("last_modified_timestamp")]
|
|
||||||
public long LastModifiedTimestamp { get; set; }
|
|
||||||
[JsonPropertyName("image")]
|
|
||||||
public byte[] Image { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserId LastOpened { get; set; }
|
public UserId LastOpened { get; set; }
|
||||||
|
|
||||||
@@ -47,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile<ProfilesJson>(_profilesJsonPath);
|
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, SerializerContext.ProfilesJson);
|
||||||
|
|
||||||
foreach (var profile in profilesJson.Profiles)
|
foreach (var profile in profilesJson.Profiles)
|
||||||
{
|
{
|
||||||
@@ -92,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllText(_profilesJsonPath, JsonHelper.Serialize(profilesJson, true));
|
JsonHelper.SerializeToFile(_profilesJsonPath, profilesJson, SerializerContext.ProfilesJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
|
// TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally.
|
||||||
// But since we use LibHac and we load one Application at a time, it's not necessary.
|
// But since we use LibHac and we load one Application at a time, it's not necessary.
|
||||||
|
|
||||||
context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock);
|
context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock);
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
|
Logger.Stub?.PrintStub(LogClass.ServiceAcc);
|
||||||
|
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc.Types;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(ProfilesJson))]
|
||||||
|
internal partial class ProfilesJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,9 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<AccountState>))]
|
||||||
public enum AccountState
|
public enum AccountState
|
||||||
{
|
{
|
||||||
Closed,
|
Closed,
|
||||||
|
10
Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs
Normal file
10
Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types
|
||||||
|
{
|
||||||
|
internal struct ProfilesJson
|
||||||
|
{
|
||||||
|
public List<UserProfileJson> Profiles { get; set; }
|
||||||
|
public string LastOpened { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types
|
||||||
|
{
|
||||||
|
internal struct UserProfileJson
|
||||||
|
{
|
||||||
|
public string UserId { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public AccountState AccountState { get; set; }
|
||||||
|
public AccountState OnlinePlayState { get; set; }
|
||||||
|
public long LastModifiedTimestamp { get; set; }
|
||||||
|
public byte[] Image { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||||||
|
|
||||||
public ILibraryAppletSelfAccessor(ServiceCtx context)
|
public ILibraryAppletSelfAccessor(ServiceCtx context)
|
||||||
{
|
{
|
||||||
if (context.Device.Application.TitleId == 0x0100000000001009)
|
if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009)
|
||||||
{
|
{
|
||||||
// Create MiiEdit data.
|
// Create MiiEdit data.
|
||||||
_appletStandalone = new AppletStandalone()
|
_appletStandalone = new AppletStandalone()
|
||||||
@@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotImplementedException($"{context.Device.Application.TitleId} applet is not implemented.");
|
throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -115,28 +115,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
|
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
|
||||||
|
|
||||||
// Mask out the low nibble of the program ID to get the application ID
|
// Mask out the low nibble of the program ID to get the application ID
|
||||||
ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul);
|
ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
|
||||||
|
|
||||||
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
|
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||||
|
|
||||||
ref ApplicationControlProperty control = ref controlHolder.Value;
|
|
||||||
|
|
||||||
if (LibHac.Common.Utilities.IsZeros(controlHolder.ByteSpan))
|
|
||||||
{
|
|
||||||
// If the current application doesn't have a loaded control property, create a dummy one
|
|
||||||
// and set the savedata sizes so a user savedata will be created.
|
|
||||||
control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
|
|
||||||
|
|
||||||
// The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
|
|
||||||
control.UserAccountSaveDataSize = 0x4000;
|
|
||||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.ServiceAm,
|
|
||||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
|
||||||
}
|
|
||||||
|
|
||||||
LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
|
LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
|
||||||
LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId);
|
LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId);
|
||||||
|
|
||||||
context.ResponseData.Write(requiredSize);
|
context.ResponseData.Write(requiredSize);
|
||||||
|
|
||||||
@@ -153,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
// TODO: When above calls are implemented, switch to using ns:am
|
// TODO: When above calls are implemented, switch to using ns:am
|
||||||
|
|
||||||
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
|
long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode;
|
||||||
int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag;
|
int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag;
|
||||||
int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
|
int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages);
|
||||||
|
|
||||||
if (firstSupported > (int)TitleLanguage.BrazilianPortuguese)
|
if (firstSupported > (int)TitleLanguage.BrazilianPortuguese)
|
||||||
@@ -196,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
public ResultCode GetDisplayVersion(ServiceCtx context)
|
public ResultCode GetDisplayVersion(ServiceCtx context)
|
||||||
{
|
{
|
||||||
// If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
|
// If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation.
|
||||||
context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion);
|
context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion);
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
@@ -251,13 +235,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
long journalSize = context.RequestData.ReadInt64();
|
long journalSize = context.RequestData.ReadInt64();
|
||||||
|
|
||||||
// Mask out the low nibble of the program ID to get the application ID
|
// Mask out the low nibble of the program ID to get the application ID
|
||||||
ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul);
|
ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul);
|
||||||
|
|
||||||
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
|
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||||
|
|
||||||
LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
|
LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
|
||||||
out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize,
|
out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize);
|
||||||
journalSize);
|
|
||||||
|
|
||||||
if (result.IsFailure()) return (ResultCode)result.Value;
|
if (result.IsFailure()) return (ResultCode)result.Value;
|
||||||
|
|
||||||
@@ -677,7 +660,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
|
throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)");
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Device.Application.LoadServiceNca(filePath);
|
context.Device.LoadNca(filePath);
|
||||||
|
|
||||||
// FIXME: Most likely not how this should be done?
|
// FIXME: Most likely not how this should be done?
|
||||||
while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
|
while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u"))
|
||||||
|
@@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
|||||||
|
|
||||||
return new ApplicationLaunchProperty
|
return new ApplicationLaunchProperty
|
||||||
{
|
{
|
||||||
TitleId = context.Device.Application.TitleId,
|
TitleId = context.Device.Processes.ActiveApplication.ProgramId,
|
||||||
Version = 0x00,
|
Version = 0x00,
|
||||||
BaseGameStorageId = (byte)StorageId.BuiltInSystem,
|
BaseGameStorageId = (byte)StorageId.BuiltInSystem,
|
||||||
UpdateGameStorageId = (byte)StorageId.None
|
UpdateGameStorageId = (byte)StorageId.None
|
||||||
|
@@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
|||||||
|
|
||||||
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
||||||
|
|
||||||
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
|
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
|
||||||
|
|
||||||
context.ResponseData.WriteStruct(applicationAlbumEntry);
|
context.ResponseData.WriteStruct(applicationAlbumEntry);
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
|||||||
|
|
||||||
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
||||||
|
|
||||||
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
|
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
|
||||||
|
|
||||||
context.ResponseData.WriteStruct(applicationAlbumEntry);
|
context.ResponseData.WriteStruct(applicationAlbumEntry);
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps
|
|||||||
|
|
||||||
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray();
|
||||||
|
|
||||||
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry);
|
ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry);
|
||||||
|
|
||||||
context.ResponseData.WriteStruct(applicationAlbumEntry);
|
context.ResponseData.WriteStruct(applicationAlbumEntry);
|
||||||
|
|
||||||
|
@@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
|
|||||||
errorReport.AppendLine();
|
errorReport.AppendLine();
|
||||||
errorReport.AppendLine("ErrorReport log:");
|
errorReport.AppendLine("ErrorReport log:");
|
||||||
|
|
||||||
errorReport.AppendLine($"\tTitleId: {context.Device.Application.TitleId:x16}");
|
errorReport.AppendLine($"\tTitleId: {context.Device.Processes.ActiveApplication.ProgramIdText}");
|
||||||
errorReport.AppendLine($"\tPid: {pid}");
|
errorReport.AppendLine($"\tPid: {pid}");
|
||||||
errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}");
|
errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}");
|
||||||
errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}");
|
errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}");
|
||||||
@@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal
|
|||||||
{
|
{
|
||||||
errorReport.AppendLine("CPU Context:");
|
errorReport.AppendLine("CPU Context:");
|
||||||
|
|
||||||
if (context.Device.Application.TitleIs64Bit)
|
if (context.Device.Processes.ActiveApplication.Is64Bit)
|
||||||
{
|
{
|
||||||
CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0];
|
CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0];
|
||||||
|
|
||||||
|
@@ -334,7 +334,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||||
ApplicationControlProperty controlProperty = context.Device.Application.ControlData.Value;
|
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
@@ -808,7 +808,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
|||||||
{
|
{
|
||||||
byte programIndex = context.RequestData.ReadByte();
|
byte programIndex = context.RequestData.ReadByte();
|
||||||
|
|
||||||
if ((context.Device.Application.TitleId & 0xf) != programIndex)
|
if ((context.Device.Processes.ActiveApplication.ProgramId & 0xf) != programIndex)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex}).");
|
throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex}).");
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
||||||
{
|
{
|
||||||
struct AtomicStorage<T> where T: unmanaged
|
struct AtomicStorage<T> where T: unmanaged, ISampledDataStruct
|
||||||
{
|
{
|
||||||
public ulong SamplingNumber;
|
public ulong SamplingNumber;
|
||||||
public T Object;
|
public T Object;
|
||||||
@@ -14,9 +14,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
|||||||
|
|
||||||
public void SetObject(ref T obj)
|
public void SetObject(ref T obj)
|
||||||
{
|
{
|
||||||
ISampledData samplingProvider = obj as ISampledData;
|
ulong samplingNumber = ISampledDataStruct.GetSamplingNumber(ref obj);
|
||||||
|
|
||||||
Interlocked.Exchange(ref SamplingNumber, samplingProvider.SamplingNumber);
|
Interlocked.Exchange(ref SamplingNumber, samplingNumber);
|
||||||
|
|
||||||
Thread.MemoryBarrier();
|
Thread.MemoryBarrier();
|
||||||
|
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
|
||||||
{
|
|
||||||
interface ISampledData
|
|
||||||
{
|
|
||||||
ulong SamplingNumber { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is a "marker interface" to add some compile-time safety to a convention-based optimization.
|
||||||
|
///
|
||||||
|
/// Any struct implementing this interface should:
|
||||||
|
/// - use <c>StructLayoutAttribute</c> (and related attributes) to explicity control how the struct is laid out in memory.
|
||||||
|
/// - ensure that the method <c>ISampledDataStruct.GetSamplingNumberFieldOffset()</c> correctly returns the offset, in bytes,
|
||||||
|
/// to the ulong "Sampling Number" field within the struct. Most types have it as the first field, so the default offset is 0.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// <c>
|
||||||
|
/// [StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||||
|
/// struct DebugPadState : ISampledDataStruct
|
||||||
|
/// {
|
||||||
|
/// public ulong SamplingNumber; // 1st field, so no need to add special handling to GetSamplingNumberFieldOffset()
|
||||||
|
/// // other members...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// [StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||||
|
/// struct SixAxisSensorState : ISampledDataStruct
|
||||||
|
/// {
|
||||||
|
/// public ulong DeltaTime;
|
||||||
|
/// public ulong SamplingNumber; // Not the first field - needs special handling in GetSamplingNumberFieldOffset()
|
||||||
|
/// // other members...
|
||||||
|
/// }
|
||||||
|
/// </c>
|
||||||
|
/// </summary>
|
||||||
|
internal interface ISampledDataStruct
|
||||||
|
{
|
||||||
|
// No Instance Members - marker interface only
|
||||||
|
|
||||||
|
public static ulong GetSamplingNumber<T>(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct
|
||||||
|
{
|
||||||
|
ReadOnlySpan<T> structSpan = MemoryMarshal.CreateReadOnlySpan(ref sampledDataStruct, 1);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> byteSpan = MemoryMarshal.Cast<T, byte>(structSpan);
|
||||||
|
|
||||||
|
int fieldOffset = GetSamplingNumberFieldOffset(ref sampledDataStruct);
|
||||||
|
|
||||||
|
if (fieldOffset > 0)
|
||||||
|
{
|
||||||
|
byteSpan = byteSpan.Slice(fieldOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong value = BinaryPrimitives.ReadUInt64LittleEndian(byteSpan);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetSamplingNumberFieldOffset<T>(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct
|
||||||
|
{
|
||||||
|
return sampledDataStruct switch
|
||||||
|
{
|
||||||
|
Npad.SixAxisSensorState _ => sizeof(ulong),
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,7 +5,7 @@ using System.Threading;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
||||||
{
|
{
|
||||||
struct RingLifo<T> where T: unmanaged
|
struct RingLifo<T> where T: unmanaged, ISampledDataStruct
|
||||||
{
|
{
|
||||||
private const ulong MaxEntries = 17;
|
private const ulong MaxEntries = 17;
|
||||||
|
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad
|
||||||
{
|
{
|
||||||
struct DebugPadState : ISampledData
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
struct DebugPadState : ISampledDataStruct
|
||||||
{
|
{
|
||||||
public ulong SamplingNumber;
|
public ulong SamplingNumber;
|
||||||
public DebugPadAttribute Attributes;
|
public DebugPadAttribute Attributes;
|
||||||
public DebugPadButton Buttons;
|
public DebugPadButton Buttons;
|
||||||
public AnalogStickState AnalogStickR;
|
public AnalogStickState AnalogStickR;
|
||||||
public AnalogStickState AnalogStickL;
|
public AnalogStickState AnalogStickL;
|
||||||
|
|
||||||
ulong ISampledData.SamplingNumber => SamplingNumber;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
|
||||||
{
|
{
|
||||||
// TODO: This seems entirely wrong
|
|
||||||
[Flags]
|
[Flags]
|
||||||
enum KeyboardModifier : uint
|
enum KeyboardModifier : ulong
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
Control = 1 << 0,
|
Control = 1 << 0,
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
|
||||||
{
|
{
|
||||||
struct KeyboardState : ISampledData
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
struct KeyboardState : ISampledDataStruct
|
||||||
{
|
{
|
||||||
public ulong SamplingNumber;
|
public ulong SamplingNumber;
|
||||||
public KeyboardModifier Modifiers;
|
public KeyboardModifier Modifiers;
|
||||||
public KeyboardKey Keys;
|
public KeyboardKey Keys;
|
||||||
|
|
||||||
ulong ISampledData.SamplingNumber => SamplingNumber;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
|
||||||
{
|
{
|
||||||
struct MouseState : ISampledData
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
struct MouseState : ISampledDataStruct
|
||||||
{
|
{
|
||||||
public ulong SamplingNumber;
|
public ulong SamplingNumber;
|
||||||
public int X;
|
public int X;
|
||||||
@@ -13,7 +15,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
|
|||||||
public int WheelDeltaY;
|
public int WheelDeltaY;
|
||||||
public MouseButton Buttons;
|
public MouseButton Buttons;
|
||||||
public MouseAttribute Attributes;
|
public MouseAttribute Attributes;
|
||||||
|
|
||||||
ulong ISampledData.SamplingNumber => SamplingNumber;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
|
||||||
{
|
{
|
||||||
struct NpadCommonState : ISampledData
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
struct NpadCommonState : ISampledDataStruct
|
||||||
{
|
{
|
||||||
public ulong SamplingNumber;
|
public ulong SamplingNumber;
|
||||||
public NpadButton Buttons;
|
public NpadButton Buttons;
|
||||||
@@ -10,7 +12,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
|
|||||||
public AnalogStickState AnalogStickR;
|
public AnalogStickState AnalogStickR;
|
||||||
public NpadAttribute Attributes;
|
public NpadAttribute Attributes;
|
||||||
private uint _reserved;
|
private uint _reserved;
|
||||||
|
|
||||||
ulong ISampledData.SamplingNumber => SamplingNumber;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
|
||||||
{
|
{
|
||||||
struct NpadGcTriggerState : ISampledData
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
struct NpadGcTriggerState : ISampledDataStruct
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
public ulong SamplingNumber;
|
public ulong SamplingNumber;
|
||||||
public uint TriggerL;
|
public uint TriggerL;
|
||||||
public uint TriggerR;
|
public uint TriggerR;
|
||||||
#pragma warning restore CS0649
|
#pragma warning restore CS0649
|
||||||
|
|
||||||
ulong ISampledData.SamplingNumber => SamplingNumber;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user