Compare commits

..

2 Commits

Author SHA1 Message Date
Frog Business
302f0531dd Merge f73ffd1fd0 into f92d09711b 2025-02-15 19:12:33 -06:00
Barış Hamil
f73ffd1fd0 Ability to assign hotkeys to cycle controllers for players 2025-02-01 12:30:51 +03:00
25 changed files with 720 additions and 764 deletions

View File

@@ -969,7 +969,6 @@
0100751007ADA000,"Don't Starve: Nintendo Switch Edition",nvdec,playable,2022-02-05 20:43:34
010088B010DD2000,"Dongo Adventure",,playable,2022-10-04 16:22:26
0100C1F0051B6000,"Donkey Kong Country™: Tropical Freeze",,playable,2024-08-05 16:46:10
01009D901BC56000,"Donkey Kong Country™: Returns HD",gpu,ingame,2025-02-16 13:44:12
0100F2C00F060000,"Doodle Derby",,boots,2020-12-04 22:51:48
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
@@ -1159,7 +1158,7 @@
010095600AA36000,"Fill-a-Pix: Phil's Epic Adventure",,playable,2020-12-22 13:48:22
0100C3A00BB76000,"Fimbul",nvdec,playable,2022-07-26 13:31:47
0100C8200E942000,"Fin and the Ancient Mystery",nvdec,playable,2020-12-17 16:40:39
01000EA014150000,"FINAL FANTASY",,playable,2025-02-16 21:27:30
01000EA014150000,"FINAL FANTASY",crash,nothing,2024-09-05 20:55:30
01006B7014156000,"FINAL FANTASY II",crash,nothing,2024-04-13 19:18:04
01006F000B056000,"FINAL FANTASY IX",audout;nvdec,playable,2021-06-05 11:35:00
0100AA201415C000,"FINAL FANTASY V",,playable,2023-04-26 01:11:55
@@ -2839,7 +2838,6 @@
01009B90006DC000,"Super Mario Maker™ 2",online-broken;ldn-broken,playable,2024-08-25 11:05:19
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
0100965017338000,"Super Mario Party Jamboree",mac-bug;gpu,ingame,2025-02-17 02:09:20
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
010049900F546000,"Super Mario™ 3D All-Stars",services-horizon;slow;vulkan;amd-vendor-bug,ingame,2024-05-07 02:38:16
1 title_id game_name labels status last_updated
969 0100751007ADA000 Don't Starve: Nintendo Switch Edition nvdec playable 2022-02-05 20:43:34
970 010088B010DD2000 Dongo Adventure playable 2022-10-04 16:22:26
971 0100C1F0051B6000 Donkey Kong Country™: Tropical Freeze playable 2024-08-05 16:46:10
01009D901BC56000 Donkey Kong Country™: Returns HD gpu ingame 2025-02-16 13:44:12
972 0100F2C00F060000 Doodle Derby boots 2020-12-04 22:51:48
973 0100416004C00000 DOOM gpu;slow;nvdec;online-broken ingame 2024-09-23 15:40:07
974 010018900DD00000 DOOM (1993) nvdec;online-broken menus 2022-09-06 13:32:19
1158 010095600AA36000 Fill-a-Pix: Phil's Epic Adventure playable 2020-12-22 13:48:22
1159 0100C3A00BB76000 Fimbul nvdec playable 2022-07-26 13:31:47
1160 0100C8200E942000 Fin and the Ancient Mystery nvdec playable 2020-12-17 16:40:39
1161 01000EA014150000 FINAL FANTASY crash playable nothing 2025-02-16 21:27:30 2024-09-05 20:55:30
1162 01006B7014156000 FINAL FANTASY II crash nothing 2024-04-13 19:18:04
1163 01006F000B056000 FINAL FANTASY IX audout;nvdec playable 2021-06-05 11:35:00
1164 0100AA201415C000 FINAL FANTASY V playable 2023-04-26 01:11:55
2838 01009B90006DC000 Super Mario Maker™ 2 online-broken;ldn-broken playable 2024-08-25 11:05:19
2839 0100000000010000 Super Mario Odyssey™ nvdec;intel-vendor-bug;mac-bug playable 2024-08-25 01:32:34
2840 010036B0034E4000 Super Mario Party™ gpu;Needs Update;ldn-works ingame 2024-06-21 05:10:16
0100965017338000 Super Mario Party Jamboree mac-bug;gpu ingame 2025-02-17 02:09:20
2841 0100BC0018138000 Super Mario RPG™ gpu;audio;nvdec ingame 2024-06-19 17:43:42
2842 Super Mario World homebrew boots 2024-06-13 01:40:31
2843 010049900F546000 Super Mario™ 3D All-Stars services-horizon;slow;vulkan;amd-vendor-bug ingame 2024-05-07 02:38:16

View File

@@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace Ryujinx.Common.Configuration.Hid
{
public class KeyboardHotkeys
@@ -13,5 +15,6 @@ namespace Ryujinx.Common.Configuration.Hid
public Key VolumeDown { get; set; }
public Key CustomVSyncIntervalIncrement { get; set; }
public Key CustomVSyncIntervalDecrement { get; set; }
public List<Key> CycleControllers { get; set; }
}
}

View File

@@ -53,9 +53,10 @@ namespace Ryujinx.Common
"0100000000010000", // Super Mario Odyssey
// Further testing is appreciated, I did not test the entire game:
//"010076f0049a2000", // Bayonetta
//"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
//"0100f4300bf2c000", // New Pokemon Snap
"01007300020fa000", // Astral Chain
"010076f0049a2000", // Bayonetta
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
"0100f4300bf2c000", // New Pokemon Snap
];
public static string GetDiscordGameAsset(string titleId)
@@ -229,7 +230,6 @@ namespace Ryujinx.Common
"01008c8012920000", // Dying Light Platinum Edition
"01001cc01b2d4000", // Goat Simulator 3
"01003620068ea000", // Hand of Fate 2
"0100f7e00c70e000", // Hogwarts Legacy
"010085500130a000", // Lego City: Undercover
"010073c01af34000", // LEGO Horizon Adventures
"0100d71004694000", // Minecraft

View File

@@ -113,7 +113,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy
public async Task<ushort> NatPunch()
{
NatDiscoverer discoverer = new();
CancellationTokenSource cts = new(2500);
CancellationTokenSource cts = new(5000);
NatDevice device;

View File

@@ -34,10 +34,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
if (errorCode != LinuxError.SUCCESS)
{
if (errorCode != LinuxError.EWOULDBLOCK)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Operation failed with error {errorCode}.");
}
result = -1;
}
@@ -70,8 +66,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32();
ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32();
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Creating socket with domain={domain}, type={type}, protocol={protocol}");
BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift);
type &= BsdSocketType.TypeMask;
@@ -101,21 +95,12 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
}
}
LinuxError errno = LinuxError.SUCCESS;
ISocket newBsdSocket;
ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol, context.Device.Configuration.MultiplayerLanInterfaceId)
{
Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking),
};
try
{
newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol, context.Device.Configuration.MultiplayerLanInterfaceId)
{
Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking),
};
}
catch (SocketException exception)
{
LinuxError errNo = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
return WriteBsdResult(context, 0, errNo);
}
LinuxError errno = LinuxError.SUCCESS;
int newSockFd = _context.RegisterFileDescriptor(newBsdSocket);
@@ -126,7 +111,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
if (exempt)
{
Logger.Info?.Print(LogClass.ServiceBsd, "Disconnecting exempt socket.");
newBsdSocket.Disconnect();
}
@@ -813,11 +797,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{
errno = socket.Listen(backlog);
}
else
{
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Invalid socket fd '{socketFd}'.");
}
return WriteBsdResult(context, 0, errno);
}

View File

@@ -92,30 +92,18 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{
newSocket = new ManagedSocket(Socket.Accept());
IPEndPoint remoteEndPoint = newSocket.RemoteEndPoint;
bool isPrivateIp = remoteEndPoint.Address.ToString().StartsWith("192.168.");
Logger.Info?.PrintMsg(LogClass.ServiceBsd,
isPrivateIp
? $"Accepted connection from {ProtocolType}/{remoteEndPoint.Address}:{remoteEndPoint.Port}"
: $"Accepted connection from {ProtocolType}/***:{remoteEndPoint.Port}");
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
newSocket = null;
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
public LinuxError Bind(IPEndPoint localEndPoint)
{
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket binding to: {ProtocolType}/{localEndPoint.Port}");
try
{
Socket.Bind(localEndPoint);
@@ -124,10 +112,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
@@ -139,15 +123,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
public LinuxError Connect(IPEndPoint remoteEndPoint)
{
bool isLDNPrivateIP = remoteEndPoint.Address.ToString().StartsWith("192.168.");
if (isLDNPrivateIP)
{
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Connecting to: {ProtocolType}/{remoteEndPoint.Address}:{remoteEndPoint.Port}");
}
else
{
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Connecting to: {ProtocolType}/***:{remoteEndPoint.Port}");
}
try
{
Socket.Connect(remoteEndPoint);
@@ -162,10 +137,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
else
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
@@ -173,13 +144,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
public void Disconnect()
{
Logger.Info?.Print(LogClass.ServiceBsd, "Socket disconnecting");
Socket.Disconnect(true);
}
public void Dispose()
{
Logger.Info?.Print(LogClass.ServiceBsd, "Socket closed");
Socket.Close();
Socket.Dispose();
}
@@ -190,16 +159,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{
Socket.Listen(backlog);
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket listening: {ProtocolType}/{(Socket.LocalEndPoint as IPEndPoint).Port}");
return LinuxError.SUCCESS;
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
@@ -219,15 +182,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
private bool _hasEmittedBlockingWarning;
bool hasEmittedBlockingWarning = false;
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
{
@@ -243,10 +202,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
shouldBlockAfterOperation = true;
}
if (Blocking && !_hasEmittedBlockingWarning)
if (Blocking && !hasEmittedBlockingWarning)
{
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
_hasEmittedBlockingWarning = true;
hasEmittedBlockingWarning = true;
}
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
@@ -255,10 +214,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
receiveSize = -1;
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
@@ -290,10 +245,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
shouldBlockAfterOperation = true;
}
if (Blocking && !_hasEmittedBlockingWarning)
if (Blocking && !hasEmittedBlockingWarning)
{
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
_hasEmittedBlockingWarning = true;
hasEmittedBlockingWarning = true;
}
if (!Socket.IsBound)
@@ -310,10 +265,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
receiveSize = -1;
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
@@ -337,10 +288,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
sendSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
@@ -357,10 +304,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
sendSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
@@ -398,10 +341,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
@@ -448,10 +387,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
@@ -584,10 +519,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}
@@ -626,10 +557,6 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
}
catch (SocketException exception)
{
if (exception.SocketErrorCode != SocketError.WouldBlock)
{
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
}
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
}

View File

@@ -1,6 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -66,18 +64,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
{
if (_proxy.Supported(domain, type, protocol))
{
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket is using LDN proxy");
return new LdnProxySocket(domain, type, protocol, _proxy);
}
else
{
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"LDN proxy does not support socket {domain}, {type}, {protocol}");
}
}
else
{
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Opening socket using host networking stack");
}
return new DefaultSocket(domain, type, protocol, lanInterfaceId);
}
}

View File

@@ -40,6 +40,7 @@ using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
@@ -49,6 +50,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -1309,6 +1311,18 @@ namespace Ryujinx.Ava
_viewModel.Volume = Device.GetVolume();
break;
case KeyboardHotkeyState.CycleControllersPlayer1:
case KeyboardHotkeyState.CycleControllersPlayer2:
case KeyboardHotkeyState.CycleControllersPlayer3:
case KeyboardHotkeyState.CycleControllersPlayer4:
case KeyboardHotkeyState.CycleControllersPlayer5:
case KeyboardHotkeyState.CycleControllersPlayer6:
case KeyboardHotkeyState.CycleControllersPlayer7:
case KeyboardHotkeyState.CycleControllersPlayer8:
var player = currentHotkeyState - KeyboardHotkeyState.CycleControllersPlayer1;
var ivm = new UI.ViewModels.Input.InputViewModel();
Dispatcher.UIThread.Invoke(() => ivm.CyclePlayerDevice(player));
break;
case KeyboardHotkeyState.None:
(_keyboardInterface as AvaloniaKeyboard).Clear();
break;
@@ -1391,6 +1405,15 @@ namespace Ryujinx.Ava
state = KeyboardHotkeyState.CustomVSyncIntervalDecrement;
}
foreach (var cycle in ConfigurationState.Instance.Hid.Hotkeys.Value.CycleControllers?.Select((value, index) => (value, index)) ?? [])
{
if (_keyboardInterface.IsPressed((Key)cycle.value))
{
state = KeyboardHotkeyState.CycleControllersPlayer1 + cycle.index;
break;
}
}
return state;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,5 +14,13 @@ namespace Ryujinx.Ava.Common
VolumeDown,
CustomVSyncIntervalIncrement,
CustomVSyncIntervalDecrement,
CycleControllersPlayer1,
CycleControllersPlayer2,
CycleControllersPlayer3,
CycleControllersPlayer4,
CycleControllersPlayer5,
CycleControllersPlayer6,
CycleControllersPlayer7,
CycleControllersPlayer8
}
}

View File

@@ -24,7 +24,7 @@ namespace Ryujinx.Ava
private static readonly string _description =
ReleaseInformation.IsValid
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}"
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}@{ReleaseInformation.BuildGitHash}"
: "dev build";
private const string ApplicationId = "1293250299716173864";

View File

@@ -47,7 +47,6 @@ namespace Ryujinx.Ava
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
{
_ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
return 0;
}
PreviewerDetached = true;

View File

@@ -119,23 +119,17 @@
TextWrapping="Wrap" >
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5" ToolTip.Tip="{Binding DynamicRichPresenceDescription}">
<ui:SymbolIcon
Foreground="ForestGreen"
Symbol="Checkmark"
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
<StackPanel Orientation="Horizontal" Spacing="5">
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
<TextBlock
Foreground="ForestGreen"
HorizontalAlignment="Stretch"
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
Text="{ext:Locale GameInfoRpcDynamic}"
TextAlignment="Start"
TextWrapping="Wrap">
TextWrapping="Wrap" >
</TextBlock>
<ui:SymbolIcon
Foreground="Red"
Symbol="Cancel"
IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
<TextBlock
Foreground="Red"
HorizontalAlignment="Stretch"

View File

@@ -1,6 +1,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
namespace Ryujinx.Ava.UI.Models.Input
{
@@ -28,8 +33,15 @@ namespace Ryujinx.Ava.UI.Models.Input
[ObservableProperty] private Key _customVSyncIntervalDecrement;
public ObservableCollection<CycleController> CycleControllers { get; set; } = new ObservableCollection<CycleController>();
public ICommand AddCycleController { get; set; }
public ICommand RemoveCycleController { get; set; }
public bool CanRemoveCycleController => CycleControllers.Count > 0 && CycleControllers.Count < 8;
public HotkeyConfig(KeyboardHotkeys config)
{
AddCycleController = MiniCommand.Create(() => CycleControllers.Add(new CycleController(CycleControllers.Count + 1, Key.Unbound)));
RemoveCycleController = MiniCommand.Create(() => CycleControllers.Remove(CycleControllers.Last()));
if (config == null)
return;
@@ -44,6 +56,7 @@ namespace Ryujinx.Ava.UI.Models.Input
VolumeDown = config.VolumeDown;
CustomVSyncIntervalIncrement = config.CustomVSyncIntervalIncrement;
CustomVSyncIntervalDecrement = config.CustomVSyncIntervalDecrement;
CycleControllers.AddRange((config.CycleControllers ?? []).Select((x, i) => new CycleController(i + 1, x)));
}
public KeyboardHotkeys GetConfig() =>
@@ -60,6 +73,7 @@ namespace Ryujinx.Ava.UI.Models.Input
VolumeDown = VolumeDown,
CustomVSyncIntervalIncrement = CustomVSyncIntervalIncrement,
CustomVSyncIntervalDecrement = CustomVSyncIntervalDecrement,
CycleControllers = CycleControllers.Select(x => x.Hotkey).ToList()
};
}
}

View File

@@ -1,7 +1,6 @@
using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.PlayReport;
namespace Ryujinx.Ava.UI.ViewModels
{
@@ -11,11 +10,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData;
public string DynamicRichPresenceDescription =>
AppData.HasDynamicRichPresenceSupport
? AppData.RichPresenceSpec.Value.Description
: GameSpec.DefaultDescription;
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);

View File

@@ -0,0 +1,48 @@
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Configuration.Hid;
namespace Ryujinx.Ava.UI.ViewModels
{
public class CycleController : BaseModel
{
private string _player;
private Key _hotkey;
public string Player
{
get => _player;
set
{
_player = value;
OnPropertyChanged(nameof(Player));
}
}
public Key Hotkey
{
get => _hotkey;
set
{
_hotkey = value;
OnPropertyChanged(nameof(Hotkey));
}
}
public CycleController(int v, Key x)
{
Player = v switch
{
1 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1],
2 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2],
3 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3],
4 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4],
5 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5],
6 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6],
7 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7],
8 => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8],
_ => LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer] + " " + v
};
Hotkey = x;
}
}
}

View File

@@ -897,5 +897,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
AvaloniaKeyboardDriver.Dispose();
}
public void CyclePlayerDevice(int player)
{
LoadDevices();
PlayerId = (PlayerIndex)player;
Device = (Device + 1) % Devices.Count;
Save();
}
}
}

View File

@@ -1347,25 +1347,6 @@ namespace Ryujinx.Ava.UI.ViewModels
OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
}
public void OpenScreenshotsFolder()
{
string screenshotsDir = Path.Combine(AppDataManager.BaseDirPath, "screenshots");
try
{
if (!Directory.Exists(screenshotsDir))
Directory.CreateDirectory(screenshotsDir);
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot");
return;
}
OpenHelper.OpenFolder(screenshotsDir);
}
public void OpenLogsFolder()
{
string logPath = AppDataManager.GetOrCreateLogsDir();

View File

@@ -66,10 +66,6 @@
Command="{Binding OpenRyujinxFolder}"
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" />
<MenuItem
Command="{Binding OpenScreenshotsFolder}"
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
ToolTip.Tip="{ext:Locale OpenScreenshotFolderTooltip}"/>
<MenuItem
Command="{Binding OpenLogsFolder}"
Header="{ext:Locale MenuBarFileOpenLogsFolder}"

View File

@@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsHotkeysView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -15,17 +15,18 @@
<viewModels:SettingsViewModel />
</Design.DataContext>
<UserControl.Styles>
<Style Selector="StackPanel > StackPanel">
<Style Selector="StackPanel StackPanel">
<Setter Property="Margin" Value="10, 0, 0, 0" />
<Setter Property="Orientation" Value="Horizontal" />
</Style>
<Style Selector="StackPanel > StackPanel > TextBlock">
<Style Selector="StackPanel StackPanel > TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Width" Value="230" />
</Style>
<Style Selector="ToggleButton">
<Style Selector="ToggleButton, Button">
<Setter Property="Width" Value="90" />
<Setter Property="Height" Value="27" />
<Setter Property="Padding" Value="0,5,0,5" /> <!-- Added vertical padding -->
</Style>
<Style Selector="ToggleButton > TextBlock">
<Setter Property="TextAlignment" Value="Center" />
@@ -39,79 +40,123 @@
VerticalScrollBarVisibility="Auto">
<Border Classes="settings">
<StackPanel
Name="SettingButtons"
Margin="10"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
Spacing="10"
Name="SettingButtons">
<TextBlock
Classes="h1"
Text="{ext:Locale SettingsTabHotkeysHotkeys}" />
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysToggleVSyncModeHotkey}" />
<ToggleButton Name="ToggleVSyncMode">
<TextBlock Text="{Binding KeyboardHotkey.ToggleVSyncMode, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
<StackPanel
Margin="10,0,0,0"
Spacing="10"
Orientation="Vertical">
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysToggleVSyncModeHotkey}" />
<ToggleButton Name="ToggleVSyncMode">
<TextBlock Text="{Binding KeyboardHotkey.ToggleVSyncMode, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysScreenshotHotkey}" />
<ToggleButton Name="Screenshot">
<TextBlock Text="{Binding KeyboardHotkey.Screenshot, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysShowUiHotkey}" />
<ToggleButton Name="ShowUI">
<TextBlock Text="{Binding KeyboardHotkey.ShowUI, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysPauseHotkey}" />
<ToggleButton Name="Pause">
<TextBlock Text="{Binding KeyboardHotkey.Pause, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysToggleMuteHotkey}" />
<ToggleButton Name="ToggleMute">
<TextBlock Text="{Binding KeyboardHotkey.ToggleMute, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysResScaleUpHotkey}" />
<ToggleButton Name="ResScaleUp">
<TextBlock Text="{Binding KeyboardHotkey.ResScaleUp, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysResScaleDownHotkey}" />
<ToggleButton Name="ResScaleDown">
<TextBlock Text="{Binding KeyboardHotkey.ResScaleDown, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysVolumeUpHotkey}" />
<ToggleButton Name="VolumeUp">
<TextBlock Text="{Binding KeyboardHotkey.VolumeUp, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysVolumeDownHotkey}" />
<ToggleButton Name="VolumeDown">
<TextBlock Text="{Binding KeyboardHotkey.VolumeDown, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey}" />
<ToggleButton Name="CustomVSyncIntervalIncrement">
<TextBlock Text="{Binding KeyboardHotkey.CustomVSyncIntervalIncrement, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey}" />
<ToggleButton Name="CustomVSyncIntervalDecrement">
<TextBlock Text="{Binding KeyboardHotkey.CustomVSyncIntervalDecrement, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysScreenshotHotkey}" />
<ToggleButton Name="Screenshot">
<TextBlock Text="{Binding KeyboardHotkey.Screenshot, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysShowUiHotkey}" />
<ToggleButton Name="ShowUI">
<TextBlock Text="{Binding KeyboardHotkey.ShowUI, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysPauseHotkey}" />
<ToggleButton Name="Pause">
<TextBlock Text="{Binding KeyboardHotkey.Pause, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysToggleMuteHotkey}" />
<ToggleButton Name="ToggleMute">
<TextBlock Text="{Binding KeyboardHotkey.ToggleMute, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysResScaleUpHotkey}" />
<ToggleButton Name="ResScaleUp">
<TextBlock Text="{Binding KeyboardHotkey.ResScaleUp, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysResScaleDownHotkey}" />
<ToggleButton Name="ResScaleDown">
<TextBlock Text="{Binding KeyboardHotkey.ResScaleDown, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysVolumeUpHotkey}" />
<ToggleButton Name="VolumeUp">
<TextBlock Text="{Binding KeyboardHotkey.VolumeUp, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel>
<TextBlock Text="{ext:Locale SettingsTabHotkeysVolumeDownHotkey}" />
<ToggleButton Name="VolumeDown">
<TextBlock Text="{Binding KeyboardHotkey.VolumeDown, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey}" />
<ToggleButton Name="CustomVSyncIntervalIncrement">
<TextBlock Text="{Binding KeyboardHotkey.CustomVSyncIntervalIncrement, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="{ext:Locale SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey}" />
<ToggleButton Name="CustomVSyncIntervalDecrement">
<TextBlock Text="{Binding KeyboardHotkey.CustomVSyncIntervalDecrement, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
<Separator Height="1" />
<StackPanel Margin="0">
<TextBlock
Classes="h1"
Text="{ext:Locale SettingsTabHotkeysCycleControllers}" />
<StackPanel Orientation="Horizontal" Spacing="10">
<Button
Content="{ext:Locale SettingsTabGeneralAdd}"
Margin="10,0,0,0"
Command="{Binding KeyboardHotkey.AddCycleController}" />
<Button
Content="{ext:Locale SettingsTabGeneralRemove}"
IsEnabled="{Binding KeyboardHotkey.CanRemoveCycleController}"
Command="{Binding KeyboardHotkey.RemoveCycleController}" />
</StackPanel>
</StackPanel>
<ItemsControl ItemsSource="{Binding KeyboardHotkey.CycleControllers}"
Name="CycleControllers">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Margin="10,0,0,0"
Orientation="Vertical"
Spacing="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Player}" />
<ToggleButton>
<TextBlock
Text="{Binding Hotkey, Converter={x:Static helpers:KeyValueConverter.Instance}}" />
</ToggleButton>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</ScrollViewer>

View File

@@ -3,11 +3,14 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using DynamicData;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System.Linq;
using Button = Ryujinx.Input.Button;
using Key = Ryujinx.Common.Configuration.Hid.Key;
@@ -21,16 +24,21 @@ namespace Ryujinx.Ava.UI.Views.Settings
public SettingsHotkeysView()
{
InitializeComponent();
RegisterEvents();
_avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this);
CycleControllers.LayoutUpdated += (_, _1) => RegisterEvents();
}
private void RegisterEvents()
{
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
{
if (visual is ToggleButton button and not CheckBox)
{
button.IsCheckedChanged -= Button_IsCheckedChanged;
button.IsCheckedChanged += Button_IsCheckedChanged;
}
}
_avaloniaKeyboardDriver = new AvaloniaKeyboardDriver(this);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
@@ -116,6 +124,13 @@ namespace Ryujinx.Ava.UI.Views.Settings
case "CustomVSyncIntervalDecrement":
viewModel.KeyboardHotkey.CustomVSyncIntervalDecrement = buttonValue.AsHidType<Key>();
break;
default:
var index = button.FindAncestorOfType<ItemsControl>().GetLogicalDescendants().OfType<ToggleButton>().IndexOf(button);
if (index >= 0 && viewModel.KeyboardHotkey.CycleControllers != null)
{
viewModel.KeyboardHotkey.CycleControllers[index].Hotkey = buttonValue.AsHidType<Key>();
}
break;
}
}
};

View File

@@ -10,7 +10,6 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.Compat;
using Ryujinx.Ava.Utilities.PlayReport;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions;
@@ -36,14 +35,9 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
{
_id = value;
Compatibility = CompatibilityCsv.Find(value);
RichPresenceSpec = PlayReports.Analyzer.TryGetSpec(IdString, out GameSpec gameSpec)
? gameSpec
: default(Optional<GameSpec>);
Compatibility = CompatibilityCsv.Find(Id);
}
}
public Optional<GameSpec> RichPresenceSpec { get; set; }
public string Developer { get; set; } = "Unknown";
public string Version { get; set; } = "0";
public int PlayerCount { get; set; }
@@ -52,7 +46,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
public bool HasDynamicRichPresenceSupport => RichPresenceSpec.HasValue;
public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString);
public TimeSpan TimePlayed { get; set; }
public DateTime? LastPlayed { get; set; }

View File

@@ -20,11 +20,6 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
public GameSpec GetSpec(string titleId) => _specs.First(x => x.TitleIds.ContainsIgnoreCase(titleId));
public bool TryGetSpec(string titleId, out GameSpec gameSpec)
=> (gameSpec = _specs.FirstOrDefault(x => x.TitleIds.ContainsIgnoreCase(titleId))) != null;
/// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
/// </summary>
@@ -133,13 +128,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
{
if (!playReport.ReportData.IsDictionary)
return FormattedValue.Unhandled;
if (!TryGetSpec(runningGameId, out GameSpec spec))
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
return FormattedValue.Unhandled;
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
{
if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value))
if (!formatSpec.Format(appMeta, playReport, out FormattedValue value))
continue;
return value;

View File

@@ -9,89 +9,72 @@ namespace Ryujinx.Ava.Utilities.PlayReport
// init lazy value
_ = Analyzer;
}
public static Analyzer Analyzer => _analyzerLazy.Value;
private static readonly Lazy<Analyzer> _analyzerLazy = new(() => new Analyzer()
.AddSpec(
"01007ef00011e000",
spec => spec
.WithDescription("based on being in Master Mode.")
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
)
.AddSpec(
"0100f2c0115b6000",
spec => spec
.WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).")
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
.AddSpec(
"01002da013484000",
spec => spec
.WithDescription("based on how many Rupees you have.")
.AddValueFormatter("rupees", SkywardSwordHD_Rupees))
.AddSpec(
"0100000000010000",
spec => spec
.WithDescription("based on if you're playing with Assist Mode.")
.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
)
.AddSpec(
"010075000ecbe000",
spec => spec
.WithDescription("based on if you're playing with Assist Mode.")
.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
)
.AddSpec(
"010028600ebda000",
spec => spec
.WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.")
.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
)
.AddSpec( // Global & China IDs
["0100152000022000", "010075100e8ec000"],
spec => spec
.WithDescription(
"based on what modes you're selecting in the menu & whether or not you're in a race.")
.AddValueFormatter("To", MarioKart8Deluxe_Mode)
)
.AddSpec(
["0100a3d008c5c000", "01008f6008c5e000"],
spec => spec
.WithDescription("based on what area of Paldea you're exploring.")
.AddValueFormatter("area_no", PokemonSVArea)
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
)
.AddSpec(
"01006a800016e000",
spec => spec
.WithDescription("based on what mode you're playing, who won, and what characters were present.")
.AddSparseMultiValueFormatter(
[
// Metadata to figure out what PlayReport we have.
"match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count",
"adv_slot",
// List of Fighters
"player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter",
"player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter",
// List of rankings/placements
"player_1_rank", "player_2_rank", "player_3_rank", "player_4_rank", "player_5_rank",
"player_6_rank", "player_7_rank", "player_8_rank"
],
SuperSmashBrosUltimate_Mode
)
)
.AddSpec(
[
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
"010012f017576000", "0100c62011050000", "0100b3c014bda000"
],
spec => spec
.WithDescription(
"based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.")
.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
)
private static readonly Lazy<Analyzer> _analyzerLazy = new(() =>
new Analyzer()
.AddSpec(
"01007ef00011e000",
spec => spec
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
)
.AddSpec(
"0100f2c0115b6000",
spec => spec
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
.AddSpec(
"0100000000010000",
spec =>
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
)
.AddSpec(
"010075000ecbe000",
spec =>
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
)
.AddSpec(
"010028600ebda000",
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
)
.AddSpec( // Global & China IDs
["0100152000022000", "010075100e8ec000"],
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
)
.AddSpec(
["0100a3d008c5c000", "01008f6008c5e000"],
spec => spec
.AddValueFormatter("area_no", PokemonSVArea)
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
)
.AddSpec(
"01006a800016e000",
spec => spec
.AddSparseMultiValueFormatter(
[
// Metadata to figure out what PlayReport we have.
"match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count",
"adv_slot",
// List of Fighters
"player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter",
"player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter",
// List of rankings/placements
"player_1_rank", "player_2_rank", "player_3_rank", "player_4_rank", "player_5_rank",
"player_6_rank", "player_7_rank", "player_8_rank"
],
SuperSmashBrosUltimate_Mode
)
)
.AddSpec(
[
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
"010012f017576000", "0100c62011050000", "0100b3c014bda000"
],
spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
)
.AddSpec("01002da013484000", spec => spec.AddValueFormatter("rupees", SkywardSwordHD_Rupees))
);
private static string Playing(string game) => $"Playing {game}";

View File

@@ -23,20 +23,6 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public required string[] TitleIds { get; init; }
public const string DefaultDescription = "Formats the details on your Discord presence based on logged data from the game.";
private string _valueDescription;
public string Description => _valueDescription ?? DefaultDescription;
public GameSpec WithDescription(string description)
{
_valueDescription = description != null
? $"Formats the details on your Discord presence {description}"
: null;
return this;
}
public List<FormatterSpecBase> ValueFormatters { get; } = [];
@@ -211,7 +197,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public string[] ReportKeys { get; init; }
public Delegate Formatter { get; init; }
public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
out FormattedValue formattedValue)
{
formattedValue = default;