CompatibilityCsv.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. using Gommon;
  2. using Humanizer;
  3. using nietras.SeparatedValues;
  4. using Ryujinx.Ava.Common.Locale;
  5. using Ryujinx.Common.Logging;
  6. using System;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Reflection;
  10. using System.Text;
  11. namespace Ryujinx.Ava.Utilities.Compat
  12. {
  13. public struct ColumnIndices(Func<ReadOnlySpan<char>, int> getIndex)
  14. {
  15. public const string TitleIdCol = "\"title_id\"";
  16. public const string GameNameCol = "\"game_name\"";
  17. public const string LabelsCol = "\"labels\"";
  18. public const string StatusCol = "\"status\"";
  19. public const string LastUpdatedCol = "\"last_updated\"";
  20. public readonly int TitleId = getIndex(TitleIdCol);
  21. public readonly int GameName = getIndex(GameNameCol);
  22. public readonly int Labels = getIndex(LabelsCol);
  23. public readonly int Status = getIndex(StatusCol);
  24. public readonly int LastUpdated = getIndex(LastUpdatedCol);
  25. }
  26. public class CompatibilityCsv
  27. {
  28. static CompatibilityCsv() => Load();
  29. public static void Load()
  30. {
  31. using Stream csvStream = Assembly.GetExecutingAssembly()
  32. .GetManifestResourceStream("RyujinxGameCompatibilityList")!;
  33. csvStream.Position = 0;
  34. using SepReader reader = Sep.Reader().From(csvStream);
  35. ColumnIndices columnIndices = new(reader.Header.IndexOf);
  36. _entries = reader
  37. .Enumerate(row => new CompatibilityEntry(ref columnIndices, row))
  38. .OrderBy(it => it.GameName)
  39. .ToArray();
  40. Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatibility");
  41. }
  42. public static void Unload()
  43. {
  44. _entries = null;
  45. }
  46. private static CompatibilityEntry[] _entries;
  47. public static CompatibilityEntry[] Entries
  48. {
  49. get
  50. {
  51. if (_entries == null)
  52. Load();
  53. return _entries;
  54. }
  55. }
  56. }
  57. public class CompatibilityEntry
  58. {
  59. public CompatibilityEntry(ref ColumnIndices indices, SepReader.Row row)
  60. {
  61. string titleIdRow = ColStr(row[indices.TitleId]);
  62. TitleId = !string.IsNullOrEmpty(titleIdRow)
  63. ? titleIdRow
  64. : default(Optional<string>);
  65. GameName = ColStr(row[indices.GameName]);
  66. Labels = ColStr(row[indices.Labels]).Split(';');
  67. Status = ColStr(row[indices.Status]).ToLower() switch
  68. {
  69. "playable" => LocaleKeys.CompatibilityListPlayable,
  70. "ingame" => LocaleKeys.CompatibilityListIngame,
  71. "menus" => LocaleKeys.CompatibilityListMenus,
  72. "boots" => LocaleKeys.CompatibilityListBoots,
  73. "nothing" => LocaleKeys.CompatibilityListNothing,
  74. _ => null
  75. };
  76. if (DateTime.TryParse(ColStr(row[indices.LastUpdated]), out var dt))
  77. LastUpdated = dt;
  78. return;
  79. string ColStr(SepReader.Col col) => col.ToString().Trim('"');
  80. }
  81. public string GameName { get; }
  82. public Optional<string> TitleId { get; }
  83. public string[] Labels { get; }
  84. public LocaleKeys? Status { get; }
  85. public DateTime LastUpdated { get; }
  86. public string LocalizedLastUpdated =>
  87. LocaleManager.FormatDynamicValue(LocaleKeys.CompatibilityListLastUpdated, LastUpdated.Humanize());
  88. public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
  89. public string FormattedTitleId => TitleId
  90. .OrElse(new string(' ', 16));
  91. public string FormattedIssueLabels => Labels
  92. .Select(FormatLabelName)
  93. .JoinToString(", ");
  94. public override string ToString()
  95. {
  96. StringBuilder sb = new("CompatibilityEntry: {");
  97. sb.Append($"{nameof(GameName)}=\"{GameName}\", ");
  98. sb.Append($"{nameof(TitleId)}={TitleId}, ");
  99. sb.Append($"{nameof(Labels)}={
  100. Labels.FormatCollection(it => $"\"{it}\"", separator: ", ", prefix: "[", suffix: "]")
  101. }, ");
  102. sb.Append($"{nameof(Status)}=\"{Status}\", ");
  103. sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"");
  104. sb.Append('}');
  105. return sb.ToString();
  106. }
  107. public static string FormatLabelName(string labelName) => labelName.ToLower() switch
  108. {
  109. "audio" => "Audio",
  110. "bug" => "Bug",
  111. "cpu" => "CPU",
  112. "gpu" => "GPU",
  113. "gui" => "GUI",
  114. "help wanted" => "Help Wanted",
  115. "horizon" => "Horizon",
  116. "infra" => "Project Infra",
  117. "invalid" => "Invalid",
  118. "kernel" => "Kernel",
  119. "ldn" => "LDN",
  120. "linux" => "Linux",
  121. "macos" => "macOS",
  122. "question" => "Question",
  123. "windows" => "Windows",
  124. "graphics-backend:opengl" => "Graphics: OpenGL",
  125. "graphics-backend:vulkan" => "Graphics: Vulkan",
  126. "ldn-works" => "LDN Works",
  127. "ldn-untested" => "LDN Untested",
  128. "ldn-broken" => "LDN Broken",
  129. "ldn-partial" => "Partial LDN",
  130. "nvdec" => "NVDEC",
  131. "services" => "NX Services",
  132. "services-horizon" => "Horizon OS Services",
  133. "slow" => "Runs Slow",
  134. "crash" => "Crashes",
  135. "deadlock" => "Deadlock",
  136. "regression" => "Regression",
  137. "opengl" => "OpenGL",
  138. "opengl-backend-bug" => "OpenGL Backend Bug",
  139. "vulkan-backend-bug" => "Vulkan Backend Bug",
  140. "mac-bug" => "Mac-specific Bug(s)",
  141. "amd-vendor-bug" => "AMD GPU Bug",
  142. "intel-vendor-bug" => "Intel GPU Bug",
  143. "loader-allocator" => "Loader Allocator",
  144. "audout" => "AudOut",
  145. "32-bit" => "32-bit Game",
  146. "UE4" => "Unreal Engine 4",
  147. "homebrew" => "Homebrew Content",
  148. "online-broken" => "Online Broken",
  149. _ => Capitalize(labelName)
  150. };
  151. public static string Capitalize(string value)
  152. {
  153. if (value == string.Empty)
  154. return string.Empty;
  155. char firstChar = value[0];
  156. string rest = value[1..];
  157. return $"{char.ToUpper(firstChar)}{rest}";
  158. }
  159. }
  160. }