Compare commits

...

5 Commits

Author SHA1 Message Date
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
7 changed files with 69 additions and 18 deletions

View File

@@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
// TODO: Remove this when GPU channel scheduling will be implemented. // TODO: Remove this when GPU channel scheduling will be implemented.
if (timeout == Timeout.InfiniteTimeSpan) if (timeout == Timeout.InfiniteTimeSpan)
{ {
timeout = TimeSpan.FromMilliseconds(500); timeout = TimeSpan.FromSeconds(1);
} }
using ManualResetEvent waitEvent = new(false); using ManualResetEvent waitEvent = new(false);

View File

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

View File

@@ -2,8 +2,9 @@ using Ryujinx.Common.Helper;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{ {
public static class NumericCharacterValidation public static class CharacterValidation
{ {
public static bool IsNumeric(char value) => Patterns.Numeric.IsMatch(value.ToString()); 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,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

@@ -1,11 +1,18 @@
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.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 +37,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()
{ {
@@ -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,54 @@ 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 void HandlePlayReport(MessagePackObject playReport)
{
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
if (_discordPresencePlaying is null) return;
if (!playReport.IsDictionary) return;
foreach ((string titleId, (string reportKey, Func<object, string> formatter)) in _playReportValues)
{
if (!TitleIDs.CurrentApplication.Value.Value.EqualsIgnoreCase(titleId))
continue;
if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
return;
_discordPresencePlaying.Details = formatter(valuePackObject.ToObject());
UpdatePlayingState();
Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report.");
}
}
// title ID -> Play Report key & value formatter
private static readonly ReadOnlyDictionary<string, (string ReportKey, Func<object, string> Formatter)>
_playReportValues = new(new Dictionary<string, (string ReportKey, Func<object, string> Formatter)>
{
{
// Breath of the Wild Master Mode display
"01007ef00011e000",
("IsHardMode", val => val is 1 ? "Playing Master Mode" : "Playing Normal Mode")
}
});
private static string TruncateToByteLength(string input) private static string TruncateToByteLength(string input)
{ {

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);