ErrorApplet.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. using LibHac.Common;
  2. using LibHac.Fs;
  3. using LibHac.Fs.Fsa;
  4. using LibHac.FsSystem;
  5. using LibHac.FsSystem.NcaUtils;
  6. using Ryujinx.Common.Logging;
  7. using Ryujinx.HLE.FileSystem;
  8. using Ryujinx.HLE.HOS.Services.Am.AppletAE;
  9. using Ryujinx.HLE.HOS.SystemState;
  10. using System;
  11. using System.Collections.Generic;
  12. using System.IO;
  13. using System.Linq;
  14. using System.Runtime.InteropServices;
  15. using System.Text;
  16. using System.Text.RegularExpressions;
  17. namespace Ryujinx.HLE.HOS.Applets.Error
  18. {
  19. internal class ErrorApplet : IApplet
  20. {
  21. private const long ErrorMessageBinaryTitleId = 0x0100000000000801;
  22. private Horizon _horizon;
  23. private AppletSession _normalSession;
  24. private CommonArguments _commonArguments;
  25. private ErrorCommonHeader _errorCommonHeader;
  26. private byte[] _errorStorage;
  27. public event EventHandler AppletStateChanged;
  28. public ErrorApplet(Horizon horizon)
  29. {
  30. _horizon = horizon;
  31. }
  32. public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
  33. {
  34. _normalSession = normalSession;
  35. _commonArguments = IApplet.ReadStruct<CommonArguments>(_normalSession.Pop());
  36. Logger.Info?.PrintMsg(LogClass.ServiceAm, $"ErrorApplet version: 0x{_commonArguments.AppletVersion:x8}");
  37. _errorStorage = _normalSession.Pop();
  38. _errorCommonHeader = IApplet.ReadStruct<ErrorCommonHeader>(_errorStorage);
  39. _errorStorage = _errorStorage.Skip(Marshal.SizeOf(typeof(ErrorCommonHeader))).ToArray();
  40. switch (_errorCommonHeader.Type)
  41. {
  42. case ErrorType.ErrorCommonArg:
  43. {
  44. ParseErrorCommonArg();
  45. break;
  46. }
  47. case ErrorType.ApplicationErrorArg:
  48. {
  49. ParseApplicationErrorArg();
  50. break;
  51. }
  52. default: throw new NotImplementedException($"ErrorApplet type {_errorCommonHeader.Type} is not implemented.");
  53. }
  54. AppletStateChanged?.Invoke(this, null);
  55. return ResultCode.Success;
  56. }
  57. private (uint module, uint description) HexToResultCode(uint resultCode)
  58. {
  59. return ((resultCode & 0x1FF) + 2000, (resultCode >> 9) & 0x3FFF);
  60. }
  61. private string SystemLanguageToLanguageKey(SystemLanguage systemLanguage)
  62. {
  63. return systemLanguage switch
  64. {
  65. SystemLanguage.Japanese => "ja",
  66. SystemLanguage.AmericanEnglish => "en-US",
  67. SystemLanguage.French => "fr",
  68. SystemLanguage.German => "de",
  69. SystemLanguage.Italian => "it",
  70. SystemLanguage.Spanish => "es",
  71. SystemLanguage.Chinese => "zh-Hans",
  72. SystemLanguage.Korean => "ko",
  73. SystemLanguage.Dutch => "nl",
  74. SystemLanguage.Portuguese => "pt",
  75. SystemLanguage.Russian => "ru",
  76. SystemLanguage.Taiwanese => "zh-HansT",
  77. SystemLanguage.BritishEnglish => "en-GB",
  78. SystemLanguage.CanadianFrench => "fr-CA",
  79. SystemLanguage.LatinAmericanSpanish => "es-419",
  80. SystemLanguage.SimplifiedChinese => "zh-Hans",
  81. SystemLanguage.TraditionalChinese => "zh-Hant",
  82. SystemLanguage.BrazilianPortuguese => "pt-BR",
  83. _ => "en-US"
  84. };
  85. }
  86. private static string CleanText(string value)
  87. {
  88. return Regex.Replace(value, @"[^\u0000\u0009\u000A\u000D\u0020-\uFFFF]..", "").Replace("\0", "");
  89. }
  90. private string GetMessageText(uint module, uint description, string key)
  91. {
  92. string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.NandSystem, NcaContentType.Data);
  93. using (LibHac.Fs.IStorage ncaFileStream = new LocalStorage(_horizon.Device.FileSystem.SwitchPathToSystemPath(binaryTitleContentPath), FileAccess.Read, FileMode.Open))
  94. {
  95. Nca nca = new Nca(_horizon.Device.FileSystem.KeySet, ncaFileStream);
  96. IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _horizon.FsIntegrityCheckLevel);
  97. string languageCode = SystemLanguageToLanguageKey(_horizon.State.DesiredSystemLanguage);
  98. string filePath = $"/{module}/{description:0000}/{languageCode}_{key}";
  99. if (romfs.FileExists(filePath))
  100. {
  101. romfs.OpenFile(out IFile binaryFile, filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
  102. StreamReader reader = new StreamReader(binaryFile.AsStream(), Encoding.Unicode);
  103. return CleanText(reader.ReadToEnd());
  104. }
  105. else
  106. {
  107. return "";
  108. }
  109. }
  110. }
  111. private string[] GetButtonsText(uint module, uint description, string key)
  112. {
  113. string buttonsText = GetMessageText(module, description, key);
  114. return (buttonsText == "") ? null : buttonsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
  115. }
  116. private void ParseErrorCommonArg()
  117. {
  118. ErrorCommonArg errorCommonArg = IApplet.ReadStruct<ErrorCommonArg>(_errorStorage);
  119. uint module = errorCommonArg.Module;
  120. uint description = errorCommonArg.Description;
  121. if (_errorCommonHeader.MessageFlag == 0)
  122. {
  123. (module, description) = HexToResultCode(errorCommonArg.ResultCode);
  124. }
  125. string message = GetMessageText(module, description, "DlgMsg");
  126. if (message == "")
  127. {
  128. message = "An error has occured.\n\n"
  129. + "Please try again later.\n\n"
  130. + "If the problem persists, please refer to the Ryujinx website.\n"
  131. + "www.ryujinx.org";
  132. }
  133. string[] buttons = GetButtonsText(module, description, "DlgBtn");
  134. bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
  135. if (showDetails)
  136. {
  137. message = GetMessageText(module, description, "FlvMsg");
  138. buttons = GetButtonsText(module, description, "FlvBtn");
  139. _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
  140. }
  141. }
  142. private void ParseApplicationErrorArg()
  143. {
  144. ApplicationErrorArg applicationErrorArg = IApplet.ReadStruct<ApplicationErrorArg>(_errorStorage);
  145. byte[] messageTextBuffer = new byte[0x800];
  146. byte[] detailsTextBuffer = new byte[0x800];
  147. applicationErrorArg.MessageText.ToSpan().CopyTo(messageTextBuffer);
  148. applicationErrorArg.DetailsText.ToSpan().CopyTo(detailsTextBuffer);
  149. string messageText = Encoding.ASCII.GetString(messageTextBuffer.TakeWhile(b => !b.Equals(0)).ToArray());
  150. string detailsText = Encoding.ASCII.GetString(detailsTextBuffer.TakeWhile(b => !b.Equals(0)).ToArray());
  151. List<string> buttons = new List<string>();
  152. // TODO: Handle the LanguageCode to return the translated "OK" and "Details".
  153. if (detailsText.Trim() != "")
  154. {
  155. buttons.Add("Details");
  156. }
  157. buttons.Add("OK");
  158. bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber}", "\n" + messageText, buttons.ToArray());
  159. if (showDetails)
  160. {
  161. buttons.RemoveAt(0);
  162. _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Number: {applicationErrorArg.ErrorNumber} (Details)", "\n" + detailsText, buttons.ToArray());
  163. }
  164. }
  165. public ResultCode GetResult()
  166. {
  167. return ResultCode.Success;
  168. }
  169. }
  170. }