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

@@ -11,13 +11,17 @@ namespace Ryujinx.Common.Helper
public static bool IsWindows => OperatingSystem.IsWindows(); public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsLinux => OperatingSystem.IsLinux(); public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsIntelMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.X64; public static bool IsArm => RuntimeInformation.OSArchitecture is Architecture.Arm64;
public static bool IsArmMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.Arm64;
public static bool IsX64Windows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.X64); public static bool IsX64 => RuntimeInformation.OSArchitecture is Architecture.X64;
public static bool IsArmWindows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
public static bool IsX64Linux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.X64); public static bool IsIntelMac => IsMacOS && IsX64;
public static bool IsArmLinux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.Arm64); public static bool IsArmMac => IsMacOS && IsArm;
public static bool IsX64Windows => IsWindows && IsX64;
public static bool IsArmWindows => IsWindows && IsArm;
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); 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) if (info == null)
{ {
@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
bool signaled = waitEvent.WaitOne(timeout); 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..."); 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, 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) public static Vendor FromId(uint id)
{ {
return id switch return id switch

View File

@@ -1,5 +1,6 @@
using Gommon; using Gommon;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader;
@@ -375,11 +376,11 @@ namespace Ryujinx.Graphics.Vulkan
GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}"; 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) 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)) 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.Ncm;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
@@ -30,9 +31,6 @@ namespace Ryujinx.HLE.HOS.Applets.Error
public event EventHandler AppletStateChanged; public event EventHandler AppletStateChanged;
[GeneratedRegex(@"[^\u0000\u0009\u000A\u000D\u0020-\uFFFF]..")]
private static partial Regex CleanTextRegex();
public ErrorApplet(Horizon horizon) public ErrorApplet(Horizon horizon)
{ {
_horizon = horizon; _horizon = horizon;
@@ -107,7 +105,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
private static string CleanText(string value) 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) 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; using System.Text.RegularExpressions;
namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Proxy 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) public static bool IsHostBlocked(string host)
{ {
foreach (Regex regex in _blockedHosts) foreach (Regex regex in Patterns.BlockedHosts)
{ {
if (regex.IsMatch(host)) if (regex.IsMatch(host))
{ {

View File

@@ -2,6 +2,7 @@ using LibHac.Common.FixedArrays;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Loader; using LibHac.Loader;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
using System.Text; using System.Text;
@@ -29,13 +30,6 @@ namespace Ryujinx.HLE.Loaders.Executables
public string Name; public string Name;
public Array32<byte> BuildId; 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) public NsoExecutable(IStorage inStorage, string name = null)
{ {
NsoReader reader = new(); NsoReader reader = new();
@@ -90,7 +84,7 @@ namespace Ryujinx.HLE.Loaders.Executables
if (string.IsNullOrEmpty(modulePath)) if (string.IsNullOrEmpty(modulePath))
{ {
Match moduleMatch = ModuleRegex().Match(rawTextBuffer); Match moduleMatch = Patterns.Module.Match(rawTextBuffer);
if (moduleMatch.Success) if (moduleMatch.Success)
{ {
modulePath = moduleMatch.Value; modulePath = moduleMatch.Value;
@@ -99,13 +93,13 @@ namespace Ryujinx.HLE.Loaders.Executables
stringBuilder.AppendLine($" Module: {modulePath}"); stringBuilder.AppendLine($" Module: {modulePath}");
Match fsSdkMatch = FsSdkRegex().Match(rawTextBuffer); Match fsSdkMatch = Patterns.FsSdk.Match(rawTextBuffer);
if (fsSdkMatch.Success) if (fsSdkMatch.Success)
{ {
stringBuilder.AppendLine($" FS SDK Version: {fsSdkMatch.Value.Replace("sdk_version: ", string.Empty)}"); 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) if (sdkMwMatches.Count != 0)
{ {
string libHeader = " SDK Libraries: "; string libHeader = " SDK Libraries: ";

View File

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

View File

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

View File

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

View File

@@ -337,7 +337,7 @@ namespace Ryujinx.Ava.Common
if (publicDataNca is null) 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 () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
@@ -349,10 +349,6 @@ namespace Ryujinx.Ava.Common
return; return;
} }
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
int index = Nca.GetSectionIndexFromType(NcaSectionType.Data, publicDataNca.Header.ContentType); int index = Nca.GetSectionIndexFromType(NcaSectionType.Data, publicDataNca.Header.ContentType);
try try

View File

@@ -44,6 +44,16 @@ namespace Ryujinx.Ava.Common.Locale
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 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] public string this[LocaleKeys key]
@@ -88,11 +98,16 @@ namespace Ryujinx.Ava.Common.Locale
public static string FormatDynamicValue(LocaleKeys key, params object[] values) public static string FormatDynamicValue(LocaleKeys key, params object[] values)
=> Instance.UpdateAndGetDynamicValue(key, values); => Instance.UpdateAndGetDynamicValue(key, values);
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values) public void SetDynamicValues(LocaleKeys key, params object[] values)
{ {
_dynamicValues[key] = values; _dynamicValues[key] = values;
OnPropertyChanged("Translation"); OnPropertyChanged("Translation");
}
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
{
SetDynamicValues(key, values);
return this[key]; return this[key];
} }

View File

@@ -1,11 +1,19 @@
using DiscordRPC; using DiscordRPC;
using Gommon; using Gommon;
using MsgPack;
using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.Horizon;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text; using System.Text;
namespace Ryujinx.Ava namespace Ryujinx.Ava
@@ -30,6 +38,7 @@ namespace Ryujinx.Ava
private static DiscordRpcClient _discordClient; private static DiscordRpcClient _discordClient;
private static RichPresence _discordPresenceMain; private static RichPresence _discordPresenceMain;
private static RichPresence _discordPresencePlaying;
public static void Initialize() public static void Initialize()
{ {
@@ -37,8 +46,7 @@ namespace Ryujinx.Ava
{ {
Assets = new Assets Assets = new Assets
{ {
LargeImageKey = "ryujinx", LargeImageKey = "ryujinx", LargeImageText = TruncateToByteLength(_description)
LargeImageText = TruncateToByteLength(_description)
}, },
Details = "Main Menu", Details = "Main Menu",
State = "Idling", State = "Idling",
@@ -47,6 +55,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue); TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
HorizonStatic.PlayReportPrinted += HandlePlayReport;
} }
private static void Update(object sender, ReactiveEventArgs<bool> evnt) private static void Update(object sender, ReactiveEventArgs<bool> evnt)
@@ -84,9 +93,8 @@ namespace Ryujinx.Ava
SwitchToMainState(); SwitchToMainState();
} }
private static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes) private static RichPresence CreatePlayingState(ApplicationMetadata appMeta, ProcessResult procRes) =>
{ new()
_discordClient?.SetPresence(new RichPresence
{ {
Assets = new Assets Assets = new Assets
{ {
@@ -100,10 +108,44 @@ namespace Ryujinx.Ava
? $"Total play time: {ValueFormatUtils.FormatTimeSpan(appMeta.TimePlayed)}" ? $"Total play time: {ValueFormatUtils.FormatTimeSpan(appMeta.TimePlayed)}"
: "Never played", : "Never played",
Timestamps = GuestAppStartedAt ??= Timestamps.Now 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) private static string TruncateToByteLength(string input)
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Vulkan; 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 bool IsInvalidLdnPassphraseVisible { get; set; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
@@ -470,7 +468,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool ValidateLdnPassphrase(string passphrase) 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) public void ValidateAndSetTimeZone(string location)

View File

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