mirror of
				https://github.com/Ryujinx/Ryujinx.git
				synced 2025-11-04 06:33:41 +00:00 
			
		
		
		
	Basic impl of Error Applet (#1551)
This commit is contained in:
		
							parent
							
								
									f89b754abb
								
							
						
					
					
						commit
						4f65043ad7
					
				@ -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) },
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										171
									
								
								Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
namespace Ryujinx.HLE.HOS.Applets.Error
 | 
			
		||||
{
 | 
			
		||||
    enum ErrorType : byte
 | 
			
		||||
    {
 | 
			
		||||
        ErrorCommonArg,
 | 
			
		||||
        SystemErrorArg,
 | 
			
		||||
        ApplicationErrorArg,
 | 
			
		||||
        ErrorEulaArg,
 | 
			
		||||
        ErrorPctlArg,
 | 
			
		||||
        ErrorRecordArg,
 | 
			
		||||
        SystemUpdateEulaArg = 8
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								Ryujinx/Ui/ErrorAppletDialog.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Ryujinx/Ui/ErrorAppletDialog.cs
									
									
									
									
									
										Normal file
									
								
							@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user