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 }