AmiiboWindow.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. using Gtk;
  2. using Ryujinx.Common;
  3. using Ryujinx.Common.Configuration;
  4. using Ryujinx.Common.Utilities;
  5. using Ryujinx.Ui.Common.Configuration;
  6. using Ryujinx.Ui.Common.Models.Amiibo;
  7. using Ryujinx.Ui.Widgets;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Net.Http;
  13. using System.Reflection;
  14. using System.Text;
  15. using System.Text.Json;
  16. using System.Threading.Tasks;
  17. using AmiiboApi = Ryujinx.Ui.Common.Models.Amiibo.AmiiboApi;
  18. using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
  19. namespace Ryujinx.Ui.Windows
  20. {
  21. public partial class AmiiboWindow : Window
  22. {
  23. private const string DEFAULT_JSON = "{ \"amiibo\": [] }";
  24. public string AmiiboId { get; private set; }
  25. public int DeviceId { get; set; }
  26. public string TitleId { get; set; }
  27. public string LastScannedAmiiboId { get; set; }
  28. public bool LastScannedAmiiboShowAll { get; set; }
  29. public ResponseType Response { get; private set; }
  30. public bool UseRandomUuid
  31. {
  32. get
  33. {
  34. return _randomUuidCheckBox.Active;
  35. }
  36. }
  37. private readonly HttpClient _httpClient;
  38. private readonly string _amiiboJsonPath;
  39. private readonly byte[] _amiiboLogoBytes;
  40. private List<AmiiboApi> _amiiboList;
  41. private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
  42. public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo")
  43. {
  44. Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
  45. InitializeComponent();
  46. _httpClient = new HttpClient()
  47. {
  48. Timeout = TimeSpan.FromMilliseconds(5000)
  49. };
  50. Directory.CreateDirectory(System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
  51. _amiiboJsonPath = System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
  52. _amiiboList = new List<AmiiboApi>();
  53. _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
  54. _amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
  55. _scanButton.Sensitive = false;
  56. _randomUuidCheckBox.Sensitive = false;
  57. _ = LoadContentAsync();
  58. }
  59. private async Task LoadContentAsync()
  60. {
  61. string amiiboJsonString = DEFAULT_JSON;
  62. if (File.Exists(_amiiboJsonPath))
  63. {
  64. amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
  65. if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
  66. {
  67. amiiboJsonString = await DownloadAmiiboJson();
  68. }
  69. }
  70. else
  71. {
  72. try
  73. {
  74. amiiboJsonString = await DownloadAmiiboJson();
  75. }
  76. catch
  77. {
  78. ShowInfoDialog();
  79. Close();
  80. }
  81. }
  82. _amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
  83. _amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
  84. if (LastScannedAmiiboShowAll)
  85. {
  86. _showAllCheckBox.Click();
  87. }
  88. ParseAmiiboData();
  89. _showAllCheckBox.Clicked += ShowAllCheckBox_Clicked;
  90. }
  91. private void ParseAmiiboData()
  92. {
  93. List<string> comboxItemList = new List<string>();
  94. for (int i = 0; i < _amiiboList.Count; i++)
  95. {
  96. if (!comboxItemList.Contains(_amiiboList[i].AmiiboSeries))
  97. {
  98. if (!_showAllCheckBox.Active)
  99. {
  100. foreach (var game in _amiiboList[i].GamesSwitch)
  101. {
  102. if (game != null)
  103. {
  104. if (game.GameId.Contains(TitleId))
  105. {
  106. comboxItemList.Add(_amiiboList[i].AmiiboSeries);
  107. _amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries);
  108. break;
  109. }
  110. }
  111. }
  112. }
  113. else
  114. {
  115. comboxItemList.Add(_amiiboList[i].AmiiboSeries);
  116. _amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries);
  117. }
  118. }
  119. }
  120. _amiiboSeriesComboBox.Changed += SeriesComboBox_Changed;
  121. _amiiboCharsComboBox.Changed += CharacterComboBox_Changed;
  122. if (LastScannedAmiiboId != "")
  123. {
  124. SelectLastScannedAmiibo();
  125. }
  126. else
  127. {
  128. _amiiboSeriesComboBox.Active = 0;
  129. }
  130. }
  131. private void SelectLastScannedAmiibo()
  132. {
  133. bool isSet = _amiiboSeriesComboBox.SetActiveId(_amiiboList.FirstOrDefault(amiibo => amiibo.Head + amiibo.Tail == LastScannedAmiiboId).AmiiboSeries);
  134. isSet = _amiiboCharsComboBox.SetActiveId(LastScannedAmiiboId);
  135. if (isSet == false)
  136. {
  137. _amiiboSeriesComboBox.Active = 0;
  138. }
  139. }
  140. private async Task<bool> NeedsUpdate(DateTime oldLastModified)
  141. {
  142. try
  143. {
  144. HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/"));
  145. if (response.IsSuccessStatusCode)
  146. {
  147. return response.Content.Headers.LastModified != oldLastModified;
  148. }
  149. return false;
  150. }
  151. catch
  152. {
  153. ShowInfoDialog();
  154. return false;
  155. }
  156. }
  157. private async Task<string> DownloadAmiiboJson()
  158. {
  159. HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/");
  160. if (response.IsSuccessStatusCode)
  161. {
  162. string amiiboJsonString = await response.Content.ReadAsStringAsync();
  163. using (FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough))
  164. {
  165. dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString));
  166. }
  167. return amiiboJsonString;
  168. }
  169. else
  170. {
  171. GtkDialog.CreateInfoDialog($"Amiibo API", "An error occured while fetching information from the API.");
  172. Close();
  173. }
  174. return DEFAULT_JSON;
  175. }
  176. private async Task UpdateAmiiboPreview(string imageUrl)
  177. {
  178. HttpResponseMessage response = await _httpClient.GetAsync(imageUrl);
  179. if (response.IsSuccessStatusCode)
  180. {
  181. byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
  182. Gdk.Pixbuf amiiboPreview = new Gdk.Pixbuf(amiiboPreviewBytes);
  183. float ratio = Math.Min((float)_amiiboImage.AllocatedWidth / amiiboPreview.Width,
  184. (float)_amiiboImage.AllocatedHeight / amiiboPreview.Height);
  185. int resizeHeight = (int)(amiiboPreview.Height * ratio);
  186. int resizeWidth = (int)(amiiboPreview.Width * ratio);
  187. _amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, Gdk.InterpType.Bilinear);
  188. }
  189. }
  190. private void ShowInfoDialog()
  191. {
  192. GtkDialog.CreateInfoDialog($"Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.");
  193. }
  194. //
  195. // Events
  196. //
  197. private void SeriesComboBox_Changed(object sender, EventArgs args)
  198. {
  199. _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed;
  200. _amiiboCharsComboBox.RemoveAll();
  201. List<AmiiboApi> amiiboSortedList = _amiiboList.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeriesComboBox.ActiveId).OrderBy(amiibo => amiibo.Name).ToList();
  202. List<string> comboxItemList = new List<string>();
  203. for (int i = 0; i < amiiboSortedList.Count; i++)
  204. {
  205. if (!comboxItemList.Contains(amiiboSortedList[i].Head + amiiboSortedList[i].Tail))
  206. {
  207. if (!_showAllCheckBox.Active)
  208. {
  209. foreach (var game in amiiboSortedList[i].GamesSwitch)
  210. {
  211. if (game != null)
  212. {
  213. if (game.GameId.Contains(TitleId))
  214. {
  215. comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail);
  216. _amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name);
  217. break;
  218. }
  219. }
  220. }
  221. }
  222. else
  223. {
  224. comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail);
  225. _amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name);
  226. }
  227. }
  228. }
  229. _amiiboCharsComboBox.Changed += CharacterComboBox_Changed;
  230. _amiiboCharsComboBox.Active = 0;
  231. _scanButton.Sensitive = true;
  232. _randomUuidCheckBox.Sensitive = true;
  233. }
  234. private void CharacterComboBox_Changed(object sender, EventArgs args)
  235. {
  236. AmiiboId = _amiiboCharsComboBox.ActiveId;
  237. _amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
  238. string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Head + amiibo.Tail == _amiiboCharsComboBox.ActiveId).Image;
  239. var usageStringBuilder = new StringBuilder();
  240. for (int i = 0; i < _amiiboList.Count; i++)
  241. {
  242. if (_amiiboList[i].Head + _amiiboList[i].Tail == _amiiboCharsComboBox.ActiveId)
  243. {
  244. bool writable = false;
  245. foreach (var item in _amiiboList[i].GamesSwitch)
  246. {
  247. if (item.GameId.Contains(TitleId))
  248. {
  249. foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
  250. {
  251. usageStringBuilder.Append(Environment.NewLine);
  252. usageStringBuilder.Append($"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
  253. writable = usageItem.Write;
  254. }
  255. }
  256. }
  257. if (usageStringBuilder.Length == 0)
  258. {
  259. usageStringBuilder.Append("Unknown.");
  260. }
  261. _gameUsageLabel.Text = $"Usage{(writable ? " (Writable)" : "")} : {usageStringBuilder}";
  262. }
  263. }
  264. _ = UpdateAmiiboPreview(imageUrl);
  265. }
  266. private void ShowAllCheckBox_Clicked(object sender, EventArgs e)
  267. {
  268. _amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
  269. _amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed;
  270. _amiiboCharsComboBox.Changed -= CharacterComboBox_Changed;
  271. _amiiboSeriesComboBox.RemoveAll();
  272. _amiiboCharsComboBox.RemoveAll();
  273. _scanButton.Sensitive = false;
  274. _randomUuidCheckBox.Sensitive = false;
  275. new Task(() => ParseAmiiboData()).Start();
  276. }
  277. private void ScanButton_Pressed(object sender, EventArgs args)
  278. {
  279. LastScannedAmiiboShowAll = _showAllCheckBox.Active;
  280. Response = ResponseType.Ok;
  281. Close();
  282. }
  283. private void CancelButton_Pressed(object sender, EventArgs args)
  284. {
  285. AmiiboId = "";
  286. LastScannedAmiiboId = "";
  287. LastScannedAmiiboShowAll = false;
  288. Response = ResponseType.Cancel;
  289. Close();
  290. }
  291. protected override void Dispose(bool disposing)
  292. {
  293. _httpClient.Dispose();
  294. base.Dispose(disposing);
  295. }
  296. }
  297. }