Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the Cabinet Applet #340

Merged
merged 5 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
196 changes: 196 additions & 0 deletions src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
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()
{
var amiibo = _system.Device.System.NfpDevices[0];
_system.Device.System.NfpDevices.Remove(amiibo);
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.",
GreemDev marked this conversation as resolved.
Show resolved Hide resolved
"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
Loading