Compare commits

...

15 Commits

Author SHA1 Message Date
Evan Husted
2d7700949c UI: Play Report Analysis V2
Support for multiple keys per game, and provide an order of resolution via Priority.

(Currently) functionally identical to before, as only BOTW Master Mode is supported.
2025-02-02 16:07:30 -06:00
Evan Husted
ea2287af03 misc: chore: Rewrite play report checker to use a simple loop instead of Gommon Optionals
(I love how a class that's supposed to guard against null values entering your code still allows them thats so cool)
2025-02-02 13:17:31 -06:00
Evan Husted
37af8c70aa UI: RPC: Add the ability for the DiscordIntegrationModule to inspect values in Play Reports and dynamically show different gameplay values, depending on a predefined map of values and formatters.
Currently only BOTW Master Mode is supported.
Open to PRs!
2025-02-02 02:21:33 -06:00
Evan Husted
50cee3fd19 feature: HorizonStatic PlayReportPrinted event 2025-02-02 02:20:14 -06:00
Evan Husted
a46aacf2e2 gpu: Switch the 500ms timeout back to 1s
It seemed like it was waiting for 1 second no matter what; might as well have the log & syncpoint map match reality.
2025-02-01 19:21:19 -06:00
Evan Husted
ad9d6588e8 misc: chore: Collapse HLE swkbd character validation utils into a single class 2025-02-01 14:11:35 -06:00
Evan Husted
38ef65aae0 misc: chore: Move all GeneratedRegex methods into one static class with static instance accessors. 2025-02-01 14:07:32 -06:00
Evan Husted
9f94aa1c79 misc: chore: gpu: Lower default Syncpoint wait timeout from 1 second to 500ms 2025-02-01 03:30:13 -06:00
Evan Husted
2c9a26c11c misc: chore: Regular Architecture bool properties in RunningPlatform without OS constraint 2025-02-01 03:29:24 -06:00
Evan Husted
a4a15a4c80 misc: chore: simplify graphics backend selection logic in RendererHost constructor 2025-02-01 03:28:49 -06:00
Evan Husted
cc3b95eee1 misc: chore: More descriptive error for trying to create a Metal EmbeddedWindow on non-ARM Mac 2025-02-01 03:28:26 -06:00
Evan Husted
2ab806f759 UI: [ci skip] Fix ContentDialog symbols being backwards for right-to-left languages 2025-02-01 01:42:12 -06:00
Evan Husted
6d75410bd2 UI: Use the dynamic Ryujinx/Ryujinx Canary for dialog titles 2025-01-30 21:57:03 -06:00
Evan Husted
196b2eaf66 misc: chore: [ci skip] Remove needless fs integrity checks get in aoc extractor 2025-01-30 20:54:08 -06:00
Evan Husted
82fe519766 misc: chore: [ci skip] fix log on AOC extraction failure 2025-01-30 20:52:12 -06:00
25 changed files with 511 additions and 312 deletions

View File

@@ -0,0 +1,118 @@
using System.Text.RegularExpressions;
namespace Ryujinx.Common.Helper
{
public static partial class Patterns
{
#region Accessors
public static readonly Regex Numeric = NumericRegex();
public static readonly Regex AmdGcn = AmdGcnRegex();
public static readonly Regex NvidiaConsumerClass = NvidiaConsumerClassRegex();
public static readonly Regex DomainLp1Ns = DomainLp1NsRegex();
public static readonly Regex DomainLp1Lp1Npln = DomainLp1Lp1NplnRegex();
public static readonly Regex DomainLp1Znc = DomainLp1ZncRegex();
public static readonly Regex DomainSbApi = DomainSbApiRegex();
public static readonly Regex DomainSbAccounts = DomainSbAccountsRegex();
public static readonly Regex DomainAccounts = DomainAccountsRegex();
public static readonly Regex Module = ModuleRegex();
public static readonly Regex FsSdk = FsSdkRegex();
public static readonly Regex SdkMw = SdkMwRegex();
// ReSharper disable once InconsistentNaming
public static readonly Regex CJK = CJKRegex();
public static readonly Regex LdnPassphrase = LdnPassphraseRegex();
public static readonly Regex CleanText = CleanTextRegex();
#endregion
#region Generated pattern stubs
#region Numeric validation
[GeneratedRegex("[0-9]|.")]
internal static partial Regex NumericRegex();
#endregion
#region GPU names
[GeneratedRegex(
"Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")]
internal static partial Regex AmdGcnRegex();
[GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")]
internal static partial Regex NvidiaConsumerClassRegex();
#endregion
#region DNS blocking
public static readonly Regex[] BlockedHosts =
[
DomainLp1Ns,
DomainLp1Lp1Npln,
DomainLp1Znc,
DomainSbApi,
DomainSbAccounts,
DomainAccounts
];
const RegexOptions DnsRegexOpts =
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
[GeneratedRegex(@"^(.*)\-lp1\.(n|s)\.n\.srv\.nintendo\.net$", DnsRegexOpts)]
internal static partial Regex DomainLp1NsRegex();
[GeneratedRegex(@"^(.*)\-lp1\.lp1\.t\.npln\.srv\.nintendo\.net$", DnsRegexOpts)]
internal static partial Regex DomainLp1Lp1NplnRegex();
[GeneratedRegex(@"^(.*)\-lp1\.(znc|p)\.srv\.nintendo\.net$", DnsRegexOpts)]
internal static partial Regex DomainLp1ZncRegex();
[GeneratedRegex(@"^(.*)\-sb\-api\.accounts\.nintendo\.com$", DnsRegexOpts)]
internal static partial Regex DomainSbApiRegex();
[GeneratedRegex(@"^(.*)\-sb\.accounts\.nintendo\.com$", DnsRegexOpts)]
internal static partial Regex DomainSbAccountsRegex();
[GeneratedRegex(@"^accounts\.nintendo\.com$", DnsRegexOpts)]
internal static partial Regex DomainAccountsRegex();
#endregion
#region Executable information
[GeneratedRegex(@"[a-z]:[\\/][ -~]{5,}\.nss", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
internal static partial Regex ModuleRegex();
[GeneratedRegex(@"sdk_version: ([0-9.]*)")]
internal static partial Regex FsSdkRegex();
[GeneratedRegex(@"SDK MW[ -~]*")]
internal static partial Regex SdkMwRegex();
#endregion
#region CJK
[GeneratedRegex(
"\\p{IsHangulJamo}|\\p{IsCJKRadicalsSupplement}|\\p{IsCJKSymbolsandPunctuation}|\\p{IsEnclosedCJKLettersandMonths}|\\p{IsCJKCompatibility}|\\p{IsCJKUnifiedIdeographsExtensionA}|\\p{IsCJKUnifiedIdeographs}|\\p{IsHangulSyllables}|\\p{IsCJKCompatibilityForms}")]
private static partial Regex CJKRegex();
#endregion
[GeneratedRegex("Ryujinx-[0-9a-f]{8}")]
private static partial Regex LdnPassphraseRegex();
[GeneratedRegex(@"[^\u0000\u0009\u000A\u000D\u0020-\uFFFF]..")]
private static partial Regex CleanTextRegex();
#endregion
}
}

View File

@@ -0,0 +1,80 @@
using Gommon;
using MsgPack;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Common.Helper
{
public class PlayReportAnalyzer
{
private readonly List<PlayReportGameSpec> _specs = [];
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
{
_specs.Add(transform(new PlayReportGameSpec { TitleIdStr = titleId }));
return this;
}
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
{
_specs.Add(new PlayReportGameSpec { TitleIdStr = titleId }.Apply(transform));
return this;
}
public Optional<string> Run(string runningGameId, MessagePackObject playReport)
{
if (!playReport.IsDictionary)
return Optional<string>.None;
if (!_specs.TryGetFirst(s => s.TitleIdStr.EqualsIgnoreCase(runningGameId), out PlayReportGameSpec spec))
return Optional<string>.None;
foreach (PlayReportValueFormatterSpec formatSpec in spec.Analyses.OrderBy(x => x.Priority))
{
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
continue;
return formatSpec.ValueFormatter(valuePackObject.ToObject());
}
return Optional<string>.None;
}
}
public class PlayReportGameSpec
{
public required string TitleIdStr { get; init; }
public List<PlayReportValueFormatterSpec> Analyses { get; } = [];
public PlayReportGameSpec AddValueFormatter(string reportKey, Func<object, string> valueFormatter)
{
Analyses.Add(new PlayReportValueFormatterSpec
{
Priority = Analyses.Count,
ReportKey = reportKey,
ValueFormatter = valueFormatter
});
return this;
}
public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, Func<object, string> valueFormatter)
{
Analyses.Add(new PlayReportValueFormatterSpec
{
Priority = priority,
ReportKey = reportKey,
ValueFormatter = valueFormatter
});
return this;
}
}
public struct PlayReportValueFormatterSpec
{
public required int Priority { get; init; }
public required string ReportKey { get; init; }
public required Func<object, string> ValueFormatter { get; init; }
}
}

View File

@@ -10,14 +10,18 @@ namespace Ryujinx.Common.Helper
public static bool IsMacOS => OperatingSystem.IsMacOS();
public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsArm => RuntimeInformation.OSArchitecture is Architecture.Arm64;
public static bool IsX64 => RuntimeInformation.OSArchitecture is Architecture.X64;
public static bool IsIntelMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.X64;
public static bool IsArmMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.Arm64;
public static bool IsIntelMac => IsMacOS && IsX64;
public static bool IsArmMac => IsMacOS && IsArm;
public static bool IsX64Windows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.X64);
public static bool IsArmWindows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
public static bool IsX64Windows => IsWindows && IsX64;
public static bool IsArmWindows => IsWindows && IsArm;
public static bool IsX64Linux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.X64);
public static bool IsArmLinux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
public static bool IsX64Linux => IsLinux && IsX64;
public static bool IsArmLinux => IsLinux && IsArmMac;
}
}

View File

@@ -87,7 +87,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
}
using ManualResetEvent waitEvent = new(false);
SyncpointWaiterHandle info = _syncpoints[id].RegisterCallback(threshold, (x) => waitEvent.Set());
SyncpointWaiterHandle info = _syncpoints[id].RegisterCallback(threshold, _ => waitEvent.Set());
if (info == null)
{
@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
bool signaled = waitEvent.WaitOne(timeout);
if (!signaled && info != null)
if (!signaled)
{
Logger.Error?.Print(LogClass.Gpu, $"Wait on syncpoint {id} for threshold {threshold} took more than {timeout.TotalMilliseconds}ms, resuming execution...");

View File

@@ -16,14 +16,8 @@ namespace Ryujinx.Graphics.Vulkan
Unknown,
}
static partial class VendorUtils
static class VendorUtils
{
[GeneratedRegex("Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")]
public static partial Regex AmdGcnRegex();
[GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")]
public static partial Regex NvidiaConsumerClassRegex();
public static Vendor FromId(uint id)
{
return id switch

View File

@@ -1,5 +1,6 @@
using Gommon;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
@@ -375,11 +376,11 @@ namespace Ryujinx.Graphics.Vulkan
GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}";
IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && Patterns.AmdGcn.IsMatch(GpuRenderer);
if (Vendor == Vendor.Nvidia)
{
Match match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer);
Match match = Patterns.NvidiaConsumerClass.Match(GpuRenderer);
if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber))
{

View File

@@ -5,6 +5,7 @@ using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using Ryujinx.HLE.HOS.SystemState;
@@ -30,9 +31,6 @@ namespace Ryujinx.HLE.HOS.Applets.Error
public event EventHandler AppletStateChanged;
[GeneratedRegex(@"[^\u0000\u0009\u000A\u000D\u0020-\uFFFF]..")]
private static partial Regex CleanTextRegex();
public ErrorApplet(Horizon horizon)
{
_horizon = horizon;
@@ -107,7 +105,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
private static string CleanText(string value)
{
return CleanTextRegex().Replace(value, string.Empty).Replace("\0", string.Empty);
return Patterns.CleanText.Replace(value, string.Empty).Replace("\0", string.Empty);
}
private string GetMessageText(uint module, uint description, string key)

View File

@@ -1,17 +0,0 @@
using System.Text.RegularExpressions;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
public static partial class CJKCharacterValidation
{
public static bool IsCJK(char value)
{
Regex regex = CJKRegex();
return regex.IsMatch(value.ToString());
}
[GeneratedRegex("\\p{IsHangulJamo}|\\p{IsCJKRadicalsSupplement}|\\p{IsCJKSymbolsandPunctuation}|\\p{IsEnclosedCJKLettersandMonths}|\\p{IsCJKCompatibility}|\\p{IsCJKUnifiedIdeographsExtensionA}|\\p{IsCJKUnifiedIdeographs}|\\p{IsHangulSyllables}|\\p{IsCJKCompatibilityForms}")]
private static partial Regex CJKRegex();
}
}

View File

@@ -0,0 +1,10 @@
using Ryujinx.Common.Helper;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
public static class CharacterValidation
{
public static bool IsNumeric(char value) => Patterns.Numeric.IsMatch(value.ToString());
public static bool IsCJK(char value) => Patterns.CJK.IsMatch(value.ToString());
}
}

View File

@@ -1,17 +0,0 @@
using System.Text.RegularExpressions;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
public static partial class NumericCharacterValidation
{
public static bool IsNumeric(char value)
{
Regex regex = NumericRegex();
return regex.IsMatch(value.ToString());
}
[GeneratedRegex("[0-9]|.")]
private static partial Regex NumericRegex();
}
}

View File

@@ -1,37 +1,13 @@
using Ryujinx.Common.Helper;
using System.Text.RegularExpressions;
namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy
{
static partial class DnsBlacklist
static class DnsBlacklist
{
const RegexOptions RegexOpts = RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
[GeneratedRegex(@"^(.*)\-lp1\.(n|s)\.n\.srv\.nintendo\.net$", RegexOpts)]
private static partial Regex BlockedHost1();
[GeneratedRegex(@"^(.*)\-lp1\.lp1\.t\.npln\.srv\.nintendo\.net$", RegexOpts)]
private static partial Regex BlockedHost2();
[GeneratedRegex(@"^(.*)\-lp1\.(znc|p)\.srv\.nintendo\.net$", RegexOpts)]
private static partial Regex BlockedHost3();
[GeneratedRegex(@"^(.*)\-sb\-api\.accounts\.nintendo\.com$", RegexOpts)]
private static partial Regex BlockedHost4();
[GeneratedRegex(@"^(.*)\-sb\.accounts\.nintendo\.com$", RegexOpts)]
private static partial Regex BlockedHost5();
[GeneratedRegex(@"^accounts\.nintendo\.com$", RegexOpts)]
private static partial Regex BlockedHost6();
private static readonly Regex[] _blockedHosts =
[
BlockedHost1(),
BlockedHost2(),
BlockedHost3(),
BlockedHost4(),
BlockedHost5(),
BlockedHost6()
];
public static bool IsHostBlocked(string host)
{
foreach (Regex regex in _blockedHosts)
foreach (Regex regex in Patterns.BlockedHosts)
{
if (regex.IsMatch(host))
{

View File

@@ -2,6 +2,7 @@ using LibHac.Common.FixedArrays;
using LibHac.Fs;
using LibHac.Loader;
using LibHac.Tools.FsSystem;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using System;
using System.Text;
@@ -29,13 +30,6 @@ namespace Ryujinx.HLE.Loaders.Executables
public string Name;
public Array32<byte> BuildId;
[GeneratedRegex(@"[a-z]:[\\/][ -~]{5,}\.nss", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex ModuleRegex();
[GeneratedRegex(@"sdk_version: ([0-9.]*)")]
private static partial Regex FsSdkRegex();
[GeneratedRegex(@"SDK MW[ -~]*")]
private static partial Regex SdkMwRegex();
public NsoExecutable(IStorage inStorage, string name = null)
{
NsoReader reader = new();
@@ -90,7 +84,7 @@ namespace Ryujinx.HLE.Loaders.Executables
if (string.IsNullOrEmpty(modulePath))
{
Match moduleMatch = ModuleRegex().Match(rawTextBuffer);
Match moduleMatch = Patterns.Module.Match(rawTextBuffer);
if (moduleMatch.Success)
{
modulePath = moduleMatch.Value;
@@ -99,13 +93,13 @@ namespace Ryujinx.HLE.Loaders.Executables
stringBuilder.AppendLine($" Module: {modulePath}");
Match fsSdkMatch = FsSdkRegex().Match(rawTextBuffer);
Match fsSdkMatch = Patterns.FsSdk.Match(rawTextBuffer);
if (fsSdkMatch.Success)
{
stringBuilder.AppendLine($" FS SDK Version: {fsSdkMatch.Value.Replace("sdk_version: ", string.Empty)}");
}
MatchCollection sdkMwMatches = SdkMwRegex().Matches(rawTextBuffer);
MatchCollection sdkMwMatches = Patterns.SdkMw.Matches(rawTextBuffer);
if (sdkMwMatches.Count != 0)
{
string libHeader = " SDK Libraries: ";

View File

@@ -1,3 +1,4 @@
using MsgPack;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
@@ -6,6 +7,10 @@ namespace Ryujinx.Horizon
{
public static class HorizonStatic
{
internal static void HandlePlayReport(MessagePackObject report) => PlayReportPrinted?.Invoke(report);
public static event Action<MessagePackObject> PlayReportPrinted;
[ThreadStatic]
private static HorizonOptions _options;

View File

@@ -230,6 +230,8 @@ namespace Ryujinx.Horizon.Prepo.Ipc
builder.AppendLine($" Room: {gameRoom}");
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
HorizonStatic.HandlePlayReport(deserializedReport);
Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());

View File

@@ -11475,126 +11475,126 @@
{
"ID": "DialogConfirmationTitle",
"Translations": {
"ar_SA": "ريوجينكس - تأكيد",
"de_DE": "Ryujinx - Bestätigung",
"el_GR": "Ryujinx - Επιβεβαίωση",
"en_US": "Ryujinx - Confirmation",
"es_ES": "Ryujinx - Confirmación",
"ar_SA": "{0} - تأكيد",
"de_DE": "{0} - Bestätigung",
"el_GR": "{0} - Επιβεβαίωση",
"en_US": "{0} - Confirmation",
"es_ES": "{0} - Confirmación",
"fr_FR": "",
"he_IL": "ריוג'ינקס - אישור",
"it_IT": "Ryujinx - Conferma",
"ja_JP": "Ryujinx - 確認",
"ko_KR": "Ryujinx - 확인",
"no_NO": "Ryujinx - Bekreftelse",
"pl_PL": "Ryujinx - Potwierdzenie",
"pt_BR": "Ryujinx - Confirmação",
"ru_RU": "Ryujinx - Подтверждение",
"sv_SE": "Ryujinx - Bekräftelse",
"th_TH": "Ryujinx - ยืนยัน",
"tr_TR": "Ryujinx - Onay",
"uk_UA": "Ryujinx - Підтвердження",
"zh_CN": "Ryujinx - 确认",
"zh_TW": "Ryujinx - 確認"
"he_IL": "{0} - אישור",
"it_IT": "{0} - Conferma",
"ja_JP": "{0} - 確認",
"ko_KR": "{0} - 확인",
"no_NO": "{0} - Bekreftelse",
"pl_PL": "{0} - Potwierdzenie",
"pt_BR": "{0} - Confirmação",
"ru_RU": "{0} - Подтверждение",
"sv_SE": "{0} - Bekräftelse",
"th_TH": "{0} - ยืนยัน",
"tr_TR": "{0} - Onay",
"uk_UA": "{0} - Підтвердження",
"zh_CN": "{0} - 确认",
"zh_TW": "{0} - 確認"
}
},
{
"ID": "DialogUpdaterTitle",
"Translations": {
"ar_SA": "ريوجينكس - المحدث",
"ar_SA": "{0} - المحدث",
"de_DE": "",
"el_GR": "Ryujinx - Ενημερωτής",
"en_US": "Ryujinx - Updater",
"es_ES": "Ryujinx - Actualizador",
"fr_FR": "Ryujinx - Mise à Jour",
"he_IL": "ריוג'ינקס - מעדכן",
"it_IT": "Ryujinx - Aggiornamento",
"ja_JP": "Ryujinx - アップデータ",
"ko_KR": "Ryujinx - 업데이터",
"no_NO": "Ryujinx Oppdaterer",
"pl_PL": "Ryujinx - Asystent aktualizacji",
"pt_BR": "Ryujinx - Atualizador",
"ru_RU": "Ryujinx - Обновление",
"sv_SE": "Ryujinx - Uppdatering",
"th_TH": "Ryujinx - อัพเดต",
"tr_TR": "Ryujinx - Güncelleyici",
"uk_UA": "Ryujinx - Програма оновлення",
"zh_CN": "Ryujinx - 更新",
"zh_TW": "Ryujinx - 更新程式"
"el_GR": "{0} - Ενημερωτής",
"en_US": "{0} - Updater",
"es_ES": "{0} - Actualizador",
"fr_FR": "{0} - Mise à Jour",
"he_IL": "{0} - מעדכן",
"it_IT": "{0} - Aggiornamento",
"ja_JP": "{0} - アップデータ",
"ko_KR": "{0} - 업데이터",
"no_NO": "{0} Oppdaterer",
"pl_PL": "{0} - Asystent aktualizacji",
"pt_BR": "{0} - Atualizador",
"ru_RU": "{0} - Обновление",
"sv_SE": "{0} - Uppdatering",
"th_TH": "{0} - อัพเดต",
"tr_TR": "{0} - Güncelleyici",
"uk_UA": "{0} - Програма оновлення",
"zh_CN": "{0} - 更新",
"zh_TW": "{0} - 更新程式"
}
},
{
"ID": "DialogErrorTitle",
"Translations": {
"ar_SA": "ريوجينكس - خطأ",
"de_DE": "Ryujinx - Fehler",
"el_GR": "Ryujinx - Σφάλμα",
"en_US": "Ryujinx - Error",
"ar_SA": "{0} - خطأ",
"de_DE": "{0} - Fehler",
"el_GR": "{0} - Σφάλμα",
"en_US": "{0} - Error",
"es_ES": "",
"fr_FR": "Ryujinx - Erreur",
"he_IL": "ריוג'ינקס - שגיאה",
"it_IT": "Ryujinx - Errore",
"ja_JP": "Ryujinx - エラー",
"ko_KR": "Ryujinx - 오류",
"no_NO": "Ryujinx - Feil",
"pl_PL": "Ryujinx - Błąd",
"pt_BR": "Ryujinx - Erro",
"ru_RU": "Ryujinx - Ошибка",
"sv_SE": "Ryujinx - Fel",
"th_TH": "Ryujinx - ผิดพลาด",
"tr_TR": "Ryujinx - Hata",
"uk_UA": "Ryujinx - Помилка",
"zh_CN": "Ryujinx - 错误",
"zh_TW": "Ryujinx - 錯誤"
"fr_FR": "{0} - Erreur",
"he_IL": "{0} - שגיאה",
"it_IT": "{0} - Errore",
"ja_JP": "{0} - エラー",
"ko_KR": "{0} - 오류",
"no_NO": "{0} - Feil",
"pl_PL": "{0} - Błąd",
"pt_BR": "{0} - Erro",
"ru_RU": "{0} - Ошибка",
"sv_SE": "{0} - Fel",
"th_TH": "{0} - ผิดพลาด",
"tr_TR": "{0} - Hata",
"uk_UA": "{0} - Помилка",
"zh_CN": "{0} - 错误",
"zh_TW": "{0} - 錯誤"
}
},
{
"ID": "DialogWarningTitle",
"Translations": {
"ar_SA": "ريوجينكس - تحذير",
"de_DE": "Ryujinx - Warnung",
"el_GR": "Ryujinx - Προειδοποίηση",
"en_US": "Ryujinx - Warning",
"es_ES": "Ryujinx - Advertencia",
"fr_FR": "Ryujinx - Avertissement",
"he_IL": "ריוג'ינקס - אזהרה",
"it_IT": "Ryujinx - Avviso",
"ja_JP": "Ryujinx - 警告",
"ko_KR": "Ryujinx - 경고",
"no_NO": "Ryujinx - Advarsel",
"pl_PL": "Ryujinx - Ostrzeżenie",
"pt_BR": "Ryujinx - Alerta",
"ru_RU": "Ryujinx - Предупреждение",
"sv_SE": "Ryujinx - Varning",
"th_TH": "Ryujinx - คำเตือน",
"tr_TR": "Ryujinx - Uyarı",
"uk_UA": "Ryujinx - Попередження",
"zh_CN": "Ryujinx - 警告",
"zh_TW": "Ryujinx - 警告"
"ar_SA": "{0} - تحذير",
"de_DE": "{0} - Warnung",
"el_GR": "{0} - Προειδοποίηση",
"en_US": "{0} - Warning",
"es_ES": "{0} - Advertencia",
"fr_FR": "{0} - Avertissement",
"he_IL": "{0} - אזהרה",
"it_IT": "{0} - Avviso",
"ja_JP": "{0} - 警告",
"ko_KR": "{0} - 경고",
"no_NO": "{0} - Advarsel",
"pl_PL": "{0} - Ostrzeżenie",
"pt_BR": "{0} - Alerta",
"ru_RU": "{0} - Предупреждение",
"sv_SE": "{0} - Varning",
"th_TH": "{0} - คำเตือน",
"tr_TR": "{0} - Uyarı",
"uk_UA": "{0} - Попередження",
"zh_CN": "{0} - 警告",
"zh_TW": "{0} - 警告"
}
},
{
"ID": "DialogExitTitle",
"Translations": {
"ar_SA": "ريوجينكس - الخروج",
"de_DE": "Ryujinx - Beenden",
"el_GR": "Ryujinx - Έξοδος",
"en_US": "Ryujinx - Exit",
"es_ES": "Ryujinx - Salir",
"fr_FR": "Ryujinx - Quitter",
"he_IL": "ריוג'ינקס - יציאה",
"it_IT": "Ryujinx - Esci",
"ja_JP": "Ryujinx - 終了",
"ko_KR": "Ryujinx - 종료",
"no_NO": "Ryujinx - Avslutt",
"pl_PL": "Ryujinx - Wyjdź",
"pt_BR": "Ryujinx - Sair",
"ru_RU": "Ryujinx - Выход",
"sv_SE": "Ryujinx - Avslut",
"th_TH": "Ryujinx - ออก",
"tr_TR": "Ryujinx - Çıkış",
"uk_UA": "Ryujinx - Вихід",
"zh_CN": "Ryujinx - 退出",
"zh_TW": "Ryujinx - 結束"
"ar_SA": "{0} - الخروج",
"de_DE": "{0} - Beenden",
"el_GR": "{0} - Έξοδος",
"en_US": "{0} - Exit",
"es_ES": "{0} - Salir",
"fr_FR": "{0} - Quitter",
"he_IL": "{0} - יציאה",
"it_IT": "{0} - Esci",
"ja_JP": "{0} - 終了",
"ko_KR": "{0} - 종료",
"no_NO": "{0} - Avslutt",
"pl_PL": "{0} - Wyjdź",
"pt_BR": "{0} - Sair",
"ru_RU": "{0} - Выход",
"sv_SE": "{0} - Avslut",
"th_TH": "{0} - ออก",
"tr_TR": "{0} - Çıkış",
"uk_UA": "{0} - Вихід",
"zh_CN": "{0} - 退出",
"zh_TW": "{0} - 結束"
}
},
{
@@ -17025,26 +17025,26 @@
{
"ID": "DialogStopEmulationTitle",
"Translations": {
"ar_SA": "ريوجينكس - إيقاف المحاكاة",
"de_DE": "Ryujinx - Beende Emulation",
"el_GR": "Ryujinx - Διακοπή εξομοίωσης",
"en_US": "Ryujinx - Stop Emulation",
"es_ES": "Ryujinx - Detener emulación",
"fr_FR": "Ryujinx - Arrêt de l'émulation",
"he_IL": "ריוג'ינקס - עצור אמולציה",
"it_IT": "Ryujinx - Ferma emulazione",
"ja_JP": "Ryujinx - エミュレーションを中止",
"ko_KR": "Ryujinx - 에뮬레이션 중지",
"no_NO": "Ryujinx - Stopp emulasjon",
"pl_PL": "Ryujinx - Zatrzymaj Emulację",
"pt_BR": "Ryujinx - Parar emulação",
"ru_RU": "Ryujinx - Остановка эмуляции",
"sv_SE": "Ryujinx - Stoppa emulering",
"th_TH": "Ryujinx - หยุดการจำลอง",
"tr_TR": "Ryujinx - Emülasyonu Durdur",
"uk_UA": "Ryujinx - Зупинити емуляцію",
"zh_CN": "Ryujinx - 停止模拟",
"zh_TW": "Ryujinx - 停止模擬"
"ar_SA": "{0} - إيقاف المحاكاة",
"de_DE": "{0} - Beende Emulation",
"el_GR": "{0} - Διακοπή εξομοίωσης",
"en_US": "{0} - Stop Emulation",
"es_ES": "{0} - Detener emulación",
"fr_FR": "{0} - Arrêt de l'émulation",
"he_IL": "{0} - עצור אמולציה",
"it_IT": "{0} - Ferma emulazione",
"ja_JP": "{0} - エミュレーションを中止",
"ko_KR": "{0} - 에뮬레이션 중지",
"no_NO": "{0} - Stopp emulasjon",
"pl_PL": "{0} - Zatrzymaj Emulację",
"pt_BR": "{0} - Parar emulação",
"ru_RU": "{0} - Остановка эмуляции",
"sv_SE": "{0} - Stoppa emulering",
"th_TH": "{0} - หยุดการจำลอง",
"tr_TR": "{0} - Emülasyonu Durdur",
"uk_UA": "{0} - Зупинити емуляцію",
"zh_CN": "{0} - 停止模拟",
"zh_TW": "{0} - 停止模擬"
}
},
{
@@ -17950,51 +17950,51 @@
{
"ID": "RyujinxInfo",
"Translations": {
"ar_SA": "ريوجينكس - معلومات",
"ar_SA": "{0} - معلومات",
"de_DE": "",
"el_GR": "Ryujinx - Πληροφορίες",
"en_US": "Ryujinx - Info",
"el_GR": "{0} - Πληροφορίες",
"en_US": "{0} - Info",
"es_ES": "",
"fr_FR": "",
"he_IL": "ריוג'ינקס - מידע",
"it_IT": "Ryujinx - Informazioni",
"ja_JP": "Ryujinx - 情報",
"ko_KR": "Ryujinx - 정보",
"no_NO": "Ryujinx - Informasjon",
"he_IL": "{0} - מידע",
"it_IT": "{0} - Informazioni",
"ja_JP": "{0} - 情報",
"ko_KR": "{0} - 정보",
"no_NO": "{0} - Informasjon",
"pl_PL": "",
"pt_BR": "Ryujinx - Informação",
"ru_RU": "Ryujinx - Информация",
"pt_BR": "{0} - Informação",
"ru_RU": "{0} - Информация",
"sv_SE": "",
"th_TH": "Ryujinx ข้อมูล",
"tr_TR": "Ryujinx - Bilgi",
"uk_UA": "Ryujin x - Інформація",
"zh_CN": "Ryujinx - 信息",
"zh_TW": "Ryujinx - 資訊"
"th_TH": "{0} ข้อมูล",
"tr_TR": "{0} - Bilgi",
"uk_UA": "{0} - Інформація",
"zh_CN": "{0} - 信息",
"zh_TW": "{0} - 資訊"
}
},
{
"ID": "RyujinxConfirm",
"Translations": {
"ar_SA": "ريوجينكس - تأكيد",
"de_DE": "Ryujinx - Bestätigung",
"el_GR": "Ryujinx - Επιβεβαίωση",
"en_US": "Ryujinx - Confirmation",
"es_ES": "Ryujinx - Confirmación",
"ar_SA": "{0} - تأكيد",
"de_DE": "{0} - Bestätigung",
"el_GR": "{0} - Επιβεβαίωση",
"en_US": "{0} - Confirmation",
"es_ES": "{0} - Confirmación",
"fr_FR": "",
"he_IL": "ריוג'ינקס - אישור",
"it_IT": "Ryujinx - Conferma",
"ja_JP": "Ryujinx - 確認",
"ko_KR": "Ryujinx - 확인",
"no_NO": "Ryujinx - Bekreftelse",
"pl_PL": "Ryujinx - Potwierdzenie",
"pt_BR": "Ryujinx - Confirmação",
"ru_RU": "Ryujinx - Подтверждение",
"sv_SE": "Ryujinx - Bekräfta",
"th_TH": "Ryujinx - ยืนยัน",
"tr_TR": "Ryujinx - Doğrulama",
"uk_UA": "Ryujinx - Підтвердження",
"zh_CN": "Ryujinx - 确认",
"zh_TW": "Ryujinx - 確認"
"he_IL": "{0} - אישור",
"it_IT": "{0} - Conferma",
"ja_JP": "{0} - 確認",
"ko_KR": "{0} - 확인",
"no_NO": "{0} - Bekreftelse",
"pl_PL": "{0} - Potwierdzenie",
"pt_BR": "{0} - Confirmação",
"ru_RU": "{0} - Подтверждение",
"sv_SE": "{0} - Bekräfta",
"th_TH": "{0} - ยืนยัน",
"tr_TR": "{0} - Doğrulama",
"uk_UA": "{0} - Підтвердження",
"zh_CN": "{0} - 确认",
"zh_TW": "{0} - 確認"
}
},
{
@@ -18800,26 +18800,26 @@
{
"ID": "RyujinxUpdater",
"Translations": {
"ar_SA": "محدث ريوجينكس",
"de_DE": "Ryujinx - Updater",
"el_GR": "Ryujinx Ενημερωτής",
"en_US": "Ryujinx Updater",
"es_ES": "Actualizador de Ryujinx",
"fr_FR": "Mise à jour de Ryujinx",
"he_IL": "מעדכן ריוג'ינקס",
"it_IT": "Aggiornamento di Ryujinx",
"ja_JP": "Ryujinx アップデータ",
"ko_KR": "Ryujinx 업데이터",
"no_NO": "Ryujinx Oppgradering",
"pl_PL": "Aktualizator Ryujinx",
"pt_BR": "Atualizador do Ryujinx",
"ru_RU": "Ryujinx - Обновление",
"sv_SE": "Uppdaterare för Ryujinx",
"th_TH": "ตัวอัปเดต Ryujinx",
"tr_TR": "Ryujinx Güncelleyicisi",
"uk_UA": "Програма оновлення Ryujinx",
"zh_CN": "Ryujinx 更新",
"zh_TW": "Ryujinx 更新程式"
"ar_SA": "محدث {0}",
"de_DE": "",
"el_GR": "{0} Ενημερωτής",
"en_US": "{0} Updater",
"es_ES": "Actualizador de {0}",
"fr_FR": "Mise à jour de {0}",
"he_IL": "מעדכן {0}",
"it_IT": "Aggiornamento di {0}",
"ja_JP": "{0} アップデータ",
"ko_KR": "{0} 업데이터",
"no_NO": "{0} Oppgradering",
"pl_PL": "Aktualizator {0}",
"pt_BR": "Atualizador do {0}",
"ru_RU": "{0} Обновление",
"sv_SE": "Uppdaterare för {0}",
"th_TH": "ตัวอัปเดต {0}",
"tr_TR": "{0} Güncelleyicisi",
"uk_UA": "Програма оновлення {0}",
"zh_CN": "{0} 更新",
"zh_TW": "{0} 更新程式"
}
},
{

View File

@@ -337,7 +337,7 @@ namespace Ryujinx.Ava.Common
if (publicDataNca is null)
{
Logger.Error?.Print(LogClass.Application, "Extraction failure. The NCA was not present in the selected file");
Logger.Error?.Print(LogClass.Application, "Extraction failure. The PublicData NCA was not present in the selected file");
Dispatcher.UIThread.InvokeAsync(async () =>
{
@@ -349,10 +349,6 @@ namespace Ryujinx.Ava.Common
return;
}
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
int index = Nca.GetSectionIndexFromType(NcaSectionType.Data, publicDataNca.Header.ContentType);
try

View File

@@ -44,6 +44,16 @@ namespace Ryujinx.Ava.Common.Locale
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
SetDynamicValues(LocaleKeys.DialogConfirmationTitle, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.DialogUpdaterTitle, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.DialogErrorTitle, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.DialogWarningTitle, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.DialogExitTitle, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.DialogStopEmulationTitle, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxInfo, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxConfirm, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxUpdater, RyujinxApp.FullAppName);
}
public string this[LocaleKeys key]
@@ -88,11 +98,16 @@ namespace Ryujinx.Ava.Common.Locale
public static string FormatDynamicValue(LocaleKeys key, params object[] values)
=> Instance.UpdateAndGetDynamicValue(key, values);
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
public void SetDynamicValues(LocaleKeys key, params object[] values)
{
_dynamicValues[key] = values;
OnPropertyChanged("Translation");
}
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
{
SetDynamicValues(key, values);
return this[key];
}

View File

@@ -1,11 +1,19 @@
using DiscordRPC;
using Gommon;
using MsgPack;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.Horizon;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace Ryujinx.Ava
@@ -16,12 +24,12 @@ namespace Ryujinx.Ava
public static Timestamps GuestAppStartedAt { get; set; }
private static string VersionString
=> (ReleaseInformation.IsCanaryBuild ? "Canary " : string.Empty) + $"v{ReleaseInformation.Version}";
=> (ReleaseInformation.IsCanaryBuild ? "Canary " : string.Empty) + $"v{ReleaseInformation.Version}";
private static readonly string _description =
ReleaseInformation.IsValid
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}@{ReleaseInformation.BuildGitHash}"
: "dev build";
private static readonly string _description =
ReleaseInformation.IsValid
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}@{ReleaseInformation.BuildGitHash}"
: "dev build";
private const string ApplicationId = "1293250299716173864";
@@ -30,6 +38,7 @@ namespace Ryujinx.Ava
private static DiscordRpcClient _discordClient;
private static RichPresence _discordPresenceMain;
private static RichPresence _discordPresencePlaying;
public static void Initialize()
{
@@ -37,8 +46,7 @@ namespace Ryujinx.Ava
{
Assets = new Assets
{
LargeImageKey = "ryujinx",
LargeImageText = TruncateToByteLength(_description)
LargeImageKey = "ryujinx", LargeImageText = TruncateToByteLength(_description)
},
Details = "Main Menu",
State = "Idling",
@@ -47,6 +55,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
HorizonStatic.PlayReportPrinted += HandlePlayReport;
}
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
@@ -77,16 +86,15 @@ namespace Ryujinx.Ava
{
if (titleId.TryGet(out string tid))
SwitchToPlayingState(
ApplicationLibrary.LoadAndSaveMetaData(tid),
ApplicationLibrary.LoadAndSaveMetaData(tid),
Switch.Shared.Processes.ActiveApplication
);
else
else
SwitchToMainState();
}
private static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes)
{
_discordClient?.SetPresence(new RichPresence
private static RichPresence CreatePlayingState(ApplicationMetadata appMeta, ProcessResult procRes) =>
new()
{
Assets = new Assets
{
@@ -100,10 +108,44 @@ namespace Ryujinx.Ava
? $"Total play time: {ValueFormatUtils.FormatTimeSpan(appMeta.TimePlayed)}"
: "Never played",
Timestamps = GuestAppStartedAt ??= Timestamps.Now
});
};
private static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes)
{
_discordClient?.SetPresence(_discordPresencePlaying ??= CreatePlayingState(appMeta, procRes));
}
private static void SwitchToMainState() => _discordClient?.SetPresence(_discordPresenceMain);
private static void UpdatePlayingState()
{
_discordClient?.SetPresence(_discordPresencePlaying);
}
private static void SwitchToMainState()
{
_discordClient?.SetPresence(_discordPresenceMain);
_discordPresencePlaying = null;
}
private static readonly PlayReportAnalyzer _playReportAnalyzer = new PlayReportAnalyzer()
.AddSpec( // Breath of the Wild
"01007ef00011e000",
gameSpec =>
gameSpec.AddValueFormatter("IsHardMode", val => val is 1 ? "Playing Master Mode" : "Playing Normal Mode")
);
private static void HandlePlayReport(MessagePackObject playReport)
{
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
if (_discordPresencePlaying is null) return;
Optional<string> details = _playReportAnalyzer.Run(TitleIDs.CurrentApplication.Value, playReport);
if (!details.HasValue) return;
_discordPresencePlaying.Details = details;
UpdatePlayingState();
Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report.");
}
private static string TruncateToByteLength(string input)
{

View File

@@ -22,12 +22,12 @@ namespace Ryujinx.Ava
{
public class RyujinxApp : Application
{
internal static string FormatTitle(LocaleKeys? windowTitleKey = null)
internal static string FormatTitle(LocaleKeys? windowTitleKey = null, bool includeVersion = true)
=> windowTitleKey is null
? $"{FullAppName} {Program.Version}"
: $"{FullAppName} {Program.Version} - {LocaleManager.Instance[windowTitleKey.Value]}";
? $"{FullAppName}{(includeVersion ? $" {Program.Version}" : string.Empty)}"
: $"{FullAppName}{(includeVersion ? $" {Program.Version}" : string.Empty)} - {LocaleManager.Instance[windowTitleKey.Value]}";
public static readonly string FullAppName = ReleaseInformation.IsCanaryBuild ? "Ryujinx Canary" : "Ryujinx";
public static readonly string FullAppName = string.Intern(ReleaseInformation.IsCanaryBuild ? "Ryujinx Canary" : "Ryujinx");
public static MainWindow MainWindow => Current!
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>()

View File

@@ -144,12 +144,12 @@ namespace Ryujinx.Ava.UI.Controls
case KeyboardMode.Numeric:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumeric);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(NumericCharacterValidation.IsNumeric);
_checkInput = text => text.All(CharacterValidation.IsNumeric);
break;
case KeyboardMode.Alphabet:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(value => !CJKCharacterValidation.IsCJK(value));
_checkInput = text => text.All(value => !CharacterValidation.IsCJK(value));
break;
case KeyboardMode.ASCII:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeASCII);

View File

@@ -159,6 +159,7 @@ namespace Ryujinx.Ava.UI.Helpers
Symbol = (Symbol)symbol,
Margin = new Thickness(10),
FontSize = 40,
FlowDirection = FlowDirection.LeftToRight,
VerticalAlignment = VerticalAlignment.Center,
};

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Helper;
using SharpMetal.QuartzCore;
using System;
@@ -7,14 +8,12 @@ namespace Ryujinx.Ava.UI.Renderer
{
public CAMetalLayer CreateSurface()
{
if (OperatingSystem.IsMacOS())
if (OperatingSystem.IsMacOS() && RunningPlatform.IsArm)
{
return new CAMetalLayer(MetalLayer);
}
else
{
throw new NotSupportedException();
}
throw new NotSupportedException($"Cannot create a {nameof(CAMetalLayer)} without being on ARM Mac.");
}
}
}

View File

@@ -43,19 +43,19 @@ namespace Ryujinx.Ava.UI.Renderer
public RendererHost(string titleId)
{
switch (TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend))
{
case GraphicsBackend.OpenGl:
EmbeddedWindow = new EmbeddedWindowOpenGL();
break;
case GraphicsBackend.Metal:
EmbeddedWindow = new EmbeddedWindowMetal();
break;
case GraphicsBackend.Vulkan:
EmbeddedWindow = new EmbeddedWindowVulkan();
break;
}
Focusable = true;
FlowDirection = FlowDirection.LeftToRight;
EmbeddedWindow =
#pragma warning disable CS8509
TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend) switch
#pragma warning restore CS8509
{
GraphicsBackend.OpenGl => new EmbeddedWindowOpenGL(),
GraphicsBackend.Metal => new EmbeddedWindowMetal(),
GraphicsBackend.Vulkan => new EmbeddedWindowVulkan(),
};
string backendText = EmbeddedWindow switch
{
EmbeddedWindowVulkan => "Vulkan",

View File

@@ -16,6 +16,7 @@ using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Vulkan;
@@ -330,9 +331,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
[GeneratedRegex("Ryujinx-[0-9a-f]{8}")]
private static partial Regex LdnPassphraseRegex();
public bool IsInvalidLdnPassphraseVisible { get; set; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
@@ -470,7 +468,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool ValidateLdnPassphrase(string passphrase)
{
return string.IsNullOrEmpty(passphrase) || (passphrase.Length == 16 && LdnPassphraseRegex().IsMatch(passphrase));
return string.IsNullOrEmpty(passphrase) || (passphrase.Length == 16 && Patterns.LdnPassphrase.IsMatch(passphrase));
}
public void ValidateAndSetTimeZone(string location)

View File

@@ -302,7 +302,7 @@ namespace Ryujinx.Ava.UI.Windows
LinuxHelper.RecommendedVmMaxMapCount);
UserResult response = await ContentDialogHelper.ShowTextDialog(
$"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}",
RyujinxApp.FormatTitle(LocaleKeys.LinuxVmMaxMapCountDialogTitle, false),
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary],
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart],