aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAc_K <Acoustik666@gmail.com>2020-09-28 00:00:38 +0200
committerGitHub <noreply@github.com>2020-09-28 00:00:38 +0200
commit4f65043ad77dcc37d9195b2a5e976d102421b82a (patch)
treead9ff1ed1a672263a419843371e732b01fa3a811
parentf89b754abb3d721c09ba6ab00f7906d2d5ccb02b (diff)
Basic impl of Error Applet (#1551)
-rw-r--r--Ryujinx.HLE/HOS/Applets/AppletManager.cs2
-rw-r--r--Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs171
-rw-r--r--Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs12
-rw-r--r--Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs17
-rw-r--r--Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs13
-rw-r--r--Ryujinx.HLE/IHostUiHandler.cs5
-rw-r--r--Ryujinx/Ui/ErrorAppletDialog.cs32
-rw-r--r--Ryujinx/Ui/GtkHostUiHandler.cs51
8 files changed, 303 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/Ryujinx.HLE/HOS/Applets/AppletManager.cs
index 5d075882..a686a832 100644
--- a/Ryujinx.HLE/HOS/Applets/AppletManager.cs
+++ b/Ryujinx.HLE/HOS/Applets/AppletManager.cs
@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Applets.Browser;
+using Ryujinx.HLE.HOS.Applets.Error;
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using System;
using System.Collections.Generic;
@@ -13,6 +14,7 @@ namespace Ryujinx.HLE.HOS.Applets
{
_appletMapping = new Dictionary<AppletId, Type>
{
+ { AppletId.Error, typeof(ErrorApplet) },
{ AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
{ AppletId.Controller, typeof(ControllerApplet) },
{ AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) },
diff --git a/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs b/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
new file mode 100644
index 00000000..c78cbf31
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
@@ -0,0 +1,171 @@
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.FsSystem.NcaUtils;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using Ryujinx.HLE.HOS.SystemState;
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ internal class ErrorApplet : IApplet
+ {
+ private const long ErrorMessageBinaryTitleId = 0x0100000000000801;
+
+ private Horizon _horizon;
+ private AppletSession _normalSession;
+ private CommonArguments _commonArguments;
+ private ErrorCommonHeader _errorCommonHeader;
+ private byte[] _errorStorage;
+
+ public event EventHandler AppletStateChanged;
+
+ public ErrorApplet(Horizon horizon)
+ {
+ _horizon = horizon;
+ }
+
+ public ResultCode Start(AppletSession normalSession,
+ AppletSession interactiveSession)
+ {
+ _normalSession = normalSession;
+ _commonArguments = IApplet.ReadStruct<CommonArguments>(_normalSession.Pop());
+
+ Logger.Info?.PrintMsg(LogClass.ServiceAm, $"ErrorApplet version: 0x{_commonArguments.AppletVersion:x8}");
+
+ _errorStorage = _normalSession.Pop();
+ _errorCommonHeader = IApplet.ReadStruct<ErrorCommonHeader>(_errorStorage);
+ _errorStorage = _errorStorage.Skip(Marshal.SizeOf(typeof(ErrorCommonHeader))).ToArray();
+
+ switch (_errorCommonHeader.Type)
+ {
+ case ErrorType.ErrorCommonArg:
+ {
+ ParseErrorCommonArg();
+
+ break;
+ }
+ default: throw new NotImplementedException($"ErrorApplet type {_errorCommonHeader.Type} is not implemented.");
+ }
+
+ AppletStateChanged?.Invoke(this, null);
+
+ return ResultCode.Success;
+ }
+
+ private (uint module, uint description) HexToResultCode(uint resultCode)
+ {
+ return ((resultCode & 0x1FF) + 2000, (resultCode >> 9) & 0x3FFF);
+ }
+
+ private string SystemLanguageToLanguageKey(SystemLanguage systemLanguage)
+ {
+ return systemLanguage switch
+ {
+ SystemLanguage.Japanese => "ja",
+ SystemLanguage.AmericanEnglish => "en-US",
+ SystemLanguage.French => "fr",
+ SystemLanguage.German => "de",
+ SystemLanguage.Italian => "it",
+ SystemLanguage.Spanish => "es",
+ SystemLanguage.Chinese => "zh-Hans",
+ SystemLanguage.Korean => "ko",
+ SystemLanguage.Dutch => "nl",
+ SystemLanguage.Portuguese => "pt",
+ SystemLanguage.Russian => "ru",
+ SystemLanguage.Taiwanese => "zh-HansT",
+ SystemLanguage.BritishEnglish => "en-GB",
+ SystemLanguage.CanadianFrench => "fr-CA",
+ SystemLanguage.LatinAmericanSpanish => "es-419",
+ SystemLanguage.SimplifiedChinese => "zh-Hans",
+ SystemLanguage.TraditionalChinese => "zh-Hant",
+ _ => "en-US"
+ };
+ }
+
+ public string CleanText(string value)
+ {
+ return Regex.Replace(Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(value)), @"[^\u0009\u000A\u000D\u0020-\u007E]", "");
+ }
+
+ private string GetMessageText(uint module, uint description, string key)
+ {
+ string binaryTitleContentPath = _horizon.ContentManager.GetInstalledContentPath(ErrorMessageBinaryTitleId, StorageId.NandSystem, NcaContentType.Data);
+
+ using (LibHac.Fs.IStorage ncaFileStream = new LocalStorage(_horizon.Device.FileSystem.SwitchPathToSystemPath(binaryTitleContentPath), FileAccess.Read, FileMode.Open))
+ {
+ Nca nca = new Nca(_horizon.Device.FileSystem.KeySet, ncaFileStream);
+ IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, _horizon.FsIntegrityCheckLevel);
+ string languageCode = SystemLanguageToLanguageKey(_horizon.State.DesiredSystemLanguage);
+ string filePath = "/" + Path.Combine(module.ToString(), $"{description:0000}", $"{languageCode}_{key}").Replace(@"\", "/");
+
+ if (romfs.FileExists(filePath))
+ {
+ romfs.OpenFile(out IFile binaryFile, filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ StreamReader reader = new StreamReader(binaryFile.AsStream());
+
+ return CleanText(reader.ReadToEnd());
+ }
+ else
+ {
+ return "";
+ }
+ }
+ }
+
+ private string[] GetButtonsText(uint module, uint description, string key)
+ {
+ string buttonsText = GetMessageText(module, description, key);
+
+ return (buttonsText == "") ? null : buttonsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
+ }
+
+ private void ParseErrorCommonArg()
+ {
+ ErrorCommonArg errorCommonArg = IApplet.ReadStruct<ErrorCommonArg>(_errorStorage);
+
+ uint module = errorCommonArg.Module;
+ uint description = errorCommonArg.Description;
+
+ if (_errorCommonHeader.MessageFlag == 0)
+ {
+ (module, description) = HexToResultCode(errorCommonArg.ResultCode);
+ }
+
+ string message = GetMessageText(module, description, "DlgMsg");
+
+ if (message == "")
+ {
+ message = "An error has occured.\n\n"
+ + "Please try again later.\n\n"
+ + "If the problem persists, please refer to the Ryujinx website.\n"
+ + "www.ryujinx.org";
+ }
+
+ string[] buttons = GetButtonsText(module, description, "DlgBtn");
+
+ bool showDetails = _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
+ if (showDetails)
+ {
+ message = GetMessageText(module, description, "FlvMsg");
+ buttons = GetButtonsText(module, description, "FlvBtn");
+
+ _horizon.Device.UiHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
+ }
+ }
+
+ public ResultCode GetResult()
+ {
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs b/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs
new file mode 100644
index 00000000..530a2ad8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ErrorCommonArg
+ {
+ public uint Module;
+ public uint Description;
+ public uint ResultCode;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs b/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs
new file mode 100644
index 00000000..b93cdd4f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs
@@ -0,0 +1,17 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ErrorCommonHeader
+ {
+ public ErrorType Type;
+ public byte JumpFlag;
+ public byte ReservedFlag1;
+ public byte ReservedFlag2;
+ public byte ReservedFlag3;
+ public byte ContextFlag;
+ public byte MessageFlag;
+ public byte ContextFlag2;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs b/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs
new file mode 100644
index 00000000..f06af1d3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Applets.Error
+{
+ enum ErrorType : byte
+ {
+ ErrorCommonArg,
+ SystemErrorArg,
+ ApplicationErrorArg,
+ ErrorEulaArg,
+ ErrorPctlArg,
+ ErrorRecordArg,
+ SystemUpdateEulaArg = 8
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/IHostUiHandler.cs b/Ryujinx.HLE/IHostUiHandler.cs
index 8e9bfc77..b85fc356 100644
--- a/Ryujinx.HLE/IHostUiHandler.cs
+++ b/Ryujinx.HLE/IHostUiHandler.cs
@@ -31,5 +31,10 @@ namespace Ryujinx.HLE
/// <param name="kind">The program kind.</param>
/// <param name="value">The value associated to the <paramref name="kind"/>.</param>
void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value);
+
+ /// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
+ /// </summary>
+ /// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns>
+ bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText);
}
} \ No newline at end of file
diff --git a/Ryujinx/Ui/ErrorAppletDialog.cs b/Ryujinx/Ui/ErrorAppletDialog.cs
new file mode 100644
index 00000000..f7a548b3
--- /dev/null
+++ b/Ryujinx/Ui/ErrorAppletDialog.cs
@@ -0,0 +1,32 @@
+using Gtk;
+using System.Reflection;
+
+namespace Ryujinx.Ui
+{
+ internal class ErrorAppletDialog : MessageDialog
+ {
+ internal static bool _isExitDialogOpen = false;
+
+ public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null)
+ {
+ Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
+
+ int responseId = 0;
+
+ if (buttons != null)
+ {
+ foreach (string buttonText in buttons)
+ {
+ AddButton(buttonText, responseId);
+ responseId++;
+ }
+ }
+ else
+ {
+ AddButton("OK", 0);
+ }
+
+ ShowAll();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx/Ui/GtkHostUiHandler.cs b/Ryujinx/Ui/GtkHostUiHandler.cs
index fd193dd7..12ba81c4 100644
--- a/Ryujinx/Ui/GtkHostUiHandler.cs
+++ b/Ryujinx/Ui/GtkHostUiHandler.cs
@@ -128,5 +128,56 @@ namespace Ryujinx.Ui
device.UserChannelPersistence.ExecuteProgram(kind, value);
MainWindow.GlWidget?.Exit();
}
+
+ public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
+ {
+ ManualResetEvent dialogCloseEvent = new ManualResetEvent(false);
+ bool showDetails = false;
+
+ Application.Invoke(delegate
+ {
+ try
+ {
+ ErrorAppletDialog msgDialog = new ErrorAppletDialog(_parent, DialogFlags.DestroyWithParent, MessageType.Error, buttons)
+ {
+ Title = title,
+ Text = message,
+ UseMarkup = true,
+ WindowPosition = WindowPosition.CenterAlways
+ };
+
+ msgDialog.SetDefaultSize(400, 0);
+
+ msgDialog.Response += (object o, ResponseArgs args) =>
+ {
+ if (buttons != null)
+ {
+ if (buttons.Length > 1)
+ {
+ if (args.ResponseId != (ResponseType)(buttons.Length - 1))
+ {
+ showDetails = true;
+ }
+ }
+ }
+
+ dialogCloseEvent.Set();
+ msgDialog?.Dispose();
+ };
+
+ msgDialog.Show();
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Error displaying ErrorApplet Dialog: {e}");
+
+ dialogCloseEvent.Set();
+ }
+ });
+
+ dialogCloseEvent.WaitOne();
+
+ return showDetails;
+ }
}
}