ErrorApplet.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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.IO;
  12. using System.Linq;
  13. using System.Runtime.InteropServices;
  14. using System.Text;
  15. using System.Text.RegularExpressions;
  16. namespace Ryujinx.HLE.HOS.Applets.Error
  17. {
  18. internal class ErrorApplet : IApplet
  19. {
  20. private const long ErrorMessageBinaryTitleId = 0x0100000000000801;
  21. private Horizon _horizon;
  22. private AppletSession _normalSession;
  23. private CommonArguments _commonArguments;
  24. private ErrorCommonHeader _errorCommonHeader;
  25. private byte[] _errorStorage;
  26. public event EventHandler AppletStateChanged;
  27. public ErrorApplet(Horizon horizon)
  28. {
  29. _horizon = horizon;
  30. }
  31. public ResultCode Start(AppletSession normalSession,
  32. 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. default: throw new NotImplementedException($"ErrorApplet type {_errorCommonHeader.Type} is not implemented.");
  48. }
  49. AppletStateChanged?.Invoke(this, null);
  50. return ResultCode.Success;
  51. }
  52. private (uint module, uint description) HexToResultCode(uint resultCode)
  53. {
  54. return ((resultCode & 0x1FF) + 2000, (resultCode >> 9) & 0x3FFF);
  55. }
  56. private string SystemLanguageToLanguageKey(SystemLanguage systemLanguage)
  57. {
  58. return systemLanguage switch
  59. {
  60. SystemLanguage.Japanese => "ja",
  61. SystemLanguage.AmericanEnglish => "en-US",
  62. SystemLanguage.French => "fr",
  63. SystemLanguage.German => "de",
  64. SystemLanguage.Italian => "it",
  65. SystemLanguage.Spanish => "es",
  66. SystemLanguage.Chinese => "zh-Hans",
  67. SystemLanguage.Korean => "ko",
  68. SystemLanguage.Dutch => "nl",
  69. SystemLanguage.Portuguese => "pt",
  70. SystemLanguage.Russian => "ru",
  71. SystemLanguage.Taiwanese => "zh-HansT",
  72. SystemLanguage.BritishEnglish => "en-GB",
  73. SystemLanguage.CanadianFrench => "fr-CA",
  74. SystemLanguage.LatinAmericanSpanish => "es-419",
  75. SystemLanguage.SimplifiedChinese => "zh-Hans",
  76. SystemLanguage.TraditionalChinese => "zh-Hant",
  77. _ => "en-US"
  78. };
  79. }
  80. public string CleanText(string value)
  81. {
  82. return Regex.Replace(Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(value)), @"[^\u0009\u000A\u000D\u0020-\u007E]", "");
  83. }
  84. private string GetMessageText(uint module, uint description, string key)
  85. {
  86. string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.NandSystem, NcaContentType.Data);
  87. using (LibHac.Fs.IStorage ncaFileStream = new LocalStorage(_horizon.Device.FileSystem.SwitchPathToSystemPath(binaryTitleContentPath), FileAccess.Read, FileMode.Open))
  88. {
  89. Nca nca = new Nca(_horizon.Device.FileSystem.KeySet, ncaFileStream);
  90. IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _horizon.FsIntegrityCheckLevel);
  91. string languageCode = SystemLanguageToLanguageKey(_horizon.State.DesiredSystemLanguage);
  92. string filePath = "/" + Path.Combine(module.ToString(), $"{description:0000}", $"{languageCode}_{key}").Replace(@"\", "/");
  93. if (romfs.FileExists(filePath))
  94. {
  95. romfs.OpenFile(out IFile binaryFile, filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
  96. StreamReader reader = new StreamReader(binaryFile.AsStream());
  97. return CleanText(reader.ReadToEnd());
  98. }
  99. else
  100. {
  101. return "";
  102. }
  103. }
  104. }
  105. private string[] GetButtonsText(uint module, uint description, string key)
  106. {
  107. string buttonsText = GetMessageText(module, description, key);
  108. return (buttonsText == "") ? null : buttonsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
  109. }
  110. private void ParseErrorCommonArg()
  111. {
  112. ErrorCommonArg errorCommonArg = IApplet.ReadStruct<ErrorCommonArg>(_errorStorage);
  113. uint module = errorCommonArg.Module;
  114. uint description = errorCommonArg.Description;
  115. if (_errorCommonHeader.MessageFlag == 0)
  116. {
  117. (module, description) = HexToResultCode(errorCommonArg.ResultCode);
  118. }
  119. string message = GetMessageText(module, description, "DlgMsg");
  120. if (message == "")
  121. {
  122. message = "An error has occured.\n\n"
  123. + "Please try again later.\n\n"
  124. + "If the problem persists, please refer to the Ryujinx website.\n"
  125. + "www.ryujinx.org";
  126. }
  127. string[] buttons = GetButtonsText(module, description, "DlgBtn");
  128. bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
  129. if (showDetails)
  130. {
  131. message = GetMessageText(module, description, "FlvMsg");
  132. buttons = GetButtonsText(module, description, "FlvBtn");
  133. _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
  134. }
  135. }
  136. public ResultCode GetResult()
  137. {
  138. return ResultCode.Success;
  139. }
  140. }
  141. }