Merge branch 'master' into master
This commit is contained in:
commit
864cd57b51
@ -42,7 +42,7 @@
|
|||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||||
<PackageVersion Include="Gommon" Version="2.7.0.1" />
|
<PackageVersion Include="Gommon" Version="2.7.0.2" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="Sep" Version="0.6.0" />
|
<PackageVersion Include="Sep" Version="0.6.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
|
@ -284,7 +284,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
|
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
|
||||||
|
|
||||||
uint[] defaultCapabilities = {
|
uint[] defaultCapabilities = {
|
||||||
0x030363F7,
|
(((uint)KScheduler.CpuCoresCount - 1) << 24) + (((uint)KScheduler.CpuCoresCount - 1) << 16) + 0x63F7u,
|
||||||
0x1FFFFFCF,
|
0x1FFFFFCF,
|
||||||
0x207FFFEF,
|
0x207FFFEF,
|
||||||
0x47E0060F,
|
0x47E0060F,
|
||||||
|
@ -63,6 +63,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||||||
TickSource = tickSource;
|
TickSource = tickSource;
|
||||||
Device = device;
|
Device = device;
|
||||||
Memory = memory;
|
Memory = memory;
|
||||||
|
KScheduler.CpuCoresCount = device.CpuCoresCount;
|
||||||
|
|
||||||
Running = true;
|
Running = true;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.DefaultCpuCore = 3;
|
process.DefaultCpuCore = KScheduler.CpuCoresCount - 1;
|
||||||
|
|
||||||
context.Processes.TryAdd(process.Pid, process);
|
context.Processes.TryAdd(process.Pid, process);
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = Capabilities.InitializeForUser(capabilities, MemoryManager);
|
result = Capabilities.InitializeForUser(capabilities, MemoryManager, IsApplication);
|
||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
{
|
{
|
||||||
|
@ -35,15 +35,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
DebuggingFlags &= ~3u;
|
DebuggingFlags &= ~3u;
|
||||||
KernelReleaseVersion = KProcess.KernelVersionPacked;
|
KernelReleaseVersion = KProcess.KernelVersionPacked;
|
||||||
|
|
||||||
return Parse(capabilities, memoryManager);
|
return Parse(capabilities, memoryManager, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
|
public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager, bool isApplication)
|
||||||
{
|
{
|
||||||
return Parse(capabilities, memoryManager);
|
return Parse(capabilities, memoryManager, isApplication);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
|
private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager, bool isApplication)
|
||||||
{
|
{
|
||||||
int mask0 = 0;
|
int mask0 = 0;
|
||||||
int mask1 = 0;
|
int mask1 = 0;
|
||||||
@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
if (cap.GetCapabilityType() != CapabilityType.MapRange)
|
if (cap.GetCapabilityType() != CapabilityType.MapRange)
|
||||||
{
|
{
|
||||||
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
|
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager, isApplication);
|
||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
{
|
{
|
||||||
@ -120,7 +120,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
|
private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager, bool isApplication)
|
||||||
{
|
{
|
||||||
CapabilityType code = cap.GetCapabilityType();
|
CapabilityType code = cap.GetCapabilityType();
|
||||||
|
|
||||||
@ -176,6 +176,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore);
|
AllowedCpuCoresMask = GetMaskFromMinMax(lowestCpuCore, highestCpuCore);
|
||||||
AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio);
|
AllowedThreadPriosMask = GetMaskFromMinMax(lowestThreadPrio, highestThreadPrio);
|
||||||
|
|
||||||
|
if (isApplication && lowestCpuCore == 0 && highestCpuCore != 2)
|
||||||
|
Ryujinx.Common.Logging.Logger.Error?.Print(Ryujinx.Common.Logging.LogClass.Application, $"Application requested cores with index range {lowestCpuCore} to {highestCpuCore}! Report this to @LotP on the Ryujinx/Ryubing discord server (discord.gg/ryujinx)!");
|
||||||
|
else if (isApplication)
|
||||||
|
Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Application, $"Application requested cores with index range {lowestCpuCore} to {highestCpuCore}");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2683,7 +2683,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
|||||||
return KernelResult.InvalidCombination;
|
return KernelResult.InvalidCombination;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((uint)preferredCore > 3)
|
if ((uint)preferredCore > KScheduler.CpuCoresCount - 1)
|
||||||
{
|
{
|
||||||
if ((preferredCore | 2) != -1)
|
if ((preferredCore | 2) != -1)
|
||||||
{
|
{
|
||||||
|
@ -9,13 +9,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
partial class KScheduler : IDisposable
|
partial class KScheduler : IDisposable
|
||||||
{
|
{
|
||||||
public const int PrioritiesCount = 64;
|
public const int PrioritiesCount = 64;
|
||||||
public const int CpuCoresCount = 4;
|
public static int CpuCoresCount;
|
||||||
|
|
||||||
private const int RoundRobinTimeQuantumMs = 10;
|
private const int RoundRobinTimeQuantumMs = 10;
|
||||||
|
|
||||||
private static readonly int[] _preemptionPriorities = { 59, 59, 59, 63 };
|
private static int[] _srcCoresHighestPrioThreads;
|
||||||
|
|
||||||
private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount];
|
|
||||||
|
|
||||||
private readonly KernelContext _context;
|
private readonly KernelContext _context;
|
||||||
private readonly int _coreId;
|
private readonly int _coreId;
|
||||||
@ -47,6 +45,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
_coreId = coreId;
|
_coreId = coreId;
|
||||||
|
|
||||||
_currentThread = null;
|
_currentThread = null;
|
||||||
|
|
||||||
|
if (_srcCoresHighestPrioThreads == null)
|
||||||
|
{
|
||||||
|
_srcCoresHighestPrioThreads = new int[CpuCoresCount];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int PreemptionPriorities(int index)
|
||||||
|
{
|
||||||
|
return index == CpuCoresCount - 1 ? 63 : 59;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ulong SelectThreads(KernelContext context)
|
public static ulong SelectThreads(KernelContext context)
|
||||||
@ -437,7 +445,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
for (int core = 0; core < CpuCoresCount; core++)
|
for (int core = 0; core < CpuCoresCount; core++)
|
||||||
{
|
{
|
||||||
RotateScheduledQueue(context, core, _preemptionPriorities[core]);
|
RotateScheduledQueue(context, core, PreemptionPriorities(core));
|
||||||
}
|
}
|
||||||
|
|
||||||
context.CriticalSection.Leave();
|
context.CriticalSection.Leave();
|
||||||
|
@ -24,14 +24,14 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
// not large enough.
|
// not large enough.
|
||||||
private const int PointerBufferSize = 0x8000;
|
private const int PointerBufferSize = 0x8000;
|
||||||
|
|
||||||
private readonly static uint[] _defaultCapabilities = {
|
private static uint[] _defaultCapabilities => [
|
||||||
0x030363F7,
|
(((uint)KScheduler.CpuCoresCount - 1) << 24) + (((uint)KScheduler.CpuCoresCount - 1) << 16) + 0x63F7u,
|
||||||
0x1FFFFFCF,
|
0x1FFFFFCF,
|
||||||
0x207FFFEF,
|
0x207FFFEF,
|
||||||
0x47E0060F,
|
0x47E0060F,
|
||||||
0x0048BFFF,
|
0x0048BFFF,
|
||||||
0x01007FFF,
|
0x01007FFF,
|
||||||
};
|
];
|
||||||
|
|
||||||
// The amount of time Dispose() will wait to Join() the thread executing the ServerLoop()
|
// The amount of time Dispose() will wait to Join() the thread executing the ServerLoop()
|
||||||
private static readonly TimeSpan _threadJoinTimeout = TimeSpan.FromSeconds(3);
|
private static readonly TimeSpan _threadJoinTimeout = TimeSpan.FromSeconds(3);
|
||||||
|
@ -32,6 +32,8 @@ namespace Ryujinx.HLE
|
|||||||
public TamperMachine TamperMachine { get; }
|
public TamperMachine TamperMachine { get; }
|
||||||
public IHostUIHandler UIHandler { get; }
|
public IHostUIHandler UIHandler { get; }
|
||||||
|
|
||||||
|
public int CpuCoresCount = 4; //Switch 1 has 4 cores
|
||||||
|
|
||||||
public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch;
|
public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch;
|
||||||
public bool CustomVSyncIntervalEnabled { get; set; } = false;
|
public bool CustomVSyncIntervalEnabled { get; set; } = false;
|
||||||
public int CustomVSyncInterval { get; set; }
|
public int CustomVSyncInterval { get; set; }
|
||||||
|
@ -22722,6 +22722,31 @@
|
|||||||
"zh_TW": ""
|
"zh_TW": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "CompatibilityListLastUpdated",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Last updated: {0}",
|
||||||
|
"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": "CompatibilityListWarning",
|
"ID": "CompatibilityListWarning",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using DiscordRPC;
|
using DiscordRPC;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Audio.Backends.SDL2;
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
using Ryujinx.Ava;
|
using Ryujinx.Ava;
|
||||||
|
@ -12,8 +12,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
private static readonly Lazy<PlayabilityStatusConverter> _shared = new(() => new());
|
private static readonly Lazy<PlayabilityStatusConverter> _shared = new(() => new());
|
||||||
public static PlayabilityStatusConverter Shared => _shared.Value;
|
public static PlayabilityStatusConverter Shared => _shared.Value;
|
||||||
|
|
||||||
public object Convert(object? value, Type _, object? __, CultureInfo ___) =>
|
public object Convert(object value, Type _, object __, CultureInfo ___)
|
||||||
value.Cast<LocaleKeys>() switch
|
=> value.Cast<LocaleKeys>() switch
|
||||||
{
|
{
|
||||||
LocaleKeys.CompatibilityListNothing or
|
LocaleKeys.CompatibilityListNothing or
|
||||||
LocaleKeys.CompatibilityListBoots or
|
LocaleKeys.CompatibilityListBoots or
|
||||||
@ -22,7 +22,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
_ => Brushes.ForestGreen
|
_ => Brushes.ForestGreen
|
||||||
};
|
};
|
||||||
|
|
||||||
public object ConvertBack(object? value, Type _, object? __, CultureInfo ___)
|
public object ConvertBack(object value, Type _, object __, CultureInfo ___)
|
||||||
=> throw new NotSupportedException();
|
=> throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -741,7 +741,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Applications.ToObservableChangeSet()
|
Applications.ToObservableChangeSet()
|
||||||
.Filter(Filter)
|
.Filter(Filter)
|
||||||
.Sort(GetComparer())
|
.Sort(GetComparer())
|
||||||
.Bind(out _appsObservableList).AsObservableList();
|
#pragma warning disable MVVMTK0034
|
||||||
|
.Bind(out _appsObservableList)
|
||||||
|
#pragma warning restore MVVMTK0034
|
||||||
|
.AsObservableList();
|
||||||
|
|
||||||
OnPropertyChanged(nameof(AppsObservableList));
|
OnPropertyChanged(nameof(AppsObservableList));
|
||||||
}
|
}
|
||||||
|
@ -32,29 +32,27 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
|
|
||||||
public string GetContentPath(ContentManager contentManager)
|
public string GetContentPath(ContentManager contentManager)
|
||||||
=> (contentManager ?? _contentManager)
|
=> (contentManager ?? _contentManager)
|
||||||
.GetInstalledContentPath(ProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
|
?.GetInstalledContentPath(ProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||||
|
|
||||||
public bool CanStart(ContentManager contentManager, out ApplicationData appData,
|
public bool CanStart(ContentManager contentManager, out ApplicationData appData,
|
||||||
out BlitStruct<ApplicationControlProperty> appControl)
|
out BlitStruct<ApplicationControlProperty> appControl)
|
||||||
{
|
{
|
||||||
contentManager ??= _contentManager;
|
contentManager ??= _contentManager;
|
||||||
if (contentManager == null)
|
if (contentManager == null)
|
||||||
{
|
goto BadData;
|
||||||
|
|
||||||
|
string contentPath = GetContentPath(contentManager);
|
||||||
|
if (string.IsNullOrEmpty(contentPath))
|
||||||
|
goto BadData;
|
||||||
|
|
||||||
|
appData = new() { Name = Name, Id = ProgramId, Path = GetContentPath(contentManager) };
|
||||||
|
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
BadData:
|
||||||
appData = null;
|
appData = null;
|
||||||
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
appData = new() { Name = Name, Id = ProgramId, Path = GetContentPath(contentManager) };
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(appData.Path))
|
|
||||||
{
|
|
||||||
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,66 @@
|
|||||||
using Gommon;
|
using Gommon;
|
||||||
|
using Humanizer;
|
||||||
using nietras.SeparatedValues;
|
using nietras.SeparatedValues;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Utilities.Compat
|
namespace Ryujinx.Ava.Utilities.Compat
|
||||||
{
|
{
|
||||||
|
public struct ColumnIndices(Func<ReadOnlySpan<char>, int> getIndex)
|
||||||
|
{
|
||||||
|
public const string TitleIdCol = "\"title_id\"";
|
||||||
|
public const string GameNameCol = "\"game_name\"";
|
||||||
|
public const string LabelsCol = "\"labels\"";
|
||||||
|
public const string StatusCol = "\"status\"";
|
||||||
|
public const string LastUpdatedCol = "\"last_updated\"";
|
||||||
|
|
||||||
|
public readonly int TitleId = getIndex(TitleIdCol);
|
||||||
|
public readonly int GameName = getIndex(GameNameCol);
|
||||||
|
public readonly int Labels = getIndex(LabelsCol);
|
||||||
|
public readonly int Status = getIndex(StatusCol);
|
||||||
|
public readonly int LastUpdated = getIndex(LastUpdatedCol);
|
||||||
|
}
|
||||||
|
|
||||||
public class CompatibilityCsv
|
public class CompatibilityCsv
|
||||||
{
|
{
|
||||||
public static CompatibilityCsv Shared { get; set; }
|
static CompatibilityCsv()
|
||||||
|
|
||||||
public CompatibilityCsv(SepReader reader)
|
|
||||||
{
|
{
|
||||||
var entries = new List<CompatibilityEntry>();
|
using Stream csvStream = Assembly.GetExecutingAssembly()
|
||||||
|
.GetManifestResourceStream("RyujinxGameCompatibilityList")!;
|
||||||
|
csvStream.Position = 0;
|
||||||
|
|
||||||
foreach (var row in reader)
|
using SepReader reader = Sep.Reader().From(csvStream);
|
||||||
{
|
ColumnIndices columnIndices = new(reader.Header.IndexOf);
|
||||||
entries.Add(new CompatibilityEntry(reader.Header, row));
|
|
||||||
|
Entries = reader
|
||||||
|
.Enumerate(row => new CompatibilityEntry(ref columnIndices, row))
|
||||||
|
.OrderBy(it => it.GameName)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatCsv");
|
||||||
}
|
}
|
||||||
|
|
||||||
Entries = entries.Where(x => x.Status != null)
|
public static CompatibilityEntry[] Entries { get; private set; }
|
||||||
.OrderBy(it => it.GameName).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompatibilityEntry[] Entries { get; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CompatibilityEntry
|
public class CompatibilityEntry
|
||||||
{
|
{
|
||||||
public CompatibilityEntry(SepReaderHeader header, SepReader.Row row)
|
public CompatibilityEntry(ref ColumnIndices indices, SepReader.Row row)
|
||||||
{
|
{
|
||||||
if (row.ColCount != header.ColNames.Count)
|
string titleIdRow = ColStr(row[indices.TitleId]);
|
||||||
throw new InvalidDataException($"CSV row {row.RowIndex} ({row.ToString()}) has mismatched column count");
|
|
||||||
|
|
||||||
var titleIdRow = ColStr(row[header.IndexOf("\"title_id\"")]);
|
|
||||||
TitleId = !string.IsNullOrEmpty(titleIdRow)
|
TitleId = !string.IsNullOrEmpty(titleIdRow)
|
||||||
? titleIdRow
|
? titleIdRow
|
||||||
: default(Optional<string>);
|
: default(Optional<string>);
|
||||||
|
|
||||||
GameName = ColStr(row[header.IndexOf("\"game_name\"")]).Trim().Trim('"');
|
GameName = ColStr(row[indices.GameName]).Trim().Trim('"');
|
||||||
|
|
||||||
IssueLabels = ColStr(row[header.IndexOf("\"labels\"")]).Split(';');
|
Labels = ColStr(row[indices.Labels]).Split(';');
|
||||||
Status = ColStr(row[header.IndexOf("\"status\"")]).ToLower() switch
|
Status = ColStr(row[indices.Status]).ToLower() switch
|
||||||
{
|
{
|
||||||
"playable" => LocaleKeys.CompatibilityListPlayable,
|
"playable" => LocaleKeys.CompatibilityListPlayable,
|
||||||
"ingame" => LocaleKeys.CompatibilityListIngame,
|
"ingame" => LocaleKeys.CompatibilityListIngame,
|
||||||
@ -54,8 +70,8 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DateTime.TryParse(ColStr(row[header.IndexOf("\"last_updated\"")]), out var dt))
|
if (DateTime.TryParse(ColStr(row[indices.LastUpdated]), out var dt))
|
||||||
LastEvent = dt;
|
LastUpdated = dt;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -64,27 +80,32 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
|
|
||||||
public string GameName { get; }
|
public string GameName { get; }
|
||||||
public Optional<string> TitleId { get; }
|
public Optional<string> TitleId { get; }
|
||||||
public string[] IssueLabels { get; }
|
public string[] Labels { get; }
|
||||||
public LocaleKeys? Status { get; }
|
public LocaleKeys? Status { get; }
|
||||||
public DateTime LastEvent { get; }
|
public DateTime LastUpdated { get; }
|
||||||
|
|
||||||
|
public string LocalizedLastUpdated =>
|
||||||
|
LocaleManager.FormatDynamicValue(LocaleKeys.CompatibilityListLastUpdated, LastUpdated.Humanize());
|
||||||
|
|
||||||
public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
|
public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
|
||||||
public string FormattedTitleId => TitleId
|
public string FormattedTitleId => TitleId
|
||||||
.OrElse(new string(' ', 16));
|
.OrElse(new string(' ', 16));
|
||||||
|
|
||||||
public string FormattedIssueLabels => IssueLabels
|
public string FormattedIssueLabels => Labels
|
||||||
.Where(it => !it.StartsWithIgnoreCase("status"))
|
.Where(it => !it.StartsWithIgnoreCase("status"))
|
||||||
.Select(FormatLabelName)
|
.Select(FormatLabelName)
|
||||||
.JoinToString(", ");
|
.JoinToString(", ");
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder("CompatibilityEntry: {");
|
StringBuilder sb = new("CompatibilityEntry: {");
|
||||||
sb.Append($"{nameof(GameName)}=\"{GameName}\", ");
|
sb.Append($"{nameof(GameName)}=\"{GameName}\", ");
|
||||||
sb.Append($"{nameof(TitleId)}={TitleId}, ");
|
sb.Append($"{nameof(TitleId)}={TitleId}, ");
|
||||||
sb.Append($"{nameof(IssueLabels)}=\"{IssueLabels}\", ");
|
sb.Append($"{nameof(Labels)}={
|
||||||
|
Labels.FormatCollection(it => $"\"{it}\"", separator: ", ", prefix: "[", suffix: "]")
|
||||||
|
}, ");
|
||||||
sb.Append($"{nameof(Status)}=\"{Status}\", ");
|
sb.Append($"{nameof(Status)}=\"{Status}\", ");
|
||||||
sb.Append($"{nameof(LastEvent)}=\"{LastEvent}\"");
|
sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"");
|
||||||
sb.Append('}');
|
sb.Append('}');
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
@ -140,8 +161,8 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
if (value == string.Empty)
|
if (value == string.Empty)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var firstChar = value[0];
|
char firstChar = value[0];
|
||||||
var rest = value[1..];
|
string rest = value[1..];
|
||||||
|
|
||||||
return $"{char.ToUpper(firstChar)}{rest}";
|
return $"{char.ToUpper(firstChar)}{rest}";
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,11 @@
|
|||||||
ItemsSource="{Binding CurrentEntries}">
|
ItemsSource="{Binding CurrentEntries}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate DataType="{x:Type local:CompatibilityEntry}">
|
<DataTemplate DataType="{x:Type local:CompatibilityEntry}">
|
||||||
<Grid Width="750" ColumnDefinitions="Auto,Auto,Auto,*"
|
<Grid Width="750"
|
||||||
Margin="5">
|
Margin="5"
|
||||||
|
ColumnDefinitions="Auto,Auto,Auto,*"
|
||||||
|
Background="Transparent"
|
||||||
|
ToolTip.Tip="{Binding LocalizedLastUpdated}">
|
||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
Text="{Binding GameName}"
|
Text="{Binding GameName}"
|
||||||
Width="320"
|
Width="320"
|
||||||
|
@ -14,15 +14,6 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
{
|
{
|
||||||
public static async Task Show()
|
public static async Task Show()
|
||||||
{
|
{
|
||||||
if (CompatibilityCsv.Shared is null)
|
|
||||||
{
|
|
||||||
await using Stream csvStream = Assembly.GetExecutingAssembly()
|
|
||||||
.GetManifestResourceStream("RyujinxGameCompatibilityList")!;
|
|
||||||
csvStream.Position = 0;
|
|
||||||
|
|
||||||
CompatibilityCsv.Shared = new CompatibilityCsv(Sep.Reader().From(csvStream));
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentDialog contentDialog = new()
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
PrimaryButtonText = string.Empty,
|
PrimaryButtonText = string.Empty,
|
||||||
@ -51,7 +42,7 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TextBox_OnTextChanged(object? sender, TextChangedEventArgs e)
|
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not CompatibilityViewModel cvm)
|
if (DataContext is not CompatibilityViewModel cvm)
|
||||||
return;
|
return;
|
||||||
|
@ -11,14 +11,13 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
{
|
{
|
||||||
[ObservableProperty] private bool _onlyShowOwnedGames = true;
|
[ObservableProperty] private bool _onlyShowOwnedGames = true;
|
||||||
|
|
||||||
private IEnumerable<CompatibilityEntry> _currentEntries = CompatibilityCsv.Shared.Entries;
|
private IEnumerable<CompatibilityEntry> _currentEntries = CompatibilityCsv.Entries;
|
||||||
private readonly string[] _ownedGameTitleIds = [];
|
private readonly string[] _ownedGameTitleIds = [];
|
||||||
private readonly ApplicationLibrary _appLibrary;
|
private readonly ApplicationLibrary _appLibrary;
|
||||||
|
|
||||||
public IEnumerable<CompatibilityEntry> CurrentEntries => OnlyShowOwnedGames
|
public IEnumerable<CompatibilityEntry> CurrentEntries => OnlyShowOwnedGames
|
||||||
? _currentEntries.Where(x =>
|
? _currentEntries.Where(x =>
|
||||||
x.TitleId.Check(tid => _ownedGameTitleIds.ContainsIgnoreCase(tid))
|
x.TitleId.Check(tid => _ownedGameTitleIds.ContainsIgnoreCase(tid)))
|
||||||
|| _appLibrary.Applications.Items.Any(a => a.Name.EqualsIgnoreCase(x.GameName)))
|
|
||||||
: _currentEntries;
|
: _currentEntries;
|
||||||
|
|
||||||
public CompatibilityViewModel() {}
|
public CompatibilityViewModel() {}
|
||||||
@ -39,11 +38,11 @@ namespace Ryujinx.Ava.Utilities.Compat
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(searchTerm))
|
if (string.IsNullOrEmpty(searchTerm))
|
||||||
{
|
{
|
||||||
SetEntries(CompatibilityCsv.Shared.Entries);
|
SetEntries(CompatibilityCsv.Entries);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetEntries(CompatibilityCsv.Shared.Entries.Where(x =>
|
SetEntries(CompatibilityCsv.Entries.Where(x =>
|
||||||
x.GameName.ContainsIgnoreCase(searchTerm)
|
x.GameName.ContainsIgnoreCase(searchTerm)
|
||||||
|| x.TitleId.Check(tid => tid.ContainsIgnoreCase(searchTerm))));
|
|| x.TitleId.Check(tid => tid.ContainsIgnoreCase(searchTerm))));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user