Skip to content

Commit

Permalink
Add the Cabinet Applet (#340)
Browse files Browse the repository at this point in the history
This adds the missing Cabinet Applet, which allows for formatting
Amiibos and changing their names.
  • Loading branch information
Jacobwasbeast authored Dec 3, 2024
1 parent 17483aa commit 08b7257
Show file tree
Hide file tree
Showing 24 changed files with 335 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/Ryujinx.HLE/HOS/Applets/AppletManager.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -31,6 +32,8 @@ public static IApplet Create(AppletId applet, Horizon system)
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!");
Expand Down
195 changes: 195 additions & 0 deletions src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 0 deletions src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ public static RegisterInfo GetRegisterInfo(ITickSource tickSource, string amiibo
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);
Expand Down
12 changes: 12 additions & 0 deletions src/Ryujinx.HLE/UI/IHostUIHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ public interface IHostUIHandler
/// <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>
Expand Down
14 changes: 14 additions & 0 deletions src/Ryujinx.Headless.SDL2/WindowBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Humanizer;
using LibHac.Tools.Fs;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
Expand Down Expand Up @@ -485,6 +486,19 @@ public bool DisplayMessageDialog(string title, string message)
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;
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/ar_SA.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 فقط",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/el_GR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/es_ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/he_IL.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/it_IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/ja_JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -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文字以外のみ",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/ko_KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 문자가 아닌 문자만 가능",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/pl_PL.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions src/Ryujinx/Assets/Locales/pt_BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
Loading

0 comments on commit 08b7257

Please sign in to comment.