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