Compare commits

...

13 Commits

Author SHA1 Message Date
Evan Husted
606e149bd3 UI: Create a ColumnIndices struct and pass it by reference to the row ctor instead of recomputing the column index for every column on every row 2025-01-09 18:48:15 -06:00
Evan Husted
a8c3407d11 missing JP title id 2025-01-09 14:25:16 -06:00
Evan Husted
daa8168985 docs: compat: the final title IDs i could find 2025-01-09 14:03:37 -06:00
Vita Chumakova
f580521e99 Update game data in the compatibility database (#507)
The entries were matched with the game database from
https://github.com/blawar/titledb/blob/master/US.en.json, allowing to
fill missing title IDs and fix game names.
2025-01-09 13:18:27 -06:00
Evan Husted
2226521f6c docs: compat: remove quotes around everything but game titles 2025-01-08 12:36:26 -06:00
Evan Husted
384416953d docs: compat: list title ID column first 2025-01-08 12:30:13 -06:00
Evan Husted
1343fabe41 docs: compat: some more missing title ids 2025-01-08 12:20:20 -06:00
Evan Husted
1e52af5e29 docs: compat: Multiple big changes:
Sort alphabetically,
Remove title IDs in "issue_title" column,
and remove all entries without a playability status.
2025-01-07 20:17:09 -06:00
Evan Husted
672f5df0f9 docs: compat: Remove issue_number & events_count columns
That's mostly for archival purposes; we don't need it.
2025-01-07 18:49:04 -06:00
Evan Husted
804d9c1efe docs: compat: remove invalid dupe 2025-01-07 06:03:35 -06:00
Evan Husted
9270b35648 no. 2025-01-07 05:53:31 -06:00
Evan Husted
5a6d01db3c docs: compat: trine 4 -> nothing
added Soul Reaver 1 & 2
2025-01-07 05:50:30 -06:00
Evan Husted
ef9c1416ec UI: compat: Only use monospaced font for title ID 2025-01-07 04:49:20 -06:00
5 changed files with 3482 additions and 4364 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +1,71 @@
using Gommon; using Gommon;
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.Collections.Generic;
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(SepReaderHeader header)
{
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 = header.IndexOf(TitleIdCol);
public readonly int GameName = header.IndexOf(GameNameCol);
public readonly int Labels = header.IndexOf(LabelsCol);
public readonly int Status = header.IndexOf(StatusCol);
public readonly int LastUpdated = header.IndexOf(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) LoadFromStream(csvStream);
{
entries.Add(new CompatibilityEntry(reader.Header, row));
} }
Entries = entries.Where(x => x.Status != null) public static void LoadFromStream(Stream stream)
.OrderBy(it => it.GameName).ToArray(); {
var reader = Sep.Reader().From(stream);
var columnIndices = new ColumnIndices(reader.Header);
Entries = reader
.Enumerate(row => new CompatibilityEntry(ref columnIndices, row))
.OrderBy(it => it.GameName)
.ToArray();
Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.");
} }
public CompatibilityEntry[] Entries { get; } public static CompatibilityEntry[] Entries { get; private set; }
} }
public class CompatibilityEntry public class CompatibilityEntry
{ {
public CompatibilityEntry(SepReaderHeader header, SepReader.Row row) public CompatibilityEntry(ref ColumnIndices indices, SepReader.Row row)
{ {
IssueNumber = row[header.IndexOf("issue_number")].Parse<int>(); var titleIdRow = ColStr(row[indices.TitleId]);
var titleIdRow = row[header.IndexOf("extracted_game_id")].ToString();
TitleId = !string.IsNullOrEmpty(titleIdRow) TitleId = !string.IsNullOrEmpty(titleIdRow)
? titleIdRow ? titleIdRow
: default(Optional<string>); : default(Optional<string>);
var issueTitleRow = row[header.IndexOf("issue_title")].ToString(); GameName = ColStr(row[indices.GameName]).Trim().Trim('"');
if (TitleId.HasValue)
issueTitleRow = issueTitleRow.ReplaceIgnoreCase($" - {TitleId}", string.Empty);
GameName = issueTitleRow.Trim().Trim('"'); Labels = ColStr(row[indices.Labels]).Split(';');
Status = ColStr(row[indices.Status]).ToLower() switch
IssueLabels = row[header.IndexOf("issue_labels")].ToString().Split(';');
Status = row[header.IndexOf("extracted_status")].ToString().ToLower() switch
{ {
"playable" => LocaleKeys.CompatibilityListPlayable, "playable" => LocaleKeys.CompatibilityListPlayable,
"ingame" => LocaleKeys.CompatibilityListIngame, "ingame" => LocaleKeys.CompatibilityListIngame,
@@ -56,25 +75,25 @@ namespace Ryujinx.Ava.Utilities.Compat
_ => null _ => null
}; };
if (row[header.IndexOf("last_event_date")].TryParse<DateTime>(out var dt)) if (DateTime.TryParse(ColStr(row[indices.LastUpdated]), out var dt))
LastEvent = dt; LastUpdated = dt;
if (row[header.IndexOf("events_count")].TryParse<int>(out var eventsCount)) return;
EventCount = eventsCount;
string ColStr(SepReader.Col col) => col.ToString().Trim('"');
} }
public int IssueNumber { get; }
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 int EventCount { get; }
public string LocalizedStatus => LocaleManager.Instance[Status!.Value]; public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
public string FormattedTitleId => TitleId.OrElse(new string(' ', 16)); public string FormattedTitleId => TitleId
.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(", ");
@@ -82,13 +101,11 @@ namespace Ryujinx.Ava.Utilities.Compat
public override string ToString() public override string ToString()
{ {
var sb = new StringBuilder("CompatibilityEntry: {"); var sb = new StringBuilder("CompatibilityEntry: {");
sb.Append($"{nameof(IssueNumber)}={IssueNumber}, ");
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}\", ");
sb.Append($"{nameof(Status)}=\"{Status}\", "); sb.Append($"{nameof(Status)}=\"{Status}\", ");
sb.Append($"{nameof(LastEvent)}=\"{LastEvent}\", "); sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"");
sb.Append($"{nameof(EventCount)}={EventCount}");
sb.Append('}'); sb.Append('}');
return sb.ToString(); return sb.ToString();

View File

@@ -47,9 +47,8 @@
<Grid Width="750" ColumnDefinitions="Auto,Auto,Auto,*" <Grid Width="750" ColumnDefinitions="Auto,Auto,Auto,*"
Margin="5"> Margin="5">
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
FontFamily="{StaticResource JetBrainsMono}"
Text="{Binding GameName}" Text="{Binding GameName}"
Width="333" Width="320"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Width="135" Width="135"
@@ -60,14 +59,12 @@
<TextBlock Grid.Column="2" <TextBlock Grid.Column="2"
Padding="7, 0" Padding="7, 0"
VerticalAlignment="Center" VerticalAlignment="Center"
FontFamily="{StaticResource JetBrainsMono}"
Text="{Binding LocalizedStatus}" Text="{Binding LocalizedStatus}"
Width="85" Width="85"
Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}" Foreground="{Binding Status, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
TextWrapping="NoWrap" /> TextWrapping="NoWrap" />
<TextBlock Grid.Column="3" <TextBlock Grid.Column="3"
VerticalAlignment="Center" VerticalAlignment="Center"
FontFamily="{StaticResource JetBrainsMono}"
Text="{Binding FormattedIssueLabels}" Text="{Binding FormattedIssueLabels}"
TextWrapping="WrapWithOverflow" /> TextWrapping="WrapWithOverflow" />
</Grid> </Grid>

View File

@@ -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,

View File

@@ -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))));
} }