Compare commits
5 Commits
Canary-1.2
...
c40ba4e2ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c40ba4e2ae | ||
|
|
7294589272 | ||
|
|
ae69b1e495 | ||
|
|
ab07537ed5 | ||
|
|
61af464b64 |
@@ -7,7 +7,6 @@ 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;
|
||||
|
||||
@@ -22,7 +21,6 @@ namespace ARMeilleure.Memory
|
||||
granularity = DefaultGranularity;
|
||||
}
|
||||
|
||||
Allocator = allocator;
|
||||
Block = allocator.Reserve(maxSize);
|
||||
_maxSize = maxSize;
|
||||
_sizeGranularity = granularity;
|
||||
|
||||
@@ -2,8 +2,6 @@ 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;
|
||||
@@ -20,8 +18,9 @@ namespace ARMeilleure.Translation.Cache
|
||||
private static readonly int _pageMask = _pageSize - 1;
|
||||
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int CacheSize = 256 * 1024 * 1024;
|
||||
private const int CacheSize = 2047 * 1024 * 1024;
|
||||
|
||||
private static ReservedRegion _jitRegion;
|
||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||
|
||||
private static CacheMemoryAllocator _cacheAllocator;
|
||||
@@ -31,9 +30,6 @@ namespace ARMeilleure.Translation.Cache
|
||||
private static readonly Lock _lock = new();
|
||||
private static bool _initialized;
|
||||
|
||||
private static readonly List<ReservedRegion> _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);
|
||||
@@ -52,9 +48,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
return;
|
||||
}
|
||||
|
||||
ReservedRegion firstRegion = new(allocator, CacheSize);
|
||||
_jitRegions.Add(firstRegion);
|
||||
_activeRegionIndex = 0;
|
||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
@@ -65,9 +59,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
JitUnwindWindows.InstallFunctionTableHandler(
|
||||
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
|
||||
);
|
||||
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
@@ -83,8 +75,8 @@ namespace ARMeilleure.Translation.Cache
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = Allocate(code.Length);
|
||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||
nint funcPtr = targetRegion.Pointer + funcOffset;
|
||||
|
||||
nint funcPtr = _jitRegion.Pointer + funcOffset;
|
||||
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@@ -98,9 +90,9 @@ namespace ARMeilleure.Translation.Cache
|
||||
}
|
||||
else
|
||||
{
|
||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsWritable(funcOffset, code.Length);
|
||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsExecutable(funcOffset, code.Length);
|
||||
|
||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@@ -124,83 +116,52 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
foreach (ReservedRegion region in _jitRegions)
|
||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
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;
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||
private static void ReprotectAsWritable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||
private static void ReprotectAsExecutable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static int Allocate(int codeSize)
|
||||
{
|
||||
codeSize = AlignCodeSize(codeSize);
|
||||
|
||||
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset < 0)
|
||||
{
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset >= 0)
|
||||
{
|
||||
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
_activeRegionIndex = i;
|
||||
return allocOffset;
|
||||
}
|
||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
||||
}
|
||||
|
||||
int exhaustedRegion = _activeRegionIndex;
|
||||
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
|
||||
_jitRegions.Add(newRegion);
|
||||
_activeRegionIndex = _jitRegions.Count - 1;
|
||||
|
||||
int newRegionNumber = _activeRegionIndex;
|
||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
|
||||
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;
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
|
||||
private static int AlignCodeSize(int codeSize)
|
||||
{
|
||||
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
|
||||
@@ -224,21 +185,18 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (ReservedRegion _ in _jitRegions)
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
index = ~index - 1;
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index - 1;
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
entry = _cacheEntries[index];
|
||||
entryIndex = index;
|
||||
return true;
|
||||
}
|
||||
if (index >= 0)
|
||||
{
|
||||
entry = _cacheEntries[index];
|
||||
entryIndex = index;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,15 +144,17 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
public List<ulong> GetBlacklistedFunctions()
|
||||
{
|
||||
List<ulong> funcs = [];
|
||||
List<ulong> funcs = new List<ulong>();
|
||||
|
||||
foreach ((ulong ptr, FuncProfile funcProfile) in ProfiledFuncs)
|
||||
foreach (var profiledFunc in ProfiledFuncs)
|
||||
{
|
||||
if (!funcProfile.Blacklist)
|
||||
continue;
|
||||
|
||||
if (!funcs.Contains(ptr))
|
||||
funcs.Add(ptr);
|
||||
if (profiledFunc.Value.Blacklist)
|
||||
{
|
||||
if (!funcs.Contains(profiledFunc.Key))
|
||||
{
|
||||
funcs.Add(profiledFunc.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return funcs;
|
||||
|
||||
@@ -164,16 +164,15 @@ 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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Humanizer;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -17,8 +15,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
private static readonly int _pageMask = _pageSize - 1;
|
||||
|
||||
private const int CodeAlignment = 4; // Bytes.
|
||||
private const int CacheSize = 256 * 1024 * 1024;
|
||||
private const int CacheSize = 2047 * 1024 * 1024;
|
||||
|
||||
private static ReservedRegion _jitRegion;
|
||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||
|
||||
private static CacheMemoryAllocator _cacheAllocator;
|
||||
@@ -27,8 +26,6 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
|
||||
private static readonly Lock _lock = new();
|
||||
private static bool _initialized;
|
||||
private static readonly List<ReservedRegion> _jitRegions = new();
|
||||
private static int _activeRegionIndex = 0;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
@@ -48,9 +45,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
return;
|
||||
}
|
||||
|
||||
ReservedRegion firstRegion = new(allocator, CacheSize);
|
||||
_jitRegions.Add(firstRegion);
|
||||
_activeRegionIndex = 0;
|
||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
@@ -70,8 +65,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int funcOffset = Allocate(code.Length);
|
||||
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||
nint funcPtr = targetRegion.Pointer + funcOffset;
|
||||
|
||||
nint funcPtr = _jitRegion.Pointer + funcOffset;
|
||||
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
@@ -85,11 +80,18 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
}
|
||||
else
|
||||
{
|
||||
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||
Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
|
||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||
ReprotectAsWritable(funcOffset, code.Length);
|
||||
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
||||
ReprotectAsExecutable(funcOffset, code.Length);
|
||||
|
||||
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
|
||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
|
||||
}
|
||||
}
|
||||
|
||||
Add(funcOffset, code.Length);
|
||||
@@ -104,80 +106,50 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
foreach (ReservedRegion region in _jitRegions)
|
||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
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;
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||
private static void ReprotectAsWritable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||
private static void ReprotectAsExecutable(int offset, int size)
|
||||
{
|
||||
int endOffs = offset + size;
|
||||
|
||||
int regionStart = offset & ~_pageMask;
|
||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||
|
||||
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||
}
|
||||
|
||||
private static int Allocate(int codeSize)
|
||||
{
|
||||
codeSize = AlignCodeSize(codeSize);
|
||||
|
||||
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset < 0)
|
||||
{
|
||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||
|
||||
if (allocOffset >= 0)
|
||||
{
|
||||
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
_activeRegionIndex = i;
|
||||
return allocOffset;
|
||||
}
|
||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
||||
}
|
||||
|
||||
int exhaustedRegion = _activeRegionIndex;
|
||||
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
|
||||
_jitRegions.Add(newRegion);
|
||||
_activeRegionIndex = _jitRegions.Count - 1;
|
||||
|
||||
int newRegionNumber = _activeRegionIndex;
|
||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||
|
||||
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;
|
||||
return allocOffset;
|
||||
}
|
||||
|
||||
private static int AlignCodeSize(int codeSize)
|
||||
|
||||
@@ -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 = 256 * 1024 * 1024;
|
||||
private const int LocalCacheSize = 128 * 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.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets.Error
|
||||
{
|
||||
|
||||
@@ -150,7 +150,6 @@ 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 },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using MsgPack;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Horizon.Prepo.Types;
|
||||
using Ryujinx.Memory;
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"ID": "MenuBarFileOpenAppletOpenMiiApplet",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Mii-Bearbeitungsapplet",
|
||||
"el_GR": "",
|
||||
"en_US": "Mii Edit Applet",
|
||||
"es_ES": "Applet Editor Mii",
|
||||
@@ -176,7 +176,7 @@
|
||||
"ID": "SettingsTabSystemMemoryManagerModeSoftware",
|
||||
"Translations": {
|
||||
"ar_SA": "البرنامج",
|
||||
"de_DE": "",
|
||||
"de_DE": "Programme",
|
||||
"el_GR": "Λογισμικό",
|
||||
"en_US": "Software",
|
||||
"es_ES": "",
|
||||
@@ -326,7 +326,7 @@
|
||||
"ID": "MenuBarFileOpenFromFileError",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Keine Anwendungen im ausgewählten Datei gefunden.",
|
||||
"el_GR": "",
|
||||
"en_US": "No applications found in selected file.",
|
||||
"es_ES": "No se encontraron aplicaciones en el archivo seleccionado.",
|
||||
@@ -376,7 +376,7 @@
|
||||
"ID": "MenuBarFileLoadDlcFromFolder",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "DLC aus Ordner laden",
|
||||
"el_GR": "",
|
||||
"en_US": "Load DLC From Folder",
|
||||
"es_ES": "Cargar DLC Desde Carpeta",
|
||||
@@ -401,7 +401,7 @@
|
||||
"ID": "MenuBarFileLoadTitleUpdatesFromFolder",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Titel-Updates aus Ordner laden",
|
||||
"el_GR": "",
|
||||
"en_US": "Load Title Updates From Folder",
|
||||
"es_ES": "Cargar Actualizaciones de Títulos Desde Carpeta",
|
||||
@@ -576,7 +576,7 @@
|
||||
"ID": "MenuBarOptionsStartGamesWithoutUI",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Spiele ohne Benutzeroberfläche starten",
|
||||
"el_GR": "",
|
||||
"en_US": "Start Games with UI Hidden",
|
||||
"es_ES": "",
|
||||
@@ -751,7 +751,7 @@
|
||||
"ID": "MenuBarActionsScanAmiiboBin",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Amiibo scannen (aus Bin-Datei)",
|
||||
"el_GR": "",
|
||||
"en_US": "Scan An Amiibo (From Bin)",
|
||||
"es_ES": "",
|
||||
@@ -851,7 +851,7 @@
|
||||
"ID": "MenuBarActionsInstallKeys",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Schlüssel installieren",
|
||||
"el_GR": "",
|
||||
"en_US": "Install Keys",
|
||||
"es_ES": "",
|
||||
@@ -876,7 +876,7 @@
|
||||
"ID": "MenuBarFileActionsInstallKeysFromFile",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Schlüssel aus KEYS oder ZIP installieren",
|
||||
"el_GR": "",
|
||||
"en_US": "Install keys from KEYS or ZIP",
|
||||
"es_ES": "Instalar keys de KEYS o ZIP",
|
||||
@@ -901,7 +901,7 @@
|
||||
"ID": "MenuBarFileActionsInstallKeysFromFolder",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Schlüssel aus einem Verzeichnis installieren",
|
||||
"el_GR": "",
|
||||
"en_US": "Install keys from a directory",
|
||||
"es_ES": "Instalar keys de un directorio",
|
||||
@@ -1001,7 +1001,7 @@
|
||||
"ID": "MenuBarActionsXCITrimmer",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "XCI-Dateien trimmen",
|
||||
"el_GR": "",
|
||||
"en_US": "Trim XCI Files",
|
||||
"es_ES": "Recortar archivos XCI",
|
||||
@@ -1226,7 +1226,7 @@
|
||||
"ID": "MenuBarHelpFaqAndGuides",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "FAQ & Anleitungen",
|
||||
"el_GR": "",
|
||||
"en_US": "FAQ & Guides",
|
||||
"es_ES": "",
|
||||
@@ -1251,7 +1251,7 @@
|
||||
"ID": "MenuBarHelpFaq",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "FAQ & Fehlerbehebung Seite",
|
||||
"el_GR": "",
|
||||
"en_US": "FAQ & Troubleshooting Page",
|
||||
"es_ES": "",
|
||||
@@ -1276,7 +1276,7 @@
|
||||
"ID": "MenuBarHelpFaqTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Öffnet die FAQ- und Fehlerbehebungsseite im offiziellen Ryujinx-Wiki",
|
||||
"el_GR": "",
|
||||
"en_US": "Opens the FAQ and Troubleshooting page on the official Ryujinx wiki",
|
||||
"es_ES": "",
|
||||
@@ -1301,7 +1301,7 @@
|
||||
"ID": "MenuBarHelpSetup",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Setup- und Konfigurationsanleitung",
|
||||
"el_GR": "",
|
||||
"en_US": "Setup & Configuration Guide",
|
||||
"es_ES": "",
|
||||
@@ -1326,7 +1326,7 @@
|
||||
"ID": "MenuBarHelpSetupTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Öffnet die Setup- und Konfigurationsanleitung im offiziellen Ryujinx-Wiki",
|
||||
"el_GR": "",
|
||||
"en_US": "Opens the Setup & Configuration guide on the official Ryujinx wiki",
|
||||
"es_ES": "",
|
||||
@@ -1351,7 +1351,7 @@
|
||||
"ID": "MenuBarHelpMultiplayer",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Multiplayer (LDN/LAN) Anleitung",
|
||||
"el_GR": "",
|
||||
"en_US": "Multiplayer (LDN/LAN) Guide",
|
||||
"es_ES": "",
|
||||
@@ -1376,7 +1376,7 @@
|
||||
"ID": "MenuBarHelpMultiplayerTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Öffnet die Multiplayer-Anleitung im offiziellen Ryujinx-Wiki",
|
||||
"el_GR": "",
|
||||
"en_US": "Opens the Multiplayer guide on the official Ryujinx wiki",
|
||||
"es_ES": "",
|
||||
@@ -1526,7 +1526,7 @@
|
||||
"ID": "GameListHeaderDeveloper",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Entwickelt von {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "Developed by {0}",
|
||||
"es_ES": "",
|
||||
@@ -1543,7 +1543,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "由 {0} 开发",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -1826,7 +1826,7 @@
|
||||
"ID": "GameListHeaderCompatibilityStatus",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Kompatibilität:",
|
||||
"el_GR": "",
|
||||
"en_US": "Compatibility:",
|
||||
"es_ES": "",
|
||||
@@ -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": "标题 ID:",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -1893,7 +1893,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "服务的游戏: {0}",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -1918,7 +1918,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "在线玩家: {0}",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -2268,7 +2268,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "清理 PPTC 缓存",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -2293,7 +2293,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "删除应用程序的所有 PPTC 缓存",
|
||||
"zh_CN": "",
|
||||
"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,101 +3350,26 @@
|
||||
{
|
||||
"ID": "SettingsTabGeneralCheckUpdatesOnLaunch",
|
||||
"Translations": {
|
||||
"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": ""
|
||||
"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": "啟動時檢查更新"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -4568,7 +4493,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "与系统时间同步",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -5822,31 +5747,6 @@
|
||||
"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": {
|
||||
@@ -6243,7 +6143,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "重置设置",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -6268,7 +6168,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "我要重置我的设置。",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -8243,7 +8143,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "彩虹滚动速度",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -13518,7 +13418,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "您正要清理 PPTC 数据:\n\n{0}\n\n您确实要继续吗?",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -16822,31 +16722,6 @@
|
||||
"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": {
|
||||
@@ -23151,7 +23026,7 @@
|
||||
"ID": "SettingsTabSystemVSyncModeTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. \"Unbounded\" ist eine unbegrenzte Bildwiederholfrequenz.",
|
||||
"el_GR": "",
|
||||
"en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate.",
|
||||
"es_ES": "",
|
||||
@@ -23176,7 +23051,7 @@
|
||||
"ID": "SettingsTabSystemVSyncModeTooltipCustom",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Emulierte vertikale Synchronisation. \"Switch\" emuliert die 60Hz-Bildwiederholfrequenz der Switch. „Unbounded“ ist eine unbegrenzte Bildwiederholfrequenz. „Benutzerdefinierte Bildwiederholfrequenz“ emuliert die angegebene benutzerdefinierte Bildwiederholfrequenz.",
|
||||
"el_GR": "",
|
||||
"en_US": "Emulated Vertical Sync. 'Switch' emulates the Switch's refresh rate of 60Hz. 'Unbounded' is an unbounded refresh rate. 'Custom Refresh Rate' emulates the specified custom refresh rate.",
|
||||
"es_ES": "",
|
||||
@@ -23201,7 +23076,7 @@
|
||||
"ID": "SettingsTabSystemEnableCustomVSyncIntervalTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Ermöglicht es dem Benutzer, eine emulierte Bildwiederholfrequenz festzulegen. In einigen Titeln kann dies die Geschwindigkeit der Spiel-Logik erhöhen oder verringern. In anderen Titeln kann dies dazu führen, dass die FPS auf ein Vielfaches der Bildwiederholfrequenz begrenzt werden oder zu unvorhersehbarem Verhalten führen. Dies ist eine experimentelle Funktion, ohne Garantien dafür, wie sich das Gameplay auswirkt. \n\nLassen Sie diese Option deaktiviert, wenn Sie sich nicht sicher sind.",
|
||||
"el_GR": "",
|
||||
"en_US": "Allows the user to specify an emulated refresh rate. In some titles, this may speed up or slow down the rate of gameplay logic. In other titles, it may allow for capping FPS at some multiple of the refresh rate, or lead to unpredictable behavior. This is an experimental feature, with no guarantees for how gameplay will be affected. \n\nLeave OFF if unsure.",
|
||||
"es_ES": "",
|
||||
@@ -23226,7 +23101,7 @@
|
||||
"ID": "SettingsTabSystemCustomVSyncIntervalValueTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Der Zielwert für die benutzerdefinierte Bildwiederholfrequenz.",
|
||||
"el_GR": "",
|
||||
"en_US": "The custom refresh rate target value.",
|
||||
"es_ES": "",
|
||||
@@ -23251,7 +23126,7 @@
|
||||
"ID": "SettingsTabSystemCustomVSyncIntervalSliderTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Die benutzerdefinierte Bildwiederholfrequenz als Prozentsatz der normalen Switch-Bildwiederholfrequenz.",
|
||||
"el_GR": "",
|
||||
"en_US": "The custom refresh rate, as a percentage of the normal Switch refresh rate.",
|
||||
"es_ES": "",
|
||||
@@ -23276,7 +23151,7 @@
|
||||
"ID": "SettingsTabSystemCustomVSyncIntervalPercentage",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Benutzerdefinierte Bildwiederholfrequenz %:",
|
||||
"el_GR": "",
|
||||
"en_US": "Custom Refresh Rate %:",
|
||||
"es_ES": "",
|
||||
@@ -23301,7 +23176,7 @@
|
||||
"ID": "SettingsTabSystemCustomVSyncIntervalValue",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Wert für benutzerdefinierte Bildwiederholfrequenz:",
|
||||
"el_GR": "",
|
||||
"en_US": "Custom Refresh Rate Value:",
|
||||
"es_ES": "",
|
||||
@@ -23351,7 +23226,7 @@
|
||||
"ID": "SettingsTabHotkeysToggleVSyncModeHotkey",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "VSync-Modus umschalten:",
|
||||
"el_GR": "",
|
||||
"en_US": "Toggle VSync mode:",
|
||||
"es_ES": "",
|
||||
@@ -23376,7 +23251,7 @@
|
||||
"ID": "SettingsTabHotkeysIncrementCustomVSyncIntervalHotkey",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Benutzerdefinierte Bildwiederholfrequenz erhöhen:",
|
||||
"el_GR": "",
|
||||
"en_US": "Raise custom refresh rate",
|
||||
"es_ES": "",
|
||||
@@ -23401,7 +23276,7 @@
|
||||
"ID": "SettingsTabHotkeysDecrementCustomVSyncIntervalHotkey",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Benutzerdefinierte Bildwiederholfrequenz senken:",
|
||||
"el_GR": "",
|
||||
"en_US": "Lower custom refresh rate:",
|
||||
"es_ES": "",
|
||||
@@ -23426,7 +23301,7 @@
|
||||
"ID": "CompatibilityListLastUpdated",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Zuletzt aktualisiert: {0}",
|
||||
"el_GR": "",
|
||||
"en_US": "Last updated: {0}",
|
||||
"es_ES": "",
|
||||
@@ -23451,7 +23326,7 @@
|
||||
"ID": "CompatibilityListWarning",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Diese Kompatibilitätsliste könnte veraltete Einträge enthalten. Teste dennoch Spiele im \"Ingame\"-Status.",
|
||||
"el_GR": "",
|
||||
"en_US": "This compatibility list might contain out of date entries.\nDo not be opposed to testing games in the \"Ingame\" status.",
|
||||
"es_ES": "",
|
||||
@@ -23476,7 +23351,7 @@
|
||||
"ID": "CompatibilityListSearchBoxWatermark",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Kompatibilitätseinträge durchsuchen...",
|
||||
"el_GR": "",
|
||||
"en_US": "Search compatibility entries...",
|
||||
"es_ES": "",
|
||||
@@ -23526,7 +23401,7 @@
|
||||
"ID": "CompatibilityListOnlyShowOwnedGames",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Nur eigene Spiele anzeigen",
|
||||
"el_GR": "",
|
||||
"en_US": "Only show owned games",
|
||||
"es_ES": "",
|
||||
@@ -23551,7 +23426,7 @@
|
||||
"ID": "CompatibilityListPlayable",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Spielbar",
|
||||
"el_GR": "",
|
||||
"en_US": "Playable",
|
||||
"es_ES": "",
|
||||
@@ -23576,7 +23451,7 @@
|
||||
"ID": "CompatibilityListIngame",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Im Spiel",
|
||||
"el_GR": "",
|
||||
"en_US": "Ingame",
|
||||
"es_ES": "",
|
||||
@@ -23693,7 +23568,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "启动和游戏时不会出现任何崩溃或任何类型的 GPU bug 且速度足够快可以在一般 PC 上尽情游玩。",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -23718,7 +23593,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "可以成功启动并进入游戏但可能会遇到以下一种或多种问题: 崩溃、卡死、GPU bug、令人无法接受的音频,或者只是太慢。仍然可以继续进行游戏,但是可能无法达到预期。",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -23743,7 +23618,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "可以启动并通过标题画面但是无法进入到主要的游戏流程。",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -23751,7 +23626,7 @@
|
||||
"ID": "CompatibilityListBootsTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Startet, kommt aber nicht über den Titelbildschirm hinaus.",
|
||||
"el_GR": "",
|
||||
"en_US": "Boots but does not make it past the title screen.",
|
||||
"es_ES": "",
|
||||
@@ -23768,7 +23643,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "可以启动但是无法通过标题画面。",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -23776,7 +23651,7 @@
|
||||
"ID": "CompatibilityListNothingTooltip",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Startet nicht oder zeigt keine Anzeichen von Aktivität.",
|
||||
"el_GR": "",
|
||||
"en_US": "Does not boot or shows no signs of activity.",
|
||||
"es_ES": "",
|
||||
@@ -23793,7 +23668,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "无法启动或显示无任何动静。",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -23801,7 +23676,7 @@
|
||||
"ID": "ExtractAocListHeader",
|
||||
"Translations": {
|
||||
"ar_SA": "",
|
||||
"de_DE": "",
|
||||
"de_DE": "Wähle ein DLC zum Extrahieren aus",
|
||||
"el_GR": "",
|
||||
"en_US": "Select a DLC to Extract",
|
||||
"es_ES": "",
|
||||
@@ -23843,7 +23718,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "Rich Presence 图像",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
},
|
||||
@@ -23868,7 +23743,7 @@
|
||||
"th_TH": "",
|
||||
"tr_TR": "",
|
||||
"uk_UA": "",
|
||||
"zh_CN": "动态 Rich Presence",
|
||||
"zh_CN": "",
|
||||
"zh_TW": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using DiscordRPC;
|
||||
using Gommon;
|
||||
using MsgPack;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using Ryujinx.Ava.Utilities.Configuration;
|
||||
@@ -10,6 +11,7 @@ using Ryujinx.HLE;
|
||||
using Ryujinx.HLE.Loaders.Processes;
|
||||
using Ryujinx.Horizon;
|
||||
using Ryujinx.Horizon.Prepo.Types;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
|
||||
@@ -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.Vulkan, HelpText = "Change Graphics Backend to use.")]
|
||||
[Option("graphics-backend", Required = false, Default = GraphicsBackend.OpenGl, 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.")]
|
||||
|
||||
@@ -55,21 +55,9 @@
|
||||
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">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock
|
||||
Text="{Binding AppData.LocalizedStatusTooltip}" />
|
||||
<Separator
|
||||
Margin="0, 10, 0, 10"
|
||||
IsVisible="{Binding AppData.HasCompatibilityLabels}" />
|
||||
<TextBlock
|
||||
IsVisible="{Binding AppData.HasCompatibilityLabels}"
|
||||
Text="{Binding AppData.FormattedCompatibilityLabels}" />
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>
|
||||
</TextBlock>
|
||||
TextWrapping="Wrap" />
|
||||
<Button.Styles>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="MinWidth"
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Styling;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
|
||||
@@ -93,19 +93,8 @@
|
||||
IsVisible="{Binding HasPlayabilityInfo}"
|
||||
Background="{DynamicResource AppListBackgroundColor}"
|
||||
Margin="-1, 0, 0, 0"
|
||||
Padding="0">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock
|
||||
Text="{Binding LocalizedStatusTooltip}" />
|
||||
<Separator
|
||||
Margin="0, 10, 0, 10"
|
||||
IsVisible="{Binding HasCompatibilityLabels}" />
|
||||
<TextBlock
|
||||
IsVisible="{Binding HasCompatibilityLabels}"
|
||||
Text="{Binding FormattedCompatibilityLabels}" />
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>
|
||||
Padding="0"
|
||||
ToolTip.Tip="{Binding LocalizedStatusTooltip}">
|
||||
<TextBlock
|
||||
Margin="1.5"
|
||||
Tag="{Binding IdString}"
|
||||
|
||||
@@ -7,6 +7,7 @@ using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using Ryujinx.Ava.Utilities.Compat;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Avalonia.Logging;
|
||||
using Avalonia.Utilities;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Utilities.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Text;
|
||||
@@ -15,19 +14,13 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
internal class LoggerAdapter : ILogSink
|
||||
{
|
||||
private static bool _avaloniaLogsEnabled = ConfigurationState.Instance.Logger.EnableAvaloniaLog;
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
AvaLogger.Sink = new LoggerAdapter();
|
||||
ConfigurationState.Instance.Logger.EnableAvaloniaLog.Event
|
||||
+= (_, e) => _avaloniaLogsEnabled = e.NewValue;
|
||||
}
|
||||
|
||||
private static RyuLogger.Log? GetLog(AvaLogLevel level, string area)
|
||||
{
|
||||
if (!_avaloniaLogsEnabled) return null;
|
||||
|
||||
return level switch
|
||||
{
|
||||
AvaLogLevel.Verbose => RyuLogger.Debug,
|
||||
|
||||
@@ -1147,10 +1147,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
List<string> dirs = result.Select(it => it.Path.LocalPath).ToList();
|
||||
int numAdded = onDirsSelected(dirs, out int numRemoved);
|
||||
|
||||
string msg = string.Join("\n",
|
||||
string msg = String.Join("\r\n", new string[] {
|
||||
string.Format(LocaleManager.Instance[localeMessageRemovedKey], numRemoved),
|
||||
string.Format(LocaleManager.Instance[localeMessageAddedKey], numAdded)
|
||||
);
|
||||
});
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
|
||||
@@ -13,7 +13,6 @@ using Ryujinx.Ava.UI.Models.Input;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Ava.Utilities.Configuration;
|
||||
using Ryujinx.Ava.Utilities.Configuration.System;
|
||||
using Ryujinx.Ava.Utilities.Configuration.UI;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
@@ -122,7 +121,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool RememberWindowState { get; set; }
|
||||
public bool ShowTitleBar { get; set; }
|
||||
public int HideCursor { get; set; }
|
||||
public int UpdateCheckerType { get; set; }
|
||||
public bool EnableDockedMode { get; set; }
|
||||
public bool EnableKeyboard { get; set; }
|
||||
public bool EnableMouse { get; set; }
|
||||
@@ -206,7 +204,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool EnableTrace { get; set; }
|
||||
public bool EnableGuest { get; set; }
|
||||
public bool EnableFsAccessLog { get; set; }
|
||||
public bool EnableAvaloniaLog { get; set; }
|
||||
public bool EnableDebug { get; set; }
|
||||
public bool IsOpenAlEnabled { get; set; }
|
||||
public bool IsSoundIoEnabled { get; set; }
|
||||
@@ -478,7 +475,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
RememberWindowState = config.RememberWindowState;
|
||||
ShowTitleBar = config.ShowTitleBar;
|
||||
HideCursor = (int)config.HideCursor.Value;
|
||||
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
|
||||
|
||||
GameDirectories.Clear();
|
||||
GameDirectories.AddRange(config.UI.GameDirs.Value);
|
||||
@@ -564,7 +560,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
EnableGuest = config.Logger.EnableGuest;
|
||||
EnableDebug = config.Logger.EnableDebug;
|
||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||
EnableAvaloniaLog = config.Logger.EnableAvaloniaLog;
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
|
||||
@@ -585,7 +580,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.RememberWindowState.Value = RememberWindowState;
|
||||
config.ShowTitleBar.Value = ShowTitleBar;
|
||||
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
||||
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
|
||||
|
||||
if (GameDirectoryChanged)
|
||||
{
|
||||
@@ -685,7 +679,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.Logger.EnableGuest.Value = EnableGuest;
|
||||
config.Logger.EnableDebug.Value = EnableDebug;
|
||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||
config.Logger.EnableAvaloniaLog.Value = EnableAvaloniaLog;
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
ToolTip.Tip="{ext:Locale DebugLogTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableDebugLogs}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableAvaloniaLog}"
|
||||
ToolTip.Tip="{ext:Locale AvaloniaLogTooltip}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableAvaloniaLogs}" />
|
||||
</CheckBox>
|
||||
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{ext:Locale FSAccessLogModeTooltip}"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
<Design.DataContext>
|
||||
@@ -31,33 +30,18 @@
|
||||
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
|
||||
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding ShowConfirmExit}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding RememberWindowState}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
|
||||
<CheckBox IsChecked="{Binding ShowTitleBar}" Name="ShowTitleBarBox">
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
|
||||
</CheckBox>
|
||||
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}"
|
||||
Width="150" />
|
||||
<ComboBox SelectedIndex="{Binding UpdateCheckerType}"
|
||||
HorizontalContentAlignment="Left"
|
||||
MinWidth="100">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchOff}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchPromptAtStartup}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchBackground}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{ext:Locale SettingsTabGeneralHideCursor}"
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
public SettingsUiView()
|
||||
{
|
||||
InitializeComponent();
|
||||
ShowTitleBarBox.IsVisible = OperatingSystem.IsWindows();
|
||||
AddGameDirButton.Command =
|
||||
Commands.Create(() => AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true));
|
||||
AddAutoloadDirButton.Command =
|
||||
|
||||
@@ -19,7 +19,6 @@ using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using Ryujinx.Ava.Utilities.Configuration;
|
||||
using Ryujinx.Ava.Utilities.Configuration.UI;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Helper;
|
||||
using Ryujinx.Common.Logging;
|
||||
@@ -401,29 +400,10 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
|
||||
}
|
||||
|
||||
if (!Updater.CanUpdate() || CommandLineState.HideAvailableUpdates)
|
||||
return;
|
||||
|
||||
switch (ConfigurationState.Instance.UpdateCheckerType.Value)
|
||||
if (ConfigurationState.Instance.CheckUpdatesOnStart && !CommandLineState.HideAvailableUpdates && Updater.CanUpdate())
|
||||
{
|
||||
case UpdaterType.PromptAtStartup:
|
||||
await Updater.BeginUpdateAsync()
|
||||
.Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"));
|
||||
break;
|
||||
case UpdaterType.CheckInBackground:
|
||||
if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions))
|
||||
{
|
||||
string newVersionString = ReleaseInformation.IsCanaryBuild
|
||||
? $"Canary {versions.Current} -> Canary {versions.Incoming}"
|
||||
: $"{versions.Current} -> {versions.Incoming}";
|
||||
|
||||
if (versions.Current < versions.Incoming)
|
||||
NotificationHelper.ShowInformation(
|
||||
title: "Update Available",
|
||||
text: newVersionString,
|
||||
onClick: () => _ = Updater.BeginUpdateAsync());
|
||||
}
|
||||
break;
|
||||
await Updater.BeginUpdateAsync()
|
||||
.Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,18 +43,7 @@ namespace Ryujinx.Ava
|
||||
private const int ConnectionCount = 4;
|
||||
|
||||
private static string _buildVer;
|
||||
|
||||
private static readonly string _platformExt =
|
||||
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 _platformExt;
|
||||
private static string _buildUrl;
|
||||
private static long _buildSize;
|
||||
private static bool _updateSuccessful;
|
||||
@@ -62,8 +51,30 @@ namespace Ryujinx.Ava
|
||||
|
||||
private static readonly string[] _windowsDependencyDirs = [];
|
||||
|
||||
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
|
||||
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
|
||||
{
|
||||
if (_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
|
||||
// Detect current platform
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_platformExt = "macos_universal.app.tar.gz";
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_platformExt = "win_x64.zip";
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64";
|
||||
_platformExt = $"linux_{arch}.tar.gz";
|
||||
}
|
||||
|
||||
if (!Version.TryParse(Program.Version, out Version currentVersion))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!");
|
||||
@@ -74,7 +85,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
_running = false;
|
||||
|
||||
return default;
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, "Checking for updates.");
|
||||
@@ -112,7 +123,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
_running = false;
|
||||
|
||||
return default;
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -138,7 +149,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
_running = false;
|
||||
|
||||
return default;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -150,7 +161,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
_running = false;
|
||||
|
||||
return default;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Version.TryParse(_buildVer, out Version newVersion))
|
||||
@@ -163,23 +174,9 @@ namespace Ryujinx.Ava
|
||||
|
||||
_running = false;
|
||||
|
||||
return (currentVersion, null);
|
||||
}
|
||||
|
||||
return (currentVersion, newVersion);
|
||||
}
|
||||
|
||||
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
|
||||
{
|
||||
if (_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
|
||||
(Version currentVersion, Version newVersion) = (await CheckVersionAsync(showVersionUpToDate)).OrDefault();
|
||||
|
||||
if (newVersion <= currentVersion)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Gommon;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
@@ -15,7 +14,6 @@ using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
@@ -34,12 +32,33 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
set
|
||||
{
|
||||
_id = value;
|
||||
|
||||
Compatibility = CompatibilityCsv.Find(Id);
|
||||
PlayabilityStatus = CompatibilityCsv.GetStatus(Id);
|
||||
}
|
||||
}
|
||||
public string Developer { get; set; } = "Unknown";
|
||||
public string Version { get; set; } = "0";
|
||||
|
||||
public bool HasPlayabilityInfo => PlayabilityStatus != null;
|
||||
|
||||
public string LocalizedStatus =>
|
||||
PlayabilityStatus.HasValue
|
||||
? LocaleManager.Instance[PlayabilityStatus!.Value]
|
||||
: string.Empty;
|
||||
|
||||
public LocaleKeys? PlayabilityStatus { get; set; }
|
||||
public string LocalizedStatusTooltip =>
|
||||
PlayabilityStatus.HasValue
|
||||
#pragma warning disable CS8509 // It is exhaustive for any value this property can contain.
|
||||
? LocaleManager.Instance[PlayabilityStatus!.Value switch
|
||||
#pragma warning restore CS8509
|
||||
{
|
||||
LocaleKeys.CompatibilityListPlayable => LocaleKeys.CompatibilityListPlayableTooltip,
|
||||
LocaleKeys.CompatibilityListIngame => LocaleKeys.CompatibilityListIngameTooltip,
|
||||
LocaleKeys.CompatibilityListMenus => LocaleKeys.CompatibilityListMenusTooltip,
|
||||
LocaleKeys.CompatibilityListBoots => LocaleKeys.CompatibilityListBootsTooltip,
|
||||
LocaleKeys.CompatibilityListNothing => LocaleKeys.CompatibilityListNothingTooltip,
|
||||
}]
|
||||
: string.Empty;
|
||||
public int PlayerCount { get; set; }
|
||||
public int GameCount { get; set; }
|
||||
|
||||
@@ -59,39 +78,11 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
|
||||
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
|
||||
|
||||
public bool HasPlayedPreviously => TimePlayed.TotalSeconds > 1;
|
||||
public bool HasPlayedPreviously => TimePlayedString != string.Empty;
|
||||
|
||||
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n");
|
||||
|
||||
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
|
||||
|
||||
public Optional<CompatibilityEntry> Compatibility { get; private set; }
|
||||
|
||||
public bool HasPlayabilityInfo => Compatibility.HasValue;
|
||||
|
||||
public string LocalizedStatus => Compatibility.Convert(x => x.LocalizedStatus);
|
||||
|
||||
public bool HasCompatibilityLabels => !FormattedCompatibilityLabels.Equals(string.Empty);
|
||||
|
||||
public string FormattedCompatibilityLabels
|
||||
=> Compatibility.Convert(x => x.FormattedIssueLabels).OrElse(string.Empty);
|
||||
|
||||
public LocaleKeys? PlayabilityStatus => Compatibility.Convert(x => x.Status).OrElse(null);
|
||||
|
||||
public string LocalizedStatusTooltip =>
|
||||
Compatibility.Convert(x =>
|
||||
#pragma warning disable CS8509 It is exhaustive for all possible values this can contain.
|
||||
LocaleManager.Instance[x.Status switch
|
||||
#pragma warning restore CS8509
|
||||
{
|
||||
LocaleKeys.CompatibilityListPlayable => LocaleKeys.CompatibilityListPlayableTooltip,
|
||||
LocaleKeys.CompatibilityListIngame => LocaleKeys.CompatibilityListIngameTooltip,
|
||||
LocaleKeys.CompatibilityListMenus => LocaleKeys.CompatibilityListMenusTooltip,
|
||||
LocaleKeys.CompatibilityListBoots => LocaleKeys.CompatibilityListBootsTooltip,
|
||||
LocaleKeys.CompatibilityListNothing => LocaleKeys.CompatibilityListNothingTooltip,
|
||||
}]
|
||||
).OrElse(string.Empty);
|
||||
|
||||
|
||||
[JsonIgnore] public string IdString => Id.ToString("x16");
|
||||
|
||||
@@ -101,16 +92,16 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
||||
|
||||
public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
|
||||
{
|
||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
|
||||
if (!System.IO.Path.Exists(titleFilePath))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist.");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
|
||||
string extension = System.IO.Path.GetExtension(titleFilePath).ToLower();
|
||||
|
||||
|
||||
@@ -60,21 +60,10 @@ namespace Ryujinx.Ava.Utilities.Compat
|
||||
}
|
||||
}
|
||||
|
||||
public static CompatibilityEntry Find(string titleId)
|
||||
=> Entries.FirstOrDefault(x => x.TitleId.HasValue && x.TitleId.Value.EqualsIgnoreCase(titleId));
|
||||
|
||||
public static CompatibilityEntry Find(ulong titleId)
|
||||
=> Find(titleId.ToString("X16"));
|
||||
|
||||
public static LocaleKeys? GetStatus(string titleId)
|
||||
=> Find(titleId)?.Status;
|
||||
=> Entries.FirstOrDefault(x => x.TitleId.HasValue && x.TitleId.Value.EqualsIgnoreCase(titleId))?.Status;
|
||||
|
||||
public static LocaleKeys? GetStatus(ulong titleId) => GetStatus(titleId.ToString("X16"));
|
||||
|
||||
public static string GetLabels(string titleId)
|
||||
=> Find(titleId)?.FormattedIssueLabels;
|
||||
|
||||
public static string GetLabels(ulong titleId) => GetLabels(titleId.ToString("X16"));
|
||||
}
|
||||
|
||||
public class CompatibilityEntry
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
/// <summary>
|
||||
/// The current version of the file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 65;
|
||||
public const int CurrentVersion = 63;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the configuration file format
|
||||
@@ -111,11 +111,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
/// Enables printing FS access log messages
|
||||
/// </summary>
|
||||
public bool LoggingEnableFsAccessLog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables log messages from Avalonia
|
||||
/// </summary>
|
||||
public bool LoggingEnableAvalonia { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls which log messages are written to the log targets
|
||||
@@ -163,14 +158,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
public bool EnableDiscordIntegration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DEPRECATED: Checks for updates when Ryujinx starts when enabled
|
||||
/// Checks for updates when Ryujinx starts when enabled
|
||||
/// </summary>
|
||||
public bool CheckUpdatesOnStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
|
||||
/// </summary>
|
||||
public UpdaterType UpdateCheckerType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Show "Confirm Exit" Dialog
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
|
||||
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
|
||||
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
|
||||
UpdateCheckerType.Value = cff.UpdateCheckerType;
|
||||
ShowConfirmExit.Value = cff.ShowConfirmExit;
|
||||
RememberWindowState.Value = cff.RememberWindowState;
|
||||
ShowTitleBar.Value = cff.ShowTitleBar;
|
||||
@@ -431,9 +430,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
}
|
||||
}),
|
||||
(62, static cff => cff.RainbowSpeed = 1f),
|
||||
(63, static cff => cff.MatchSystemTime = false),
|
||||
(64, static cff => cff.LoggingEnableAvalonia = false),
|
||||
(65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off)
|
||||
(63, static cff => cff.MatchSystemTime = false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using ARMeilleure;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Utilities.Configuration.System;
|
||||
using Ryujinx.Ava.Utilities.Configuration.UI;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
@@ -255,11 +254,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
/// Enables printing FS access log messages
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables log messages from Avalonia
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableAvaloniaLog { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls which log messages are written to the log targets
|
||||
@@ -287,7 +281,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
EnableTrace = new ReactiveObject<bool>();
|
||||
EnableGuest = new ReactiveObject<bool>();
|
||||
EnableFsAccessLog = new ReactiveObject<bool>();
|
||||
EnableAvaloniaLog = new ReactiveObject<bool>();
|
||||
FilteredClasses = new ReactiveObject<LogClass[]>();
|
||||
EnableFileLog = new ReactiveObject<bool>();
|
||||
EnableFileLog.LogChangesToValue(nameof(EnableFileLog));
|
||||
@@ -768,11 +761,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
/// Checks for updates when Ryujinx starts when enabled
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> CheckUpdatesOnStart { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
|
||||
/// </summary>
|
||||
public ReactiveObject<UpdaterType> UpdateCheckerType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Show "Confirm Exit" Dialog
|
||||
@@ -810,7 +798,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
Hacks = new HacksSection();
|
||||
EnableDiscordIntegration = new ReactiveObject<bool>();
|
||||
CheckUpdatesOnStart = new ReactiveObject<bool>();
|
||||
UpdateCheckerType = new ReactiveObject<UpdaterType>();
|
||||
ShowConfirmExit = new ReactiveObject<bool>();
|
||||
RememberWindowState = new ReactiveObject<bool>();
|
||||
ShowTitleBar = new ReactiveObject<bool>();
|
||||
|
||||
@@ -46,7 +46,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
LoggingEnableTrace = Logger.EnableTrace,
|
||||
LoggingEnableGuest = Logger.EnableGuest,
|
||||
LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
|
||||
LoggingEnableAvalonia = Logger.EnableAvaloniaLog,
|
||||
LoggingFilteredClasses = Logger.FilteredClasses,
|
||||
LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel,
|
||||
SystemLanguage = System.Language,
|
||||
@@ -56,7 +55,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
DockedMode = System.EnableDockedMode,
|
||||
EnableDiscordIntegration = EnableDiscordIntegration,
|
||||
CheckUpdatesOnStart = CheckUpdatesOnStart,
|
||||
UpdateCheckerType = UpdateCheckerType,
|
||||
ShowConfirmExit = ShowConfirmExit,
|
||||
RememberWindowState = RememberWindowState,
|
||||
ShowTitleBar = ShowTitleBar,
|
||||
@@ -167,7 +165,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
Logger.EnableTrace.Value = false;
|
||||
Logger.EnableGuest.Value = true;
|
||||
Logger.EnableFsAccessLog.Value = false;
|
||||
Logger.EnableAvaloniaLog.Value = false;
|
||||
Logger.FilteredClasses.Value = [];
|
||||
Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
|
||||
System.Language.Value = Language.AmericanEnglish;
|
||||
@@ -176,7 +173,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
System.SystemTimeOffset.Value = 0;
|
||||
System.EnableDockedMode.Value = true;
|
||||
EnableDiscordIntegration.Value = true;
|
||||
UpdateCheckerType.Value = UpdaterType.PromptAtStartup;
|
||||
CheckUpdatesOnStart.Value = true;
|
||||
ShowConfirmExit.Value = true;
|
||||
RememberWindowState.Value = true;
|
||||
ShowTitleBar.Value = !OperatingSystem.IsWindows();
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.Configuration.UI
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<UpdaterType>))]
|
||||
public enum UpdaterType
|
||||
{
|
||||
Off,
|
||||
PromptAtStartup,
|
||||
CheckInBackground
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Gommon;
|
||||
using MsgPack;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -30,7 +31,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
Guard.Ensure(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)));
|
||||
_specs.Add(transform(new GameSpec { TitleIds = [titleId] }));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -44,7 +46,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
Guard.Ensure(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));
|
||||
_specs.Add(new GameSpec { TitleIds = [titleId] }.Apply(transform));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,7 +63,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
||||
|
||||
return AddSpec(transform(GameSpec.Create(tids)));
|
||||
_specs.Add(transform(new GameSpec { TitleIds = [..tids] }));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,17 +79,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
||||
|
||||
return AddSpec(GameSpec.Create(tids).Apply(transform));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an analysis spec matching a specific game by title ID, with the provided pre-configured spec.
|
||||
/// </summary>
|
||||
/// <param name="spec">The <see cref="GameSpec"/> to add.</param>
|
||||
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||
public Analyzer AddSpec(GameSpec spec)
|
||||
{
|
||||
_specs.Add(spec);
|
||||
_specs.Add(new GameSpec { TitleIds = [..tids] }.Apply(transform));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MsgPack;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
|
||||
@@ -1,632 +0,0 @@
|
||||
using Gommon;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
public partial class PlayReports
|
||||
{
|
||||
private static FormattedValue BreathOfTheWild_MasterMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
|
||||
|
||||
private static FormattedValue TearsOfTheKingdom_CurrentField(SingleValue value) =>
|
||||
value.Matched.DoubleValue switch
|
||||
{
|
||||
> 800d => "Exploring the Sky Islands",
|
||||
< -201d => "Exploring the Depths",
|
||||
_ => "Roaming Hyrule"
|
||||
};
|
||||
|
||||
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
||||
|
||||
private static FormattedValue SuperMarioOdysseyChina_AssistMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
|
||||
|
||||
private static FormattedValue SuperMario3DWorldOrBowsersFury(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
|
||||
|
||||
private static FormattedValue MarioKart8Deluxe_Mode(SingleValue value)
|
||||
=> value.Matched.StringValue switch
|
||||
{
|
||||
// Single Player
|
||||
"Single" => "Single Player",
|
||||
// Multiplayer
|
||||
"Multi-2players" => "Multiplayer 2 Players",
|
||||
"Multi-3players" => "Multiplayer 3 Players",
|
||||
"Multi-4players" => "Multiplayer 4 Players",
|
||||
// Wireless/LAN Play
|
||||
"Local-Single" => "Wireless/LAN Play",
|
||||
"Local-2players" => "Wireless/LAN Play 2 Players",
|
||||
// CC Classes
|
||||
"50cc" => "50cc",
|
||||
"100cc" => "100cc",
|
||||
"150cc" => "150cc",
|
||||
"Mirror" => "Mirror (150cc)",
|
||||
"200cc" => "200cc",
|
||||
// Modes
|
||||
"GrandPrix" => "Grand Prix",
|
||||
"TimeAttack" => "Time Trials",
|
||||
"VS" => "VS Races",
|
||||
"Battle" => "Battle Mode",
|
||||
"RaceStart" => "Selecting a Course",
|
||||
"Race" => "Racing",
|
||||
_ => FormattedValue.ForceReset
|
||||
};
|
||||
|
||||
private static FormattedValue PokemonSVUnionCircle(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
|
||||
|
||||
private static FormattedValue PokemonSVArea(SingleValue value)
|
||||
=> value.Matched.StringValue switch
|
||||
{
|
||||
// Base Game Locations
|
||||
"a_w01" => "South Area One",
|
||||
"a_w02" => "Mesagoza",
|
||||
"a_w03" => "The Pokemon League",
|
||||
"a_w04" => "South Area Two",
|
||||
"a_w05" => "South Area Four",
|
||||
"a_w06" => "South Area Six",
|
||||
"a_w07" => "South Area Five",
|
||||
"a_w08" => "South Area Three",
|
||||
"a_w09" => "West Area One",
|
||||
"a_w10" => "Asado Desert",
|
||||
"a_w11" => "West Area Two",
|
||||
"a_w12" => "Medali",
|
||||
"a_w13" => "Tagtree Thicket",
|
||||
"a_w14" => "East Area Three",
|
||||
"a_w15" => "Artazon",
|
||||
"a_w16" => "East Area Two",
|
||||
"a_w18" => "Casseroya Lake",
|
||||
"a_w19" => "Glaseado Mountain",
|
||||
"a_w20" => "North Area Three",
|
||||
"a_w21" => "North Area One",
|
||||
"a_w22" => "North Area Two",
|
||||
"a_w23" => "The Great Crater of Paldea",
|
||||
"a_w24" => "South Paldean Sea",
|
||||
"a_w25" => "West Paldean Sea",
|
||||
"a_w26" => "East Paldean Sea",
|
||||
"a_w27" => "Nouth Paldean Sea",
|
||||
//TODO DLC Locations
|
||||
_ => FormattedValue.ForceReset
|
||||
};
|
||||
|
||||
private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values)
|
||||
{
|
||||
// Check if the PlayReport is for a challenger approach or an achievement.
|
||||
if (values.Matched.TryGetValue("fighter", out Value fighter) && values.Matched.ContainsKey("reason"))
|
||||
{
|
||||
return $"Challenger Approaches - {SuperSmashBrosUltimate_Character(fighter)}";
|
||||
}
|
||||
|
||||
if (values.Matched.TryGetValue("fighter", out fighter) && values.Matched.ContainsKey("challenge_count"))
|
||||
{
|
||||
return $"Fighter Unlocked - {SuperSmashBrosUltimate_Character(fighter)}";
|
||||
}
|
||||
|
||||
if (values.Matched.TryGetValue("anniversary", out Value anniversary))
|
||||
{
|
||||
return $"Achievement Unlocked - ID: {anniversary}";
|
||||
}
|
||||
|
||||
if (values.Matched.ContainsKey("adv_slot"))
|
||||
{
|
||||
return
|
||||
"Playing Adventure Mode"; // Doing this as it can be a placeholder until we can grab the character.
|
||||
}
|
||||
|
||||
// Check if we have a match_mode at this point, if not, go to default.
|
||||
if (!values.Matched.TryGetValue("match_mode", out Value matchMode))
|
||||
{
|
||||
return "Smashing";
|
||||
}
|
||||
|
||||
return matchMode.BoxedValue switch
|
||||
{
|
||||
0 when values.Matched.TryGetValue("player_1_fighter", out Value player) &&
|
||||
values.Matched.TryGetValue("player_2_fighter", out Value challenger)
|
||||
=> $"Last Smashed: {SuperSmashBrosUltimate_Character(challenger)}'s Fighter Challenge - {SuperSmashBrosUltimate_Character(player)}",
|
||||
1 => $"Last Smashed: Normal Battle - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
2 when values.Matched.TryGetValue("player_1_rank", out Value team)
|
||||
=> team.BoxedValue is 0
|
||||
? "Last Smashed: Squad Strike - Red Team Wins"
|
||||
: "Last Smashed: Squad Strike - Blue Team Wins",
|
||||
3 => $"Last Smashed: Custom Smash - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
4 => $"Last Smashed: Super Sudden Death - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
5 => $"Last Smashed: Smashdown - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
6 => $"Last Smashed: Tourney Battle - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
7 when values.Matched.TryGetValue("player_1_fighter", out Value player)
|
||||
=> $"Last Smashed: Spirit Board Battle as {SuperSmashBrosUltimate_Character(player)}",
|
||||
8 when values.Matched.TryGetValue("player_1_fighter", out Value player)
|
||||
=> $"Playing Adventure Mode as {SuperSmashBrosUltimate_Character(player)}",
|
||||
10 when values.Matched.TryGetValue("match_submode", out Value battle) &&
|
||||
values.Matched.TryGetValue("player_1_fighter", out Value player)
|
||||
=> $"Last Smashed: Classic Mode, Battle {(int)battle.BoxedValue + 1}/8 as {SuperSmashBrosUltimate_Character(player)}",
|
||||
12 => $"Last Smashed: Century Smash - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
13 => $"Last Smashed: All-Star Smash - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
14 => $"Last Smashed: Cruel Smash - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
15 when values.Matched.TryGetValue("player_1_fighter", out Value player)
|
||||
=> $"Last Smashed: Home-Run Contest - {SuperSmashBrosUltimate_Character(player)}",
|
||||
16 when values.Matched.TryGetValue("player_1_fighter", out Value player1) &&
|
||||
values.Matched.TryGetValue("player_2_fighter", out Value player2)
|
||||
=> $"Last Smashed: Home-Run Content (Co-op) - {SuperSmashBrosUltimate_Character(player1)} and {SuperSmashBrosUltimate_Character(player2)}",
|
||||
17 => $"Last Smashed: Home-Run Contest (Versus) - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
18 when values.Matched.TryGetValue("player_1_fighter", out Value player1) &&
|
||||
values.Matched.TryGetValue("player_2_fighter", out Value player2)
|
||||
=> $"Fresh out of Training mode - {SuperSmashBrosUltimate_Character(player1)} with {SuperSmashBrosUltimate_Character(player2)}",
|
||||
58 => $"Last Smashed: LDN Battle - {SuperSmashBrosUltimate_PlayerListing(values)}",
|
||||
63 when values.Matched.TryGetValue("player_1_fighter", out Value player)
|
||||
=> $"Last Smashed: DLC Spirit Board Battle as {SuperSmashBrosUltimate_Character(player)}",
|
||||
_ => "Smashing"
|
||||
};
|
||||
}
|
||||
|
||||
private static string SuperSmashBrosUltimate_Character(Value value) =>
|
||||
BinaryPrimitives.ReverseEndianness(
|
||||
BitConverter.ToInt64(((MsgPack.MessagePackExtendedTypeObject)value.BoxedValue).GetBody(), 0)) switch
|
||||
{
|
||||
0x0 => "Mario",
|
||||
0x1 => "Donkey Kong",
|
||||
0x2 => "Link",
|
||||
0x3 => "Samus",
|
||||
0x4 => "Dark Samus",
|
||||
0x5 => "Yoshi",
|
||||
0x6 => "Kirby",
|
||||
0x7 => "Fox",
|
||||
0x8 => "Pikachu",
|
||||
0x9 => "Luigi",
|
||||
0xA => "Ness",
|
||||
0xB => "Captain Falcon",
|
||||
0xC => "Jigglypuff",
|
||||
0xD => "Peach",
|
||||
0xE => "Daisy",
|
||||
0xF => "Bowser",
|
||||
0x10 => "Ice Climbers",
|
||||
0x11 => "Sheik",
|
||||
0x12 => "Zelda",
|
||||
0x13 => "Dr. Mario",
|
||||
0x14 => "Pichu",
|
||||
0x15 => "Falco",
|
||||
0x16 => "Marth",
|
||||
0x17 => "Lucina",
|
||||
0x18 => "Young Link",
|
||||
0x19 => "Ganondorf",
|
||||
0x1A => "Mewtwo",
|
||||
0x1B => "Roy",
|
||||
0x1C => "Chrom",
|
||||
0x1D => "Mr Game & Watch",
|
||||
0x1E => "Meta Knight",
|
||||
0x1F => "Pit",
|
||||
0x20 => "Dark Pit",
|
||||
0x21 => "Zero Suit Samus",
|
||||
0x22 => "Wario",
|
||||
0x23 => "Snake",
|
||||
0x24 => "Ike",
|
||||
0x25 => "Pokémon Trainer",
|
||||
0x26 => "Diddy Kong",
|
||||
0x27 => "Lucas",
|
||||
0x28 => "Sonic",
|
||||
0x29 => "King Dedede",
|
||||
0x2A => "Olimar",
|
||||
0x2B => "Lucario",
|
||||
0x2C => "R.O.B.",
|
||||
0x2D => "Toon Link",
|
||||
0x2E => "Wolf",
|
||||
0x2F => "Villager",
|
||||
0x30 => "Mega Man",
|
||||
0x31 => "Wii Fit Trainer",
|
||||
0x32 => "Rosalina & Luma",
|
||||
0x33 => "Little Mac",
|
||||
0x34 => "Greninja",
|
||||
0x35 => "Palutena",
|
||||
0x36 => "Pac-Man",
|
||||
0x37 => "Robin",
|
||||
0x38 => "Shulk",
|
||||
0x39 => "Bowser Jr.",
|
||||
0x3A => "Duck Hunt",
|
||||
0x3B => "Ryu",
|
||||
0x3C => "Ken",
|
||||
0x3D => "Cloud",
|
||||
0x3E => "Corrin",
|
||||
0x3F => "Bayonetta",
|
||||
0x40 => "Richter",
|
||||
0x41 => "Inkling",
|
||||
0x42 => "Ridley",
|
||||
0x43 => "King K. Rool",
|
||||
0x44 => "Simon",
|
||||
0x45 => "Isabelle",
|
||||
0x46 => "Incineroar",
|
||||
0x47 => "Mii Brawler",
|
||||
0x48 => "Mii Swordfighter",
|
||||
0x49 => "Mii Gunner",
|
||||
0x4A => "Piranha Plant",
|
||||
0x4B => "Joker",
|
||||
0x4C => "Hero",
|
||||
0x4D => "Banjo",
|
||||
0x4E => "Terry",
|
||||
0x4F => "Byleth",
|
||||
0x50 => "Min Min",
|
||||
0x51 => "Steve",
|
||||
0x52 => "Sephiroth",
|
||||
0x53 => "Pyra/Mythra",
|
||||
0x54 => "Kazuya",
|
||||
0x55 => "Sora",
|
||||
0xFE => "Random",
|
||||
0xFF => "Scripted Entity",
|
||||
_ => "Unknown"
|
||||
};
|
||||
|
||||
private static string SuperSmashBrosUltimate_PlayerListing(SparseMultiValue values)
|
||||
{
|
||||
List<(string Character, int PlayerNumber, int? Rank)> players = [];
|
||||
|
||||
foreach (KeyValuePair<string, Value> player in values.Matched)
|
||||
{
|
||||
if (player.Key.StartsWith("player_") && player.Key.EndsWith("_fighter") &&
|
||||
player.Value.BoxedValue is not null)
|
||||
{
|
||||
if (!int.TryParse(player.Key.Split('_')[1], out int playerNumber))
|
||||
continue;
|
||||
|
||||
string character = SuperSmashBrosUltimate_Character(player.Value);
|
||||
int? rank = values.Matched.TryGetValue($"player_{playerNumber}_rank", out Value rankValue)
|
||||
? rankValue.IntValue
|
||||
: null;
|
||||
|
||||
players.Add((character, playerNumber, rank));
|
||||
}
|
||||
}
|
||||
|
||||
players = players.OrderBy(p => p.Rank ?? int.MaxValue).ToList();
|
||||
|
||||
return players.Count > 4
|
||||
? $"{players.Count} Players - {
|
||||
players.Take(3)
|
||||
.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")
|
||||
.JoinToString(", ")
|
||||
}"
|
||||
: players
|
||||
.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")
|
||||
.JoinToString(", ");
|
||||
|
||||
string RankMedal(int? rank) => rank switch
|
||||
{
|
||||
0 => "🥇",
|
||||
1 => "🥈",
|
||||
2 => "🥉",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
private static FormattedValue NsoEmulator_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
|
||||
{
|
||||
#region SEGA Genesis
|
||||
|
||||
"m_0054_e" => Playing("Alien Soldier"),
|
||||
"m_3978_e" => Playing("Alien Storm"),
|
||||
"m_5234_e" => Playing("ALISIA DRAGOON"),
|
||||
"m_5003_e" => Playing("Streets of Rage 2"),
|
||||
"m_4843_e" => Playing("Kid Chameleon"),
|
||||
"m_2874_e" => Playing("Columns"),
|
||||
"m_3167_e" => Playing("Comix Zone"),
|
||||
"m_5007_e" => Playing("Contra: Hard Corps"),
|
||||
"m_0865_e" => Playing("Ghouls 'n Ghosts"),
|
||||
"m_0935_e" => Playing("Dynamite Headdy"),
|
||||
"m_8314_e" => Playing("Earthworm Jim"),
|
||||
"m_5012_e" => Playing("Ecco the Dolphin"),
|
||||
"m_2207_e" => Playing("Flicky"),
|
||||
"m_9432_e" => Playing("Golden Axe II"),
|
||||
"m_5015_e" => Playing("Golden Axe"),
|
||||
"m_5017_e" => Playing("Gunstar Heroes"),
|
||||
"m_0732_e" => Playing("Altered Beast"),
|
||||
"m_2245_e" or "m_2245_pd" or "m_2245_pf" => Playing("Landstalker"),
|
||||
"m_1654_e" => Playing("Target Earth"),
|
||||
"m_7050_e" => Playing("Light Crusader"),
|
||||
"m_5027_e" => Playing("M.U.S.H.A."),
|
||||
"m_5028_e" => Playing("Phantasy Star IV"),
|
||||
"m_9155_e" => Playing("Pulseman"),
|
||||
"m_5030_e" => Playing("Dr. Robotnik's Mean Bean Machine"),
|
||||
"m_0098_e" => Playing("Crusader of Centy"),
|
||||
"m_0098_k" => Playing("신창세기 라그나센티"),
|
||||
"m_0098_pd" or "m_0098_pf" or "m_0098_ps" => Playing("Soleil"),
|
||||
"m_5033_e" => Playing("Ristar"),
|
||||
"m_1987_e" => Playing("MEGA MAN: THE WILY WARS"),
|
||||
"m_2609_e" => Playing("WOLF OF THE BATTLEFIELD: MERCS"),
|
||||
"m_3353_e" => Playing("Shining Force II"),
|
||||
"m_5036_e" => Playing("Shining Force"),
|
||||
"m_9866_e" => Playing("Sonic The Hedgehog Spinball"),
|
||||
"m_5041_e" => Playing("Sonic The Hedgehog 2"),
|
||||
"m_5523_e" => Playing("Space Harrier II"),
|
||||
"m_0041_e" => Playing("STREET FIGHTER II' : SPECIAL CHAMPION EDITION"),
|
||||
"m_5044_e" => Playing("STRIDER"),
|
||||
"m_6353_e" => Playing("Super Fantasy Zone"),
|
||||
"m_9569_e" => Playing("Beyond Oasis"),
|
||||
"m_9569_k" => Playing("스토리 오브 도어"),
|
||||
"m_9569_pd" or "m_9569_ps" => Playing("The Story of Thor"),
|
||||
"m_9569_pf" => Playing("La Légende de Thor"),
|
||||
"m_5049_e" => Playing("Shinobi III: Return of the Ninja Master"),
|
||||
"m_6811_e" => Playing("The Revenge of Shinobi"),
|
||||
"m_4372_e" => Playing("Thunder Force II"),
|
||||
"m_1535_e" => Playing("ToeJam & Earl in Panic on Funkotron"),
|
||||
"m_0432_e" => Playing("ToeJam & Earl"),
|
||||
"m_5052_e" => Playing("Castlevania: BLOODLINES"),
|
||||
"m_3626_e" => Playing("VectorMan"),
|
||||
"m_7955_e" => Playing("Sword of Vermilion"),
|
||||
"m_0394_e" => Playing("Virtua Fighter 2"),
|
||||
"m_9417_e" => Playing("Zero Wing"),
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nintendo 64
|
||||
|
||||
"n_1653_e" or "n_1653_p" => Playing("1080º ™ Snowboarding"),
|
||||
"n_4868_e" or "n_4868_p" => Playing("Banjo Kazooie™"),
|
||||
"n_1226_e" or "n_1226_p" => Playing("Banjo-Tooie™"),
|
||||
"n_3083_e" or "n_3083_p" => Playing("Blast Corps"),
|
||||
"n_3007_e" => Playing("Dr. Mario™ 64"),
|
||||
"n_4238_e" => Playing("Excitebike™ 64"),
|
||||
"n_1870_e" => Playing("Extreme G"),
|
||||
"n_2456_e" => Playing("F-Zero™ X"),
|
||||
"n_4631_e" => Playing("GoldenEye 007"),
|
||||
"n_1635_e" => Playing("Harvest Moon 64"),
|
||||
"n_2225_e" => Playing("Iggy’s Reckin’ Balls"),
|
||||
"n_1625_e" or "n_1625_p" => Playing("JET FORCE GEMINI™"),
|
||||
"n_3052_e" => Playing("Kirby 64™: The Crystal Shards"),
|
||||
"n_4371_e" => Playing("Mario Golf™"),
|
||||
"n_3013_e" => Playing("Mario Kart™ 64"),
|
||||
"n_1053_e" or "n_1053_p" => Playing("Mario Party™ 2"),
|
||||
"n_2965_e" or "n_2965_p" => Playing("Mario Party™ 3"),
|
||||
"n_4737_e" or "n_4737_p" => Playing("Mario Party™"),
|
||||
"n_3017_e" => Playing("Mario Tennis™"),
|
||||
"n_2992_e" or "n_2992_p" => Playing("Paper Mario™"),
|
||||
"n_3783_e" or "n_3783_p" => Playing("Pilotwings™ 64"),
|
||||
"n_1848_e" or "n_1848_pd" or "n_1848_pf" => Playing("Pokémon™ Puzzle League"),
|
||||
"n_3240_e" or "n_3240_pd" or "n_3240_pf" or "n_3240_pi" or "n_3240_ps" => Playing("Pokémon Snap™"),
|
||||
"n_4590_e" or "n_4590_pd" or "n_4590_pf" or "n_4590_pi" or "n_4590_ps" => Playing("Pokémon Stadium™"),
|
||||
"n_3309_e" or "n_3309_pd" or "n_3309_pf" or "n_3309_pi" or "n_3309_ps" => Playing("Pokémon Stadium 2™"),
|
||||
"n_3029_e" => Playing("Sin & Punishment™"),
|
||||
"n_3030_e" => Playing("Star Fox™ 64"),
|
||||
"n_3030_p" => Playing("Lylat Wars™"),
|
||||
"n_3031_e" or "n_3031_p" => Playing("Super Mario 64™"),
|
||||
"n_4813_e" or "n_4813_p" => Playing("Wave Race™ 64"),
|
||||
"n_3034_e" => Playing("WIN BACK: COVERT OPERATIONS"),
|
||||
"n_3034_p" => Playing("OPERATION: WIN BACK"),
|
||||
"n_3036_e" or "n_3036_p" => Playing("Yoshi's Story™"),
|
||||
"n_1407_e" or "n_1407_p" => Playing("The Legend of Zelda™: Majora's Mask™"),
|
||||
"n_3038_e" or "n_3038_p" => Playing("The Legend of Zelda™: Ocarina of Time™"),
|
||||
|
||||
#endregion
|
||||
|
||||
#region NES
|
||||
|
||||
"clv_p_naaae" => Playing("Super Mario Bros.™"),
|
||||
"clv_p_naabe" => Playing("Super Mario Bros.™: The Lost Levels"),
|
||||
"clv_p_naace" or "clv_p_naace_sp1" => Playing("Super Mario Bros.™ 3"),
|
||||
"clv_p_naade" => Playing("Super Mario Bros.™ 2"),
|
||||
"clv_p_naaee" => Playing("Donkey Kong™"),
|
||||
"clv_p_naafe" => Playing("Donkey Kong Jr.™"),
|
||||
"clv_p_naage" => Playing("Donkey Kong™ 3"),
|
||||
"clv_p_naahe" => Playing("Excitebike™"),
|
||||
"clv_p_naaje" => Playing("EarthBound Beginnings"),
|
||||
"clv_p_naame" => Playing("NES™ Open Tournament Golf"),
|
||||
"clv_p_naane" or "clv_p_naane_sp1" => Playing("The Legend of Zelda™"),
|
||||
"clv_p_naape" or "clv_p_naape_sp1" => Playing("Kirby's Adventure™"),
|
||||
"clv_p_naaqe" or "clv_p_naaqe_sp1" or "clv_p_naaqe_sp2" => Playing("Metroid™"),
|
||||
"clv_p_naare" => Playing("Balloon Fight™"),
|
||||
"clv_p_naase" or "clv_p_naase_sp1" => Playing("Zelda II - The Adventure of Link™"),
|
||||
"clv_p_naate" => Playing("Punch-Out!!™ Featuring Mr. Dream"),
|
||||
"clv_p_naaue" => Playing("Ice Climber™"),
|
||||
"clv_p_naave" or "clv_p_naave_sp1" => Playing("Kid Icarus™"),
|
||||
"clv_p_naawe" => Playing("Mario Bros.™"),
|
||||
"clv_p_naaxe" or "clv_p_naaxe_sp1" => Playing("Dr. Mario™"),
|
||||
"clv_p_naaye" => Playing("Yoshi™"),
|
||||
"clv_p_naaze" => Playing("StarTropics™"),
|
||||
"clv_p_nabce" or "clv_p_nabce_sp1" => Playing("Ghosts'n Goblins™"),
|
||||
"clv_p_nabre" or "clv_p_nabre_sp1" or "clv_p_nabre_sp2" => Playing("Gradius"),
|
||||
"clv_p_nacbe" or "clv_p_nacbe_sp1" => Playing("Ninja Gaiden"),
|
||||
"clv_p_nacce" => Playing("Solomon's Key"),
|
||||
"clv_p_nacde" => Playing("Tecmo Bowl"),
|
||||
"clv_p_nacfe" => Playing("Double Dragon"),
|
||||
"clv_p_nache" => Playing("Double Dragon II: The Revenge"),
|
||||
"clv_p_nacje" => Playing("River City Ransom"),
|
||||
"clv_p_nacke" => Playing("Super Dodge Ball"),
|
||||
"clv_p_nacle" => Playing("Downtown Nekketsu March Super-Awesome Field Day!"),
|
||||
"clv_p_nacpe" => Playing("The Mystery of Atlantis"),
|
||||
"clv_p_nacre" => Playing("Soccer"),
|
||||
"clv_p_nacse" or "clv_p_nacse_sp1" => Playing("Ninja JaJaMaru-kun"),
|
||||
"clv_p_nacte" => Playing("Ice Hockey"),
|
||||
"clv_p_nacue" or "clv_p_nacue_sp1" => Playing("Blaster Master"),
|
||||
"clv_p_nacwe" => Playing("ADVENTURES OF LOLO"),
|
||||
"clv_p_nacxe" => Playing("Wario's Woods™"),
|
||||
"clv_p_nacye" => Playing("Tennis"),
|
||||
"clv_p_nacze" => Playing("Wrecking Crew™"),
|
||||
"clv_p_nadbe" => Playing("Joy Mech Fight™"),
|
||||
"clv_p_nadde" or "clv_p_nadde_sp1" => Playing("Star Soldier"),
|
||||
"clv_p_nadke" => Playing("Tetris®"),
|
||||
"clv_p_nadle" => Playing("Pro Wrestling"),
|
||||
"clv_p_nadpe" => Playing("Baseball"),
|
||||
"clv_p_nadte" or "clv_p_nadte_sp1" => Playing("TwinBee"),
|
||||
"clv_p_nadue" or "clv_p_nadue_sp1" => Playing("Mighty Bomb Jack"),
|
||||
"clv_p_nadve" => Playing("Kung-Fu Heroes"),
|
||||
"clv_p_nadxe" => Playing("City Connection"),
|
||||
"clv_p_nadye" => Playing("Rygar"),
|
||||
"clv_p_naeae" => Playing("Crystalis"),
|
||||
"clv_p_naece" => Playing("Vice: Project Doom"),
|
||||
"clv_p_naehe" => Playing("Clu Clu Land™"),
|
||||
"clv_p_naeie" => Playing("VS. Excitebike™"),
|
||||
"clv_p_naeje" => Playing("Volleyball™"),
|
||||
"clv_p_naeke" => Playing("JOURNEY TO SILIUS"),
|
||||
"clv_p_naele" => Playing("S.C.A.T.: Special Cybernetic Attack Team"),
|
||||
"clv_p_naeme" => Playing("Shadow of the Ninja"),
|
||||
"clv_p_naene" => Playing("Nightshade"),
|
||||
"clv_p_naepe" => Playing("The Immortal"),
|
||||
"clv_p_naeqe" => Playing("Eliminator Boat Duel"),
|
||||
"clv_p_naere" => Playing("Fire 'n Ice"),
|
||||
"clv_p_nafce" => Playing("XEVIOUS"),
|
||||
"clv_p_nagpe" => Playing("DAIVA STORY 6 IMPERIAL OF NIRSARTIA"),
|
||||
"clv_p_nagqe" => Playing("DIG DUGⅡ"),
|
||||
"clv_p_nague" => Playing("MAPPY-LAND"),
|
||||
"clv_p_nahhe" => Playing("Mach Rider™"),
|
||||
"clv_p_nahje" => Playing("Pinball"),
|
||||
"clv_p_nahre" => Playing("Mystery Tower"),
|
||||
"clv_p_nahte" => Playing("Urban Champion™"),
|
||||
"clv_p_nahue" => Playing("Donkey Kong Jr.™ Math"),
|
||||
"clv_p_nahve" => Playing("The Mysterious Murasame Castle"),
|
||||
"clv_p_najae" => Playing("DEVIL WORLD™"),
|
||||
"clv_p_najbe" => Playing("Golf"),
|
||||
"clv_p_najpe" => Playing("R.C. PRO-AM™"),
|
||||
"clv_p_najre" => Playing("COBRA TRIANGLE™"),
|
||||
"clv_p_najse" => Playing("SNAKE RATTLE N ROLL™"),
|
||||
"clv_p_najte" => Playing("SOLAR® JETMAN"),
|
||||
|
||||
#endregion
|
||||
|
||||
#region SNES
|
||||
|
||||
"s_2180_e" => Playing("BATTLETOADS™ DOUBLE DRAGON™"),
|
||||
"s_2179_e" => Playing("BATTLETOADS™ IN BATTLEMANIACS"),
|
||||
"s_2182_e" => Playing("BIG RUN"),
|
||||
"s_2156_e" => Playing("Bombuzal"),
|
||||
"s_2002_e" => Playing("BRAWL BROTHERS"),
|
||||
"s_2025_e" => Playing("Breath of Fire II"),
|
||||
"s_2003_e" => Playing("Breath Of Fire"),
|
||||
"s_2163_e" => Playing("Claymates"),
|
||||
"s_2150_e" => Playing("Congo's Caper"),
|
||||
"s_2171_e" => Playing("COSMO GANG THE PUZZLE"),
|
||||
"s_2004_e" => Playing("Demon's Crest"),
|
||||
"s_2026_e" => Playing("Kunio-kun no Dodgeball da yo Zen'in Shūgō!"),
|
||||
"s_2060_e" => Playing("Donkey Kong Country 2: Diddy's Kong Quest"),
|
||||
"s_2061_e" => Playing("Donkey Kong Country 3: Dixie Kong's Double Trouble!"),
|
||||
"s_2055_e" => Playing("Donkey Kong Country"),
|
||||
"s_2139_e" => Playing("DOOMSDAY WARRIOR"),
|
||||
"s_2051_e" => Playing("EarthBound"),
|
||||
"s_2162_e" => Playing("Earthworm Jim™ 2"),
|
||||
"s_2005_e" => Playing("F-ZERO™"),
|
||||
"s_2183_e" => Playing("FATAL FURY 2"),
|
||||
"s_2174_e" => Playing("Fighter's History"),
|
||||
"s_2037_e" => Playing("Harvest Moon"),
|
||||
"s_2161_e" => Playing("Jelly Boy"),
|
||||
"s_2006_e" => Playing("Joe & Mac 2: Lost in the Tropics"),
|
||||
"s_2169_e" => Playing("Caveman Ninja"),
|
||||
"s_2181_e" => Playing("KILLER INSTINCT™"),
|
||||
"s_2029_e" or "s_2029_e_sp1" => Playing("Kirby Super Star™"),
|
||||
"s_2121_e" => Playing("Kirby's Avalanche™"),
|
||||
"s_2007_e" or "s_2007_e_sp1" => Playing("Kirby's Dream Course™"),
|
||||
"s_2008_e" or "s_2008_e_sp1" => Playing("Kirby's Dream Land™ 3"),
|
||||
"s_2172_e" => Playing("Kirby’s Star Stacker™"),
|
||||
"s_2151_e" => Playing("Magical Drop2"),
|
||||
"s_2044_e" => Playing("Mario's Super Picross"),
|
||||
"s_2038_e" => Playing("Natsume Championship Wrestling"),
|
||||
"s_2140_e" => Playing("Operation Logic Bomb"),
|
||||
"s_2034_e" => Playing("Panel de Pon"),
|
||||
"s_2009_e" => Playing("Pilotwings™"),
|
||||
"s_2010_e" => Playing("Pop'n TwinBee"),
|
||||
"s_2157_e" => Playing("Prehistorik Man"),
|
||||
"s_2145_e" => Playing("Psycho Dream"),
|
||||
"s_2141_e" => Playing("Rival Turf!"),
|
||||
"s_2152_e" => Playing("SIDE POCKET"),
|
||||
"s_2158_e" => Playing("Spanky’s™ Quest"),
|
||||
"s_2031_e" => Playing("Star Fox™ 2"),
|
||||
"s_2011_e" => Playing("Star Fox™"),
|
||||
"s_2012_e" => Playing("Stunt Race FX™"),
|
||||
"s_2032_e" => Playing("Amazing Hebereke"),
|
||||
"s_2159_e" => Playing("Super Baseball Simulator 1.000"),
|
||||
"s_2013_e" => Playing("SUPER E.D.F. EARTH DEFENSE FORCE"),
|
||||
"s_2014_e" => Playing("Smash Tennis"),
|
||||
"s_2015_e" => Playing("Super Ghouls'n Ghosts™"),
|
||||
"s_2033_e" => Playing("Super Mario All-Stars™"),
|
||||
"s_2016_e" or "s_2016_e_sp1" => Playing("Super Mario Kart™"),
|
||||
"s_2017_e" or "s_2017_e_sp1" => Playing("Super Mario World™"),
|
||||
"s_2018_e" or "s_2018_e_sp1" => Playing("Super Metroid™"),
|
||||
"s_2184_e" => Playing("Super Ninja Boy"),
|
||||
"s_2019_e" or "s_2019_e_sp1" => Playing("Super Punch-Out!!™"),
|
||||
"s_2020_e" => Playing("Super Puyo Puyo 2"),
|
||||
"s_2133_e" => Playing("SUPER R-TYPE"),
|
||||
"s_2021_e" => Playing("Super Soccer"),
|
||||
"s_2022_e" => Playing("Super Tennis"),
|
||||
"s_2136_e" => Playing("Sutte Hakkun"),
|
||||
"s_2142_e" => Playing("The Ignition Factor"),
|
||||
"s_2143_e" => Playing("The Peace Keepers"),
|
||||
"s_2146_e" => Playing("Tuff E Nuff"),
|
||||
"s_2144_e" => Playing("SUPER VALIS Ⅳ"),
|
||||
"s_2049_e" => Playing("Wild Guns"),
|
||||
"s_2096_e" => Playing("Wrecking Crew™ '98"),
|
||||
"s_2023_e" => Playing("Super Mario World™ 2: Yoshi's Island™"),
|
||||
"s_2024_e" => Playing("The Legend of Zelda™: A Link to the Past™"),
|
||||
|
||||
#endregion
|
||||
|
||||
#region GameBoy
|
||||
|
||||
"c_7224_e" or "c_7224_p" => Playing("Alone in the Dark: The New Nightmare"),
|
||||
"c_5022_e" => Playing("Blaster Master: Enemy Below"),
|
||||
"c_3381_e" => Playing("Game & Watch™ Gallery 3"),
|
||||
"c_0282_e" => Playing("Kirby Tilt ‘n’ Tumble™"),
|
||||
"c_4471_e" or "c_4471_p" => Playing("Mario Golf™"),
|
||||
"c_9947_e" => Playing("Mario Tennis™"),
|
||||
"c_3191_e" or "c_3191_p" or "c_3191_x" => Playing("Pokémon™ Trading Card Game"),
|
||||
"c_8914_e" or "c_8914_p" => Playing("Quest for Camelot™"),
|
||||
"c_2648_e" => Playing("Tetris® DX"),
|
||||
"c_5928_e" => Playing("Wario Land™ 3"),
|
||||
"c_3996_e" or "c_3996_pd" or "c_3996_pf" => Playing("The Legend of Zelda™: Link's Awakening DX™"),
|
||||
"c_8852_e" or "c_8852_p" => Playing("The Legend of Zelda™: Oracle of Ages™"),
|
||||
"c_9130_e" or "c_9130_p" => Playing("The Legend of Zelda™: Oracle of Seasons™"),
|
||||
"d_6879_e" => Playing("Alleyway™"),
|
||||
"d_7618_e" => Playing("Baseball"),
|
||||
"d_6005_e" => Playing("BurgerTime Deluxe"),
|
||||
"d_7120_e" => Playing("Castlevania Legends"),
|
||||
"d_2744_e" => Playing("Dr. Mario™"),
|
||||
"d_1593_e" => Playing("Donkey Kong Land 2™"),
|
||||
"d_7216_e" => Playing("Donkey Kong Land III™"),
|
||||
"d_4971_e" => Playing("Donkey Kong Land™"),
|
||||
"d_7984_e" => Playing("GARGOYLE'S QUEST"),
|
||||
"d_8212_e" => Playing("Kirby's Dream Land™ 2"),
|
||||
"d_5661_e" => Playing("Kirby's Dream Land™"),
|
||||
"d_3837_e" => Playing("MEGA MAN II"),
|
||||
"d_1965_e" => Playing("MEGA MAN III"),
|
||||
"d_0194_e" => Playing("MEGA MAN IV"),
|
||||
"d_1425_e" => Playing("MEGA MAN V"),
|
||||
"d_9324_e" => Playing("MEGA MAN: DR. WILY'S REVENGE"),
|
||||
"d_1577_e" => Playing("Metroid™ II - Return of Samus™"),
|
||||
"d_5124_e" => Playing("Super Mario Land™ 2 - 6 Golden Coins™"),
|
||||
"d_7970_e" => Playing("Super Mario Land™"),
|
||||
"d_8484_e" => Playing("Tetris®"),
|
||||
|
||||
#endregion
|
||||
|
||||
#region GameBoy Advance
|
||||
|
||||
"a_9694_e" => Playing("Densetsu no Starfy 1"),
|
||||
"a_5600_e" => Playing("Densetsu no Starfy 2"),
|
||||
"a_7565_e" => Playing("Densetsu no Starfy 3"),
|
||||
"a_6553_e" => Playing("F-ZERO CLIMAX"),
|
||||
"a_7842_e" or "a_7842_p" => Playing("F-Zero™- GP Legend"),
|
||||
"a_9283_e" => Playing("F-Zero™ Maximum Velocity"),
|
||||
"a_3744_e" or "a_3744_x" or "a_3744_y" => Playing("Fire Emblem™"),
|
||||
"a_8978_d" or "a_8978_e" or "a_8978_f" or "a_8978_i" or "a_8978_s" => Playing("Golden Sun™: The Lost Age"),
|
||||
"a_3108_d" or "a_3108_e" or "a_3108_f" or "a_3108_i" or "a_3108_s" => Playing("Golden Sun™"),
|
||||
"a_3654_e" or "a_3654_p" => Playing("Kirby™ & The Amazing Mirror"),
|
||||
"a_7279_p" => Playing("Kuru Kuru Kururin™"),
|
||||
"a_7311_e" or "a_7311_p" => Playing("Mario & Luigi™: Superstar Saga"),
|
||||
"a_6845_e" => Playing("Mario Kart™: Super Circuit™"),
|
||||
"a_4139_e" or "a_4139_p" => Playing("Metroid™ Fusion"),
|
||||
"a_6834_e" or "a_6834_p" => Playing("Metroid™: Zero Mission"),
|
||||
"a_8989_e" or "a_8989_p" => Playing("Pokémon™ Mystery Dungeon: Red Rescue Team"),
|
||||
"a_9444_e" => Playing("Super Mario™ Advance"),
|
||||
"a_9901_e" or "a_9901_p" => Playing("Super Mario™ Advance 4: Super Mario Bros.™ 3"),
|
||||
"a_2939_e" => Playing("Super Mario World™: Super Mario Advance 2"),
|
||||
"a_2939_p" => Playing("Super Mario World™: Super Mario Advance 2™"),
|
||||
"a_1302_e" => Playing("WarioWare™, Inc.: Mega Microgame$!"),
|
||||
"a_1302_p" => Playing("WarioWare™, Inc.: Minigame Mania."),
|
||||
"a_6960_e" or "a_6960_p" => Playing("Yoshi's Island™: Super Mario™ Advance 3"),
|
||||
"a_5190_e" or "a_5190_p" => Playing("The Legend of Zelda™: A Link to the Past™ Four Swords"),
|
||||
"a_8665_e" or "a_8665_p" => Playing("The Legend of Zelda™: The Minish Cap"),
|
||||
|
||||
#endregion
|
||||
|
||||
_ => FormattedValue.ForceReset
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
{
|
||||
public static partial class PlayReports
|
||||
public static class PlayReports
|
||||
{
|
||||
public static Analyzer Analyzer { get; } = new Analyzer()
|
||||
.AddSpec(
|
||||
@@ -37,32 +37,91 @@
|
||||
spec => spec
|
||||
.AddValueFormatter("area_no", PokemonSVArea)
|
||||
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
|
||||
)
|
||||
.AddSpec(
|
||||
"01006a800016e000",
|
||||
spec => spec
|
||||
.AddSparseMultiValueFormatter(
|
||||
[
|
||||
// Metadata to figure out what PlayReport we have.
|
||||
"match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count",
|
||||
"adv_slot",
|
||||
// List of Fighters
|
||||
"player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter",
|
||||
"player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter",
|
||||
// List of rankings/placements
|
||||
"player_1_rank", "player_2_rank", "player_3_rank", "player_4_rank", "player_5_rank",
|
||||
"player_6_rank", "player_7_rank", "player_8_rank"
|
||||
],
|
||||
SuperSmashBrosUltimate_Mode
|
||||
)
|
||||
)
|
||||
.AddSpec(
|
||||
[
|
||||
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
|
||||
"010012f017576000", "0100c62011050000", "0100b3c014bda000"],
|
||||
spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
|
||||
);
|
||||
|
||||
private static string Playing(string game) => $"Playing {game}";
|
||||
private static FormattedValue BreathOfTheWild_MasterMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
|
||||
|
||||
private static FormattedValue TearsOfTheKingdom_CurrentField(SingleValue value) =>
|
||||
value.Matched.DoubleValue switch
|
||||
{
|
||||
> 800d => "Exploring the Sky Islands",
|
||||
< -201d => "Exploring the Depths",
|
||||
_ => "Roaming Hyrule"
|
||||
};
|
||||
|
||||
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
||||
|
||||
private static FormattedValue SuperMarioOdysseyChina_AssistMode(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
|
||||
|
||||
private static FormattedValue SuperMario3DWorldOrBowsersFury(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
|
||||
|
||||
private static FormattedValue MarioKart8Deluxe_Mode(SingleValue value)
|
||||
=> value.Matched.StringValue switch
|
||||
{
|
||||
// Single Player
|
||||
"Single" => "Single Player",
|
||||
// Multiplayer
|
||||
"Multi-2players" => "Multiplayer 2 Players",
|
||||
"Multi-3players" => "Multiplayer 3 Players",
|
||||
"Multi-4players" => "Multiplayer 4 Players",
|
||||
// Wireless/LAN Play
|
||||
"Local-Single" => "Wireless/LAN Play",
|
||||
"Local-2players" => "Wireless/LAN Play 2 Players",
|
||||
// CC Classes
|
||||
"50cc" => "50cc",
|
||||
"100cc" => "100cc",
|
||||
"150cc" => "150cc",
|
||||
"Mirror" => "Mirror (150cc)",
|
||||
"200cc" => "200cc",
|
||||
// Modes
|
||||
"GrandPrix" => "Grand Prix",
|
||||
"TimeAttack" => "Time Trials",
|
||||
"VS" => "VS Races",
|
||||
"Battle" => "Battle Mode",
|
||||
"RaceStart" => "Selecting a Course",
|
||||
"Race" => "Racing",
|
||||
_ => FormattedValue.ForceReset
|
||||
};
|
||||
|
||||
private static FormattedValue PokemonSVUnionCircle(SingleValue value)
|
||||
=> value.Matched.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
|
||||
|
||||
private static FormattedValue PokemonSVArea(SingleValue value)
|
||||
=> value.Matched.StringValue switch
|
||||
{
|
||||
// Base Game Locations
|
||||
"a_w01" => "South Area One",
|
||||
"a_w02" => "Mesagoza",
|
||||
"a_w03" => "The Pokemon League",
|
||||
"a_w04" => "South Area Two",
|
||||
"a_w05" => "South Area Four",
|
||||
"a_w06" => "South Area Six",
|
||||
"a_w07" => "South Area Five",
|
||||
"a_w08" => "South Area Three",
|
||||
"a_w09" => "West Area One",
|
||||
"a_w10" => "Asado Desert",
|
||||
"a_w11" => "West Area Two",
|
||||
"a_w12" => "Medali",
|
||||
"a_w13" => "Tagtree Thicket",
|
||||
"a_w14" => "East Area Three",
|
||||
"a_w15" => "Artazon",
|
||||
"a_w16" => "East Area Two",
|
||||
"a_w18" => "Casseroya Lake",
|
||||
"a_w19" => "Glaseado Mountain",
|
||||
"a_w20" => "North Area Three",
|
||||
"a_w21" => "North Area One",
|
||||
"a_w22" => "North Area Two",
|
||||
"a_w23" => "The Great Crater of Paldea",
|
||||
"a_w24" => "South Paldean Sea",
|
||||
"a_w25" => "West Paldean Sea",
|
||||
"a_w26" => "East Paldean Sea",
|
||||
"a_w27" => "Nouth Paldean Sea",
|
||||
//TODO DLC Locations
|
||||
_ => FormattedValue.ForceReset
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MsgPack;
|
||||
using FluentAvalonia.Core;
|
||||
using MsgPack;
|
||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,14 +14,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
/// </summary>
|
||||
public class GameSpec
|
||||
{
|
||||
public static GameSpec Create(string requiredTitleId, params IEnumerable<string> otherTitleIds)
|
||||
=> new() { TitleIds = otherTitleIds.Prepend(requiredTitleId).ToArray() };
|
||||
|
||||
public static GameSpec Create(IEnumerable<string> titleIds)
|
||||
=> new() { TitleIds = titleIds.ToArray() };
|
||||
|
||||
private int _lastPriority;
|
||||
|
||||
|
||||
public required string[] TitleIds { get; init; }
|
||||
|
||||
public List<FormatterSpecBase> ValueFormatters { get; } = [];
|
||||
@@ -33,10 +28,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
/// <param name="reportKey">The key name to match.</param>
|
||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(
|
||||
string reportKey,
|
||||
SingleValueFormatter valueFormatter
|
||||
) => AddValueFormatter(_lastPriority++, reportKey, valueFormatter);
|
||||
public GameSpec AddValueFormatter(string reportKey, SingleValueFormatter valueFormatter)
|
||||
=> AddValueFormatter(_lastPriority++, reportKey, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Add a value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
@@ -46,14 +39,15 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
/// <param name="reportKey">The key name to match.</param>
|
||||
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddValueFormatter(
|
||||
int priority,
|
||||
string reportKey,
|
||||
SingleValueFormatter valueFormatter
|
||||
) => AddValueFormatter(new FormatterSpec
|
||||
public GameSpec AddValueFormatter(int priority, string reportKey,
|
||||
SingleValueFormatter valueFormatter)
|
||||
{
|
||||
Priority = priority, ReportKeys = [reportKey], Formatter = valueFormatter
|
||||
});
|
||||
ValueFormatters.Add(new FormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = [reportKey], Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
|
||||
@@ -62,10 +56,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(
|
||||
string[] reportKeys,
|
||||
MultiValueFormatter valueFormatter
|
||||
) => AddMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
|
||||
public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
|
||||
=> AddMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
@@ -75,14 +67,15 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddMultiValueFormatter(
|
||||
int priority,
|
||||
string[] reportKeys,
|
||||
MultiValueFormatter valueFormatter
|
||||
) => AddValueFormatter(new MultiFormatterSpec
|
||||
public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
|
||||
MultiValueFormatter valueFormatter)
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
|
||||
});
|
||||
ValueFormatters.Add(new MultiFormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
|
||||
@@ -94,10 +87,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddSparseMultiValueFormatter(
|
||||
string[] reportKeys,
|
||||
SparseMultiValueFormatter valueFormatter
|
||||
) => AddSparseMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
|
||||
public GameSpec AddSparseMultiValueFormatter(string[] reportKeys, SparseMultiValueFormatter valueFormatter)
|
||||
=> AddSparseMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
|
||||
|
||||
/// <summary>
|
||||
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
|
||||
@@ -110,18 +101,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
/// <param name="reportKeys">The key names to match.</param>
|
||||
/// <param name="valueFormatter">The function which can format the values.</param>
|
||||
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
|
||||
public GameSpec AddSparseMultiValueFormatter(
|
||||
int priority,
|
||||
string[] reportKeys,
|
||||
SparseMultiValueFormatter valueFormatter
|
||||
) => AddValueFormatter(new SparseMultiFormatterSpec
|
||||
public GameSpec AddSparseMultiValueFormatter(int priority, string[] reportKeys,
|
||||
SparseMultiValueFormatter valueFormatter)
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
|
||||
});
|
||||
|
||||
private GameSpec AddValueFormatter<T>(T formatterSpec) where T : FormatterSpecBase
|
||||
{
|
||||
ValueFormatters.Add(formatterSpec);
|
||||
ValueFormatters.Add(new SparseMultiFormatterSpec
|
||||
{
|
||||
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -152,7 +138,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
|
||||
{
|
||||
List<MessagePackObject> packedObjects = [];
|
||||
foreach (string reportKey in ReportKeys)
|
||||
foreach (var reportKey in ReportKeys)
|
||||
{
|
||||
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||
{
|
||||
@@ -176,7 +162,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
|
||||
{
|
||||
Dictionary<string, MessagePackObject> packedObjects = [];
|
||||
foreach (string reportKey in ReportKeys)
|
||||
foreach (var reportKey in ReportKeys)
|
||||
{
|
||||
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||
continue;
|
||||
@@ -188,17 +174,16 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public abstract class FormatterSpecBase
|
||||
{
|
||||
public abstract bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object data);
|
||||
|
||||
|
||||
public int Priority { get; init; }
|
||||
public string[] ReportKeys { get; init; }
|
||||
public Delegate Formatter { get; init; }
|
||||
|
||||
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
|
||||
out FormattedValue formattedValue)
|
||||
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, out FormattedValue formattedValue)
|
||||
{
|
||||
formattedValue = default;
|
||||
if (!GetData(playReport, out object data))
|
||||
@@ -212,20 +197,15 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
||||
|
||||
switch (Formatter)
|
||||
{
|
||||
case SingleValueFormatter svf when data is MessagePackObject match:
|
||||
formattedValue = svf(
|
||||
new SingleValue(match) { Application = appMeta, PlayReport = playReport }
|
||||
);
|
||||
case SingleValueFormatter svf when data is MessagePackObject mpo:
|
||||
formattedValue = svf(new SingleValue(mpo) { Application = appMeta, PlayReport = playReport });
|
||||
return true;
|
||||
case MultiValueFormatter mvf when data is List<MessagePackObject> matches:
|
||||
formattedValue = mvf(
|
||||
new MultiValue(matches) { Application = appMeta, PlayReport = playReport }
|
||||
);
|
||||
case MultiValueFormatter mvf when data is List<MessagePackObject> messagePackObjects:
|
||||
formattedValue = mvf(new MultiValue(messagePackObjects) { Application = appMeta, PlayReport = playReport });
|
||||
return true;
|
||||
case SparseMultiValueFormatter smvf when data is Dictionary<string, MessagePackObject> sparseMatches:
|
||||
formattedValue = smvf(
|
||||
new SparseMultiValue(sparseMatches) { Application = appMeta, PlayReport = playReport }
|
||||
);
|
||||
case SparseMultiValueFormatter smvf when
|
||||
data is Dictionary<string, MessagePackObject> sparseMessagePackObjects:
|
||||
formattedValue = smvf(new SparseMultiValue(sparseMessagePackObjects) { Application = appMeta, PlayReport = playReport });
|
||||
return true;
|
||||
default:
|
||||
throw new InvalidOperationException("Formatter delegate is not of a known type!");
|
||||
|
||||
Reference in New Issue
Block a user