Compare commits

...

19 Commits

Author SHA1 Message Date
Evan Husted 208e685e27 Merge 38ed71ddfe into 9227cbe5a7 2025-02-25 19:31:01 -05:00
Piplup 9227cbe5a7 Dynamic RPC: Improve Pokemon Scarlet/Violet (#723)
Updated Pokemon Scarlet and Violet to use multi parser it now displays
if your in a team circle and area of the game

![image](https://github.com/user-attachments/assets/6d8d52c5-65a9-4ac3-af91-cb0c03576ad6)
2025-02-25 17:48:47 -06:00
Evan Husted 332bcdfaf1 UI: Updater: Add support for eventual Windows on ARM updates 2025-02-25 17:34:56 -06:00
FluffyOMC 1c8276197f SSBU DRPC - Stage Editing (#707)
Adds it so the Rich Presence now notices when the player edits a custom
stage!
2025-02-25 15:48:35 -06:00
LotP1 a3596ba858 Reset in-memory JIT cache on game quit + fix Purge PPTC (#709)
Jit cache now fully resets when booting a game multiple times.
This should fix random jit cache crashes.
Also removed some redundant code related to region allocation and fixed
PPTC Purge not fully purging all PPTC files in the backup folder.
2025-02-25 15:34:21 -06:00
Evan Husted 38ed71ddfe Merge branch 'master' into metal 2025-02-23 17:29:01 -06:00
Evan Husted 3ffcc72117 UI: Fix compatibility list crashing (missing font) 2025-02-22 23:48:47 -06:00
Evan Husted 80d619f010 Revert "Revert the Metal Experiment (#701)"
This reverts commit fe1617ffea.
2025-02-22 21:33:38 -06:00
Evan Husted fe1617ffea Revert the Metal Experiment (#701)
Metal sounded like a good idea to get in the emulator but frankly I
underestimated just how experimental and not ready it was.
From my write up in the Discord:
```
As is, Metal supports only a few games.
The games it does support freeze on first use of not playing them via Vulkan, because shader translation is broken.
So you need to use a dirty hack to not delete all your shaders.
Not to mention it breaks many games via MoltenVK because of changes to the shared GPU code.

Merging Metal seemed like a great idea, because of the few games it does support.
But I don't think it's worth it. Many of the games it breaks via MoltenVK *don't work via Metal*. 
Which effectively makes current Ryubing worse for Mac users than Ryujinx 1.1.1403.

I think what I'm gonna do is revert Metal, and reopen it as a PR. That way, you can still take advantage of the Metal backend as is, but without making other games worse with no solution.
```

For what it's worth, the shader translation part could at least be
"fixed" by always applying a 30ms delay for shader translation to Metal.
That being said, that solution sucks ass.
The MoltenVK regressions are even worse.



I hope this is not a let down to the Mac users. I hope you realize I'm
reverting this because you're actively getting a worse experience with
it in the emulator.
2025-02-22 21:26:46 -06:00
shinyoyo eb6b0e9adc Updated Zh-CN Simplified Chinese. (#703) 2025-02-22 02:16:28 -06:00
Evan Husted 9631bdfe16 docs: compat: Hogwarts Legacy is an Unreal Engine 4 game 2025-02-20 20:56:52 -06:00
Evan Husted 2a84656ffc misc: chore: use new array in LdnGameDataReceivedEventArgs instead of collecting the ldn datas into a list 2025-02-20 19:49:17 -06:00
Evan Husted 6c6580ddcc misc: chore: Move the LDN constants into a SharedConstants class 2025-02-20 19:30:00 -06:00
Evan Husted c47448628c UI: Print LED setting failed on Debug 2025-02-20 19:29:18 -06:00
Evan Husted d0ac83b493 misc: chore: Prevent firmware installation prompt from showing up multiple times during runtime when using --install-firmware 2025-02-20 18:52:45 -06:00
rrondo e0ddbe55c0 Ukrainian localization changes (1.2.82) (#678)
Some changes and new lines for Ukrainian (uk_UA) localization.
2025-02-20 18:11:56 -06:00
FluffyOMC 4a4078865f Add Melatonin to compatibility list (#667)
It's playable, no bugs, and can run pretty easily way above intended
FPS, which shows there's not really any performance issues lol.


![image](https://github.com/user-attachments/assets/63402c9f-2412-4b43-9e5d-42a19436ac55)
2025-02-20 16:09:14 -06:00
Milihraim 3f59bade94 Update Russian Translation (#695) 2025-02-20 14:32:35 -06:00
Evan Husted c2ed0fd5fd UI: --install-firmware startup flag.
Has the normal UI flow, this is just for systems where the file picker doesn't show up.
2025-02-19 23:07:50 -06:00
21 changed files with 383 additions and 267 deletions
+1 -1
View File
@@ -42,7 +42,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Gommon" Version="2.7.1" /> <PackageVersion Include="Gommon" Version="2.7.1.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.6.0" /> <PackageVersion Include="Sep" Version="0.6.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />
+2 -1
View File
@@ -1436,7 +1436,7 @@
010083A018262000,"Hitman: Blood Money — Reprisal",deadlock,ingame,2024-09-28 16:28:50 010083A018262000,"Hitman: Blood Money — Reprisal",deadlock,ingame,2024-09-28 16:28:50
01004B100A5CC000,"Hob: The Definitive Edition",,playable,2021-01-13 09:39:19 01004B100A5CC000,"Hob: The Definitive Edition",,playable,2021-01-13 09:39:19
0100F7300ED2C000,"Hoggy2",,playable,2022-10-10 13:53:35 0100F7300ED2C000,"Hoggy2",,playable,2022-10-10 13:53:35
0100F7E00C70E000,"Hogwarts Legacy",slow,ingame,2024-09-03 19:53:58 0100F7E00C70E000,"Hogwarts Legacy",UE4;slow,ingame,2024-09-03 19:53:58
0100633007D48000,"Hollow Knight",nvdec,playable,2023-01-16 15:44:56 0100633007D48000,"Hollow Knight",nvdec,playable,2023-01-16 15:44:56
0100F2100061E800,"Hollow0",UE4;gpu,ingame,2021-03-03 23:42:56 0100F2100061E800,"Hollow0",UE4;gpu,ingame,2021-03-03 23:42:56
0100342009E16000,"Holy Potatoes! What The Hell?!",,playable,2020-07-03 10:48:56 0100342009E16000,"Holy Potatoes! What The Hell?!",,playable,2020-07-03 10:48:56
@@ -1800,6 +1800,7 @@
010005A00B312000,"Megaton Rainfall",gpu;opengl,boots,2022-08-04 18:29:43 010005A00B312000,"Megaton Rainfall",gpu;opengl,boots,2022-08-04 18:29:43
0100EA100DF92000,"Meiji Katsugeki Haikara Ryuuseigumi - Seibai Shimaseu, Yonaoshi Kagyou",32-bit;nvdec,playable,2022-12-05 13:19:12 0100EA100DF92000,"Meiji Katsugeki Haikara Ryuuseigumi - Seibai Shimaseu, Yonaoshi Kagyou",32-bit;nvdec,playable,2022-12-05 13:19:12
0100B360068B2000,"Mekorama",gpu,boots,2021-06-17 16:37:21 0100B360068B2000,"Mekorama",gpu,boots,2021-06-17 16:37:21
010012301932A000,"Melatonin",,playable,2025-02-16 04:08:17
01000FA010340000,"Melbits World",nvdec;online,menus,2021-11-26 13:51:22 01000FA010340000,"Melbits World",nvdec;online,menus,2021-11-26 13:51:22
0100F68019636000,"Melon Journey",,playable,2023-04-23 21:20:01 0100F68019636000,"Melon Journey",,playable,2023-04-23 21:20:01
010079C012896000,"Memories Off -Innocent Fille- for Dearest",,playable,2020-08-04 07:31:22 010079C012896000,"Memories Off -Innocent Fille- for Dearest",,playable,2020-08-04 07:31:22
1 title_id game_name labels status last_updated
1436 010083A018262000 Hitman: Blood Money — Reprisal deadlock ingame 2024-09-28 16:28:50
1437 01004B100A5CC000 Hob: The Definitive Edition playable 2021-01-13 09:39:19
1438 0100F7300ED2C000 Hoggy2 playable 2022-10-10 13:53:35
1439 0100F7E00C70E000 Hogwarts Legacy slow UE4;slow ingame 2024-09-03 19:53:58
1440 0100633007D48000 Hollow Knight nvdec playable 2023-01-16 15:44:56
1441 0100F2100061E800 Hollow0 UE4;gpu ingame 2021-03-03 23:42:56
1442 0100342009E16000 Holy Potatoes! What The Hell?! playable 2020-07-03 10:48:56
1800 010005A00B312000 Megaton Rainfall gpu;opengl boots 2022-08-04 18:29:43
1801 0100EA100DF92000 Meiji Katsugeki Haikara Ryuuseigumi - Seibai Shimaseu, Yonaoshi Kagyou 32-bit;nvdec playable 2022-12-05 13:19:12
1802 0100B360068B2000 Mekorama gpu boots 2021-06-17 16:37:21
1803 010012301932A000 Melatonin playable 2025-02-16 04:08:17
1804 01000FA010340000 Melbits World nvdec;online menus 2021-11-26 13:51:22
1805 0100F68019636000 Melon Journey playable 2023-04-23 21:20:01
1806 010079C012896000 Memories Off -Innocent Fille- for Dearest playable 2020-08-04 07:31:22
+33 -28
View File
@@ -24,7 +24,7 @@ namespace ARMeilleure.Translation.Cache
private static JitCacheInvalidation _jitCacheInvalidator; private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator; private static List<CacheMemoryAllocator> _cacheAllocators = [];
private static readonly List<CacheEntry> _cacheEntries = []; private static readonly List<CacheEntry> _cacheEntries = [];
@@ -40,37 +40,48 @@ namespace ARMeilleure.Translation.Cache
public static void Initialize(IJitMemoryAllocator allocator) public static void Initialize(IJitMemoryAllocator allocator)
{ {
if (_initialized)
{
return;
}
lock (_lock) lock (_lock)
{ {
if (_initialized) if (_initialized)
{ {
return; if (OperatingSystem.IsWindows())
{
JitUnwindWindows.RemoveFunctionTableHandler(
_jitRegions[0].Pointer);
}
for (int i = 0; i < _jitRegions.Count; i++)
{
_jitRegions[i].Dispose();
}
_jitRegions.Clear();
_cacheAllocators.Clear();
} }
else
{
_initialized = true;
}
_activeRegionIndex = 0;
ReservedRegion firstRegion = new(allocator, CacheSize); ReservedRegion firstRegion = new(allocator, CacheSize);
_jitRegions.Add(firstRegion); _jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
_cacheAllocators.Add(firstCacheAllocator);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{ {
_jitCacheInvalidator = new JitCacheInvalidation(allocator); _jitCacheInvalidator = new JitCacheInvalidation(allocator);
} }
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
JitUnwindWindows.InstallFunctionTableHandler( JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize) firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
); );
} }
_initialized = true;
} }
} }
@@ -136,7 +147,7 @@ namespace ARMeilleure.Translation.Cache
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); _cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex); _cacheEntries.RemoveAt(entryIndex);
} }
@@ -167,30 +178,24 @@ namespace ARMeilleure.Translation.Cache
{ {
codeSize = AlignCodeSize(codeSize); codeSize = AlignCodeSize(codeSize);
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++) int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
if (allocOffset >= 0)
{ {
int allocOffset = _cacheAllocator.Allocate(codeSize); _jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
return allocOffset;
if (allocOffset >= 0)
{
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset;
}
} }
int exhaustedRegion = _activeRegionIndex; int exhaustedRegion = _activeRegionIndex;
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize); ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion); _jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1; _activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation)."); Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
int allocOffsetNew = _cacheAllocator.Allocate(codeSize); _cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
if (allocOffsetNew < 0) if (allocOffsetNew < 0)
{ {
throw new OutOfMemoryException("Failed to allocate in new Cache Region!"); throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
@@ -52,6 +52,11 @@ namespace ARMeilleure.Translation.Cache
nint context, nint context,
[MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll); [MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static unsafe partial bool RtlDeleteFunctionTable(
ulong tableIdentifier);
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback; private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
private static int _sizeOfRuntimeFunction; private static int _sizeOfRuntimeFunction;
@@ -91,6 +96,23 @@ namespace ARMeilleure.Translation.Cache
} }
} }
public static void RemoveFunctionTableHandler(nint codeCachePointer)
{
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
bool result;
unsafe
{
result = RtlDeleteFunctionTable(codeCachePtr | 3);
}
if (!result)
{
throw new InvalidOperationException("Failure removing function table callback.");
}
}
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context) private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
{ {
int offset = (int)((long)controlPc - context.ToInt64()); int offset = (int)((long)controlPc - context.ToInt64());
+21 -2
View File
@@ -5,15 +5,34 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Common.Helper namespace Ryujinx.Common.Helper
{ {
public enum OperatingSystemType
{
MacOS,
Linux,
Windows
}
public static class RunningPlatform public static class RunningPlatform
{ {
public static readonly OperatingSystemType CurrentOS
= IsMacOS
? OperatingSystemType.MacOS
: IsWindows
? OperatingSystemType.Windows
: IsLinux
? OperatingSystemType.Linux
: throw new PlatformNotSupportedException();
public static Architecture Architecture => RuntimeInformation.OSArchitecture;
public static Architecture CurrentProcessArchitecture => RuntimeInformation.ProcessArchitecture;
public static bool IsMacOS => OperatingSystem.IsMacOS(); public static bool IsMacOS => OperatingSystem.IsMacOS();
public static bool IsWindows => OperatingSystem.IsWindows(); public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsLinux => OperatingSystem.IsLinux(); public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsArm => RuntimeInformation.OSArchitecture is Architecture.Arm64; public static bool IsArm => Architecture is Architecture.Arm64;
public static bool IsX64 => RuntimeInformation.OSArchitecture is Architecture.X64; public static bool IsX64 => Architecture is Architecture.X64;
public static bool IsIntelMac => IsMacOS && IsX64; public static bool IsIntelMac => IsMacOS && IsX64;
public static bool IsArmMac => IsMacOS && IsArm; public static bool IsArmMac => IsMacOS && IsArm;
+9
View File
@@ -0,0 +1,9 @@
namespace Ryujinx.Common
{
public static class SharedConstants
{
public const string DefaultLanPlayHost = "ryuldn.vudjun.com";
public const short LanPlayPort = 30456;
public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
}
}
@@ -23,9 +23,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{ {
class IUserLocalCommunicationService : IpcService, IDisposable class IUserLocalCommunicationService : IpcService, IDisposable
{ {
public static string DefaultLanPlayHost = "ryuldn.vudjun.com";
public static short LanPlayPort = 30456;
public INetworkClient NetworkClient { get; private set; } public INetworkClient NetworkClient { get; private set; }
private const int NifmRequestID = 90; private const int NifmRequestID = 90;
@@ -1092,20 +1089,18 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
case MultiplayerMode.LdnRyu: case MultiplayerMode.LdnRyu:
try try
{ {
string ldnServer = context.Device.Configuration.MultiplayerLdnServer; string ldnServer = context.Device.Configuration.MultiplayerLdnServer
if (string.IsNullOrEmpty(ldnServer)) ?? throw new InvalidOperationException("Cannot initialize RyuLDN with a null Multiplayer server.");
{
ldnServer = DefaultLanPlayHost;
}
if (!IPAddress.TryParse(ldnServer, out IPAddress ipAddress)) if (!IPAddress.TryParse(ldnServer, out IPAddress ipAddress))
{ {
ipAddress = Dns.GetHostEntry(ldnServer).AddressList[0]; ipAddress = Dns.GetHostEntry(ldnServer).AddressList[0];
} }
NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), LanPlayPort, context.Device.Configuration); NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), SharedConstants.LanPlayPort, context.Device.Configuration);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate LdnRyu server. Defaulting to stubbed wireless."); Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate RyuLDN server. Defaulting to stubbed wireless.");
Logger.Error?.Print(LogClass.ServiceLdn, ex.Message); Logger.Error?.Print(LogClass.ServiceLdn, ex.Message);
NetworkClient = new LdnDisabledClient(); NetworkClient = new LdnDisabledClient();
} }
+1 -1
View File
@@ -111,7 +111,7 @@ namespace Ryujinx.Input.SDL2
byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0; byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0;
if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0) if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0)
Logger.Error?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting."); Logger.Debug?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting.");
} }
private GamepadFeaturesFlag GetFeaturesFlag() private GamepadFeaturesFlag GetFeaturesFlag()
+1 -1
View File
@@ -951,7 +951,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Multiplayer.Mode, ConfigurationState.Instance.Multiplayer.Mode,
ConfigurationState.Instance.Multiplayer.DisableP2p, ConfigurationState.Instance.Multiplayer.DisableP2p,
ConfigurationState.Instance.Multiplayer.LdnPassphrase, ConfigurationState.Instance.Multiplayer.LdnPassphrase,
ConfigurationState.Instance.Multiplayer.LdnServer, ConfigurationState.Instance.Multiplayer.GetLdnServer(),
ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value, ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value,
ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : null)); ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : null));
} }
File diff suppressed because it is too large Load Diff
@@ -200,7 +200,7 @@ namespace Ryujinx.Ava.UI.Controls
if (backupDir.Exists) if (backupDir.Exists)
{ {
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
cacheFiles.AddRange(mainDir.EnumerateFiles("*.info")); cacheFiles.AddRange(backupDir.EnumerateFiles("*.info"));
} }
if (cacheFiles.Count > 0) if (cacheFiles.Count > 0)
@@ -793,7 +793,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return false; return false;
} }
private async Task HandleFirmwareInstallation(string filename) public async Task HandleFirmwareInstallation(string filename)
{ {
try try
{ {
+20 -9
View File
@@ -139,8 +139,24 @@ namespace Ryujinx.Ava.UI.Windows
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
NotificationHelper.SetNotificationManager(this); NotificationHelper.SetNotificationManager(this);
Executor.ExecuteBackgroundAsync(ShowIntelMacWarningAsync); Executor.ExecuteBackgroundAsync(async () =>
{
await ShowIntelMacWarningAsync();
FilePath firmwarePath = CommandLineState.FirmwareToInstallPathArg;
if (firmwarePath is not null)
{
if ((firmwarePath.ExistsAsFile && firmwarePath.Extension is "xci" or "zip") ||
firmwarePath.ExistsAsDirectory)
{
await Dispatcher.UIThread.InvokeAsync(() =>
ViewModel.HandleFirmwareInstallation(firmwarePath));
CommandLineState.FirmwareToInstallPathArg = null;
}
else
Logger.Notice.Print(LogClass.UI, "Invalid firmware type provided. Path must be a directory, or a .zip or .xci file.");
}
});
} }
private void OnScalingChanged(object sender, EventArgs e) private void OnScalingChanged(object sender, EventArgs e)
@@ -173,17 +189,12 @@ namespace Ryujinx.Ava.UI.Windows
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
List<LdnGameData> ldnGameDataArray = e.LdnData.ToList();
ViewModel.LdnData.Clear(); ViewModel.LdnData.Clear();
foreach (ApplicationData application in ViewModel.Applications.Where(it => it.HasControlHolder)) foreach (ApplicationData application in ViewModel.Applications.Where(it => it.HasControlHolder))
{ {
ref ApplicationControlProperty controlHolder = ref application.ControlHolder.Value; ref ApplicationControlProperty controlHolder = ref application.ControlHolder.Value;
ViewModel.LdnData[application.IdString] = ViewModel.LdnData[application.IdString] = e.LdnData.Where(ref controlHolder);
LdnGameData.GetArrayForApp(
ldnGameDataArray,
ref controlHolder
);
UpdateApplicationWithLdnData(application); UpdateApplicationWithLdnData(application);
} }
+31 -10
View File
@@ -43,17 +43,9 @@ namespace Ryujinx.Ava
private const int ConnectionCount = 4; private const int ConnectionCount = 4;
private static string _buildVer; private static string _buildVer;
private static readonly string _platformExt = private static readonly string _platformExt = BuildPlatformExtension();
RunningPlatform.IsMacOS
? "macos_universal.app.tar.gz"
: RunningPlatform.IsWindows
? "win_x64.zip"
: RunningPlatform.IsX64Linux
? "linux_x64.tar.gz"
: RunningPlatform.IsArmLinux
? "linux_arm64.tar.gz"
: throw new PlatformNotSupportedException();
private static string _buildUrl; private static string _buildUrl;
private static long _buildSize; private static long _buildSize;
@@ -780,5 +772,34 @@ namespace Ryujinx.Ava
public static void CleanupUpdate() => public static void CleanupUpdate() =>
Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories) Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)
.ForEach(File.Delete); .ForEach(File.Delete);
private static string BuildPlatformExtension()
{
if (RunningPlatform.IsMacOS)
return "macos_universal.app.tar.gz";
#pragma warning disable CS8509 // It is exhaustive for any values this can contain.
string osPrefix = RunningPlatform.CurrentOS switch
{
OperatingSystemType.Linux => "linux",
OperatingSystemType.Windows => "win"
};
string archSuffix = RunningPlatform.Architecture switch
{
Architecture.Arm64 => "arm64",
Architecture.X64 => "x64",
_ => throw new PlatformNotSupportedException($"Unknown architecture {Enum.GetName(RunningPlatform.Architecture)}."),
};
string fileExtension = RunningPlatform.CurrentOS switch
#pragma warning restore CS8509
{
OperatingSystemType.Linux => "tar.gz",
OperatingSystemType.Windows => "zip"
};
return $"{osPrefix}_{archSuffix}.{fileExtension}";
}
} }
} }
@@ -42,7 +42,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
{ {
public class ApplicationLibrary public class ApplicationLibrary
{ {
public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
public Language DesiredLanguage { get; set; } public Language DesiredLanguage { get; set; }
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated; public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
public event Action<LdnGameDataReceivedEventArgs> LdnGameDataReceived; public event Action<LdnGameDataReceivedEventArgs> LdnGameDataReceived;
@@ -826,7 +825,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public async Task RefreshLdn() public async Task RefreshLdn()
{ {
if (ConfigurationState.Instance.Multiplayer.Mode == MultiplayerMode.LdnRyu) if (ConfigurationState.Instance.Multiplayer.Mode == MultiplayerMode.LdnRyu)
{ {
try try
@@ -834,33 +832,22 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
string ldnWebHost = ConfigurationState.Instance.Multiplayer.LdnServer; string ldnWebHost = ConfigurationState.Instance.Multiplayer.LdnServer;
if (string.IsNullOrEmpty(ldnWebHost)) if (string.IsNullOrEmpty(ldnWebHost))
{ {
ldnWebHost = DefaultLanPlayWebHost; ldnWebHost = SharedConstants.DefaultLanPlayWebHost;
} }
IEnumerable<LdnGameData> ldnGameDataArray = Array.Empty<LdnGameData>();
using HttpClient httpClient = new(); using HttpClient httpClient = new();
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games"); string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData); LdnGameData[] ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData).ToArray();
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs(ldnGameDataArray));
{ return;
LdnData = ldnGameDataArray
});
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}"); Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}");
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
{
LdnData = Array.Empty<LdnGameData>()
});
} }
} }
else
{ LdnGameDataReceived?.Invoke(LdnGameDataReceivedEventArgs.Empty);
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs
{
LdnData = Array.Empty<LdnGameData>()
});
}
} }
// Replace the currently stored DLC state for the game with the provided DLC state. // Replace the currently stored DLC state for the game with the provided DLC state.
@@ -18,7 +18,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public IEnumerable<string> Players { get; set; } public IEnumerable<string> Players { get; set; }
public static Array GetArrayForApp( public static Array GetArrayForApp(
IEnumerable<LdnGameData> receivedData, ref ApplicationControlProperty acp) LdnGameData[] receivedData, ref ApplicationControlProperty acp)
{ {
LibHac.Common.FixedArrays.Array8<ulong> communicationId = acp.LocalCommunicationId; LibHac.Common.FixedArrays.Array8<ulong> communicationId = acp.LocalCommunicationId;
@@ -40,4 +40,10 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public int GameCount => _ldnDatas.Length; public int GameCount => _ldnDatas.Length;
} }
} }
public static class LdnGameDataHelper
{
public static LdnGameData.Array Where(this LdnGameData[] unfilteredDatas, ref ApplicationControlProperty acp)
=> LdnGameData.GetArrayForApp(unfilteredDatas, ref acp);
}
} }
@@ -5,6 +5,14 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
{ {
public class LdnGameDataReceivedEventArgs : EventArgs public class LdnGameDataReceivedEventArgs : EventArgs
{ {
public IEnumerable<LdnGameData> LdnData { get; set; } public static new readonly LdnGameDataReceivedEventArgs Empty = new(null);
public LdnGameDataReceivedEventArgs(LdnGameData[] ldnData)
{
LdnData = ldnData ?? [];
}
public LdnGameData[] LdnData { get; set; }
} }
} }
+15
View File
@@ -1,3 +1,4 @@
using Gommon;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System.Collections.Generic; using System.Collections.Generic;
@@ -13,6 +14,7 @@ namespace Ryujinx.Ava.Utilities
public static string OverrideBackendThreading { get; private set; } public static string OverrideBackendThreading { 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 FilePath FirmwareToInstallPathArg { get; set; }
public static string Profile { get; private set; } public static string Profile { get; private set; }
public static string LaunchPathArg { get; private set; } public static string LaunchPathArg { get; private set; }
public static string LaunchApplicationId { get; private set; } public static string LaunchApplicationId { get; private set; }
@@ -41,6 +43,19 @@ namespace Ryujinx.Ava.Utilities
BaseDirPathArg = args[++i]; BaseDirPathArg = args[++i];
arguments.Add(arg);
arguments.Add(args[i]);
break;
case "--install-firmware":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
FirmwareToInstallPathArg = new FilePath(args[++i]);
arguments.Add(arg); arguments.Add(arg);
arguments.Add(args[i]); arguments.Add(args[i]);
break; break;
@@ -1,5 +1,6 @@
using ARMeilleure; using ARMeilleure;
using Gommon; using Gommon;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration.System; using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Ava.Utilities.Configuration.UI;
using Ryujinx.Common; using Ryujinx.Common;
@@ -647,6 +648,14 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary> /// </summary>
public ReactiveObject<string> LdnServer { get; private set; } public ReactiveObject<string> LdnServer { get; private set; }
public string GetLdnServer()
{
string ldnServer = LdnServer;
return string.IsNullOrEmpty(ldnServer)
? SharedConstants.DefaultLanPlayHost
: ldnServer;
}
public MultiplayerSection() public MultiplayerSection()
{ {
LanInterfaceId = new ReactiveObject<string>(); LanInterfaceId = new ReactiveObject<string>();
@@ -59,12 +59,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"Race" => "Racing", "Race" => "Racing",
_ => FormattedValue.ForceReset _ => FormattedValue.ForceReset
}; };
private static FormattedValue PokemonSVUnionCircle(SingleValue value) private static FormattedValue PokemonSV(MultiValue values)
=> value.Matched.BoxedValue is 0 ? "Playing Alone" : "Playing in a group"; {
private static FormattedValue PokemonSVArea(SingleValue value) string playStatus = values.Matched[0].BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
=> value.Matched.StringValue switch
FormattedValue locations = values.Matched[1].ToString() switch
{ {
// Base Game Locations // Base Game Locations
"a_w01" => "South Area One", "a_w01" => "South Area One",
@@ -92,10 +93,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"a_w24" => "South Paldean Sea", "a_w24" => "South Paldean Sea",
"a_w25" => "West Paldean Sea", "a_w25" => "West Paldean Sea",
"a_w26" => "East Paldean Sea", "a_w26" => "East Paldean Sea",
"a_w27" => "Nouth Paldean Sea", "a_w27" => "North Paldean Sea",
//TODO DLC Locations //TODO DLC Locations
_ => FormattedValue.ForceReset _ => FormattedValue.ForceReset
}; };
return$"{playStatus} in {locations}";
}
private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values) private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values)
{ {
@@ -115,6 +119,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
return $"Achievement Unlocked - ID: {anniversary}"; return $"Achievement Unlocked - ID: {anniversary}";
} }
if (values.Matched.ContainsKey("is_created"))
{
return "Edited a Custom Stage!";
}
if (values.Matched.ContainsKey("adv_slot")) if (values.Matched.ContainsKey("adv_slot"))
{ {
return return
@@ -59,9 +59,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
.AddSpec( .AddSpec(
["0100a3d008c5c000", "01008f6008c5e000"], ["0100a3d008c5c000", "01008f6008c5e000"],
spec => spec spec => spec
.WithDescription("based on what area of Paldea you're exploring.") .WithDescription("based on if you're playing alone or in a group and what area of Paldea you're exploring.")
.AddValueFormatter("area_no", PokemonSVArea) .AddMultiValueFormatter(["team_circle", "area_no"], PokemonSV)
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
) )
.AddSpec( .AddSpec(
"01006a800016e000", "01006a800016e000",
@@ -71,7 +70,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
[ [
// Metadata to figure out what PlayReport we have. // Metadata to figure out what PlayReport we have.
"match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count", "match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count",
"adv_slot", "adv_slot", "is_created",
// List of Fighters // List of Fighters
"player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter", "player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter",
"player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter", "player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter",