CompatibilityCsv.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. using Gommon;
  2. using nietras.SeparatedValues;
  3. using Ryujinx.Ava.Common.Locale;
  4. using Ryujinx.Common.Logging;
  5. using System;
  6. using System.Collections.Generic;
  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<string, 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()
  29. {
  30. using Stream csvStream = Assembly.GetExecutingAssembly()
  31. .GetManifestResourceStream("RyujinxGameCompatibilityList")!;
  32. csvStream.Position = 0;
  33. using SepReader reader = Sep.Reader().From(csvStream);
  34. ColumnIndices columnIndices = new(reader.Header.IndexOf);
  35. Entries = reader
  36. .Enumerate(row => new CompatibilityEntry(ref columnIndices, row))
  37. .OrderBy(it => it.GameName)
  38. .ToArray();
  39. Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatCsv");
  40. }
  41. public static CompatibilityEntry[] Entries { get; private set; }
  42. }
  43. public class CompatibilityEntry
  44. {
  45. public CompatibilityEntry(ref ColumnIndices indices, SepReader.Row row)
  46. {
  47. string titleIdRow = ColStr(row[indices.TitleId]);
  48. TitleId = !string.IsNullOrEmpty(titleIdRow)
  49. ? titleIdRow
  50. : default(Optional<string>);
  51. GameName = ColStr(row[indices.GameName]).Trim().Trim('"');
  52. Labels = ColStr(row[indices.Labels]).Split(';');
  53. Status = ColStr(row[indices.Status]).ToLower() switch
  54. {
  55. "playable" => LocaleKeys.CompatibilityListPlayable,
  56. "ingame" => LocaleKeys.CompatibilityListIngame,
  57. "menus" => LocaleKeys.CompatibilityListMenus,
  58. "boots" => LocaleKeys.CompatibilityListBoots,
  59. "nothing" => LocaleKeys.CompatibilityListNothing,
  60. _ => null
  61. };
  62. if (DateTime.TryParse(ColStr(row[indices.LastUpdated]), out var dt))
  63. LastUpdated = dt;
  64. return;
  65. string ColStr(SepReader.Col col) => col.ToString().Trim('"');
  66. }
  67. public string GameName { get; }
  68. public Optional<string> TitleId { get; }
  69. public string[] Labels { get; }
  70. public LocaleKeys? Status { get; }
  71. public DateTime LastUpdated { get; }
  72. public string LocalizedStatus => LocaleManager.Instance[Status!.Value];
  73. public string FormattedTitleId => TitleId
  74. .OrElse(new string(' ', 16));
  75. public string FormattedIssueLabels => Labels
  76. .Where(it => !it.StartsWithIgnoreCase("status"))
  77. .Select(FormatLabelName)
  78. .JoinToString(", ");
  79. public override string ToString()
  80. {
  81. StringBuilder sb = new("CompatibilityEntry: {");
  82. sb.Append($"{nameof(GameName)}=\"{GameName}\", ");
  83. sb.Append($"{nameof(TitleId)}={TitleId}, ");
  84. sb.Append($"{nameof(Labels)}=\"{Labels}\", ");
  85. sb.Append($"{nameof(Status)}=\"{Status}\", ");
  86. sb.Append($"{nameof(LastUpdated)}=\"{LastUpdated}\"");
  87. sb.Append('}');
  88. return sb.ToString();
  89. }
  90. public static string FormatLabelName(string labelName) => labelName.ToLower() switch
  91. {
  92. "audio" => "Audio",
  93. "bug" => "Bug",
  94. "cpu" => "CPU",
  95. "gpu" => "GPU",
  96. "gui" => "GUI",
  97. "help wanted" => "Help Wanted",
  98. "horizon" => "Horizon",
  99. "infra" => "Project Infra",
  100. "invalid" => "Invalid",
  101. "kernel" => "Kernel",
  102. "ldn" => "LDN",
  103. "linux" => "Linux",
  104. "macos" => "macOS",
  105. "question" => "Question",
  106. "windows" => "Windows",
  107. "graphics-backend:opengl" => "Graphics: OpenGL",
  108. "graphics-backend:vulkan" => "Graphics: Vulkan",
  109. "ldn-works" => "LDN Works",
  110. "ldn-untested" => "LDN Untested",
  111. "ldn-broken" => "LDN Broken",
  112. "ldn-partial" => "Partial LDN",
  113. "nvdec" => "NVDEC",
  114. "services" => "NX Services",
  115. "services-horizon" => "Horizon OS Services",
  116. "slow" => "Runs Slow",
  117. "crash" => "Crashes",
  118. "deadlock" => "Deadlock",
  119. "regression" => "Regression",
  120. "opengl" => "OpenGL",
  121. "opengl-backend-bug" => "OpenGL Backend Bug",
  122. "vulkan-backend-bug" => "Vulkan Backend Bug",
  123. "mac-bug" => "Mac-specific Bug(s)",
  124. "amd-vendor-bug" => "AMD GPU Bug",
  125. "intel-vendor-bug" => "Intel GPU Bug",
  126. "loader-allocator" => "Loader Allocator",
  127. "audout" => "AudOut",
  128. "32-bit" => "32-bit Game",
  129. "UE4" => "Unreal Engine 4",
  130. "homebrew" => "Homebrew Content",
  131. "online-broken" => "Online Broken",
  132. _ => Capitalize(labelName)
  133. };
  134. public static string Capitalize(string value)
  135. {
  136. if (value == string.Empty)
  137. return string.Empty;
  138. char firstChar = value[0];
  139. string rest = value[1..];
  140. return $"{char.ToUpper(firstChar)}{rest}";
  141. }
  142. }
  143. }