Implement Cabinet applet
This implements Nickname support for Amiibos, as well as means of editing the name of an amiibo in games.
This commit is contained in:
parent
a23d8cb92f
commit
02e84fd59a
8 changed files with 229 additions and 1 deletions
|
@ -18,6 +18,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||
{ AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
|
||||
{ AppletId.Controller, typeof(ControllerApplet) },
|
||||
{ AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) },
|
||||
{ AppletId.Cabinet, typeof(CabinetApplet) },
|
||||
{ AppletId.LibAppletWeb, typeof(BrowserApplet) },
|
||||
{ AppletId.LibAppletShop, typeof(BrowserApplet) },
|
||||
{ AppletId.LibAppletOff, typeof(BrowserApplet) },
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
[Flags]
|
||||
enum AmiiboSettingsReturnFlag : byte
|
||||
{
|
||||
Cancel = 0,
|
||||
|
||||
HasTagInfo = 1 << 1,
|
||||
HasRegisterInfo = 1 << 2,
|
||||
|
||||
HasCompleteInfo = HasTagInfo | HasRegisterInfo,
|
||||
}
|
||||
}
|
123
src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs
Normal file
123
src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs
Normal file
|
@ -0,0 +1,123 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
internal class CabinetApplet : IApplet
|
||||
{
|
||||
private readonly Horizon _system;
|
||||
|
||||
private AppletSession _normalSession;
|
||||
private StartParamForAmiiboSettings _startArguments;
|
||||
|
||||
public event EventHandler AppletStateChanged;
|
||||
|
||||
public CabinetApplet(Horizon system)
|
||||
{
|
||||
_system = system;
|
||||
}
|
||||
|
||||
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
|
||||
{
|
||||
_normalSession = normalSession;
|
||||
|
||||
CommonArguments commonArguments = IApplet.ReadStruct<CommonArguments>(normalSession.Pop());
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceAm, $"CabinetApplet version: 0x{commonArguments.AppletVersion:x8}");
|
||||
|
||||
_startArguments = IApplet.ReadStruct<StartParamForAmiiboSettings>(normalSession.Pop());
|
||||
switch (_startArguments.Type)
|
||||
{
|
||||
case StartParamForAmiiboSettingsType.NicknameAndOwnerSettings:
|
||||
{
|
||||
// TODO: allow changing the owning Mii
|
||||
ChangeNickname();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"CabinetStartType {_startArguments.Type} is not implemented.");
|
||||
}
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public ResultCode GetResult()
|
||||
{
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private void ChangeNickname()
|
||||
{
|
||||
string nickname = null;
|
||||
if (_startArguments.Flags.HasFlag(AmiiboSettingsReturnFlag.HasRegisterInfo))
|
||||
{
|
||||
nickname = Encoding.UTF8.GetString(_startArguments.RegisterInfo.Nickname.AsSpan()).TrimEnd('\0');
|
||||
}
|
||||
|
||||
SoftwareKeyboardUIArgs inputParameters = new()
|
||||
{
|
||||
HeaderText = "Enter a new nickname for this amiibo.",
|
||||
GuideText = nickname,
|
||||
StringLengthMin = 1,
|
||||
StringLengthMax = 10,
|
||||
};
|
||||
|
||||
bool inputResult = _system.Device.UIHandler.DisplayInputDialog(inputParameters, out string newNickname);
|
||||
if (!inputResult)
|
||||
{
|
||||
ReturnCancel();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualAmiibo.SetNickname(_startArguments.TagInfo.Uuid.AsSpan()[..9].ToArray(), newNickname);
|
||||
|
||||
ReturnValueForAmiiboSettings returnValue = new()
|
||||
{
|
||||
Flags = AmiiboSettingsReturnFlag.HasCompleteInfo,
|
||||
DeviceHandle = (ulong)_system.NfpDevices[0].Handle,
|
||||
TagInfo = _startArguments.TagInfo,
|
||||
RegisterInfo = _startArguments.RegisterInfo,
|
||||
};
|
||||
|
||||
Span<byte> nicknameData = returnValue.RegisterInfo.Nickname.AsSpan();
|
||||
nicknameData.Clear();
|
||||
|
||||
Encoding.UTF8.GetBytes(newNickname).CopyTo(nicknameData);
|
||||
|
||||
_normalSession.Push(BuildResponse(returnValue));
|
||||
AppletStateChanged?.Invoke(this, null);
|
||||
_system.ReturnFocus();
|
||||
}
|
||||
|
||||
private void ReturnCancel()
|
||||
{
|
||||
_normalSession.Push(BuildResponse());
|
||||
AppletStateChanged?.Invoke(this, null);
|
||||
_system.ReturnFocus();
|
||||
}
|
||||
|
||||
private static byte[] BuildResponse(ReturnValueForAmiiboSettings result)
|
||||
{
|
||||
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||
using BinaryWriter writer = new(stream);
|
||||
|
||||
writer.WriteStruct(result);
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] BuildResponse()
|
||||
{
|
||||
return BuildResponse(new ReturnValueForAmiiboSettings { Flags = AmiiboSettingsReturnFlag.Cancel });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x188, Pack = 1)]
|
||||
struct ReturnValueForAmiiboSettings
|
||||
{
|
||||
public AmiiboSettingsReturnFlag Flags;
|
||||
public Array3<byte> Padding;
|
||||
public ulong DeviceHandle;
|
||||
public TagInfo TagInfo;
|
||||
public RegisterInfo RegisterInfo;
|
||||
public Array36<byte> Ignored;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x1a8)]
|
||||
struct StartParamForAmiiboSettings
|
||||
{
|
||||
public byte Unused1;
|
||||
public StartParamForAmiiboSettingsType Type;
|
||||
public AmiiboSettingsReturnFlag Flags;
|
||||
public Array9<byte> StartParamData1;
|
||||
public TagInfo TagInfo;
|
||||
public RegisterInfo RegisterInfo;
|
||||
public Array32<byte> StartParamData2;
|
||||
public Array36<byte> Unused2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.HLE.HOS.Applets
|
||||
{
|
||||
enum StartParamForAmiiboSettingsType : byte
|
||||
{
|
||||
NicknameAndOwnerSettings = 0,
|
||||
GameDataEraser = 1,
|
||||
Restorer = 2,
|
||||
Formatter = 3,
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
|
|||
struct VirtualAmiiboFile
|
||||
{
|
||||
public uint FileVersion { get; set; }
|
||||
public string Nickname { get; set; }
|
||||
public byte[] TagUuid { get; set; }
|
||||
public string AmiiboId { get; set; }
|
||||
public DateTime FirstWriteDate { get; set; }
|
||||
|
|
|
@ -8,6 +8,8 @@ using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||
{
|
||||
|
@ -85,7 +87,8 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
Reserved1 = new Array64<byte>(),
|
||||
Reserved2 = new Array58<byte>(),
|
||||
};
|
||||
"Ryujinx"u8.CopyTo(registerInfo.Nickname.AsSpan());
|
||||
|
||||
Encoding.UTF8.GetBytes(amiiboFile.Nickname).CopyTo(registerInfo.Nickname.AsSpan());
|
||||
|
||||
return registerInfo;
|
||||
}
|
||||
|
@ -163,6 +166,15 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
}
|
||||
}
|
||||
|
||||
public static void SetNickname(byte[] tagUuid, string nickname)
|
||||
{
|
||||
VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(tagUuid);
|
||||
|
||||
virtualAmiiboFile.Nickname = nickname;
|
||||
|
||||
SaveAmiiboFile(virtualAmiiboFile);
|
||||
}
|
||||
|
||||
private static VirtualAmiiboFile LoadAmiiboFile(string amiiboId)
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
|
||||
|
@ -174,12 +186,20 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
if (File.Exists(filePath))
|
||||
{
|
||||
virtualAmiiboFile = JsonHelper.DeserializeFromFile(filePath, _serializerContext.VirtualAmiiboFile);
|
||||
|
||||
if (virtualAmiiboFile.Nickname == null)
|
||||
{
|
||||
virtualAmiiboFile.Nickname = "Ryujinx";
|
||||
|
||||
SaveAmiiboFile(virtualAmiiboFile);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
virtualAmiiboFile = new VirtualAmiiboFile()
|
||||
{
|
||||
FileVersion = 0,
|
||||
Nickname = "Ryujinx",
|
||||
TagUuid = Array.Empty<byte>(),
|
||||
AmiiboId = amiiboId,
|
||||
FirstWriteDate = DateTime.Now,
|
||||
|
@ -194,6 +214,28 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
|||
return virtualAmiiboFile;
|
||||
}
|
||||
|
||||
private static VirtualAmiiboFile LoadAmiiboFile(byte[] tagUuid)
|
||||
{
|
||||
VirtualAmiiboFile virtualAmiiboFile;
|
||||
|
||||
string[] paths = Directory.GetFiles(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"), "*.json");
|
||||
foreach (string path in paths)
|
||||
{
|
||||
if (path.EndsWith("Amiibo.json"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
virtualAmiiboFile = JsonHelper.DeserializeFromFile(path, _serializerContext.VirtualAmiiboFile);
|
||||
if (virtualAmiiboFile.TagUuid.SequenceEqual(tagUuid))
|
||||
{
|
||||
return virtualAmiiboFile;
|
||||
}
|
||||
}
|
||||
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile)
|
||||
{
|
||||
string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json");
|
||||
|
|
Loading…
Reference in a new issue