Kaynağa Gözat

Add the Cabinet Applet (#340)

This adds the missing Cabinet Applet, which allows for formatting
Amiibos and changing their names.
Jacobwasbeast 1 yıl önce
ebeveyn
işleme
08b7257be5

+ 3 - 0
src/Ryujinx.HLE/HOS/Applets/AppletManager.cs

@@ -1,5 +1,6 @@
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Applets.Browser;
+using Ryujinx.HLE.HOS.Applets.Cabinet;
 using Ryujinx.HLE.HOS.Applets.Dummy;
 using Ryujinx.HLE.HOS.Applets.Error;
 using Ryujinx.HLE.HOS.Services.Am.AppletAE;
@@ -31,6 +32,8 @@ namespace Ryujinx.HLE.HOS.Applets
                 case AppletId.MiiEdit:
                     Logger.Warning?.Print(LogClass.Application, $"Please use the MiiEdit inside File/Open Applet");
                     return new DummyApplet(system);
+                case AppletId.Cabinet:
+                    return new CabinetApplet(system);
             }
 
             Logger.Warning?.Print(LogClass.Application, $"Applet {applet} not implemented!");

+ 195 - 0
src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs

@@ -0,0 +1,195 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using Ryujinx.HLE.HOS.Services.Hid.HidServer;
+using Ryujinx.HLE.HOS.Services.Hid;
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Applets.Cabinet
+{
+    internal unsafe class CabinetApplet : IApplet
+    {
+        private readonly Horizon _system;
+        private AppletSession _normalSession;
+
+        public event EventHandler AppletStateChanged;
+
+        public CabinetApplet(Horizon system)
+        {
+            _system = system;
+        }
+
+        public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
+        {
+            _normalSession = normalSession;
+
+            byte[] launchParams = _normalSession.Pop();
+            byte[] startParamBytes = _normalSession.Pop();
+
+            StartParamForAmiiboSettings startParam = IApplet.ReadStruct<StartParamForAmiiboSettings>(startParamBytes);
+
+            Logger.Stub?.PrintStub(LogClass.ServiceAm, $"CabinetApplet Start Type: {startParam.Type}");
+
+            switch (startParam.Type)
+            {
+                case 0:
+                    StartNicknameAndOwnerSettings(ref startParam);
+                    break;
+                case 1:
+                case 3:
+                    StartFormatter(ref startParam);
+                    break;
+                default:
+                    Logger.Error?.Print(LogClass.ServiceAm, $"Unknown AmiiboSettings type: {startParam.Type}");
+                    break;
+            }
+
+            // Prepare the response
+            ReturnValueForAmiiboSettings returnValue = new()
+            {
+                AmiiboSettingsReturnFlag = (byte)AmiiboSettingsReturnFlag.HasRegisterInfo,
+                DeviceHandle = new DeviceHandle
+                {
+                    Handle = 0 // Dummy device handle
+                },
+                RegisterInfo = startParam.RegisterInfo
+            };
+
+            // Push the response
+            _normalSession.Push(BuildResponse(returnValue));
+            AppletStateChanged?.Invoke(this, null);
+
+            _system.ReturnFocus();
+
+            return ResultCode.Success;
+        }
+
+        public ResultCode GetResult()
+        {
+            _system.Device.System.NfpDevices.RemoveAt(0);
+            return ResultCode.Success;
+        }
+
+        private void StartFormatter(ref StartParamForAmiiboSettings startParam)
+        {
+            // Initialize RegisterInfo
+            startParam.RegisterInfo = new RegisterInfo();
+        }
+
+        private void StartNicknameAndOwnerSettings(ref StartParamForAmiiboSettings startParam)
+        {
+            _system.Device.UIHandler.DisplayCabinetDialog(out string newName);
+            byte[] nameBytes = Encoding.UTF8.GetBytes(newName);
+            Array41<byte> nickName = new Array41<byte>();
+            nameBytes.CopyTo(nickName.AsSpan());
+            startParam.RegisterInfo.Nickname = nickName;
+            NfpDevice devicePlayer1 = new()
+            {
+                NpadIdType = NpadIdType.Player1,
+                Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
+                State = NfpDeviceState.SearchingForTag,
+            };
+            _system.Device.System.NfpDevices.Add(devicePlayer1);
+            _system.Device.UIHandler.DisplayCabinetMessageDialog();
+            string amiiboId = string.Empty;
+            bool scanned = false;
+            while (!scanned)
+            {
+                for (int i = 0; i < _system.Device.System.NfpDevices.Count; i++)
+                {
+                    if (_system.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
+                    {
+                        amiiboId = _system.Device.System.NfpDevices[i].AmiiboId;
+                        scanned = true;
+                    }
+                }
+            }
+            VirtualAmiibo.UpdateNickName(amiiboId, newName);
+        }
+
+        private static byte[] BuildResponse(ReturnValueForAmiiboSettings returnValue)
+        {
+            int size = Unsafe.SizeOf<ReturnValueForAmiiboSettings>();
+            byte[] bytes = new byte[size];
+
+            fixed (byte* bytesPtr = bytes)
+            {
+                Unsafe.Write(bytesPtr, returnValue);
+            }
+
+            return bytes;
+        }
+
+        public static T ReadStruct<T>(byte[] data) where T : unmanaged
+        {
+            if (data.Length < Unsafe.SizeOf<T>())
+            {
+                throw new ArgumentException("Not enough data to read the struct");
+            }
+
+            fixed (byte* dataPtr = data)
+            {
+                return Unsafe.Read<T>(dataPtr);
+            }
+        }
+
+        #region Structs
+
+        [StructLayout(LayoutKind.Sequential, Pack = 1)]
+        public unsafe struct TagInfo
+        {
+            public fixed byte Data[0x58];
+        }
+
+        [StructLayout(LayoutKind.Sequential, Pack = 1)]
+        public unsafe struct StartParamForAmiiboSettings
+        {
+            public byte ZeroValue; // Left at zero by sdknso
+            public byte Type;
+            public byte Flags;
+            public byte AmiiboSettingsStartParamOffset28;
+            public ulong AmiiboSettingsStartParam0;
+
+            public TagInfo TagInfo; // Only enabled when flags bit 1 is set
+            public RegisterInfo RegisterInfo; // Only enabled when flags bit 2 is set
+
+            public fixed byte StartParamExtraData[0x20];
+
+            public fixed byte Reserved[0x24];
+        }
+
+        [StructLayout(LayoutKind.Sequential, Pack = 1)]
+        public unsafe struct ReturnValueForAmiiboSettings
+        {
+            public byte AmiiboSettingsReturnFlag;
+            private byte Padding1;
+            private byte Padding2;
+            private byte Padding3;
+            public DeviceHandle DeviceHandle;
+            public TagInfo TagInfo;
+            public RegisterInfo RegisterInfo;
+            public fixed byte IgnoredBySdknso[0x24];
+        }
+
+        [StructLayout(LayoutKind.Sequential, Pack = 1)]
+        public struct DeviceHandle
+        {
+            public ulong Handle;
+        }
+
+        public enum AmiiboSettingsReturnFlag : byte
+        {
+            Cancel = 0,
+            HasTagInfo = 2,
+            HasRegisterInfo = 4,
+            HasTagInfoAndRegisterInfo = 6
+        }
+
+        #endregion
+    }
+}

+ 7 - 0
src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs

@@ -93,6 +93,13 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
             return registerInfo;
         }
 
+        public static void UpdateNickName(string amiiboId, string newNickName)
+        {
+            VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+            virtualAmiiboFile.NickName = newNickName;
+            SaveAmiiboFile(virtualAmiiboFile);
+        }
+
         public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId)
         {
             VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);

+ 12 - 0
src/Ryujinx.HLE/UI/IHostUIHandler.cs

@@ -24,6 +24,18 @@ namespace Ryujinx.HLE.UI
         /// <returns>True when OK is pressed, False otherwise.</returns>
         bool DisplayMessageDialog(ControllerAppletUIArgs args);
 
+        /// <summary>
+        /// Displays an Input Dialog box to the user so they can enter the Amiibo's new name
+        /// </summary>
+        /// <param name="userText">Text that the user entered. Set to `null` on internal errors</param>
+        /// <returns>True when OK is pressed, False otherwise. Also returns True on internal errors</returns>
+        bool DisplayCabinetDialog(out string userText);
+
+        /// <summary>
+        /// Displays a Message Dialog box to the user to notify them to scan the Amiibo.
+        /// </summary>
+        void DisplayCabinetMessageDialog();
+
         /// <summary>
         /// Tell the UI that we need to transition to another program.
         /// </summary>

+ 14 - 0
src/Ryujinx.Headless.SDL2/WindowBase.cs

@@ -1,4 +1,5 @@
 using Humanizer;
+using LibHac.Tools.Fs;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Configuration.Hid;
 using Ryujinx.Common.Logging;
@@ -485,6 +486,19 @@ namespace Ryujinx.Headless.SDL2
             return true;
         }
 
+        public bool DisplayCabinetDialog(out string userText)
+        {
+            // SDL2 doesn't support input dialogs
+            userText = "Ryujinx";
+
+            return true;
+        }
+
+        public void DisplayCabinetMessageDialog()
+        {
+            SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, "Cabinet Dialog", "Please scan your Amiibo now.", WindowHandle);
+        }
+
         public bool DisplayMessageDialog(ControllerAppletUIArgs args)
         {
             if (_ignoreControllerApplet) return false;

+ 3 - 0
src/Ryujinx/Assets/Locales/ar_SA.json

@@ -702,6 +702,9 @@
   "Never": "مطلقا",
   "SwkbdMinCharacters": "يجب أن يبلغ طوله {0} حرفا على الأقل",
   "SwkbdMinRangeCharacters": "يجب أن يتكون من {0}-{1} حرفا",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "لوحة المفاتيح البرمجية",
   "SoftwareKeyboardModeNumeric": "يجب أن يكون 0-9 أو '.' فقط",
   "SoftwareKeyboardModeAlphabet": "يجب أن تكون الأحرف غير CJK فقط",

+ 3 - 0
src/Ryujinx/Assets/Locales/de_DE.json

@@ -702,6 +702,9 @@
   "Never": "Niemals",
   "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein",
   "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Software-Tastatur",
   "SoftwareKeyboardModeNumeric": "Darf nur 0-9 oder \".\" sein",
   "SoftwareKeyboardModeAlphabet": "Keine CJK-Zeichen",

+ 3 - 0
src/Ryujinx/Assets/Locales/el_GR.json

@@ -702,6 +702,9 @@
   "Never": "Ποτέ",
   "SwkbdMinCharacters": "Πρέπει να έχει μήκος τουλάχιστον {0} χαρακτήρες",
   "SwkbdMinRangeCharacters": "Πρέπει να έχει μήκος {0}-{1} χαρακτήρες",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Εικονικό Πληκτρολόγιο",
   "SoftwareKeyboardModeNumeric": "Πρέπει να είναι 0-9 ή '.' μόνο",
   "SoftwareKeyboardModeAlphabet": "Πρέπει να μην είναι μόνο χαρακτήρες CJK",

+ 3 - 0
src/Ryujinx/Assets/Locales/en_US.json

@@ -714,6 +714,9 @@
   "Never": "Never",
   "SwkbdMinCharacters": "Must be at least {0} characters long",
   "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Software Keyboard",
   "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only",
   "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only",

+ 3 - 0
src/Ryujinx/Assets/Locales/es_ES.json

@@ -702,6 +702,9 @@
   "Never": "Nunca",
   "SwkbdMinCharacters": "Debe tener al menos {0} caracteres",
   "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Teclado de software",
   "SoftwareKeyboardModeNumeric": "Debe ser sólo 0-9 o '.'",
   "SoftwareKeyboardModeAlphabet": "Solo deben ser caracteres no CJK",

+ 3 - 0
src/Ryujinx/Assets/Locales/fr_FR.json

@@ -702,6 +702,9 @@
   "Never": "Jamais",
   "SwkbdMinCharacters": "Doit comporter au moins {0} caractères",
   "SwkbdMinRangeCharacters": "Doit comporter entre {0} et {1} caractères",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Clavier logiciel",
   "SoftwareKeyboardModeNumeric": "Doit être 0-9 ou '.' uniquement",
   "SoftwareKeyboardModeAlphabet": "Doit être uniquement des caractères non CJK",

+ 3 - 0
src/Ryujinx/Assets/Locales/he_IL.json

@@ -702,6 +702,9 @@
   "Never": "אף פעם",
   "SwkbdMinCharacters": "לפחות {0} תווים",
   "SwkbdMinRangeCharacters": "באורך {0}-{1} תווים",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "מקלדת וירטואלית",
   "SoftwareKeyboardModeNumeric": "חייב להיות בין 0-9 או '.' בלבד",
   "SoftwareKeyboardModeAlphabet": "מחויב להיות ללא אותיות CJK",

+ 3 - 0
src/Ryujinx/Assets/Locales/it_IT.json

@@ -702,6 +702,9 @@
   "Never": "Mai",
   "SwkbdMinCharacters": "Non può avere meno di {0} caratteri",
   "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Tastiera software",
   "SoftwareKeyboardModeNumeric": "Deve essere solo 0-9 o '.'",
   "SoftwareKeyboardModeAlphabet": "Deve essere solo caratteri non CJK",

+ 3 - 0
src/Ryujinx/Assets/Locales/ja_JP.json

@@ -702,6 +702,9 @@
   "Never": "決して",
   "SwkbdMinCharacters": "最低 {0} 文字必要です",
   "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "ソフトウェアキーボード",
   "SoftwareKeyboardModeNumeric": "0-9 または '.' のみでなければなりません",
   "SoftwareKeyboardModeAlphabet": "CJK文字以外のみ",

+ 3 - 0
src/Ryujinx/Assets/Locales/ko_KR.json

@@ -702,6 +702,9 @@
   "Never": "절대 안 함",
   "SwkbdMinCharacters": "{0}자 이상이어야 함",
   "SwkbdMinRangeCharacters": "{0}-{1}자 길이여야 함",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "소프트웨어 키보드",
   "SoftwareKeyboardModeNumeric": "0-9 또는 '.'만 가능",
   "SoftwareKeyboardModeAlphabet": "CJK 문자가 아닌 문자만 가능",

+ 3 - 0
src/Ryujinx/Assets/Locales/pl_PL.json

@@ -702,6 +702,9 @@
   "Never": "Nigdy",
   "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków",
   "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Klawiatura Oprogramowania",
   "SoftwareKeyboardModeNumeric": "Może składać się jedynie z 0-9 lub '.'",
   "SoftwareKeyboardModeAlphabet": "Nie może zawierać znaków CJK",

+ 3 - 0
src/Ryujinx/Assets/Locales/pt_BR.json

@@ -701,6 +701,9 @@
   "Never": "Nunca",
   "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres",
   "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Teclado por Software",
   "SoftwareKeyboardModeNumeric": "Deve ser somente 0-9 ou '.'",
   "SoftwareKeyboardModeAlphabet": "Apenas devem ser caracteres não CJK.",

+ 3 - 0
src/Ryujinx/Assets/Locales/ru_RU.json

@@ -702,6 +702,9 @@
   "Never": "Никогда",
   "SwkbdMinCharacters": "Должно быть не менее {0} символов.",
   "SwkbdMinRangeCharacters": "Должно быть {0}-{1} символов",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Программная клавиатура",
   "SoftwareKeyboardModeNumeric": "Должно быть в диапазоне 0-9 или '.'",
   "SoftwareKeyboardModeAlphabet": "Не должно быть CJK-символов",

+ 3 - 0
src/Ryujinx/Assets/Locales/th_TH.json

@@ -702,6 +702,9 @@
   "Never": "ไม่ต้อง",
   "SwkbdMinCharacters": "ต้องมีความยาวของตัวอักษรอย่างน้อย {0} ตัว",
   "SwkbdMinRangeCharacters": "ต้องมีความยาวของตัวอักษร {0}-{1} ตัว",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "ซอฟต์แวร์คีย์บอร์ด",
   "SoftwareKeyboardModeNumeric": "ต้องเป็น 0-9 หรือ '.' เท่านั้น",
   "SoftwareKeyboardModeAlphabet": "ต้องเป็นตัวอักษรที่ไม่ใช่ประเภท CJK เท่านั้น",

+ 3 - 0
src/Ryujinx/Assets/Locales/tr_TR.json

@@ -702,6 +702,9 @@
   "Never": "Hiçbir Zaman",
   "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı",
   "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Yazılım Klavyesi",
   "SoftwareKeyboardModeNumeric": "Sadece 0-9 veya '.' olabilir",
   "SoftwareKeyboardModeAlphabet": "Sadece CJK-characters olmayan karakterler olabilir",

+ 3 - 0
src/Ryujinx/Assets/Locales/uk_UA.json

@@ -702,6 +702,9 @@
   "Never": "Ніколи",
   "SwkbdMinCharacters": "Мінімальна кількість символів: {0}",
   "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "Програмна клавіатура",
   "SoftwareKeyboardModeNumeric": "Повинно бути лише 0-9 або “.”",
   "SoftwareKeyboardModeAlphabet": "Повинно бути лише не CJK-символи",

+ 3 - 0
src/Ryujinx/Assets/Locales/zh_CN.json

@@ -702,6 +702,9 @@
   "Never": "从不",
   "SwkbdMinCharacters": "不少于 {0} 个字符",
   "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字符",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "软键盘",
   "SoftwareKeyboardModeNumeric": "只能输入 0-9 或 \".\"",
   "SoftwareKeyboardModeAlphabet": "仅支持非中文字符",

+ 3 - 0
src/Ryujinx/Assets/Locales/zh_TW.json

@@ -702,6 +702,9 @@
   "Never": "從不",
   "SwkbdMinCharacters": "長度必須至少為 {0} 個字元",
   "SwkbdMinRangeCharacters": "長度必須為 {0} 到 {1} 個字元",
+  "CabinetTitle": "Cabinet Dialog",
+  "CabinetDialog": "Enter your Amiibo's new name",
+  "CabinetScanDialog": "Please scan your Amiibo now.",
   "SoftwareKeyboard": "軟體鍵盤",
   "SoftwareKeyboardModeNumeric": "必須是 0 到 9 或「.」",
   "SoftwareKeyboardModeAlphabet": "必須是「非中日韓字元」 (non CJK)",

+ 50 - 0
src/Ryujinx/UI/Applet/AvaHostUIHandler.cs

@@ -7,6 +7,7 @@ using Ryujinx.Ava.UI.Helpers;
 using Ryujinx.Ava.UI.Windows;
 using Ryujinx.HLE;
 using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
 using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
 using Ryujinx.HLE.UI;
 using Ryujinx.UI.Common.Configuration;
@@ -155,6 +156,55 @@ namespace Ryujinx.Ava.UI.Applet
             return error || okPressed;
         }
 
+        public bool DisplayCabinetDialog(out string userText)
+        {
+            ManualResetEvent dialogCloseEvent = new(false);
+            bool okPressed = false;
+            string inputText = "My Amiibo";
+            Dispatcher.UIThread.InvokeAsync(async () =>
+            {
+                try
+                {
+                    _parent.ViewModel.AppHost.NpadManager.BlockInputUpdates();
+                    SoftwareKeyboardUIArgs args = new SoftwareKeyboardUIArgs();
+                    args.KeyboardMode = KeyboardMode.Default;
+                    args.InitialText = "Ryujinx";
+                    args.StringLengthMin = 1;
+                    args.StringLengthMax = 25;
+                    (UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
+                    if (result == UserResult.Ok)
+                    {
+                        inputText = userInput;
+                        okPressed = true;
+                    }
+                }
+                finally
+                {
+                    dialogCloseEvent.Set();
+                }
+            });
+            dialogCloseEvent.WaitOne();
+            _parent.ViewModel.AppHost.NpadManager.UnblockInputUpdates();
+            userText = inputText;
+            return okPressed;
+        }
+
+        public void DisplayCabinetMessageDialog()
+        {
+            ManualResetEvent dialogCloseEvent = new(false);
+            Dispatcher.UIThread.InvokeAsync(async () =>
+            {
+                dialogCloseEvent.Set();
+                await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
+                string.Empty,
+                LocaleManager.Instance[LocaleKeys.InputDialogOk],
+                string.Empty,
+                LocaleManager.Instance[LocaleKeys.CabinetTitle]);
+            });
+            dialogCloseEvent.WaitOne();
+        }
+
+
         public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
         {
             device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);