ErrorApplet.cs 8.5 KB

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