Compare commits

..

21 Commits

Author SHA1 Message Date
Willians
b16b844760 Update PT-BR Big Translation & Add DKC Returns HD to compatibility list (#666)
Today I had a free day to look at and work on the entire Brazilian
Portuguese language.

Translations that had not been done before have been added
Fixed several translations that didn't make sense
Translated description of functions that were not translated correctly
Words were written as close to the original English
Add Donkey Kong Country Returns HD to compatibility list
2025-02-16 15:39:42 -06:00
Vudjun
bc07bc482d Gracefully handle errors in socket creation (#662)
In all other calls, exceptions are handled within the ManagedSocket
class, this can't easily be done here as the exception is thrown from
the constructor, so the exception is handled in ISocket and the correct
error is passed to the application.

This should fix #660
2025-02-16 02:26:37 -06:00
Vudjun
61975ca44d Add logging in socket implementation (#661)
Adds logging to most Socket calls to help with debugging network
issues.

Shouldn't affect any functionality. There's a small chance it could spam
the log in some games.
2025-02-16 01:03:44 -06:00
Evan Husted
66054dd225 UI: RPC: Remove git hash from RPC ryujinx logo hover information 2025-02-16 00:58:25 -06:00
Evan Husted
b1f61e5143 misc: chore: [ci skip] Reformat PlayReports.cs 2025-02-15 20:52:03 -06:00
Evan Husted
0d7d0e8092 UI: Add descriptions of what each dynamic RPC formatter actually shows when hovering whether it has support in the game info popup 2025-02-15 20:45:27 -06:00
Evan Husted
aa2178dbe5 UI: Button to open screenshots folder in File menu 2025-02-15 20:25:17 -06:00
Evan Husted
f92d09711b misc: chore: rewrite PlayReports.Analyzer creation to use Lazy and create the value alongside DiscordIntegrationModule init 2025-02-15 19:02:53 -06:00
Evan Husted
45ee8cd0e8 misc: Play Report Analyzer: Skyward Sword HD rupee count 2025-02-15 19:02:04 -06:00
Evan Husted
395bbd144a misc: chore: Change Analyzer AddSpec logic to log the non-hexadecimal value and ignore the added entry instead of throwing an exception 2025-02-15 19:01:24 -06:00
Evan Husted
744d813b87 misc: i18n: change localization of Ignore Applet to Ignore Controller Applet & redo tooltip 2025-02-15 00:40:18 -06:00
Evan Husted
7d59ada798 misc: chore: rename IgnoreApplet to IgnoreControllerApplet 2025-02-15 00:25:28 -06:00
Evan Husted
a4b5304935 UI: Refresh game list when emulated Switch language is changed (to show different logos/names) 2025-02-15 00:20:01 -06:00
Evan Husted
0965ee905d misc: Fix Match System Time not persisting between emulator launches 2025-02-14 22:18:15 -06:00
FluffyOMC
855161b23b Prevent log from showing negative JIT cache sizes (32bit-int overflow) (#664)
![image](https://github.com/user-attachments/assets/5820ce7b-cbfe-4908-8f5e-7ee82040ee1a)
2025-02-14 21:37:19 -06:00
Daenorth
6b55d158b7 Norwegian locale update & update Jackbox 5 & 6 compat (#658) 2025-02-13 22:38:55 -06:00
Daniel Nylander
91f73a4891 Updated Swedish translation (#594) 2025-02-13 19:28:39 -06:00
Evan Husted
883d4d863a i18n: chore: [ci skip] missing closing single quote in translation (not JSON breaking) 2025-02-13 19:16:05 -06:00
Dennis
ca5de909a1 Updated German translation (#626) 2025-02-13 19:14:02 -06:00
Willians
5172567b08 Update Brazilian Translation PT-BR (#633)
I updated the Brazilian Portuguese translation, I added the texts that
were not translated.
2025-02-13 19:08:48 -06:00
heihei123456780
6fe4cee7c0 Update zh_CN translation (#650) 2025-02-13 18:16:12 -06:00
44 changed files with 1093 additions and 1736 deletions

View File

@@ -969,6 +969,7 @@
0100751007ADA000,"Don't Starve: Nintendo Switch Edition",nvdec,playable,2022-02-05 20:43:34 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 010088B010DD2000,"Dongo Adventure",,playable,2022-10-04 16:22:26
0100C1F0051B6000,"Donkey Kong Country™: Tropical Freeze",,playable,2024-08-05 16:46:10 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 0100F2C00F060000,"Doodle Derby",,boots,2020-12-04 22:51:48
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07 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 010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
@@ -2988,8 +2989,8 @@
010015D003EE4000,"The Jackbox Party Pack 2",online-working,playable,2022-08-22 18:23:40 010015D003EE4000,"The Jackbox Party Pack 2",online-working,playable,2022-08-22 18:23:40
0100CC80013D6000,"The Jackbox Party Pack 3",slow;online-working,playable,2022-08-22 18:41:06 0100CC80013D6000,"The Jackbox Party Pack 3",slow;online-working,playable,2022-08-22 18:41:06
0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34 0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34
01006fe0096ac000,"The Jackbox Party Pack 5",ldn-untested,boots,2025-02-03 22:32:00 01006fe0096ac000,"The Jackbox Party Pack 5",slow;online-working,ingame,2025-02-14 05:32:00
01005a400db52000,"The Jackbox Party Pack 6",ldn-untested,boots,2025-02-03 22:32:00 01005a400db52000,"The Jackbox Party Pack 6",slow;online-working,ingame,2025-02-14 05:26:00
010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41 010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41
01006BC00B188000,"The Journey Down: Chapter Three",nvdec,playable,2021-02-24 13:45:27 01006BC00B188000,"The Journey Down: Chapter Three",nvdec,playable,2021-02-24 13:45:27
01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13 01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13
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
972 01009D901BC56000 Donkey Kong Country™: Returns HD gpu ingame 2025-02-16 13:44:12
973 0100F2C00F060000 Doodle Derby boots 2020-12-04 22:51:48
974 0100416004C00000 DOOM gpu;slow;nvdec;online-broken ingame 2024-09-23 15:40:07
975 010018900DD00000 DOOM (1993) nvdec;online-broken menus 2022-09-06 13:32:19
2989 010015D003EE4000 The Jackbox Party Pack 2 online-working playable 2022-08-22 18:23:40
2990 0100CC80013D6000 The Jackbox Party Pack 3 slow;online-working playable 2022-08-22 18:41:06
2991 0100E1F003EE8000 The Jackbox Party Pack 4 online-working playable 2022-08-22 18:56:34
2992 01006fe0096ac000 The Jackbox Party Pack 5 ldn-untested slow;online-working boots ingame 2025-02-03 22:32:00 2025-02-14 05:32:00
2993 01005a400db52000 The Jackbox Party Pack 6 ldn-untested slow;online-working boots ingame 2025-02-03 22:32:00 2025-02-14 05:26:00
2994 010052C00B184000 The Journey Down: Chapter One nvdec playable 2021-02-24 13:32:41
2995 01006BC00B188000 The Journey Down: Chapter Three nvdec playable 2021-02-24 13:45:27
2996 01009AB00B186000 The Journey Down: Chapter Two nvdec playable 2021-02-24 13:32:13

View File

@@ -186,7 +186,7 @@ namespace ARMeilleure.Translation.Cache
int newRegionNumber = _activeRegionIndex; int newRegionNumber = _activeRegionIndex;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation)."); Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize); _cacheAllocator = new CacheMemoryAllocator(CacheSize);

View File

@@ -166,7 +166,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
int newRegionNumber = _activeRegionIndex; int newRegionNumber = _activeRegionIndex;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation)."); Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize); _cacheAllocator = new CacheMemoryAllocator(CacheSize);

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy; using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -64,10 +66,18 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
{ {
if (_proxy.Supported(domain, type, protocol)) if (_proxy.Supported(domain, type, protocol))
{ {
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket is using LDN proxy");
return new LdnProxySocket(domain, type, protocol, _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); return new DefaultSocket(domain, type, protocol, lanInterfaceId);
} }
} }

View File

@@ -582,13 +582,6 @@ namespace Ryujinx.Ava
Rainbow.Disable(); Rainbow.Disable();
Rainbow.Reset(); Rainbow.Reset();
// Reload settings when the game is turned off
// (resets custom settings if there were any)
Program.ReloadConfig();
//Updates the gameList (changes the status of the user setting if it was added or removed during the game)
RyujinxApp.MainWindow.GameListUpdate();
_isStopped = true; _isStopped = true;
Stop(); Stop();
} }

View File

@@ -1,8 +1,7 @@
<Styles <Styles
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"> xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia">
<Design.PreviewWith> <Design.PreviewWith>
<Border Height="2000" <Border Height="2000"
@@ -31,8 +30,7 @@
<Button <Button
Name="btnRem" Name="btnRem"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Content="Add" Content="Add" />
Classes="red"/>
<TextBox <TextBox
Width="100" Width="100"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -43,11 +41,7 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
<ui:NumberBox Value="1" /> <ui:NumberBox Value="1" />
<MenuItem
Header="123 0000"
ToolTip.Tip="What this"/>
</StackPanel> </StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="DropDownButton"> <Style Selector="DropDownButton">
@@ -379,16 +373,6 @@
<Setter Property="Background" <Setter Property="Background"
Value="{DynamicResource AppListHoverBackgroundColor}" /> Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style> </Style>
<Style Selector="Button.red /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Background" Value="red"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style Selector="Button.red:pointerover /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Background" Value="{DynamicResource WarningBackgroundColor}" />
<Setter Property="Foreground" Value="White"/>
</Style>
<Styles.Resources> <Styles.Resources>
<SolidColorBrush x:Key="ThemeAccentColorBrush" <SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" /> Color="{DynamicResource SystemAccentColor}" />

View File

@@ -1,4 +1,4 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.ThemeDictionaries> <ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default"> <ResourceDictionary x:Key="Default">
@@ -12,13 +12,11 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color> <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color> <Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color> <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color> <Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color> <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color> <Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color> <Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color> <Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary> </ResourceDictionary>
<ResourceDictionary x:Key="Light"> <ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -31,13 +29,11 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color> <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color> <Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color> <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color> <Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color> <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#13c3a4</Color> <Color x:Key="Switch">#13c3a4</Color>
<Color x:Key="Unbounded">#FFFF4554</Color> <Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color> <Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary> </ResourceDictionary>
<ResourceDictionary x:Key="Dark"> <ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -50,13 +46,11 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color> <Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color> <Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color> <Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color> <Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color> <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color> <Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color> <Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color> <Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#FFA500</Color>
</ResourceDictionary> </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries> </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary> </ResourceDictionary>

File diff suppressed because it is too large Load Diff

View File

@@ -54,7 +54,6 @@ namespace Ryujinx.Ava.Common.Locale
SetDynamicValues(LocaleKeys.RyujinxInfo, RyujinxApp.FullAppName); SetDynamicValues(LocaleKeys.RyujinxInfo, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxConfirm, RyujinxApp.FullAppName); SetDynamicValues(LocaleKeys.RyujinxConfirm, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxUpdater, RyujinxApp.FullAppName); SetDynamicValues(LocaleKeys.RyujinxUpdater, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxRebooter, RyujinxApp.FullAppName);
} }
public string this[LocaleKeys key] public string this[LocaleKeys key]

View File

@@ -24,7 +24,7 @@ namespace Ryujinx.Ava
private static readonly string _description = private static readonly string _description =
ReleaseInformation.IsValid ReleaseInformation.IsValid
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}@{ReleaseInformation.BuildGitHash}" ? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}"
: "dev build"; : "dev build";
private const string ApplicationId = "1293250299716173864"; private const string ApplicationId = "1293250299716173864";
@@ -56,6 +56,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.PlayReport += HandlePlayReport; HorizonStatic.PlayReport += HandlePlayReport;
PlayReports.Initialize();
} }
private static void Update(object sender, ReactiveEventArgs<bool> evnt) private static void Update(object sender, ReactiveEventArgs<bool> evnt)

View File

@@ -148,7 +148,7 @@ namespace Ryujinx.Headless
IgnoreMissingServices = configurationState.System.IgnoreMissingServices; IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
if (NeedsOverride(nameof(IgnoreControllerApplet))) if (NeedsOverride(nameof(IgnoreControllerApplet)))
IgnoreControllerApplet = configurationState.System.IgnoreApplet; IgnoreControllerApplet = configurationState.System.IgnoreControllerApplet;
return; return;

View File

@@ -32,10 +32,8 @@ namespace Ryujinx.Ava
public static double DesktopScaleFactor { get; set; } = 1.0; public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; } public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; } public static string ConfigurationPath { get; private set; }
public static string GlobalConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; } public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; } public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; }
[LibraryImport("user32.dll", SetLastError = true)] [LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
@@ -157,43 +155,11 @@ namespace Ryujinx.Ava
} }
} }
public static bool FindGameConfig(string gameDir)
{
if (File.Exists(gameDir))
{
return true;
}
return false;
}
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
{
string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName);
// Should load with the game if there is a custom setting for the game
if (rememberGlobalDir)
{
GlobalConfigurationPath = ConfigurationPath;
}
if (changeFolderForGame)
{
ConfigurationPath = gameDir;
}
return gameDir;
}
public static void ReloadConfig() public static void ReloadConfig()
{ {
//It is necessary that when a user setting appears, the global setting remains available
GlobalConfigurationPath = null;
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered // Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath)) if (File.Exists(localConfigurationPath))
{ {
@@ -251,11 +217,6 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.Graphics.BackendThreading _ => ConfigurationState.Instance.Graphics.BackendThreading
}; };
if (CommandLineState.OverrideBackendThreadingAfterReboot is not null)
{
BackendThreadingArg = CommandLineState.OverrideBackendThreadingAfterReboot;
}
// Check if docked mode was overriden. // Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue) if (CommandLineState.OverrideDockedMode.HasValue)
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
@@ -271,33 +232,6 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.HideCursor, _ => ConfigurationState.Instance.HideCursor,
}; };
// Check if memoryManagerMode was overridden.
if (CommandLineState.OverrideMemoryManagerMode is not null)
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result))
{
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
}
// Check if PPTC was overridden.
if (CommandLineState.OverridePPTC is not null)
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result))
{
ConfigurationState.Instance.System.EnablePtc.Value = result;
}
// Check if region was overridden.
if (CommandLineState.OverrideSystemRegion is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Ryujinx.HLE.HOS.SystemState.RegionCode result))
{
ConfigurationState.Instance.System.Region.Value = (Utilities.Configuration.System.Region)result;
}
//Check if language was overridden.
if (CommandLineState.OverrideSystemLanguage is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Ryujinx.HLE.HOS.SystemState.SystemLanguage result))
{
ConfigurationState.Instance.System.Language.Value = (Utilities.Configuration.System.Language)result;
}
// Check if hardware-acceleration was overridden. // Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null) if (CommandLineState.OverrideHardwareAcceleration != null)

View File

@@ -1,95 +0,0 @@
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava
{
internal static class Rebooter
{
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
public static void RebootAppWithGame(string gamePath, List<string> args)
{
_ = Reboot(gamePath, args);
}
private static async Task Reboot(string gamePath, List<string> args)
{
bool shouldRestart = true;
TaskDialog taskDialog = new()
{
Header = LocaleManager.Instance[LocaleKeys.RyujinxRebooter],
SubHeader = LocaleManager.Instance[LocaleKeys.DialogRebooterMessage],
IconSource = new SymbolIconSource { Symbol = Symbol.Games },
XamlRoot = RyujinxApp.MainWindow,
};
if (shouldRestart)
{
List<string> arguments = CommandLineState.Arguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
// On macOS we perform the update at relaunch.
if (OperatingSystem.IsMacOS())
{
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app");
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
string currentPid = Environment.ProcessId.ToString();
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
Process.Start("/bin/bash", arguments);
}
else
{
var dialogTask = taskDialog.ShowAsync(true);
await Task.Delay(500);
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty;
// Some operating systems can see the renamed executable, so strip off the .ryuold if found.
if (ryuName.EndsWith(".ryuold"))
{
ryuName = ryuName[..^7];
}
// Fallback if the executable could not be found.
if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
foreach (var arg in args)
{
processStart.ArgumentList.Add(arg);
}
processStart.ArgumentList.Add(gamePath);
Process.Start(processStart);
}
Environment.Exit(0);
}
}
}
}

View File

@@ -41,7 +41,7 @@ namespace Ryujinx.Ava.UI.Applet
bool okPressed = false; bool okPressed = false;
if (ConfigurationState.Instance.System.IgnoreApplet) if (ConfigurationState.Instance.System.IgnoreControllerApplet)
return false; return false;
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>

View File

@@ -19,11 +19,6 @@
Header="{ext:Locale GameListContextMenuCreateShortcut}" Header="{ext:Locale GameListContextMenuCreateShortcut}"
Icon="{ext:Icon fa-solid fa-bookmark}" Icon="{ext:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<MenuItem
Click="EditGameConfiguration_Click"
Header="{ext:Locale GameListContextMenuEditGameConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale EditGameConfigurationToolTip}" />
<MenuItem <MenuItem
IsVisible="{Binding HasCompatibilityEntry}" IsVisible="{Binding HasCompatibilityEntry}"
Click="OpenApplicationCompatibility_Click" Click="OpenApplicationCompatibility_Click"

View File

@@ -387,19 +387,6 @@ namespace Ryujinx.Ava.UI.Controls
); );
} }
public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await new GameSpecificSettingsWindow(viewModel).ShowDialog((Window)viewModel.TopLevel);
//just checking for file presence
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
viewModel.RefreshView();
}
}
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args) public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
{ {
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })

View File

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

View File

@@ -7,7 +7,6 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
Focusable="True" Focusable="True"
@@ -74,18 +73,12 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}"> IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock
<TextBlock HorizontalAlignment="Center"
Text="{Binding Name}" VerticalAlignment="Center"
TextAlignment="Center" Text="{Binding Name}"
TextWrapping="Wrap" /> TextAlignment="Center"
<TextBlock TextWrapping="Wrap" />
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale UserConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel>
</Panel> </Panel>
</Grid> </Grid>
</Border> </Border>
@@ -93,28 +86,10 @@
Margin="5,5,0,0" Margin="5,5,0,0"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Top" VerticalAlignment="Top"
FontSize="18" FontSize="16"
Foreground="{DynamicResource FavoriteApplicationIconColor}" Foreground="{DynamicResource FavoriteApplicationIconColor}"
IsVisible="{Binding Favorite}" IsVisible="{Binding Favorite}"
Symbol="StarFilled" /> Symbol="StarFilled" />
<Grid IsVisible="{Binding !$parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<Border
Margin="15,35,5,15"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="90"
Height="20"
CornerRadius="4"
IsVisible="{Binding HasIndependentConfiguration}"
Background="{DynamicResource ThemeContentBackgroundColor}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{ext:Locale UserConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Border>
</Grid>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>

View File

@@ -6,7 +6,6 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
Focusable="True" Focusable="True"
@@ -157,13 +156,6 @@
Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}" Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}"
TextAlignment="Start" TextAlignment="Start"
TextWrapping="Wrap"/> TextWrapping="Wrap"/>
<TextBlock
HorizontalAlignment="Stretch"
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale UserConfigurationHeader}"
TextAlignment="Start"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.Column="4" Grid.Column="4"

View File

@@ -1,6 +1,7 @@
using Gommon; using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.PlayReport;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
@@ -10,6 +11,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData; 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 FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer); public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension); public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);

View File

@@ -355,11 +355,6 @@ namespace Ryujinx.Ava.UI.ViewModels
_ => null, _ => null,
}; };
} }
set
{
ListSelectedApplication = value;
GridSelectedApplication = value;
}
} }
public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo; public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo;
@@ -1352,6 +1347,25 @@ namespace Ryujinx.Ava.UI.ViewModels
OpenHelper.OpenFolder(AppDataManager.BaseDirPath); 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() public void OpenLogsFolder()
{ {
string logPath = AppDataManager.GetOrCreateLogsDir(); string logPath = AppDataManager.GetOrCreateLogsDir();
@@ -1536,50 +1550,8 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public bool InitializeUserConfig(ApplicationData application)
{
// Code where conditions will be met before loading the user configuration (Global Config)
BackendThreading backendThreadingValue = ConfigurationState.Instance.Graphics.BackendThreading.Value;
string BackendThreadingInit = Program.BackendThreadingArg;
if (BackendThreadingInit is null)
{
BackendThreadingInit = ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
}
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
string idGame = application.IdBaseString;
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
{
// Loads the user configuration, having previously changed the global configuration to the user configuration
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
}
// Code where conditions will be executed after loading user configuration
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
{
List<string> Arguments = new List<string>
{
"--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
};
Rebooter.RebootAppWithGame(application.Path, Arguments);
return true;
}
return false;
}
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null) public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null)
{ {
if (InitializeUserConfig(application))
{
return;
}
if (AppHost != null) if (AppHost != null)
{ {
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
@@ -1640,7 +1612,6 @@ namespace Ryujinx.Ava.UI.ViewModels
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start(); gameThread.Start();
} }
public void SwitchToRenderer(bool startFullscreen) => public void SwitchToRenderer(bool startFullscreen) =>

View File

@@ -1,10 +1,8 @@
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using Humanizer;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SDL2;
@@ -29,7 +27,6 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -52,8 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private int _graphicsBackendMultithreadingIndex; private int _graphicsBackendMultithreadingIndex;
private float _volume; private float _volume;
[ObservableProperty] private bool _isVulkanAvailable = true; [ObservableProperty] private bool _isVulkanAvailable = true;
[ObservableProperty] private bool _gameDirectoryChanged; [ObservableProperty] private bool _gameListNeedsRefresh;
[ObservableProperty] private bool _autoloadDirectoryChanged;
private readonly List<string> _gpuIds = []; private readonly List<string> _gpuIds = [];
private int _graphicsBackendIndex; private int _graphicsBackendIndex;
private int _scalingFilter; private int _scalingFilter;
@@ -72,17 +68,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public SettingsHacksViewModel DirtyHacks { get; } public SettingsHacksViewModel DirtyHacks { get; }
private readonly bool _isGameRunning;
private Bitmap _gameIcon;
private string _gameTitle;
private string _gamePath;
private string _gameId;
public bool IsGameRunning => _isGameRunning;
public Bitmap GameIcon => _gameIcon;
public string GamePath => _gamePath;
public string GameTitle => _gameTitle;
public string GameId => _gameId;
public int ResolutionScale public int ResolutionScale
{ {
get => _resolutionScale; get => _resolutionScale;
@@ -350,7 +335,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; } public bool IsInvalidLdnPassphraseVisible { get; set; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false) public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_contentManager = contentManager; _contentManager = contentManager;
@@ -363,51 +348,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public SettingsViewModel( public SettingsViewModel()
VirtualFileSystem virtualFileSystem,
ContentManager contentManager,
bool gameRunning,
string gamePath,
string gameName,
string gameId,
byte[] gameIconData,
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
if (gameIconData != null && gameIconData.Length > 0)
{
using (var ms = new MemoryStream(gameIconData))
{
_gameIcon = new Bitmap(ms);
}
}
_isGameRunning = gameRunning;
_gamePath = gamePath;
_gameTitle = gameName;
_gameId = gameId;
if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
{
string gameDir = Program.GetDirGameUserConfig(gameId, false, true);
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
}
LoadCurrentConfiguration(); // Needed to load custom configuration
}
if (Program.PreviewerDetached)
{
Task.Run(LoadTimeZones);
}
}
public SettingsViewModel(bool noLoadGlobalConfig = false)
{ {
GameDirectories = []; GameDirectories = [];
AutoloadDirectories = []; AutoloadDirectories = [];
@@ -422,11 +363,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
Task.Run(LoadAvailableGpus); Task.Run(LoadAvailableGpus);
LoadCurrentConfiguration();
if (!noLoadGlobalConfig)// Default is false, but loading custom config avoids double call
{
LoadCurrentConfiguration();
}
DirtyHacks = new SettingsHacksViewModel(this); DirtyHacks = new SettingsHacksViewModel(this);
} }
@@ -537,35 +474,29 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
ConfigurationState config = ConfigurationState.Instance; ConfigurationState config = ConfigurationState.Instance;
// User Interface
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
RememberWindowState = config.RememberWindowState;
ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value;
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
FocusLostActionType = (int)config.FocusLostActionType.Value;
//It is necessary that the data is used from the global configuration file GameDirectories.Clear();
if (string.IsNullOrEmpty(GameId)) GameDirectories.AddRange(config.UI.GameDirs.Value);
AutoloadDirectories.Clear();
AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value);
BaseStyleIndex = config.UI.BaseStyle.Value switch
{ {
// User Interface "Auto" => 0,
EnableDiscordIntegration = config.EnableDiscordIntegration; "Light" => 1,
CheckUpdatesOnStart = config.CheckUpdatesOnStart; "Dark" => 2,
ShowConfirmExit = config.ShowConfirmExit; _ => 0
RememberWindowState = config.RememberWindowState; };
ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value;
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
FocusLostActionType = (int)config.FocusLostActionType.Value;
GameDirectories.Clear();
GameDirectories.AddRange(config.UI.GameDirs.Value);
AutoloadDirectories.Clear();
AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value);
BaseStyleIndex = config.UI.BaseStyle.Value switch
{
"Auto" => 0,
"Light" => 1,
"Dark" => 2,
_ => 0
};
}
// Input // Input
EnableDockedMode = config.System.EnableDockedMode; EnableDockedMode = config.System.EnableDockedMode;
@@ -586,6 +517,7 @@ namespace Ryujinx.Ava.UI.ViewModels
DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset); DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset);
CurrentDate = currentDateTime.Date; CurrentDate = currentDateTime.Date;
CurrentTime = currentDateTime.TimeOfDay; CurrentTime = currentDateTime.TimeOfDay;
MatchSystemTime = config.System.MatchSystemTime; MatchSystemTime = config.System.MatchSystemTime;
EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval; EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval;
@@ -594,7 +526,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks; EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
DramSize = config.System.DramSize; DramSize = config.System.DramSize;
IgnoreMissingServices = config.System.IgnoreMissingServices; IgnoreMissingServices = config.System.IgnoreMissingServices;
IgnoreApplet = config.System.IgnoreApplet; IgnoreApplet = config.System.IgnoreControllerApplet;
// CPU // CPU
EnablePptc = config.System.EnablePtc; EnablePptc = config.System.EnablePtc;
@@ -650,39 +582,26 @@ namespace Ryujinx.Ava.UI.ViewModels
public void SaveSettings() public void SaveSettings()
{ {
ConfigurationState config = ConfigurationState.Instance; ConfigurationState config = ConfigurationState.Instance;
bool userConfigFile = string.IsNullOrEmpty(GameId);
// User Interface
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.RememberWindowState.Value = RememberWindowState;
config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor;
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
config.UI.GameDirs.Value = [..GameDirectories];
config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
if (userConfigFile) config.UI.BaseStyle.Value = BaseStyleIndex switch
{ {
// User Interface 0 => "Auto",
config.EnableDiscordIntegration.Value = EnableDiscordIntegration; 1 => "Light",
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; 2 => "Dark",
config.ShowConfirmExit.Value = ShowConfirmExit; _ => "Auto"
config.RememberWindowState.Value = RememberWindowState; };
config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor;
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
if (GameDirectoryChanged)
{
config.UI.GameDirs.Value = [.. GameDirectories];
}
if (AutoloadDirectoryChanged)
{
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
}
config.UI.BaseStyle.Value = BaseStyleIndex switch
{
0 => "Auto",
1 => "Light",
2 => "Dark",
_ => "Auto"
};
}
// Input // Input
config.System.EnableDockedMode.Value = EnableDockedMode; config.System.EnableDockedMode.Value = EnableDockedMode;
@@ -695,8 +614,11 @@ namespace Ryujinx.Ava.UI.ViewModels
// System // System
config.System.Region.Value = (Region)Region; config.System.Region.Value = (Region)Region;
config.System.Language.Value = (Language)Language;
if (config.System.Language.Value != (Language)Language)
GameListNeedsRefresh = true;
config.System.Language.Value = (Language)Language;
if (_validTzRegions.Contains(TimeZone)) if (_validTzRegions.Contains(TimeZone))
{ {
config.System.TimeZone.Value = TimeZone; config.System.TimeZone.Value = TimeZone;
@@ -707,7 +629,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks; config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
config.System.DramSize.Value = DramSize; config.System.DramSize.Value = DramSize;
config.System.IgnoreMissingServices.Value = IgnoreMissingServices; config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
config.System.IgnoreApplet.Value = IgnoreApplet; config.System.IgnoreControllerApplet.Value = IgnoreApplet;
// CPU // CPU
config.System.EnablePtc.Value = EnablePptc; config.System.EnablePtc.Value = EnablePptc;
@@ -780,26 +702,19 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled; config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled;
config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay; config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay;
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig(); MainWindow.UpdateGraphicsConfig();
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged(); RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
SaveSettingsEvent?.Invoke(); SaveSettingsEvent?.Invoke();
GameDirectoryChanged = false; GameListNeedsRefresh = false;
AutoloadDirectoryChanged = false;
} }
private static void RevertIfNotSaved() private static void RevertIfNotSaved()
{ {
// maybe this is an unnecessary check(all options need to be tested) Program.ReloadConfig();
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
{
Program.ReloadConfig();
}
} }
public void ApplyButton() public void ApplyButton()
@@ -807,26 +722,6 @@ namespace Ryujinx.Ava.UI.ViewModels
SaveSettings(); SaveSettings();
} }
public void DeleteConfigGame()
{
string gameDir = Program.GetDirGameUserConfig(GameId,false,false);
if (File.Exists(gameDir))
{
File.Delete(gameDir);
}
RevertIfNotSaved();
CloseWindow?.Invoke();
}
public void SaveUserConfig()
{
SaveSettings();
RevertIfNotSaved(); // Revert global configuration after saving user configuration
CloseWindow?.Invoke();
}
public void OkButton() public void OkButton()
{ {
SaveSettings(); SaveSettings();

View File

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

View File

@@ -131,24 +131,7 @@ namespace Ryujinx.Ava.UI.Views.Main
Rainbow.Enable(); Rainbow.Enable();
if (ViewModel.SelectedApplication is null) // Checks if game data exists await Window.SettingsWindow.ShowDialog(Window);
{
await Window.SettingsWindow.ShowDialog(Window);
}
else
{
bool userConfigExist = Program.FindGameConfig(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString, false, false));
if (!ViewModel.IsGameRunning || !userConfigExist)
{
await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist
}
else
{
// If there is a custom configuration in the folder
await new GameSpecificSettingsWindow(ViewModel, userConfigExist).ShowDialog((Window)ViewModel.TopLevel);
}
}
Rainbow.Disable(); Rainbow.Disable();
Rainbow.Reset(); Rainbow.Reset();

View File

@@ -316,8 +316,8 @@
</CheckBox> </CheckBox>
<CheckBox <CheckBox
IsChecked="{Binding IgnoreApplet}" IsChecked="{Binding IgnoreApplet}"
ToolTip.Tip="{ext:Locale IgnoreAppletTooltip}"> ToolTip.Tip="{ext:Locale IgnoreControllerAppletTooltip}">
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreApplet}" /> <TextBlock Text="{ext:Locale SettingsTabSystemIgnoreControllerApplet}" />
</CheckBox> </CheckBox>
<CheckBox <CheckBox
IsChecked="{Binding EnableCustomVSyncInterval}" IsChecked="{Binding EnableCustomVSyncInterval}"

View File

@@ -37,10 +37,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
addDirBox.Clear(); addDirBox.Clear();
if (isGameList) ViewModel.GameListNeedsRefresh = true;
ViewModel.GameDirectoryChanged = true;
else
ViewModel.AutoloadDirectoryChanged = true;
} }
else else
{ {
@@ -50,10 +47,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
{ {
directories.Add(folder.Value.Path.LocalPath); directories.Add(folder.Value.Path.LocalPath);
if (isGameList) ViewModel.GameListNeedsRefresh = true;
ViewModel.GameDirectoryChanged = true;
else
ViewModel.AutoloadDirectoryChanged = true;
} }
} }
} }
@@ -65,7 +59,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
foreach (string path in new List<string>(GameDirsList.SelectedItems.Cast<string>())) foreach (string path in new List<string>(GameDirsList.SelectedItems.Cast<string>()))
{ {
ViewModel.GameDirectories.Remove(path); ViewModel.GameDirectories.Remove(path);
ViewModel.GameDirectoryChanged = true; ViewModel.GameListNeedsRefresh = true;
} }
if (GameDirsList.ItemCount > 0) if (GameDirsList.ItemCount > 0)
@@ -81,7 +75,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
foreach (string path in new List<string>(AutoloadDirsList.SelectedItems.Cast<string>())) foreach (string path in new List<string>(AutoloadDirsList.SelectedItems.Cast<string>()))
{ {
ViewModel.AutoloadDirectories.Remove(path); ViewModel.AutoloadDirectories.Remove(path);
ViewModel.AutoloadDirectoryChanged = true; ViewModel.GameListNeedsRefresh = true;
} }
if (AutoloadDirsList.ItemCount > 0) if (AutoloadDirsList.ItemCount > 0)

View File

@@ -1,149 +0,0 @@
<window:StyleableAppWindow
x:Class="Ryujinx.Ava.UI.Windows.GameSpecificSettingsWindow"
xmlns="https://github.com/avaloniaui"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100"
Height="768"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"
x:DataType="viewModels:SettingsViewModel"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600" RowDefinitions="Auto,*,Auto">
<ContentPresenter
x:Name="ContentPresenter"
Grid.Row="1"
IsVisible="False"
KeyboardNavigation.IsTabStop="False"/>
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
<settings:SettingsInputView Name="InputPage" />
<settings:SettingsSystemView Name="SystemPage" />
<settings:SettingsCPUView Name="CpuPage" />
<settings:SettingsGraphicsView Name="GraphicsPage" />
<settings:SettingsAudioView Name="AudioPage" />
<settings:SettingsNetworkView Name="NetworkPage" />
<settings:SettingsLoggingView Name="LoggingPage" />
<settings:SettingsHacksView Name="HacksPage" />
</Grid>
<ui:NavigationView
Grid.Row="1"
IsSettingsVisible="False"
Name="NavPanel"
IsBackEnabled="False"
PaneDisplayMode="Left"
Margin="2,10,10,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
OpenPaneLength="200"
IsPaneToggleButtonVisible="False">
<!-- For image -->
<ui:NavigationView.PaneHeader>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding GameId}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,10"
TextAlignment="Center" Grid.Row="0" />
<Image Source="{Binding GameIcon}"
Width="160"
Height="160"
Grid.Row="1"
Margin="0,0,0,10"
HorizontalAlignment="Center" />
<TextBlock Text="{Binding GameTitle}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,10"
TextAlignment="Center" Grid.Row="2" />
<Separator Height="1" Grid.Row="3" Margin="0,0,0,10" HorizontalAlignment="Stretch"/>
</Grid>
</ui:NavigationView.PaneHeader>
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabInput}"
Tag="InputPage"
IconSource="Games" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabSystem}"
Tag="SystemPage"
IconSource="Settings" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabCpu}"
Tag="CpuPage">
<ui:NavigationViewItem.IconSource>
<ui:FontIconSource
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
Glyph="{helpers:GlyphValueConverter Chip}" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabGraphics}"
Tag="GraphicsPage"
IconSource="Image" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabAudio}"
IconSource="Audio"
Tag="AudioPage" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabNetwork}"
Tag="NetworkPage"
IconSource="Globe" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabLogging}"
Tag="LoggingPage"
IconSource="Document" />
<ui:NavigationViewItem
IsVisible="{Binding ShowDirtyHacks}"
Content="Dirty Hacks"
Tag="HacksPage"
IconSource="Code" />
</ui:NavigationView.MenuItems>
</ui:NavigationView>
<ReversibleStackPanel
Grid.Row="2"
Margin="10"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right"
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
<Button
Content="{ext:Locale SettingsButtonSave}"
Command="{Binding SaveUserConfig}" />
<Button
HotKey="Escape"
Content="{ext:Locale SettingsButtonClose}"
Command="{Binding CancelButton}" />
<Button
IsVisible="{Binding IsGameRunning}"
Content="{ext:Locale SettingsButtonApply}"
Command="{Binding ApplyButton}" />
<Button
IsVisible="{Binding !IsGameRunning}"
Content="{ext:Locale UserProfilesDelete}"
Command="{Binding DeleteConfigGame}"
Classes="red"/>
</ReversibleStackPanel>
</Grid>
</window:StyleableAppWindow>

View File

@@ -1,117 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Projektanker.Icons.Avalonia;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Configuration;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using System;
using System.IO;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows
{
public partial class GameSpecificSettingsWindow : StyleableAppWindow
{
internal readonly SettingsViewModel ViewModel;
public GameSpecificSettingsWindow(MainWindowViewModel viewModel, bool findUserConfigDir = true)
{
Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString);
DataContext = ViewModel = new SettingsViewModel(
viewModel.VirtualFileSystem,
viewModel.ContentManager,
viewModel.IsGameRunning,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Icon,
findUserConfigDir);
ViewModel.CloseWindow += Close;
ViewModel.SaveSettingsEvent += SaveSettings;
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public void SaveSettings()
{
InputPage.InputView?.SaveCurrentProfile();
}
private void Load()
{
Pages.Children.Clear();
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
}
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
{
if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null)
{
switch (navItem.Tag.ToString())
{
case nameof(InputPage):
NavPanel.Content = InputPage;
break;
case nameof(SystemPage):
SystemPage.ViewModel = ViewModel;
NavPanel.Content = SystemPage;
break;
case nameof(CpuPage):
NavPanel.Content = CpuPage;
break;
case nameof(GraphicsPage):
NavPanel.Content = GraphicsPage;
break;
case nameof(AudioPage):
NavPanel.Content = AudioPage;
break;
case nameof(NetworkPage):
NetworkPage.ViewModel = ViewModel;
NavPanel.Content = NetworkPage;
break;
case nameof(LoggingPage):
NavPanel.Content = LoggingPage;
break;
case nameof(HacksPage):
HacksPage.DataContext = ViewModel;
NavPanel.Content = HacksPage;
break;
default:
throw new NotImplementedException();
}
}
}
protected override void OnClosing(WindowClosingEventArgs e)
{
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
base.OnClosing(e);
}
}
}

View File

@@ -374,7 +374,6 @@ namespace Ryujinx.Ava.UI.Windows
if (applicationData != null) if (applicationData != null)
{ {
ViewModel.SelectedApplication = applicationData;
await ViewModel.LoadApplication(applicationData, _startFullscreen); await ViewModel.LoadApplication(applicationData, _startFullscreen);
} }
else else
@@ -386,7 +385,6 @@ namespace Ryujinx.Ava.UI.Windows
else else
{ {
applicationData = applications[0]; applicationData = applications[0];
ViewModel.SelectedApplication = applicationData;
await ViewModel.LoadApplication(applicationData, _startFullscreen); await ViewModel.LoadApplication(applicationData, _startFullscreen);
} }
} }
@@ -660,11 +658,6 @@ namespace Ryujinx.Ava.UI.Windows
ReloadGameList(); ReloadGameList();
} }
public void GameListUpdate()
{
ReloadGameList();
}
public void ToggleFileType(string fileType) public void ToggleFileType(string fileType)
{ {
switch (fileType) switch (fileType)

View File

@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.UI.Windows
{ {
InputPage.InputView?.SaveCurrentProfile(); InputPage.InputView?.SaveCurrentProfile();
if (Owner is MainWindow window && (ViewModel.GameDirectoryChanged || ViewModel.AutoloadDirectoryChanged)) if (Owner is MainWindow window && ViewModel.GameListNeedsRefresh)
{ {
window.LoadApplications(); window.LoadApplications();
} }

View File

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

View File

@@ -533,7 +533,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
data.Favorite = appMetadata.Favorite; data.Favorite = appMetadata.Favorite;
data.TimePlayed = appMetadata.TimePlayed; data.TimePlayed = appMetadata.TimePlayed;
data.LastPlayed = appMetadata.LastPlayed; data.LastPlayed = appMetadata.LastPlayed;
data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // Just check user config
} }
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(); data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();

View File

@@ -6,16 +6,11 @@ namespace Ryujinx.Ava.Utilities
public static class CommandLineState public static class CommandLineState
{ {
public static string[] Arguments { get; private set; } public static string[] Arguments { get; private set; }
public static int CountArguments { get; private set; }
public static bool? OverrideDockedMode { get; private set; } public static bool? OverrideDockedMode { get; private set; }
public static bool? OverrideHardwareAcceleration { get; private set; } public static bool? OverrideHardwareAcceleration { get; private set; }
public static string OverrideGraphicsBackend { get; private set; } public static string OverrideGraphicsBackend { get; private set; }
public static string OverrideBackendThreading { get; private set; } public static string OverrideBackendThreading { get; private set; }
public static string OverrideBackendThreadingAfterReboot { get; private set; }
public static string OverridePPTC { get; private set; }
public static string OverrideMemoryManagerMode { get; private set; }
public static string OverrideSystemRegion { get; private set; }
public static string OverrideSystemLanguage { get; private set; }
public static string OverrideHideCursor { get; private set; } public static string OverrideHideCursor { get; private set; }
public static string BaseDirPathArg { get; private set; } public static string BaseDirPathArg { get; private set; }
public static string Profile { get; private set; } public static string Profile { get; private set; }
@@ -24,7 +19,6 @@ namespace Ryujinx.Ava.Utilities
public static bool StartFullscreenArg { get; private set; } public static bool StartFullscreenArg { get; private set; }
public static bool HideAvailableUpdates { get; private set; } public static bool HideAvailableUpdates { get; private set; }
public static void ParseArguments(string[] args) public static void ParseArguments(string[] args)
{ {
List<string> arguments = []; List<string> arguments = [];
@@ -34,11 +28,6 @@ namespace Ryujinx.Ava.Utilities
{ {
string arg = args[i]; string arg = args[i];
if (arg.Contains("-") || arg.Contains("--"))
{
CountArguments++;
}
switch (arg) switch (arg)
{ {
case "-r": case "-r":
@@ -96,57 +85,6 @@ namespace Ryujinx.Ava.Utilities
OverrideBackendThreading = args[++i]; OverrideBackendThreading = args[++i];
break; break;
case "--bt":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideBackendThreadingAfterReboot = args[++i];
break;
case "--pptc":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverridePPTC = args[++i];
break;
case "-m":
case "--memory-manager-mode":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideMemoryManagerMode = args[++i];
break;
case "--system-region":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideSystemRegion = args[++i];
break;
case "--system-language":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideSystemLanguage = args[++i];
break;
case "-i": case "-i":
case "--application-id": case "--application-id":
LaunchApplicationId = args[++i]; LaunchApplicationId = args[++i];

View File

@@ -183,7 +183,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
public bool ShowConfirmExit { get; set; } public bool ShowConfirmExit { get; set; }
/// <summary> /// <summary>
/// ignore "Applet" dialog /// Ignore Controller Applet dialog
/// </summary> /// </summary>
public bool IgnoreApplet { get; set; } public bool IgnoreApplet { get; set; }

View File

@@ -18,15 +18,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
{ {
public partial class ConfigurationState public partial class ConfigurationState
{ {
public void Load(ConfigurationFileFormat cff, string configurationFilePath, string titleId = "") public void Load(ConfigurationFileFormat cff, string configurationFilePath)
{ {
bool configurationFileUpdated = false; bool configurationFileUpdated = false;
bool shouldLoadFromFile = true;
if (!string.IsNullOrEmpty(titleId))
{
shouldLoadFromFile = false;
}
if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion) if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion)
{ {
@@ -49,16 +43,15 @@ namespace Ryujinx.Ava.Utilities.Configuration
configurationFileUpdated = true; configurationFileUpdated = true;
} }
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
EnableDiscordIntegration.Value = shouldLoadFromFile ? cff.EnableDiscordIntegration : EnableDiscordIntegration.Value; // Get from global config only CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
CheckUpdatesOnStart.Value = shouldLoadFromFile ? cff.CheckUpdatesOnStart : CheckUpdatesOnStart.Value; // Get from global config only UpdateCheckerType.Value = cff.UpdateCheckerType;
UpdateCheckerType.Value = shouldLoadFromFile ? cff.UpdateCheckerType : UpdateCheckerType.Value; // Get from global config only FocusLostActionType.Value = cff.FocusLostActionType;
FocusLostActionType.Value = shouldLoadFromFile ? cff.FocusLostActionType : FocusLostActionType.Value; // Get from global config only ShowConfirmExit.Value = cff.ShowConfirmExit;
ShowConfirmExit.Value = shouldLoadFromFile ? cff.ShowConfirmExit : ShowConfirmExit.Value; // Get from global config only RememberWindowState.Value = cff.RememberWindowState;
RememberWindowState.Value = shouldLoadFromFile ? cff.RememberWindowState : RememberWindowState.Value; // Get from global config only ShowTitleBar.Value = cff.ShowTitleBar;
ShowTitleBar.Value = shouldLoadFromFile ? cff.ShowTitleBar : ShowTitleBar.Value; // Get from global config only EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration;
EnableHardwareAcceleration.Value = shouldLoadFromFile ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only HideCursor.Value = cff.HideCursor;
HideCursor.Value = shouldLoadFromFile ? cff.HideCursor : HideCursor.Value; // Get from global config only
Logger.EnableFileLog.Value = cff.EnableFileLog; Logger.EnableFileLog.Value = cff.EnableFileLog;
Logger.EnableDebug.Value = cff.LoggingEnableDebug; Logger.EnableDebug.Value = cff.LoggingEnableDebug;
@@ -94,7 +87,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.Language.Value = cff.SystemLanguage; System.Language.Value = cff.SystemLanguage;
System.Region.Value = cff.SystemRegion; System.Region.Value = cff.SystemRegion;
System.TimeZone.Value = cff.SystemTimeZone; System.TimeZone.Value = cff.SystemTimeZone;
System.SystemTimeOffset.Value = shouldLoadFromFile ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only System.SystemTimeOffset.Value = cff.SystemTimeOffset;
System.MatchSystemTime.Value = cff.MatchSystemTime;
System.EnableDockedMode.Value = cff.DockedMode; System.EnableDockedMode.Value = cff.DockedMode;
System.EnablePtc.Value = cff.EnablePtc; System.EnablePtc.Value = cff.EnablePtc;
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc; System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
@@ -106,50 +100,49 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.MemoryManagerMode.Value = cff.MemoryManagerMode; System.MemoryManagerMode.Value = cff.MemoryManagerMode;
System.DramSize.Value = cff.DramSize; System.DramSize.Value = cff.DramSize;
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices; System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
System.IgnoreApplet.Value = cff.IgnoreApplet; System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
System.UseHypervisor.Value = cff.UseHypervisor; System.UseHypervisor.Value = cff.UseHypervisor;
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value; UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value; UI.GuiColumns.IconColumn.Value = cff.GuiColumns.IconColumn;
UI.GuiColumns.AppColumn.Value = shouldLoadFromFile ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value; UI.GuiColumns.AppColumn.Value = cff.GuiColumns.AppColumn;
UI.GuiColumns.DevColumn.Value = shouldLoadFromFile ? cff.GuiColumns.DevColumn : UI.GuiColumns.DevColumn.Value; UI.GuiColumns.DevColumn.Value = cff.GuiColumns.DevColumn;
UI.GuiColumns.VersionColumn.Value = shouldLoadFromFile ? cff.GuiColumns.VersionColumn : UI.GuiColumns.VersionColumn.Value; UI.GuiColumns.VersionColumn.Value = cff.GuiColumns.VersionColumn;
UI.GuiColumns.TimePlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.TimePlayedColumn : UI.GuiColumns.TimePlayedColumn.Value; UI.GuiColumns.TimePlayedColumn.Value = cff.GuiColumns.TimePlayedColumn;
UI.GuiColumns.LastPlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.LastPlayedColumn : UI.GuiColumns.LastPlayedColumn.Value; UI.GuiColumns.LastPlayedColumn.Value = cff.GuiColumns.LastPlayedColumn;
UI.GuiColumns.FileExtColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileExtColumn : UI.GuiColumns.FileExtColumn.Value; UI.GuiColumns.FileExtColumn.Value = cff.GuiColumns.FileExtColumn;
UI.GuiColumns.FileSizeColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileSizeColumn : UI.GuiColumns.FileSizeColumn.Value; UI.GuiColumns.FileSizeColumn.Value = cff.GuiColumns.FileSizeColumn;
UI.GuiColumns.PathColumn.Value = shouldLoadFromFile ? cff.GuiColumns.PathColumn : UI.GuiColumns.PathColumn.Value; UI.GuiColumns.PathColumn.Value = cff.GuiColumns.PathColumn;
UI.ColumnSort.SortColumnId.Value = shouldLoadFromFile ? cff.ColumnSort.SortColumnId : UI.ColumnSort.SortColumnId.Value; UI.ColumnSort.SortColumnId.Value = cff.ColumnSort.SortColumnId;
UI.ColumnSort.SortAscending.Value = shouldLoadFromFile ? cff.ColumnSort.SortAscending : UI.ColumnSort.SortAscending.Value; UI.ColumnSort.SortAscending.Value = cff.ColumnSort.SortAscending;
UI.GameDirs.Value = shouldLoadFromFile ? cff.GameDirs : UI.GameDirs.Value; UI.GameDirs.Value = cff.GameDirs;
UI.AutoloadDirs.Value = shouldLoadFromFile ? (cff.AutoloadDirs ?? []) : UI.AutoloadDirs.Value; UI.AutoloadDirs.Value = cff.AutoloadDirs ?? [];
UI.ShownFileTypes.NSP.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSP : UI.ShownFileTypes.NSP.Value; UI.ShownFileTypes.NSP.Value = cff.ShownFileTypes.NSP;
UI.ShownFileTypes.PFS0.Value = shouldLoadFromFile ? cff.ShownFileTypes.PFS0 : UI.ShownFileTypes.PFS0.Value; UI.ShownFileTypes.PFS0.Value = cff.ShownFileTypes.PFS0;
UI.ShownFileTypes.XCI.Value = shouldLoadFromFile ? cff.ShownFileTypes.XCI : UI.ShownFileTypes.XCI.Value; UI.ShownFileTypes.XCI.Value = cff.ShownFileTypes.XCI;
UI.ShownFileTypes.NCA.Value = shouldLoadFromFile ? cff.ShownFileTypes.NCA : UI.ShownFileTypes.NCA.Value; UI.ShownFileTypes.NCA.Value = cff.ShownFileTypes.NCA;
UI.ShownFileTypes.NRO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NRO : UI.ShownFileTypes.NRO.Value; UI.ShownFileTypes.NRO.Value = cff.ShownFileTypes.NRO;
UI.ShownFileTypes.NSO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value; UI.ShownFileTypes.NSO.Value = cff.ShownFileTypes.NSO;
UI.LanguageCode.Value = shouldLoadFromFile ? cff.LanguageCode : UI.LanguageCode.Value; UI.LanguageCode.Value = cff.LanguageCode;
UI.BaseStyle.Value = shouldLoadFromFile ? cff.BaseStyle : UI.BaseStyle.Value; UI.BaseStyle.Value = cff.BaseStyle;
UI.GameListViewMode.Value = shouldLoadFromFile ? cff.GameListViewMode : UI.GameListViewMode.Value; UI.GameListViewMode.Value = cff.GameListViewMode;
UI.ShowNames.Value = shouldLoadFromFile ? cff.ShowNames : UI.ShowNames.Value; UI.ShowNames.Value = cff.ShowNames;
UI.IsAscendingOrder.Value = shouldLoadFromFile ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value; UI.IsAscendingOrder.Value = cff.IsAscendingOrder;
UI.GridSize.Value = shouldLoadFromFile ? cff.GridSize : UI.GridSize.Value; UI.GridSize.Value = cff.GridSize;
UI.ApplicationSort.Value = shouldLoadFromFile ? cff.ApplicationSort : UI.ApplicationSort.Value; UI.ApplicationSort.Value = cff.ApplicationSort;
UI.StartFullscreen.Value = shouldLoadFromFile ? cff.StartFullscreen : UI.StartFullscreen.Value; UI.StartFullscreen.Value = cff.StartFullscreen;
UI.StartNoUI.Value = shouldLoadFromFile ? cff.StartNoUI : UI.StartNoUI.Value; UI.StartNoUI.Value = cff.StartNoUI;
UI.ShowConsole.Value = shouldLoadFromFile ? cff.ShowConsole : UI.ShowConsole.Value; UI.ShowConsole.Value = cff.ShowConsole;
UI.WindowStartup.WindowSizeWidth.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeWidth : UI.WindowStartup.WindowSizeWidth.Value; UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth;
UI.WindowStartup.WindowSizeHeight.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeHeight : UI.WindowStartup.WindowSizeHeight.Value; UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight;
UI.WindowStartup.WindowPositionX.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value; UI.WindowStartup.WindowPositionX.Value = cff.WindowStartup.WindowPositionX;
UI.WindowStartup.WindowPositionY.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value; UI.WindowStartup.WindowPositionY.Value = cff.WindowStartup.WindowPositionY;
UI.WindowStartup.WindowMaximized.Value = shouldLoadFromFile ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value; UI.WindowStartup.WindowMaximized.Value = cff.WindowStartup.WindowMaximized;
Hid.EnableKeyboard.Value = cff.EnableKeyboard; Hid.EnableKeyboard.Value = cff.EnableKeyboard;
Hid.EnableMouse.Value = cff.EnableMouse; Hid.EnableMouse.Value = cff.EnableMouse;
Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus: Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only Hid.DisableInputWhenOutOfFocus.Value = cff.DisableInputWhenOutOfFocus;
Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only Hid.Hotkeys.Value = cff.Hotkeys;
Hid.InputConfig.Value = cff.InputConfig ?? []; Hid.InputConfig.Value = cff.InputConfig ?? [];
Hid.RainbowSpeed.Value = cff.RainbowSpeed; Hid.RainbowSpeed.Value = cff.RainbowSpeed;

View File

@@ -383,7 +383,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary> /// <summary>
/// Ignore Controller Applet /// Ignore Controller Applet
/// </summary> /// </summary>
public ReactiveObject<bool> IgnoreApplet { get; private set; } public ReactiveObject<bool> IgnoreControllerApplet { get; private set; }
/// <summary> /// <summary>
/// Uses Hypervisor over JIT if available /// Uses Hypervisor over JIT if available
@@ -424,8 +424,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
DramSize.LogChangesToValue(nameof(DramSize)); DramSize.LogChangesToValue(nameof(DramSize));
IgnoreMissingServices = new ReactiveObject<bool>(); IgnoreMissingServices = new ReactiveObject<bool>();
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices)); IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
IgnoreApplet = new ReactiveObject<bool>(); IgnoreControllerApplet = new ReactiveObject<bool>();
IgnoreApplet.LogChangesToValue(nameof(IgnoreApplet)); IgnoreControllerApplet.LogChangesToValue(nameof(IgnoreControllerApplet));
AudioVolume = new ReactiveObject<float>(); AudioVolume = new ReactiveObject<float>();
AudioVolume.LogChangesToValue(nameof(AudioVolume)); AudioVolume.LogChangesToValue(nameof(AudioVolume));
UseHypervisor = new ReactiveObject<bool>(); UseHypervisor = new ReactiveObject<bool>();

View File

@@ -53,6 +53,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
SystemRegion = System.Region, SystemRegion = System.Region,
SystemTimeZone = System.TimeZone, SystemTimeZone = System.TimeZone,
SystemTimeOffset = System.SystemTimeOffset, SystemTimeOffset = System.SystemTimeOffset,
MatchSystemTime = System.MatchSystemTime,
DockedMode = System.EnableDockedMode, DockedMode = System.EnableDockedMode,
EnableDiscordIntegration = EnableDiscordIntegration, EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart, CheckUpdatesOnStart = CheckUpdatesOnStart,
@@ -80,7 +81,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
MemoryManagerMode = System.MemoryManagerMode, MemoryManagerMode = System.MemoryManagerMode,
DramSize = System.DramSize, DramSize = System.DramSize,
IgnoreMissingServices = System.IgnoreMissingServices, IgnoreMissingServices = System.IgnoreMissingServices,
IgnoreApplet = System.IgnoreApplet, IgnoreApplet = System.IgnoreControllerApplet,
UseHypervisor = System.UseHypervisor, UseHypervisor = System.UseHypervisor,
GuiColumns = new GuiColumns GuiColumns = new GuiColumns
{ {
@@ -204,7 +205,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe; System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB; System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
System.IgnoreMissingServices.Value = false; System.IgnoreMissingServices.Value = false;
System.IgnoreApplet.Value = false; System.IgnoreControllerApplet.Value = false;
System.UseHypervisor.Value = true; System.UseHypervisor.Value = true;
Multiplayer.LanInterfaceId.Value = "0"; Multiplayer.LanInterfaceId.Value = "0";
Multiplayer.Mode.Value = MultiplayerMode.Disabled; Multiplayer.Mode.Value = MultiplayerMode.Disabled;

View File

@@ -1,5 +1,6 @@
using Gommon; using Gommon;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Common.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -19,6 +20,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs); 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> /// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration. /// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
/// </summary> /// </summary>
@@ -27,10 +33,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns> /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform) public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform)
{ {
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _), if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _))
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}."); return AddSpec(transform(GameSpec.Create(titleId)));
return AddSpec(transform(GameSpec.Create(titleId))); Logger.Notice.PrintMsg(LogClass.Application,
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'");
return this;
} }
/// <summary> /// <summary>
@@ -41,10 +49,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns> /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public Analyzer AddSpec(string titleId, Action<GameSpec> transform) public Analyzer AddSpec(string titleId, Action<GameSpec> transform)
{ {
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _), if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _))
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}."); return AddSpec(GameSpec.Create(titleId).Apply(transform));
return AddSpec(GameSpec.Create(titleId).Apply(transform)); Logger.Notice.PrintMsg(LogClass.Application,
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'");
return this;
} }
/// <summary> /// <summary>
@@ -57,10 +67,19 @@ namespace Ryujinx.Ava.Utilities.PlayReport
Func<GameSpec, GameSpec> transform) Func<GameSpec, GameSpec> transform)
{ {
string[] tids = titleIds.ToArray(); string[] tids = titleIds.ToArray();
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)), if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x)))
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}."); return AddSpec(transform(GameSpec.Create(tids)));
return AddSpec(transform(GameSpec.Create(tids))); Logger.Notice.PrintMsg(LogClass.Application,
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{
tids.FormatCollection(
x => x,
separator: ", ",
prefix: "[",
suffix: "]"
)
}'");
return this;
} }
/// <summary> /// <summary>
@@ -72,10 +91,19 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform) public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform)
{ {
string[] tids = titleIds.ToArray(); string[] tids = titleIds.ToArray();
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)), if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x)))
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}."); return AddSpec(GameSpec.Create(tids).Apply(transform));
return AddSpec(GameSpec.Create(tids).Apply(transform)); Logger.Notice.PrintMsg(LogClass.Application,
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{
tids.FormatCollection(
x => x,
separator: ", ",
prefix: "[",
suffix: "]"
)
}'");
return this;
} }
/// <summary> /// <summary>
@@ -106,12 +134,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
if (!playReport.ReportData.IsDictionary) if (!playReport.ReportData.IsDictionary)
return FormattedValue.Unhandled; return FormattedValue.Unhandled;
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec)) if (!TryGetSpec(runningGameId, out GameSpec spec))
return FormattedValue.Unhandled; return FormattedValue.Unhandled;
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority)) foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
{ {
if (!formatSpec.Format(appMeta, playReport, out FormattedValue value)) if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value))
continue; continue;
return value; return value;

View File

@@ -1,4 +1,5 @@
using Gommon; using Gommon;
using Humanizer;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
@@ -19,6 +20,9 @@ namespace Ryujinx.Ava.Utilities.PlayReport
_ => "Roaming Hyrule" _ => "Roaming Hyrule"
}; };
private static FormattedValue SkywardSwordHD_Rupees(SingleValue value)
=> "rupee".ToQuantity(value.Matched.IntValue);
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value) private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode"; => value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";

View File

@@ -1,11 +1,22 @@
namespace Ryujinx.Ava.Utilities.PlayReport using System;
namespace Ryujinx.Ava.Utilities.PlayReport
{ {
public static partial class PlayReports public static partial class PlayReports
{ {
public static Analyzer Analyzer { get; } = new Analyzer() public static void Initialize()
{
// init lazy value
_ = Analyzer;
}
public static Analyzer Analyzer => _analyzerLazy.Value;
private static readonly Lazy<Analyzer> _analyzerLazy = new(() => new Analyzer()
.AddSpec( .AddSpec(
"01007ef00011e000", "01007ef00011e000",
spec => spec spec => spec
.WithDescription("based on being in Master Mode.")
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen // reset to normal status when switching between normal & master mode in title screen
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets) .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
@@ -13,34 +24,49 @@
.AddSpec( .AddSpec(
"0100f2c0115b6000", "0100f2c0115b6000",
spec => spec spec => spec
.WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).")
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
.AddSpec(
"01002da013484000",
spec => spec
.WithDescription("based on how many Rupees you have.")
.AddValueFormatter("rupees", SkywardSwordHD_Rupees))
.AddSpec( .AddSpec(
"0100000000010000", "0100000000010000",
spec => spec => spec
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) .WithDescription("based on if you're playing with Assist Mode.")
.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
) )
.AddSpec( .AddSpec(
"010075000ecbe000", "010075000ecbe000",
spec => spec => spec
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) .WithDescription("based on if you're playing with Assist Mode.")
.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
) )
.AddSpec( .AddSpec(
"010028600ebda000", "010028600ebda000",
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) spec => spec
.WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.")
.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
) )
.AddSpec( // Global & China IDs .AddSpec( // Global & China IDs
["0100152000022000", "010075100e8ec000"], ["0100152000022000", "010075100e8ec000"],
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode) 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( .AddSpec(
["0100a3d008c5c000", "01008f6008c5e000"], ["0100a3d008c5c000", "01008f6008c5e000"],
spec => spec spec => spec
.WithDescription("based on what area of Paldea you're exploring.")
.AddValueFormatter("area_no", PokemonSVArea) .AddValueFormatter("area_no", PokemonSVArea)
.AddValueFormatter("team_circle", PokemonSVUnionCircle) .AddValueFormatter("team_circle", PokemonSVUnionCircle)
) )
.AddSpec( .AddSpec(
"01006a800016e000", "01006a800016e000",
spec => spec spec => spec
.WithDescription("based on what mode you're playing, who won, and what characters were present.")
.AddSparseMultiValueFormatter( .AddSparseMultiValueFormatter(
[ [
// Metadata to figure out what PlayReport we have. // Metadata to figure out what PlayReport we have.
@@ -59,9 +85,14 @@
.AddSpec( .AddSpec(
[ [
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000", "0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
"010012f017576000", "0100c62011050000", "0100b3c014bda000"], "010012f017576000", "0100c62011050000", "0100b3c014bda000"
spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame) ],
); 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 string Playing(string game) => $"Playing {game}"; private static string Playing(string game) => $"Playing {game}";
} }

View File

@@ -23,6 +23,20 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public required string[] TitleIds { get; init; } 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; } = []; public List<FormatterSpecBase> ValueFormatters { get; } = [];
@@ -197,7 +211,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public string[] ReportKeys { get; init; } public string[] ReportKeys { get; init; }
public Delegate Formatter { get; init; } public Delegate Formatter { get; init; }
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
out FormattedValue formattedValue) out FormattedValue formattedValue)
{ {
formattedValue = default; formattedValue = default;

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Ava.Utilities
public static class ShortcutHelper public static class ShortcutHelper
{ {
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath, string args = "") private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
{ {
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe"); string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
iconPath += ".ico"; iconPath += ".ico";
@@ -22,13 +22,13 @@ namespace Ryujinx.Ava.Utilities
image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High); image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
SaveBitmapAsIcon(image, iconPath); SaveBitmapAsIcon(image, iconPath);
Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId, args), iconPath, 0); Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
shortcut.StringData.NameString = cleanedAppName; shortcut.StringData.NameString = cleanedAppName;
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk")); shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
} }
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName, string args = "") private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
{ {
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh"); string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop"); string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop");
@@ -40,11 +40,11 @@ namespace Ryujinx.Ava.Utilities
data.SaveTo(file); data.SaveTo(file);
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop")); using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId, args)}"); outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
} }
[SupportedOSPlatform("macos")] [SupportedOSPlatform("macos")]
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName, string args = "") private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName)
{ {
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"); string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist"); string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist");
@@ -63,7 +63,7 @@ namespace Ryujinx.Ava.Utilities
string scriptPath = Path.Combine(scriptFolderPath, ScriptName); string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
using StreamWriter scriptFile = new(scriptPath); using StreamWriter scriptFile = new(scriptPath);
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId, args)); scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId));
// Set execute permission // Set execute permission
FileInfo fileInfo = new(scriptPath); FileInfo fileInfo = new(scriptPath);
@@ -87,7 +87,7 @@ namespace Ryujinx.Ava.Utilities
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName); outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
} }
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData, string args = "") public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
{ {
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars())); string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
@@ -96,7 +96,7 @@ namespace Ryujinx.Ava.Utilities
{ {
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app"); string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath, args); CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath);
return; return;
} }
@@ -106,14 +106,14 @@ namespace Ryujinx.Ava.Utilities
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx"); string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
Directory.CreateDirectory(iconPath); Directory.CreateDirectory(iconPath);
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName, args); CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
return; return;
} }
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
{ {
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName, args); CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName);
return; return;
} }
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.Utilities
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS."); throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
} }
private static string GetArgsString(string appFilePath, string applicationId, string config = "") private static string GetArgsString(string appFilePath, string applicationId)
{ {
// args are first defined as a list, for easier adjustments in the future // args are first defined as a list, for easier adjustments in the future
List<string> argsList = []; List<string> argsList = [];
@@ -132,11 +132,6 @@ namespace Ryujinx.Ava.Utilities
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\""); argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
} }
if (!string.IsNullOrEmpty(config))
{
argsList.Add(config);
}
if (appFilePath.ToLower().EndsWith(".xci")) if (appFilePath.ToLower().EndsWith(".xci"))
{ {
argsList.Add("--application-id"); argsList.Add("--application-id");