diff --git a/Directory.Packages.props b/Directory.Packages.props
index 57db5394..c2427448 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -17,6 +17,7 @@
+
@@ -37,6 +38,7 @@
+
@@ -49,4 +51,4 @@
-
\ No newline at end of file
+
diff --git a/src/Ryujinx.Common/Ryujinx.Common.csproj b/src/Ryujinx.Common/Ryujinx.Common.csproj
index da2f13a2..11ce8543 100644
--- a/src/Ryujinx.Common/Ryujinx.Common.csproj
+++ b/src/Ryujinx.Common/Ryujinx.Common.csproj
@@ -10,6 +10,7 @@
+
diff --git a/src/Ryujinx/Modules/Compatibility/CompatibilityCsv.cs b/src/Ryujinx/Modules/Compatibility/CompatibilityCsv.cs
new file mode 100644
index 00000000..861891e8
--- /dev/null
+++ b/src/Ryujinx/Modules/Compatibility/CompatibilityCsv.cs
@@ -0,0 +1,150 @@
+using Humanizer;
+using nietras.SeparatedValues;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Common.Logging;
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace Ryujinx.Ava.Modules.Compatibility
+{
+ public struct ColumnIndices(Func 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
+ {
+ static CompatibilityCsv()
+ {
+ using Stream csvStream = Assembly.GetExecutingAssembly()
+ .GetManifestResourceStream("RyujinxGameCompatibilityList")!;
+ csvStream.Position = 0;
+
+ using SepReader reader = Sep.Reader().From(csvStream);
+ ColumnIndices columnIndices = new(reader.Header.IndexOf);
+
+ Entries = reader
+ .Enumerate(row => new CompatibilityEntry(ref columnIndices, row))
+ .OrderBy(it => it.GameName)
+ .ToArray();
+
+ Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatCsv");
+ }
+
+ public static CompatibilityEntry[] Entries { get; private set; }
+ }
+
+ public class CompatibilityEntry
+ {
+ public CompatibilityEntry(ref ColumnIndices indices, SepReader.Row row)
+ {
+ string titleIdRow = ColStr(row[indices.TitleId]);
+ TitleId = !string.IsNullOrEmpty(titleIdRow)
+ ? titleIdRow
+ : null;
+
+ GameName = ColStr(row[indices.GameName]);
+
+ Labels = ColStr(row[indices.Labels]).Split(';');
+ Status = ColStr(row[indices.Status]).ToLower() switch
+ {
+ "playable" => LocaleKeys.CompatibilityListPlayable,
+ "ingame" => LocaleKeys.CompatibilityListIngame,
+ "menus" => LocaleKeys.CompatibilityListMenus,
+ "boots" => LocaleKeys.CompatibilityListBoots,
+ "nothing" => LocaleKeys.CompatibilityListNothing,
+ _ => null
+ };
+
+ if (DateTime.TryParse(ColStr(row[indices.LastUpdated]), out var dt))
+ LastUpdated = dt;
+
+ return;
+
+ string ColStr(SepReader.Col col) => col.ToString().Trim('"');
+ }
+
+ public string GameName { get; }
+ public string TitleId { get; }
+ public string[] Labels { get; }
+ internal LocaleKeys? Status { get; }
+ public DateTime LastUpdated { get; }
+
+ public string LocalizedLastUpdated =>
+ LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CompatibilityListLastUpdated, LastUpdated.Humanize());
+
+ public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
+ public string FormattedTitleId => TitleId ?? new string(' ', 16);
+
+ public string FormattedIssueLabels => string.Join(",", Labels
+ .Select(FormatLabelName));
+
+ public static string FormatLabelName(string labelName) => labelName.ToLower() switch
+ {
+ "audio" => "Audio",
+ "bug" => "Bug",
+ "cpu" => "CPU",
+ "gpu" => "GPU",
+ "gui" => "GUI",
+ "help wanted" => "Help Wanted",
+ "horizon" => "Horizon",
+ "infra" => "Project Infra",
+ "invalid" => "Invalid",
+ "kernel" => "Kernel",
+ "ldn" => "LDN",
+ "linux" => "Linux",
+ "macos" => "macOS",
+ "question" => "Question",
+ "windows" => "Windows",
+ "graphics-backend:opengl" => "Graphics: OpenGL",
+ "graphics-backend:vulkan" => "Graphics: Vulkan",
+ "ldn-works" => "LDN Works",
+ "ldn-untested" => "LDN Untested",
+ "ldn-broken" => "LDN Broken",
+ "ldn-partial" => "Partial LDN",
+ "nvdec" => "NVDEC",
+ "services" => "NX Services",
+ "services-horizon" => "Horizon OS Services",
+ "slow" => "Runs Slow",
+ "crash" => "Crashes",
+ "deadlock" => "Deadlock",
+ "regression" => "Regression",
+ "opengl" => "OpenGL",
+ "opengl-backend-bug" => "OpenGL Backend Bug",
+ "vulkan-backend-bug" => "Vulkan Backend Bug",
+ "mac-bug" => "Mac-specific Bug(s)",
+ "amd-vendor-bug" => "AMD GPU Bug",
+ "intel-vendor-bug" => "Intel GPU Bug",
+ "loader-allocator" => "Loader Allocator",
+ "audout" => "AudOut",
+ "32-bit" => "32-bit Game",
+ "UE4" => "Unreal Engine 4",
+ "homebrew" => "Homebrew Content",
+ "online-broken" => "Online Broken",
+ _ => Capitalize(labelName)
+ };
+
+ public static string Capitalize(string value)
+ {
+ if (value == string.Empty)
+ return string.Empty;
+
+ char firstChar = value[0];
+ string rest = value[1..];
+
+ return $"{char.ToUpper(firstChar)}{rest}";
+ }
+ }
+}
diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj
index 8303fac5..5d390591 100644
--- a/src/Ryujinx/Ryujinx.csproj
+++ b/src/Ryujinx/Ryujinx.csproj
@@ -44,7 +44,6 @@
-
@@ -52,6 +51,7 @@
+
@@ -137,6 +137,9 @@
+
+ Assets\RyujinxGameCompatibility.csv
+