diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml
index 1554b8f6b..fbd76a2d8 100644
--- a/.github/workflows/canary.yml
+++ b/.github/workflows/canary.yml
@@ -29,7 +29,7 @@ env:
jobs:
tag:
name: Create tag
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
steps:
- name: Get version info
id: version_info
@@ -202,7 +202,7 @@ jobs:
macos_release:
name: Release MacOS universal
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 072c6bf2f..d4292162a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,7 +18,7 @@ env:
jobs:
tag:
name: Create tag
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
steps:
- name: Get version info
id: version_info
@@ -183,7 +183,7 @@ jobs:
macos_release:
name: Release MacOS universal
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
diff --git a/README.md b/README.md
index ef3e683e6..bb51dee13 100644
--- a/README.md
+++ b/README.md
@@ -39,12 +39,12 @@
Click below to join the Discord:
-
+
-
+
## Usage
diff --git a/docs/compatibility.csv b/docs/compatibility.csv
index 0fd8eadca..6cb10e8d8 100644
--- a/docs/compatibility.csv
+++ b/docs/compatibility.csv
@@ -1249,7 +1249,7 @@
0100A6B00D4EC000,"Furwind",,playable,2021-02-19 19:44:08
0100ECE00C0C4000,"Fury Unleashed",crash;services,ingame,2020-10-18 11:52:40
010070000ED9E000,"Fury Unleashed Demo",,playable,2020-10-08 20:09:21
-0100E1F013674000,"FUSER™",nvdec;UE4,playable,2022-10-17 20:58:32
+0100E1F013674000,"FUSER™",nvdec;UE4;slow;gpu,ingame,2025-02-12 16:03:00
0100A7A015E4C000,"Fushigi no Gensokyo Lotus Labyrinth",Needs Update;audio;gpu;nvdec,ingame,2021-01-20 15:30:02
01003C300B274000,"Futari de! Nyanko Daisensou",,playable,2024-01-05 22:26:52
010055801134E000,"FUZE Player",online-broken;vulkan-backend-bug,ingame,2022-10-18 12:23:53
@@ -2063,7 +2063,7 @@
010002700C34C000,"Numbala",,playable,2020-05-11 12:01:07
010020500C8C8000,"Number Place 10000",gpu,menus,2021-11-24 09:14:23
010003701002C000,"Nurse Love Syndrome",,playable,2022-10-13 10:05:22
-0000000000000000,"nx-hbmenu",Needs Update;homebrew,boots,2024-04-06 22:05:32
+,"nx-hbmenu",Needs Update;homebrew,boots,2024-04-06 22:05:32
,"nxquake2",services;crash;homebrew,nothing,2022-08-04 23:14:04
010049F00EC30000,"Nyan Cat: Lost in Space",online,playable,2021-06-12 13:22:03
01002E6014FC4000,"O---O",,playable,2022-10-29 12:12:14
@@ -2471,7 +2471,7 @@
0100AFE00DDAC000,"Royal Roads",,playable,2020-11-17 12:54:38
0100E2C00B414000,"RPG Maker MV",nvdec,playable,2021-01-05 20:12:01
01005CD015986000,"rRootage Reloaded",,playable,2022-08-05 23:20:18
-0000000000000000,"RSDKv5u",homebrew,ingame,2024-04-01 16:25:34
+,"RSDKv5u",homebrew,ingame,2024-04-01 16:25:34
010009B00D33C000,"Rugby Challenge 4",slow;online-broken;UE4,playable,2022-10-06 12:45:53
01006EC00F2CC000,"RUINER",UE4,playable,2022-10-03 14:11:33
010074F00DE4A000,"Run the Fan",,playable,2021-02-27 13:36:28
@@ -2480,6 +2480,7 @@
010081C0191D8000,"Rune Factory 3 Special",,playable,2023-10-15 08:32:49
010051D00E3A4000,"Rune Factory 4 Special",32-bit;crash;nvdec,ingame,2023-05-06 08:49:17
010014D01216E000,"Rune Factory 5 (JP)",gpu,ingame,2021-06-01 12:00:36
+010071E0145F8000,"Rustler",,playable,2025-02-10 20:17:12
0100E21013908000,"RWBY: Grimm Eclipse - Definitive Edition",online-broken,playable,2022-11-03 10:44:01
010012C0060F0000,"RXN -Raijin-",nvdec,playable,2021-01-10 16:05:43
0100B8B012ECA000,"S.N.I.P.E.R. - Hunter Scope",,playable,2021-04-19 15:58:09
@@ -2673,10 +2674,10 @@
01004F401BEBE000,"Song of Nunu: A League of Legends Story",,ingame,2024-07-12 18:53:44
0100E5400BF94000,"Songbird Symphony",,playable,2021-02-27 02:44:04
010031D00A604000,"Songbringer",,playable,2020-06-22 10:42:02
-0000000000000000,"Sonic 1 (2013)",crash;homebrew,ingame,2024-04-06 18:31:20
-0000000000000000,"Sonic 2 (2013)",crash;homebrew,ingame,2024-04-01 16:25:30
-0000000000000000,"Sonic A.I.R",homebrew,ingame,2024-04-01 16:25:32
-0000000000000000,"Sonic CD",crash;homebrew,ingame,2024-04-01 16:25:31
+,"Sonic 1 (2013)",crash;homebrew,ingame,2024-04-06 18:31:20
+,"Sonic 2 (2013)",crash;homebrew,ingame,2024-04-01 16:25:30
+,"Sonic A.I.R",homebrew,ingame,2024-04-01 16:25:32
+,"Sonic CD",crash;homebrew,ingame,2024-04-01 16:25:31
010040E0116B8000,"Sonic Colors: Ultimate",,playable,2022-11-12 21:24:26
01001270012B6000,"SONIC FORCES™",,playable,2024-07-28 13:11:21
01004AD014BF0000,"Sonic Frontiers",gpu;deadlock;amd-vendor-bug;intel-vendor-bug,ingame,2024-09-05 09:18:53
@@ -2693,7 +2694,7 @@
0100707011722000,"Space Elite Force",,playable,2020-11-27 15:21:05
010047B010260000,"Space Pioneer",,playable,2022-10-20 12:24:37
010010A009830000,"Space Ribbon",,playable,2022-08-15 17:17:10
-0000000000000000,"SpaceCadetPinball",homebrew,ingame,2024-04-18 19:30:04
+,"SpaceCadetPinball",homebrew,ingame,2024-04-18 19:30:04
0100D9B0041CE000,"Spacecats with Lasers",,playable,2022-08-15 17:22:44
010034800FB60000,"Spaceland",,playable,2020-11-01 14:31:56
010028D0045CE000,"Sparkle 2",,playable,2020-10-19 11:51:39
@@ -2838,7 +2839,7 @@
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
-0000000000000000,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
+,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
010049900F546000,"Super Mario™ 3D All-Stars",services-horizon;slow;vulkan;amd-vendor-bug,ingame,2024-05-07 02:38:16
010028600EBDA000,"Super Mario™ 3D World + Bowser’s Fury",ldn-works,playable,2024-07-31 10:45:37
01004F8006A78000,"Super Meat Boy",services,playable,2020-04-02 23:10:07
diff --git a/src/ARMeilleure/Memory/ReservedRegion.cs b/src/ARMeilleure/Memory/ReservedRegion.cs
index a3ebd610d..dfe17c933 100644
--- a/src/ARMeilleure/Memory/ReservedRegion.cs
+++ b/src/ARMeilleure/Memory/ReservedRegion.cs
@@ -7,6 +7,7 @@ namespace ARMeilleure.Memory
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
public IJitMemoryBlock Block { get; }
+ public IJitMemoryAllocator Allocator { get; }
public nint Pointer => Block.Pointer;
@@ -21,6 +22,7 @@ namespace ARMeilleure.Memory
granularity = DefaultGranularity;
}
+ Allocator = allocator;
Block = allocator.Reserve(maxSize);
_maxSize = maxSize;
_sizeGranularity = granularity;
diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs
index d7e8201d8..bc75e7d3f 100644
--- a/src/ARMeilleure/Translation/Cache/JitCache.cs
+++ b/src/ARMeilleure/Translation/Cache/JitCache.cs
@@ -2,6 +2,8 @@ using ARMeilleure.CodeGen;
using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Memory;
using ARMeilleure.Native;
+using Humanizer;
+using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
@@ -18,9 +20,8 @@ namespace ARMeilleure.Translation.Cache
private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes.
- private const int CacheSize = 2047 * 1024 * 1024;
+ private const int CacheSize = 256 * 1024 * 1024;
- private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator;
@@ -30,6 +31,9 @@ namespace ARMeilleure.Translation.Cache
private static readonly Lock _lock = new();
private static bool _initialized;
+ private static readonly List _jitRegions = new();
+ private static int _activeRegionIndex = 0;
+
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
@@ -48,7 +52,9 @@ namespace ARMeilleure.Translation.Cache
return;
}
- _jitRegion = new ReservedRegion(allocator, CacheSize);
+ ReservedRegion firstRegion = new(allocator, CacheSize);
+ _jitRegions.Add(firstRegion);
+ _activeRegionIndex = 0;
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
@@ -59,7 +65,9 @@ namespace ARMeilleure.Translation.Cache
if (OperatingSystem.IsWindows())
{
- JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
+ JitUnwindWindows.InstallFunctionTableHandler(
+ firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
+ );
}
_initialized = true;
@@ -75,8 +83,8 @@ namespace ARMeilleure.Translation.Cache
Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length);
-
- nint funcPtr = _jitRegion.Pointer + funcOffset;
+ ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
+ nint funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
@@ -90,9 +98,9 @@ namespace ARMeilleure.Translation.Cache
}
else
{
- ReprotectAsWritable(funcOffset, code.Length);
+ ReprotectAsWritable(targetRegion, funcOffset, code.Length);
Marshal.Copy(code, 0, funcPtr, code.Length);
- ReprotectAsExecutable(funcOffset, code.Length);
+ ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
@@ -116,52 +124,83 @@ namespace ARMeilleure.Translation.Cache
{
Debug.Assert(_initialized);
- int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
-
- if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
+ foreach (ReservedRegion region in _jitRegions)
{
- _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
- _cacheEntries.RemoveAt(entryIndex);
+ if (pointer.ToInt64() < region.Pointer.ToInt64() ||
+ pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
+ {
+ continue;
+ }
+
+ int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
+
+ if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
+ {
+ _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
+ _cacheEntries.RemoveAt(entryIndex);
+ }
+
+ return;
}
}
}
- private static void ReprotectAsWritable(int offset, int size)
+ private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;
-
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
- _jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
+ region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
- private static void ReprotectAsExecutable(int offset, int size)
+ private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;
-
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
- _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
+ region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static int Allocate(int codeSize)
{
codeSize = AlignCodeSize(codeSize);
- int allocOffset = _cacheAllocator.Allocate(codeSize);
-
- if (allocOffset < 0)
+ for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
{
- throw new OutOfMemoryException("JIT Cache exhausted.");
+ int allocOffset = _cacheAllocator.Allocate(codeSize);
+
+ if (allocOffset >= 0)
+ {
+ _jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
+ _activeRegionIndex = i;
+ return allocOffset;
+ }
}
- _jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
+ int exhaustedRegion = _activeRegionIndex;
+ var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
+ _jitRegions.Add(newRegion);
+ _activeRegionIndex = _jitRegions.Count - 1;
+
+ int newRegionNumber = _activeRegionIndex;
- return allocOffset;
+ Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
+
+ _cacheAllocator = new CacheMemoryAllocator(CacheSize);
+
+ int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
+ if (allocOffsetNew < 0)
+ {
+ throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
+ }
+
+ newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
+ return allocOffsetNew;
}
+
private static int AlignCodeSize(int codeSize)
{
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
@@ -185,18 +224,21 @@ namespace ARMeilleure.Translation.Cache
{
lock (_lock)
{
- int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
-
- if (index < 0)
+ foreach (ReservedRegion _ in _jitRegions)
{
- index = ~index - 1;
- }
+ int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
- if (index >= 0)
- {
- entry = _cacheEntries[index];
- entryIndex = index;
- return true;
+ if (index < 0)
+ {
+ index = ~index - 1;
+ }
+
+ if (index >= 0)
+ {
+ entry = _cacheEntries[index];
+ entryIndex = index;
+ return true;
+ }
}
}
diff --git a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs
index de0b78dbe..f3aaa58ff 100644
--- a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs
+++ b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs
@@ -144,17 +144,15 @@ namespace ARMeilleure.Translation.PTC
public List GetBlacklistedFunctions()
{
- List funcs = new List();
+ List funcs = [];
- foreach (var profiledFunc in ProfiledFuncs)
+ foreach ((ulong ptr, FuncProfile funcProfile) in ProfiledFuncs)
{
- if (profiledFunc.Value.Blacklist)
- {
- if (!funcs.Contains(profiledFunc.Key))
- {
- funcs.Add(profiledFunc.Key);
- }
- }
+ if (!funcProfile.Blacklist)
+ continue;
+
+ if (!funcs.Contains(ptr))
+ funcs.Add(ptr);
}
return funcs;
diff --git a/src/Ryujinx.Common/TitleIDs.cs b/src/Ryujinx.Common/TitleIDs.cs
index 42322c8a2..76d873f60 100644
--- a/src/Ryujinx.Common/TitleIDs.cs
+++ b/src/Ryujinx.Common/TitleIDs.cs
@@ -164,15 +164,16 @@ namespace Ryujinx.Common
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
//NSO Membership games
- "0100ccf019c8c000", // F-ZERO 99
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
"0100c9a00ece6000", // N64 - Nintendo Switch Online
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
"0100d870045b6000", // NES - Nintendo Switch Online
+ "0100b3c014bda000", // SEGA Genesis - Nintendo Switch Online
+ "01008d300c50c000", // SNES - Nintendo Switch Online
+ "0100ccf019c8c000", // F-ZERO 99
"0100ad9012510000", // PAC-MAN 99
"010040600c5ce000", // Tetris 99
- "01008d300c50c000", // SNES - Nintendo Switch Online
"0100277011f1a000", // Super Mario Bros. 35
//Misc Nintendo 1st party games
@@ -218,6 +219,7 @@ namespace Ryujinx.Common
//Misc Games
"010056e00853a000", // A Hat in Time
"0100fd1014726000", // Baldurs Gate: Dark Alliance
+ "01008c2019598000", // Bluey: The Video Game
"0100c6800b934000", // Brawlhalla
"0100dbf01000a000", // Burnout Paradise Remastered
"0100744001588000", // Cars 3: Driven to Win
diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs
index 5849401ab..ccf8ad964 100644
--- a/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Cache/JitCache.cs
@@ -1,4 +1,6 @@
using ARMeilleure.Memory;
+using Humanizer;
+using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Collections.Generic;
@@ -15,9 +17,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes.
- private const int CacheSize = 2047 * 1024 * 1024;
+ private const int CacheSize = 256 * 1024 * 1024;
- private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator;
@@ -26,6 +27,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private static readonly Lock _lock = new();
private static bool _initialized;
+ private static readonly List _jitRegions = new();
+ private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
@@ -45,7 +48,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
return;
}
- _jitRegion = new ReservedRegion(allocator, CacheSize);
+ ReservedRegion firstRegion = new(allocator, CacheSize);
+ _jitRegions.Add(firstRegion);
+ _activeRegionIndex = 0;
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
@@ -65,8 +70,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
Debug.Assert(_initialized);
int funcOffset = Allocate(code.Length);
-
- nint funcPtr = _jitRegion.Pointer + funcOffset;
+ ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
+ nint funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
@@ -80,18 +85,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache
}
else
{
- ReprotectAsWritable(funcOffset, code.Length);
- code.CopyTo(new Span((void*)funcPtr, code.Length));
- ReprotectAsExecutable(funcOffset, code.Length);
+ ReprotectAsWritable(targetRegion, funcOffset, code.Length);
+ Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
+ ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
- if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
- {
- FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length);
- }
- else
- {
- _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
- }
+ _jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
}
Add(funcOffset, code.Length);
@@ -106,50 +104,80 @@ namespace Ryujinx.Cpu.LightningJit.Cache
{
Debug.Assert(_initialized);
- int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
-
- if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
+ foreach (ReservedRegion region in _jitRegions)
{
- _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
- _cacheEntries.RemoveAt(entryIndex);
+ if (pointer.ToInt64() < region.Pointer.ToInt64() ||
+ pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
+ {
+ continue;
+ }
+
+ int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
+
+ if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
+ {
+ _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
+ _cacheEntries.RemoveAt(entryIndex);
+ }
+
+ return;
}
}
}
- private static void ReprotectAsWritable(int offset, int size)
+ private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;
-
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
- _jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
+ region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
- private static void ReprotectAsExecutable(int offset, int size)
+ private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;
-
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
- _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
+ region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static int Allocate(int codeSize)
{
codeSize = AlignCodeSize(codeSize);
- int allocOffset = _cacheAllocator.Allocate(codeSize);
-
- if (allocOffset < 0)
+ for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
{
- throw new OutOfMemoryException("JIT Cache exhausted.");
+ int allocOffset = _cacheAllocator.Allocate(codeSize);
+
+ if (allocOffset >= 0)
+ {
+ _jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
+ _activeRegionIndex = i;
+ return allocOffset;
+ }
}
- _jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
+ int exhaustedRegion = _activeRegionIndex;
+ ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
+ _jitRegions.Add(newRegion);
+ _activeRegionIndex = _jitRegions.Count - 1;
+
+ int newRegionNumber = _activeRegionIndex;
- return allocOffset;
+ Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
+
+ _cacheAllocator = new CacheMemoryAllocator(CacheSize);
+
+ int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
+ if (allocOffsetNew < 0)
+ {
+ throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
+ }
+
+ newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
+ return allocOffsetNew;
}
private static int AlignCodeSize(int codeSize)
diff --git a/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs b/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs
index 1bbf70182..65d297c28 100644
--- a/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs
+++ b/src/Ryujinx.Cpu/LightningJit/Cache/NoWxCache.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
{
private const int CodeAlignment = 4; // Bytes.
private const int SharedCacheSize = 2047 * 1024 * 1024;
- private const int LocalCacheSize = 128 * 1024 * 1024;
+ private const int LocalCacheSize = 256 * 1024 * 1024;
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
// and allow the guest to take the fast path.
diff --git a/src/Ryujinx.Graphics.Vulkan/Vendor.cs b/src/Ryujinx.Graphics.Vulkan/Vendor.cs
index 87c6407cd..550ba0221 100644
--- a/src/Ryujinx.Graphics.Vulkan/Vendor.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Vendor.cs
@@ -1,5 +1,4 @@
using Silk.NET.Vulkan;
-using System.Text.RegularExpressions;
namespace Ryujinx.Graphics.Vulkan
{
diff --git a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
index b41bc60b1..47bfadc4c 100644
--- a/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
+++ b/src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
@@ -15,7 +15,6 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
-using System.Text.RegularExpressions;
namespace Ryujinx.HLE.HOS.Applets.Error
{
@@ -159,13 +158,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
string[] buttons = GetButtonsText(module, description, "DlgBtn");
- bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
+ (uint Module, uint Description) errorCodeTuple = (module, uint.Parse(description.ToString("0000")));
+
+ bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
if (showDetails)
{
message = GetMessageText(module, description, "FlvMsg");
buttons = GetButtonsText(module, description, "FlvBtn");
- _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
+ _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs
index 3db2712f3..018bb8f14 100644
--- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs
@@ -150,6 +150,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
{ BsdSocketOption.SoLinger, SocketOptionName.Linger },
{ BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline },
{ BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress },
+ { BsdSocketOption.SoNoSigpipe, SocketOptionName.DontLinger },
{ BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer },
{ BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer },
{ BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater },
diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
index 8ccb5cf89..3748eef39 100644
--- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs
+++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
@@ -45,10 +45,12 @@ namespace Ryujinx.HLE.UI
/// The value associated to the .
void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value);
+ ///
/// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
///
/// False when OK is pressed, True when another button (Details) is pressed.
- bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText);
+ // ReSharper disable once UnusedParameter.Global
+ bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null);
///
/// Creates a handler to process keyboard inputs into text strings.
diff --git a/src/Ryujinx.Horizon/HorizonStatic.cs b/src/Ryujinx.Horizon/HorizonStatic.cs
index 15689f0c8..a936a5a2d 100644
--- a/src/Ryujinx.Horizon/HorizonStatic.cs
+++ b/src/Ryujinx.Horizon/HorizonStatic.cs
@@ -1,5 +1,5 @@
-using MsgPack;
using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Prepo.Types;
using Ryujinx.Memory;
using System;
using System.Threading;
@@ -8,7 +8,7 @@ namespace Ryujinx.Horizon
{
public static class HorizonStatic
{
- internal static void HandlePlayReport(MessagePackObject report) =>
+ internal static void HandlePlayReport(PlayReport report) =>
new Thread(() => PlayReport?.Invoke(report))
{
Name = "HLE.PlayReportEvent",
@@ -16,7 +16,7 @@ namespace Ryujinx.Horizon
Priority = ThreadPriority.AboveNormal
}.Start();
- public static event Action PlayReport;
+ public static event Action PlayReport;
[field: ThreadStatic]
public static HorizonOptions Options { get; private set; }
diff --git a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
index 2f8657e0b..0ca851e6e 100644
--- a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
+++ b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs
@@ -1,4 +1,3 @@
-using Gommon;
using MsgPack;
using MsgPack.Serialization;
using Ryujinx.Common.Logging;
@@ -12,19 +11,12 @@ using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
using System.Text;
-using System.Threading;
using ApplicationId = Ryujinx.Horizon.Sdk.Ncm.ApplicationId;
namespace Ryujinx.Horizon.Prepo.Ipc
{
partial class PrepoService : IPrepoService
{
- enum PlayReportKind
- {
- Normal,
- System,
- }
-
private readonly ArpApi _arp;
private readonly PrepoServicePermissionLevel _permissionLevel;
private ulong _systemSessionId;
@@ -196,10 +188,17 @@ namespace Ryujinx.Horizon.Prepo.Ipc
{
return PrepoResult.InvalidBufferSize;
}
-
+
StringBuilder builder = new();
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
+ PlayReport playReport = new()
+ {
+ Kind = playReportKind,
+ Room = gameRoom,
+ ReportData = deserializedReport
+ };
+
builder.AppendLine();
builder.AppendLine("PlayReport log:");
builder.AppendLine($" Kind: {playReportKind}");
@@ -209,10 +208,12 @@ namespace Ryujinx.Horizon.Prepo.Ipc
if (pid != 0)
{
builder.AppendLine($" Pid: {pid}");
+ playReport.Pid = pid;
}
else
{
builder.AppendLine($" ApplicationId: {applicationId}");
+ playReport.AppId = applicationId;
}
Result result = _arp.GetApplicationInstanceId(out ulong applicationInstanceId, pid);
@@ -223,17 +224,20 @@ namespace Ryujinx.Horizon.Prepo.Ipc
_arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, applicationInstanceId).AbortOnFailure();
+ playReport.Version = applicationLaunchProperty.Version;
+
builder.AppendLine($" ApplicationVersion: {applicationLaunchProperty.Version}");
if (!userId.IsNull)
{
builder.AppendLine($" UserId: {userId}");
+ playReport.UserId = userId;
}
builder.AppendLine($" Room: {gameRoom}");
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
- HorizonStatic.HandlePlayReport(deserializedReport);
+ HorizonStatic.HandlePlayReport(playReport);
Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());
diff --git a/src/Ryujinx.Horizon/Prepo/Types/PlayReport.cs b/src/Ryujinx.Horizon/Prepo/Types/PlayReport.cs
new file mode 100644
index 000000000..e896219d5
--- /dev/null
+++ b/src/Ryujinx.Horizon/Prepo/Types/PlayReport.cs
@@ -0,0 +1,24 @@
+using MsgPack;
+using Ryujinx.Horizon.Sdk.Account;
+using Ryujinx.Horizon.Sdk.Ncm;
+
+namespace Ryujinx.Horizon.Prepo.Types
+{
+ public struct PlayReport
+ {
+ public PlayReportKind Kind { get; init; }
+ public string Room { get; init; }
+ public MessagePackObject ReportData { get; init; }
+
+ public ApplicationId? AppId;
+ public ulong? Pid;
+ public uint Version;
+ public Uid? UserId;
+ }
+
+ public enum PlayReportKind
+ {
+ Normal,
+ System,
+ }
+}
diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs
index 4a54b7ead..21219d91b 100644
--- a/src/Ryujinx.Input/HLE/NpadManager.cs
+++ b/src/Ryujinx.Input/HLE/NpadManager.cs
@@ -185,6 +185,15 @@ namespace Ryujinx.Input.HLE
}
}
+ public bool InputUpdatesBlocked
+ {
+ get
+ {
+ lock (_lock)
+ return _blockInputUpdates;
+ }
+ }
+
public void BlockInputUpdates()
{
lock (_lock)
diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs
index 25f451858..b26921e6a 100644
--- a/src/Ryujinx/AppHost.cs
+++ b/src/Ryujinx/AppHost.cs
@@ -517,7 +517,7 @@ namespace Ryujinx.Ava
Device?.System.ChangeDockedModeState(e.NewValue);
}
- private void UpdateAudioVolumeState(object sender, ReactiveEventArgs e)
+ public void UpdateAudioVolumeState(object sender, ReactiveEventArgs e)
{
Device?.SetVolume(e.NewValue);
@@ -1041,6 +1041,7 @@ namespace Ryujinx.Ava
if (_viewModel.StartGamesInFullscreen)
{
_viewModel.WindowState = WindowState.FullScreen;
+ _viewModel.Window.TitleBar.ExtendsContentIntoTitleBar = true;
}
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUI)
diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json
index a10196711..0fcb804ca 100644
--- a/src/Ryujinx/Assets/locales.json
+++ b/src/Ryujinx/Assets/locales.json
@@ -1543,7 +1543,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "由 {0} 开发",
"zh_TW": ""
}
},
@@ -1576,50 +1576,50 @@
"ID": "GameListHeaderTimePlayed",
"Translations": {
"ar_SA": "",
- "de_DE": "Spielzeit: {0}",
- "el_GR": "Χρόνος: {0}",
- "en_US": "Play Time: {0}",
- "es_ES": "Tiempo jugado: {0}",
- "fr_FR": "Temps de jeu: {0}",
+ "de_DE": "Spielzeit:",
+ "el_GR": "Χρόνος:",
+ "en_US": "Play Time:",
+ "es_ES": "Tiempo jugado:",
+ "fr_FR": "Temps de jeu:",
"he_IL": "",
- "it_IT": "Tempo di gioco: {0}",
- "ja_JP": "プレイ時間: {0}",
- "ko_KR": "플레이 타임: {0}",
- "no_NO": "Spilletid: {0}",
- "pl_PL": "Czas w grze: {0}",
- "pt_BR": "Tempo de jogo: {0}",
- "ru_RU": "Время в игре: {0}",
- "sv_SE": "Speltid: {0}",
- "th_TH": "เล่นไปแล้ว: {0}",
- "tr_TR": "Oynama Süresi: {0}",
- "uk_UA": "Зіграно часу: {0}",
- "zh_CN": "游玩时长: {0}",
- "zh_TW": "遊玩時數: {0}"
+ "it_IT": "Tempo di gioco:",
+ "ja_JP": "プレイ時間:",
+ "ko_KR": "플레이 타임:",
+ "no_NO": "Spilletid:",
+ "pl_PL": "Czas w grze:",
+ "pt_BR": "Tempo de jogo:",
+ "ru_RU": "Время в игре:",
+ "sv_SE": "Speltid:",
+ "th_TH": "เล่นไปแล้ว:",
+ "tr_TR": "Oynama Süresi:",
+ "uk_UA": "Зіграно часу:",
+ "zh_CN": "游玩时长:",
+ "zh_TW": "遊玩時數:"
}
},
{
"ID": "GameListHeaderLastPlayed",
"Translations": {
"ar_SA": "",
- "de_DE": "Zuletzt gespielt: {0}",
- "el_GR": "Παίχτηκε: {0}",
- "en_US": "Last Played: {0}",
- "es_ES": "Jugado por última vez: {0}",
- "fr_FR": "Dernière partie jouée: {0}",
+ "de_DE": "Zuletzt gespielt: ",
+ "el_GR": "Παίχτηκε: ",
+ "en_US": "Last Played:",
+ "es_ES": "Jugado por última vez:",
+ "fr_FR": "Dernière partie jouée:",
"he_IL": "",
- "it_IT": "Ultima partita: {0}",
- "ja_JP": "最終プレイ日時: {0}",
- "ko_KR": "마지막 플레이: {0}",
- "no_NO": "Sist Spilt: {0}",
- "pl_PL": "Ostatnio grane: {0}",
- "pt_BR": "Último jogo: {0}",
- "ru_RU": "Последний запуск: {0}",
- "sv_SE": "Senast spelad: {0}",
- "th_TH": "เล่นล่าสุด: {0}",
- "tr_TR": "Son Oynama Tarihi: {0}",
- "uk_UA": "Востаннє зіграно: {0}",
- "zh_CN": "最近游玩: {0}",
- "zh_TW": "最近遊玩: {0}"
+ "it_IT": "Ultima partita:",
+ "ja_JP": "最終プレイ日時:",
+ "ko_KR": "마지막 플레이:",
+ "no_NO": "Sist Spilt:",
+ "pl_PL": "Ostatnio grane:",
+ "pt_BR": "Último jogo:",
+ "ru_RU": "Последний запуск:",
+ "sv_SE": "Senast spelad:",
+ "th_TH": "เล่นล่าสุด:",
+ "tr_TR": "Son Oynama Tarihi:",
+ "uk_UA": "Востаннє зіграно:",
+ "zh_CN": "最近游玩:",
+ "zh_TW": "最近遊玩:"
}
},
{
@@ -1843,7 +1843,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "兼容性:",
"zh_TW": ""
}
},
@@ -1868,7 +1868,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "标题 ID:",
"zh_TW": ""
}
},
@@ -1893,7 +1893,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "服务的游戏: {0}",
"zh_TW": ""
}
},
@@ -1918,7 +1918,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "在线玩家: {0}",
"zh_TW": ""
}
},
@@ -2268,7 +2268,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "清理 PPTC 缓存",
"zh_TW": ""
}
},
@@ -2293,7 +2293,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "删除应用程序的所有 PPTC 缓存",
"zh_TW": ""
}
},
@@ -2768,7 +2768,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "显示兼容性项目",
"zh_TW": ""
}
},
@@ -2793,7 +2793,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "在兼容性列表中显示选定的游戏,您通常可以通过帮助菜单访问。",
"zh_TW": ""
}
},
@@ -2818,7 +2818,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "显示游戏信息",
"zh_TW": ""
}
},
@@ -2843,7 +2843,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "显示当前选定游戏的状态与详细信息。",
"zh_TW": ""
}
},
@@ -3350,26 +3350,251 @@
{
"ID": "SettingsTabGeneralCheckUpdatesOnLaunch",
"Translations": {
- "ar_SA": "التحقق من وجود تحديثات عند التشغيل",
- "de_DE": "Beim Start nach Updates suchen",
- "el_GR": "Έλεγχος για Ενημερώσεις στην Εκκίνηση",
- "en_US": "Check for Updates on Launch",
- "es_ES": "Buscar actualizaciones al iniciar",
- "fr_FR": "Vérifier les mises à jour au démarrage",
- "he_IL": "בדוק אם קיימים עדכונים בהפעלה",
- "it_IT": "Controlla aggiornamenti all'avvio",
- "ja_JP": "起動時にアップデートを確認する",
- "ko_KR": "시작 시, 업데이트 확인",
- "no_NO": "Se etter oppdateringer ved oppstart",
- "pl_PL": "Sprawdzaj aktualizacje przy uruchomieniu",
- "pt_BR": "Verificar se há atualizações ao iniciar",
- "ru_RU": "Проверять наличие обновлений при запуске",
- "sv_SE": "Leta efter uppdatering vid uppstart",
- "th_TH": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม",
- "tr_TR": "Her Açılışta Güncellemeleri Denetle",
- "uk_UA": "Перевіряти наявність оновлень під час запуску",
- "zh_CN": "启动时检查更新",
- "zh_TW": "啟動時檢查更新"
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Check for Updates:",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralCheckUpdatesOnLaunchOff",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Off",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralCheckUpdatesOnLaunchPromptAtStartup",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Prompt",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralCheckUpdatesOnLaunchBackground",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Background",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralFocusLossType",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "On Emulator Focus Lost:",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralFocusLossTypeDoNothing",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Do Nothing",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralFocusLossTypeBlockInput",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Block Input",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralFocusLossTypeMuteAudio",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Mute Volume",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralFocusLossTypeBlockInputAndMuteAudio",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Block Input & Mute Volume",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "SettingsTabGeneralFocusLossTypePauseEmulation",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Pause Emulation",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
}
},
{
@@ -3422,6 +3647,31 @@
"zh_TW": "記住視窗大小/位置"
}
},
+ {
+ "ID": "SettingsTabGeneralDisableInputWhenOutOfFocus",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Disable Input when Out of Focus",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "Deaktiver inndata når vinduet er ute av fokus",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
{
"ID": "SettingsTabGeneralShowTitleBar",
"Translations": {
@@ -4493,7 +4743,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "与系统时间同步",
"zh_TW": ""
}
},
@@ -5747,6 +5997,31 @@
"zh_TW": "啟用客體日誌"
}
},
+ {
+ "ID": "SettingsTabLoggingEnableAvaloniaLogs",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Enable UI Logs",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
{
"ID": "SettingsTabLoggingEnableFsAccessLogs",
"Translations": {
@@ -6143,7 +6418,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "重置设置",
"zh_TW": ""
}
},
@@ -6168,7 +6443,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "我要重置我的设置。",
"zh_TW": ""
}
},
@@ -8143,7 +8418,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "彩虹滚动速度",
"zh_TW": ""
}
},
@@ -13418,7 +13693,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "您正要清理 PPTC 数据:\n\n{0}\n\n您确实要继续吗?",
"zh_TW": ""
}
},
@@ -16722,6 +16997,31 @@
"zh_TW": "謹慎使用"
}
},
+ {
+ "ID": "AvaloniaLogTooltip",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Prints Avalonia (UI) log messages in the console.",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
{
"ID": "OpenGlLogLevel",
"Translations": {
@@ -17622,6 +17922,31 @@
"zh_TW": "更新已停用!"
}
},
+ {
+ "ID": "UpdaterBackgroundStatusBarButtonText",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Update Available!",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
{
"ID": "ControllerSettingsRotate90",
"Translations": {
@@ -23568,7 +23893,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "启动和游戏时不会出现任何崩溃或任何类型的 GPU bug 且速度足够快可以在一般 PC 上尽情游玩。",
"zh_TW": ""
}
},
@@ -23593,7 +23918,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "可以成功启动并进入游戏但可能会遇到以下一种或多种问题: 崩溃、卡死、GPU bug、令人无法接受的音频,或者只是太慢。仍然可以继续进行游戏,但是可能无法达到预期。",
"zh_TW": ""
}
},
@@ -23618,7 +23943,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "可以启动并通过标题画面但是无法进入到主要的游戏流程。",
"zh_TW": ""
}
},
@@ -23643,7 +23968,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "可以启动但是无法通过标题画面。",
"zh_TW": ""
}
},
@@ -23668,7 +23993,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
- "zh_CN": "",
+ "zh_CN": "无法启动或显示无任何动静。",
"zh_TW": ""
}
},
@@ -23696,6 +24021,56 @@
"zh_CN": "选择一个要解压的 DLC",
"zh_TW": ""
}
+ },
+ {
+ "ID": "GameInfoRpcImage",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Rich Presence Image",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "Rich Presence 图像",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "GameInfoRpcDynamic",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Dynamic Rich Presence",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "动态 Rich Presence",
+ "zh_TW": ""
+ }
}
]
}
diff --git a/src/Ryujinx/Common/ThemeManager.cs b/src/Ryujinx/Common/ThemeManager.cs
deleted file mode 100644
index 6da01bfa7..000000000
--- a/src/Ryujinx/Common/ThemeManager.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-
-namespace Ryujinx.Ava.Common
-{
- public static class ThemeManager
- {
- public static event Action ThemeChanged;
-
- public static void OnThemeChanged()
- {
- ThemeChanged?.Invoke();
- }
- }
-}
diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs
index d95bb80dd..47fc8ad69 100644
--- a/src/Ryujinx/DiscordIntegrationModule.cs
+++ b/src/Ryujinx/DiscordIntegrationModule.cs
@@ -1,6 +1,5 @@
using DiscordRPC;
using Gommon;
-using MsgPack;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration;
@@ -10,6 +9,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.Horizon;
+using Ryujinx.Horizon.Prepo.Types;
using System.Text;
namespace Ryujinx.Ava
@@ -37,6 +37,9 @@ namespace Ryujinx.Ava
private static RichPresence _discordPresencePlaying;
private static ApplicationMetadata _currentApp;
+ public static bool HasAssetImage(string titleId) => TitleIDs.DiscordGameAssetKeys.ContainsIgnoreCase(titleId);
+ public static bool HasAnalyzer(string titleId) => PlayReports.Analyzer.TitleIds.ContainsIgnoreCase(titleId);
+
public static void Initialize()
{
_discordPresenceMain = new RichPresence
@@ -120,20 +123,22 @@ namespace Ryujinx.Ava
_currentApp = null;
}
- private static void HandlePlayReport(MessagePackObject playReport)
+ private static void HandlePlayReport(PlayReport playReport)
{
if (_discordClient is null) return;
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
if (_discordPresencePlaying is null) return;
- Analyzer.FormattedValue formattedValue =
+ FormattedValue formattedValue =
PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
if (!formattedValue.Handled) return;
- _discordPresencePlaying.Details = formattedValue.Reset
- ? $"Playing {_currentApp.Title}"
- : formattedValue.FormattedString;
+ _discordPresencePlaying.Details = TruncateToByteLength(
+ formattedValue.Reset
+ ? $"Playing {_currentApp.Title}"
+ : formattedValue.FormattedString
+ );
if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details))
return; //don't trigger an update if the set presence Details are identical to current
diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs
index 1b10145fe..a57863d5d 100644
--- a/src/Ryujinx/Headless/Options.cs
+++ b/src/Ryujinx/Headless/Options.cs
@@ -387,7 +387,7 @@ namespace Ryujinx.Headless
[Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")]
public string GraphicsShadersDumpPath { get; set; }
- [Option("graphics-backend", Required = false, Default = GraphicsBackend.OpenGl, HelpText = "Change Graphics Backend to use.")]
+ [Option("graphics-backend", Required = false, Default = GraphicsBackend.Vulkan, HelpText = "Change Graphics Backend to use.")]
public GraphicsBackend GraphicsBackend { get; set; }
[Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")]
diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs
index c9d672af4..081998a00 100644
--- a/src/Ryujinx/Headless/Windows/WindowBase.cs
+++ b/src/Ryujinx/Headless/Windows/WindowBase.cs
@@ -513,7 +513,7 @@ namespace Ryujinx.Headless
Exit();
}
- public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
+ public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null)
{
SDL_MessageBoxData data = new()
{
@@ -521,7 +521,7 @@ namespace Ryujinx.Headless
message = message,
buttons = new SDL_MessageBoxButtonData[buttonsText.Length],
numbuttons = buttonsText.Length,
- window = WindowHandle,
+ window = WindowHandle
};
for (int i = 0; i < buttonsText.Length; i++)
diff --git a/src/Ryujinx/RyujinxApp.axaml.cs b/src/Ryujinx/RyujinxApp.axaml.cs
index 32318776a..90552cd16 100644
--- a/src/Ryujinx/RyujinxApp.axaml.cs
+++ b/src/Ryujinx/RyujinxApp.axaml.cs
@@ -22,6 +22,8 @@ namespace Ryujinx.Ava
{
public class RyujinxApp : Application
{
+ public static event Action ThemeChanged;
+
internal static string FormatTitle(LocaleKeys? windowTitleKey = null, bool includeVersion = true)
=> windowTitleKey is null
? $"{FullAppName}{(includeVersion ? $" {Program.Version}" : string.Empty)}"
@@ -112,7 +114,7 @@ namespace Ryujinx.Ava
baseStyle = ConfigurationState.Instance.UI.BaseStyle;
}
- ThemeManager.OnThemeChanged();
+ ThemeChanged?.Invoke();
RequestedThemeVariant = baseStyle switch
{
diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
index c75e532ec..c03c4c45f 100644
--- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
+++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
@@ -75,31 +75,32 @@ namespace Ryujinx.Ava.UI.Applet
bool opened = false;
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
- title,
- message,
- string.Empty,
- LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
- string.Empty,
- LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
- (int)Symbol.Important,
- deferEvent,
- async window =>
- {
- if (opened)
- {
- return;
- }
+ title,
+ message,
+ string.Empty,
+ LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
+ string.Empty,
+ LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
+ (int)Symbol.Important,
+ deferEvent,
+ async window =>
+ {
+ if (opened)
+ {
+ return;
+ }
- opened = true;
+ opened = true;
- _parent.SettingsWindow = new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
+ _parent.SettingsWindow =
+ new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
- await _parent.SettingsWindow.ShowDialog(window);
+ await _parent.SettingsWindow.ShowDialog(window);
- _parent.SettingsWindow = null;
+ _parent.SettingsWindow = null;
- opened = false;
- });
+ opened = false;
+ });
if (response == UserResult.Ok)
{
@@ -110,7 +111,9 @@ namespace Ryujinx.Ava.UI.Applet
}
catch (Exception ex)
{
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
+ await ContentDialogHelper.CreateErrorDialog(
+ LocaleManager.Instance.UpdateAndGetDynamicValue(
+ LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
dialogCloseEvent.Set();
}
@@ -134,7 +137,9 @@ namespace Ryujinx.Ava.UI.Applet
try
{
_parent.ViewModel.AppHost.NpadManager.BlockInputUpdates();
- (UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
+ (UserResult result, string userInput) =
+ await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard],
+ args);
if (result == UserResult.Ok)
{
@@ -146,7 +151,9 @@ namespace Ryujinx.Ava.UI.Applet
{
error = true;
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
+ await ContentDialogHelper.CreateErrorDialog(
+ LocaleManager.Instance.UpdateAndGetDynamicValue(
+ LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
}
finally
{
@@ -177,7 +184,8 @@ namespace Ryujinx.Ava.UI.Applet
args.InitialText = "Ryujinx";
args.StringLengthMin = 1;
args.StringLengthMax = 25;
- (UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
+ (UserResult result, string userInput) =
+ await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
if (result == UserResult.Ok)
{
inputText = userInput;
@@ -201,11 +209,13 @@ namespace Ryujinx.Ava.UI.Applet
Dispatcher.UIThread.InvokeAsync(async () =>
{
dialogCloseEvent.Set();
- await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
- string.Empty,
- LocaleManager.Instance[LocaleKeys.InputDialogOk],
- string.Empty,
- LocaleManager.Instance[LocaleKeys.CabinetTitle]);
+ await ContentDialogHelper.CreateInfoDialog(
+ LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
+ string.Empty,
+ LocaleManager.Instance[LocaleKeys.InputDialogOk],
+ string.Empty,
+ LocaleManager.Instance[LocaleKeys.CabinetTitle]
+ );
});
dialogCloseEvent.WaitOne();
}
@@ -217,7 +227,8 @@ namespace Ryujinx.Ava.UI.Applet
_parent.ViewModel.AppHost?.Stop();
}
- public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
+ public bool DisplayErrorAppletDialog(string title, string message, string[] buttons,
+ (uint Module, uint Description)? errorCode = null)
{
ManualResetEvent dialogCloseEvent = new(false);
@@ -229,9 +240,7 @@ namespace Ryujinx.Ava.UI.Applet
{
ErrorAppletWindow msgDialog = new(_parent, buttons, message)
{
- Title = title,
- WindowStartupLocation = WindowStartupLocation.CenterScreen,
- Width = 400
+ Title = title, WindowStartupLocation = WindowStartupLocation.CenterScreen, Width = 400
};
object response = await msgDialog.Run();
@@ -249,7 +258,9 @@ namespace Ryujinx.Ava.UI.Applet
{
dialogCloseEvent.Set();
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
+ await ContentDialogHelper.CreateErrorDialog(
+ LocaleManager.Instance.UpdateAndGetDynamicValue(
+ LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
}
});
@@ -259,38 +270,36 @@ namespace Ryujinx.Ava.UI.Applet
}
public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent);
-
+
public UserProfile ShowPlayerSelectDialog()
{
UserId selected = UserId.Null;
byte[] defaultGuestImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg");
UserProfile guest = new(new UserId("00000000000000000000000000000080"), "Guest", defaultGuestImage);
-
+
ManualResetEvent dialogCloseEvent = new(false);
-
+
Dispatcher.UIThread.InvokeAsync(async () =>
{
ObservableCollection profiles = [];
NavigationDialogHost nav = new();
-
+
_parent.AccountManager.GetAllUsers()
.OrderBy(x => x.Name)
.ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
-
+
profiles.Add(new Models.UserProfile(guest, nav));
- UserSelectorDialogViewModel viewModel = new()
+ ProfileSelectorDialogViewModel viewModel = new()
{
- Profiles = profiles,
- SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
+ Profiles = profiles, SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
};
- UserSelectorDialog content = new(viewModel);
- (selected, _) = await UserSelectorDialog.ShowInputDialog(content);
-
+ (selected, _) = await ProfileSelectorDialog.ShowInputDialog(viewModel);
+
dialogCloseEvent.Set();
});
-
+
dialogCloseEvent.WaitOne();
-
+
UserProfile profile = _parent.AccountManager.LastOpenedUser;
if (selected == guest.UserId)
{
@@ -311,6 +320,7 @@ namespace Ryujinx.Ava.UI.Applet
}
}
}
+
return profile;
}
}
diff --git a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml
similarity index 96%
rename from src/Ryujinx/UI/Applet/UserSelectorDialog.axaml
rename to src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml
index 2816afbce..d929cc501 100644
--- a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml
+++ b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml
@@ -1,5 +1,5 @@
+ x:DataType="viewModels:ProfileSelectorDialogViewModel">
-
+
diff --git a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs
similarity index 85%
rename from src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs
rename to src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs
index 95081913e..b2c396b69 100644
--- a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml.cs
+++ b/src/Ryujinx/UI/Applet/ProfileSelectorDialog.axaml.cs
@@ -16,15 +16,15 @@ using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
namespace Ryujinx.Ava.UI.Applet
{
- public partial class UserSelectorDialog : UserControl, INotifyPropertyChanged
+ public partial class ProfileSelectorDialog : UserControl
{
- public UserSelectorDialogViewModel ViewModel { get; set; }
+ public ProfileSelectorDialogViewModel ViewModel { get; set; }
- public UserSelectorDialog(UserSelectorDialogViewModel viewModel)
+ public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
{
+ DataContext = ViewModel = viewModel;
+
InitializeComponent();
- ViewModel = viewModel;
- DataContext = ViewModel;
}
private void Grid_PointerEntered(object sender, PointerEventArgs e)
@@ -54,7 +54,7 @@ namespace Ryujinx.Ava.UI.Applet
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
{
ViewModel.SelectedUserId = userProfile.UserId;
- Logger.Info?.Print(LogClass.UI, $"Selected user: {userProfile.UserId}");
+ Logger.Info?.Print(LogClass.UI, $"Selected: {userProfile.UserId}", "ProfileSelector");
ObservableCollection newProfiles = [];
@@ -79,7 +79,7 @@ namespace Ryujinx.Ava.UI.Applet
}
}
- public static async Task<(UserId Id, bool Result)> ShowInputDialog(UserSelectorDialog content)
+ public static async Task<(UserId Id, bool Result)> ShowInputDialog(ProfileSelectorDialogViewModel viewModel)
{
ContentDialog contentDialog = new()
{
@@ -87,22 +87,25 @@ namespace Ryujinx.Ava.UI.Applet
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
- Content = content,
+ Content = new ProfileSelectorDialog(viewModel),
Padding = new Thickness(0)
};
UserId result = UserId.Null;
bool input = false;
+
+ contentDialog.Closed += Handler;
+ await ContentDialogHelper.ShowAsync(contentDialog);
+
+ return (result, input);
+
void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs)
{
if (eventArgs.Result == ContentDialogResult.Primary)
{
- if (contentDialog.Content is UserSelectorDialog view)
- {
- result = view.ViewModel.SelectedUserId;
- input = true;
- }
+ result = viewModel.SelectedUserId;
+ input = true;
}
else
{
@@ -110,12 +113,6 @@ namespace Ryujinx.Ava.UI.Applet
input = false;
}
}
-
- contentDialog.Closed += Handler;
-
- await ContentDialogHelper.ShowAsync(contentDialog);
-
- return (result, input);
}
}
}
diff --git a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml
index 45ae75639..aee8f7b36 100644
--- a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml
+++ b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml
@@ -4,6 +4,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:ext="using:Ryujinx.Ava.Common.Markup"
xmlns:viewModels="using:Ryujinx.Ava.UI.ViewModels"
+ xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Controls.ApplicationDataView"
@@ -54,9 +55,21 @@
Tag="{Binding AppData.IdString}"
Text="{Binding AppData.LocalizedStatus}"
Foreground="{Binding AppData.PlayabilityStatus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
- ToolTip.Tip="{Binding AppData.LocalizedStatusTooltip}"
TextAlignment="Start"
- TextWrapping="Wrap" />
+ TextWrapping="Wrap">
+
+
+
+
+
+
+
+