From d42088d2e0465d5040bcc944d20a674ed41cd0c2 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Sun, 22 Dec 2024 22:54:50 -0600 Subject: [PATCH 1/4] Add Mii Support to Bin Amiibos --- src/Ryujinx.HLE/HOS/Horizon.cs | 13 +- .../Nfc/AmiiboDecryption/AmiiboBinReader.cs | 120 +++----- .../Nfc/AmiiboDecryption/CharInfoBin.cs | 265 ++++++++++++++++++ .../AmiiboDecryption/VirtualAmiiboBinFile.cs | 70 +++++ .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 39 ++- 5 files changed, 395 insertions(+), 112 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index f9c5ddecf..4635ed037 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -339,10 +339,9 @@ public void SimulateWakeUpMessage() public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid) { - if (VirtualAmiibo.ApplicationBytes.Length > 0) + if (VirtualAmiibo.VirtualAmiiboBinFile != null) { - VirtualAmiibo.ApplicationBytes = Array.Empty(); - VirtualAmiibo.InputBin = string.Empty; + VirtualAmiibo.VirtualAmiiboBinFile.SaveFile(); } if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag) { @@ -353,13 +352,7 @@ public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid) } public void ScanAmiiboFromBin(string path) { - VirtualAmiibo.InputBin = path; - if (VirtualAmiibo.ApplicationBytes.Length > 0) - { - VirtualAmiibo.ApplicationBytes = Array.Empty(); - } - byte[] encryptedData = File.ReadAllBytes(path); - VirtualAmiiboFile newFile = AmiiboBinReader.ReadBinFile(encryptedData); + VirtualAmiiboFile newFile = AmiiboBinReader.ReadBinFile(path); if (SearchingForAmiibo(out int nfpDeviceId)) { NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound; diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs index 03063cb53..6c318fec8 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -19,8 +19,19 @@ private static byte CalculateBCC1(byte[] uid) return (byte)(uid[3] ^ uid[4] ^ uid[5] ^ uid[6]); } - public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes) + public static VirtualAmiiboFile ReadBinFile(string filePath) { + Logger.Info?.Print(LogClass.ServiceNfp, "Reading bin file."); + byte[] fileBytes; + try + { + fileBytes = File.ReadAllBytes(filePath); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.ServiceNfp, $"Error reading file: {ex.Message}"); + return new VirtualAmiiboFile(); + } string keyRetailBinPath = GetKeyRetailBinPath(); if (string.IsNullOrEmpty(keyRetailBinPath)) { @@ -39,6 +50,7 @@ public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes) byte[] newFileBytes = new byte[totalBytes]; Array.Copy(fileBytes, newFileBytes, fileBytes.Length); fileBytes = newFileBytes; + Logger.Info?.Print(LogClass.ServiceNfp, "Added 8 bytes to the end of the file."); } AmiiboDecryptor amiiboDecryptor = new(keyRetailBinPath); @@ -59,6 +71,7 @@ public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes) byte[] dataFull = amiiboDump.GetData(); Logger.Debug?.Print(LogClass.ServiceNfp, $"Data Full Length: {dataFull.Length}"); byte[] uid = new byte[7]; + byte[] mii = new byte[98]; Array.Copy(dataFull, 0, uid, 0, 7); byte bcc0 = CalculateBCC0(uid); @@ -96,6 +109,10 @@ public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes) setID[0] = pageData[2]; formData = pageData[3]; break; + case >= 40 and <= 63: + int miiOffset = (page - 40) * 4; + Array.Copy(pageData, 0, mii, miiOffset, 4); + break; case 64: case 65: // Extract title ID @@ -113,7 +130,6 @@ public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes) break; } } - string usedCharacterStr = BitConverter.ToString(usedCharacter).Replace("-", ""); string variationStr = BitConverter.ToString(variation).Replace("-", ""); string amiiboIDStr = BitConverter.ToString(amiiboID).Replace("-", ""); @@ -141,14 +157,20 @@ public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes) LastWriteDate = writeDateTime, WriteCounter = writeCounterValue, }; - if (writeCounterValue > 0) + VirtualAmiiboBinFile virtualAmiiboBinFile = new VirtualAmiiboBinFile { - VirtualAmiibo.ApplicationBytes = applicationAreas; - } - VirtualAmiibo.NickName = nickName; + ApplicationBytes = applicationAreas, + MiiBytes = mii, + InputBin = filePath, + NickName = nickName, + WriteCounter = writeCounterValue, + LastWriteDate = writeDateTime, + TagUuid = uid, + }; + VirtualAmiibo.VirtualAmiiboBinFile = virtualAmiiboBinFile; return virtualAmiiboFile; } - public static bool SaveBinFile(string inputFile, byte[] appData) + public static bool SaveBinFile(string inputFile, VirtualAmiiboBinFile virtualAmiiboBinFile) { Logger.Info?.Print(LogClass.ServiceNfp, "Saving bin file."); byte[] readBytes; @@ -168,12 +190,6 @@ public static bool SaveBinFile(string inputFile, byte[] appData) return false; } - if (appData.Length != 216) // Ensure application area size is valid - { - Logger.Error?.Print(LogClass.ServiceNfp, "Invalid application data length. Expected 216 bytes."); - return false; - } - if (readBytes.Length == 532) { // add 8 bytes to the end of the file @@ -184,76 +200,22 @@ public static bool SaveBinFile(string inputFile, byte[] appData) AmiiboDecryptor amiiboDecryptor = new AmiiboDecryptor(keyRetailBinPath); AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(readBytes); - - byte[] oldData = amiiboDump.GetData(); - if (oldData.Length != 540) // Verify the expected length for NTAG215 tags - { - Logger.Error?.Print(LogClass.ServiceNfp, "Invalid tag data length. Expected 540 bytes."); - return false; - } - - byte[] newData = new byte[oldData.Length]; - Array.Copy(oldData, newData, oldData.Length); - - // Replace application area with appData - int appAreaOffset = 76 * 4; // Starting page (76) times 4 bytes per page - Array.Copy(appData, 0, newData, appAreaOffset, appData.Length); - - AmiiboDump encryptedDump = amiiboDecryptor.EncryptAmiiboDump(newData); - byte[] encryptedData = encryptedDump.GetData(); - - if (encryptedData == null || encryptedData.Length != readBytes.Length) - { - Logger.Error?.Print(LogClass.ServiceNfp, "Failed to encrypt data correctly."); - return false; - } - inputFile = inputFile.Replace("_modified", string.Empty); - // Save the encrypted data to file or return it for saving externally - string outputFilePath = Path.Combine(Path.GetDirectoryName(inputFile), Path.GetFileNameWithoutExtension(inputFile) + "_modified.bin"); - try - { - File.WriteAllBytes(outputFilePath, encryptedData); - Logger.Info?.Print(LogClass.ServiceNfp, $"Modified Amiibo data saved to {outputFilePath}."); - return true; - } - catch (Exception ex) - { - Logger.Error?.Print(LogClass.ServiceNfp, $"Error saving file: {ex.Message}"); - return false; - } - } - public static bool SaveBinFile(string inputFile, string newNickName) - { - Logger.Info?.Print(LogClass.ServiceNfp, "Saving bin file."); - byte[] readBytes; - try - { - readBytes = File.ReadAllBytes(inputFile); - } - catch (Exception ex) + amiiboDump.AmiiboNickname = virtualAmiiboBinFile.NickName; + byte[] appData = virtualAmiiboBinFile.ApplicationBytes; + if (appData.Length != 216) { - Logger.Error?.Print(LogClass.ServiceNfp, $"Error reading file: {ex.Message}"); - return false; - } - string keyRetailBinPath = GetKeyRetailBinPath(); - if (string.IsNullOrEmpty(keyRetailBinPath)) - { - Logger.Error?.Print(LogClass.ServiceNfp, "Key retail path is empty."); + Logger.Error?.Print(LogClass.ServiceNfp, "Invalid application data length. Expected 216 bytes."); return false; } - - if (readBytes.Length == 532) - { - // add 8 bytes to the end of the file - byte[] newFileBytes = new byte[540]; - Array.Copy(readBytes, newFileBytes, readBytes.Length); - readBytes = newFileBytes; - } - - AmiiboDecryptor amiiboDecryptor = new AmiiboDecryptor(keyRetailBinPath); - AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(readBytes); - amiiboDump.AmiiboNickname = newNickName; byte[] oldData = amiiboDump.GetData(); + Array.Copy(appData, 0, oldData, 304, 216); + // apply write counter and write date change + byte[] writeCounter = BitConverter.GetBytes(virtualAmiiboBinFile.WriteCounter); + byte[] writeDate = BitConverter.GetBytes((ushort)virtualAmiiboBinFile.LastWriteDate.Day); + writeDate = BitConverter.GetBytes((ushort)(writeDate[0] | (virtualAmiiboBinFile.LastWriteDate.Month << 5) | (virtualAmiiboBinFile.LastWriteDate.Year - 2000) << 9)); + Array.Copy(writeCounter, 0, oldData, 264, 2); + Array.Copy(writeDate, 0, oldData, 26, 2); + if (oldData.Length != 540) // Verify the expected length for NTAG215 tags { Logger.Error?.Print(LogClass.ServiceNfp, "Invalid tag data length. Expected 540 bytes."); diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs new file mode 100644 index 000000000..9c3e53519 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs @@ -0,0 +1,265 @@ +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.SystemState; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption +{ + internal class CharInfoBin + { + public byte MiiVersion { get; private set; } + public byte[] CreateID { get; private set; } + public bool AllowCopying { get; private set; } + public bool ProfanityFlag { get; private set; } + public int RegionLock { get; private set; } + public int CharacterSet { get; private set; } + public int PageIndex { get; private set; } + public int SlotIndex { get; private set; } + public int DeviceOrigin { get; private set; } + public ulong SystemId { get; private set; } + public uint MiiId { get; private set; } + public string CreatorMac { get; private set; } + public bool IsMale { get; private set; } + public int BirthdayMonth { get; private set; } + public int BirthdayDay { get; private set; } + public int FavoriteColor { get; private set; } + public bool FavoriteMii { get; private set; } + public string MiiName { get; private set; } + public string AuthorName { get; private set; } + public int Width { get; private set; } + public int Height { get; private set; } + public bool DisableSharing { get; private set; } + public int FaceShape { get; private set; } + public int SkinColor { get; private set; } + public int Wrinkles { get; private set; } + public int Makeup { get; private set; } + public int HairStyle { get; private set; } + public int HairColor { get; private set; } + public bool FlipHair { get; private set; } + public int EyeStyle { get; private set; } + public int EyeColor { get; private set; } + public int EyeScale { get; private set; } + public int EyeYScale { get; private set; } + public int EyeRotation { get; private set; } + public int EyeXSpacing { get; private set; } + public int EyeYPosition { get; private set; } + public int EyebrowStyle { get; private set; } + public int EyebrowColor { get; private set; } + public int EyebrowScale { get; private set; } + public int EyebrowYScale { get; private set; } + public int EyebrowRotation { get; private set; } + public int EyebrowXSpacing { get; private set; } + public int EyebrowYPosition { get; private set; } + public int NoseStyle { get; private set; } + public int NoseScale { get; private set; } + public int NoseYPosition { get; private set; } + public int MouthStyle { get; private set; } + public int MouthColor { get; private set; } + public int MouthScale { get; private set; } + public int MouthYScale { get; private set; } + public int MouthYPosition { get; private set; } + public int MustacheStyle { get; private set; } + public int BeardStyle { get; private set; } + public int BeardColor { get; private set; } + public int MustacheScale { get; private set; } + public int MustacheYPosition { get; private set; } + public int GlassesStyle { get; private set; } + public int GlassesColor { get; private set; } + public int GlassesScale { get; private set; } + public int GlassesYPosition { get; private set; } + public bool MoleEnabled { get; private set; } + public int MoleScale { get; private set; } + public int MoleXPosition { get; private set; } + public int MoleYPosition { get; private set; } + public static CharInfoBin Parse(byte[] data) + { + if (data.Length < 0x5C) + throw new ArgumentException("Data too short to parse Mii data."); + + var mii = new CharInfoBin(); + + mii.MiiVersion = data[0x0]; + + byte flags1 = data[0x1]; + mii.AllowCopying = (flags1 & 0x1) != 0; + mii.ProfanityFlag = (flags1 & 0x2) != 0; + mii.RegionLock = (flags1 >> 2) & 0x3; + mii.CharacterSet = (flags1 >> 4) & 0x3; + // add the first 0x10 bytes to the create id + mii.CreateID = data[0x0..0x10]; + byte position = data[0x2]; + mii.PageIndex = position & 0xF; + mii.SlotIndex = (position >> 4) & 0xF; + + byte deviceInfo = data[0x3]; + mii.DeviceOrigin = (deviceInfo >> 4) & 0x7; + + mii.SystemId = BitConverter.ToUInt64(data, 0x4); + + mii.MiiId = BitConverter.ToUInt32(data, 0xC); + + mii.CreatorMac = BitConverter.ToString(data, 0x10, 0x6).Replace("-", ":"); + + ushort flags2 = BitConverter.ToUInt16(data, 0x18); + mii.IsMale = (flags2 & 0x1) == 0; + mii.BirthdayMonth = (flags2 >> 1) & 0xF; + mii.BirthdayDay = (flags2 >> 5) & 0x1F; + mii.FavoriteColor = (flags2 >> 10) & 0xF; + mii.FavoriteMii = (flags2 & 0x4000) != 0; + + mii.MiiName = ParseUtf16String(data, 0x1A, 20); + + ushort dimensions = BitConverter.ToUInt16(data, 0x2E); + mii.Width = dimensions & 0xFF; + mii.Height = (dimensions >> 8) & 0xFF; + + byte flags3 = data[0x30]; + mii.DisableSharing = (flags3 & 0x1) != 0; + mii.FaceShape = (flags3 >> 1) & 0xF; + mii.SkinColor = (flags3 >> 5) & 0x7; + + byte flags4 = data[0x31]; + mii.Wrinkles = flags4 & 0xF; + mii.Makeup = (flags4 >> 4) & 0xF; + + mii.HairStyle = data[0x32]; + + byte hairInfo = data[0x33]; + mii.HairColor = hairInfo & 0x7; + mii.FlipHair = (hairInfo & 0x8) != 0; + + uint eyeInfo = BitConverter.ToUInt32(data, 0x34); + mii.EyeStyle = (int)(eyeInfo & 0x3F); + mii.EyeColor = (int)((eyeInfo >> 6) & 0x7); + mii.EyeScale = (int)((eyeInfo >> 9) & 0xF); + mii.EyeYScale = (int)((eyeInfo >> 13) & 0x7); + mii.EyeRotation = (int)((eyeInfo >> 16) & 0x1F); + mii.EyeXSpacing = (int)((eyeInfo >> 21) & 0xF); + mii.EyeYPosition = (int)((eyeInfo >> 25) & 0x1F); + + uint eyebrowInfo = BitConverter.ToUInt32(data, 0x38); + mii.EyebrowStyle = (int)(eyebrowInfo & 0x1F); + mii.EyebrowColor = (int)((eyebrowInfo >> 5) & 0x7); + mii.EyebrowScale = (int)((eyebrowInfo >> 8) & 0xF); + mii.EyebrowYScale = (int)((eyebrowInfo >> 12) & 0x7); + mii.EyebrowRotation = (int)((eyebrowInfo >> 16) & 0xF); + mii.EyebrowXSpacing = (int)((eyebrowInfo >> 21) & 0xF); + mii.EyebrowYPosition = (int)((eyebrowInfo >> 25) & 0x1F); + + ushort noseInfo = BitConverter.ToUInt16(data, 0x3C); + mii.NoseStyle = noseInfo & 0x1F; + mii.NoseScale = (noseInfo >> 5) & 0xF; + mii.NoseYPosition = (noseInfo >> 9) & 0x1F; + + ushort mouthInfo = BitConverter.ToUInt16(data, 0x3E); + mii.MouthStyle = mouthInfo & 0x3F; + mii.MouthColor = (mouthInfo >> 6) & 0x7; + mii.MouthScale = (mouthInfo >> 9) & 0xF; + mii.MouthYScale = (mouthInfo >> 13) & 0x7; + + ushort miscInfo = BitConverter.ToUInt16(data, 0x40); + mii.MouthYPosition = miscInfo & 0x1F; + mii.MustacheStyle = (miscInfo >> 5) & 0x7; + + ushort beardInfo = BitConverter.ToUInt16(data, 0x42); + mii.BeardStyle = beardInfo & 0x7; + mii.BeardColor = (beardInfo >> 3) & 0x7; + + ushort mustacheInfo = BitConverter.ToUInt16(data, 0x44); + mii.MustacheScale = mustacheInfo & 0xF; + mii.MustacheYPosition = (mustacheInfo >> 4) & 0x1F; + + ushort glassesInfo = BitConverter.ToUInt16(data, 0x46); + mii.GlassesStyle = glassesInfo & 0xF; + mii.GlassesColor = (glassesInfo >> 4) & 0x7; + mii.GlassesScale = (glassesInfo >> 7) & 0xF; + mii.GlassesYPosition = (glassesInfo >> 11) & 0x1F; + + byte moleFlags = data[0x48]; + mii.MoleEnabled = (moleFlags & 0x1) != 0; + mii.MoleScale = (moleFlags >> 1) & 0xF; + + ushort moleInfo = BitConverter.ToUInt16(data, 0x46); + mii.MoleEnabled = (moleInfo & 0x1) != 0; + mii.MoleScale = (moleInfo >> 1) & 0xF; // 4 bits for scale + mii.MoleXPosition = (moleInfo >> 5) & 0x1F; // 5 bits for X position + mii.MoleYPosition = (moleInfo >> 10) & 0x1F; // 5 bits for Y position + + + return mii; + } + + private static string ParseUtf16String(byte[] data, int offset, int maxLength) + { + int length = 0; + for (int i = 0; i < maxLength * 2; i += 2) + { + if (data[offset + i] == 0 && data[offset + i + 1] == 0) + break; + length++; + } + + return Encoding.Unicode.GetString(data, offset, length * 2); + } + + public CharInfo ConvertToCharInfo() + { + CharInfo Info = new CharInfo(); + UInt128 CreateId = BitConverter.ToUInt128(CreateID, 0); + Info.CreateId = new CreateId(CreateId); + Info.Build = 64; + Info.Type = 0; + Info.Nickname = Nickname.FromString(MiiName); + Info.FavoriteColor = (byte)FavoriteColor; + Info.Gender = IsMale ? Gender.Male : Gender.Female; + Info.Height = (byte)Height; + Info.FacelineType = (FacelineType)FaceShape; + Info.FacelineColor = (FacelineColor)SkinColor; + Info.FacelineWrinkle = (FacelineWrinkle)Wrinkles; + Info.FacelineMake = (FacelineMake)Makeup; + Info.HairType = (HairType)HairStyle; + Info.HairColor = (CommonColor)HairColor; + Info.HairFlip = FlipHair ? HairFlip.Left : HairFlip.Right; + Info.EyeType = (EyeType)EyeStyle; + Info.EyeColor = (CommonColor)EyeColor; + Info.EyeScale = (byte)EyeScale; + Info.EyeAspect = (byte)EyeYScale; + Info.EyeRotate = (byte)EyeRotation; + Info.EyeX = (byte)EyeXSpacing; + Info.EyeY = (byte)EyeYPosition; + Info.EyebrowType = (EyebrowType)EyebrowStyle; + Info.EyebrowColor = (CommonColor)EyebrowColor; + Info.EyebrowScale = (byte)EyebrowScale; + Info.EyebrowAspect = (byte)EyebrowYScale; + Info.EyebrowRotate = (byte)EyebrowRotation; + Info.EyebrowX = (byte)EyebrowXSpacing; + Info.EyebrowY = (byte)EyebrowYPosition; + Info.NoseType = (NoseType)NoseStyle; + Info.NoseScale = (byte)NoseScale; + Info.NoseY = (byte)NoseYPosition; + Info.MouthType = (MouthType)MouthStyle; + Info.MouthColor = (CommonColor)MouthColor; + Info.MouthScale = (byte)MouthScale; + Info.MouthAspect = (byte)MouthYScale; + Info.MouthY = (byte)MouthYPosition; + Info.BeardType = (BeardType)BeardStyle; + Info.BeardColor = (CommonColor)BeardColor; + Info.MustacheType = (MustacheType)MustacheStyle; + Info.MustacheScale = (byte)MustacheScale; + Info.MustacheY = (byte)MustacheYPosition; + Info.GlassType = (GlassType)GlassesStyle; + Info.GlassColor = (CommonColor)GlassesColor; + Info.GlassScale = (byte)GlassesScale; + Info.GlassY = (byte)GlassesYPosition; + Info.MoleType = MoleEnabled ? MoleType.OneDot : MoleType.None; + Info.MoleScale = (byte)MoleScale; + Info.MoleX = (byte)MoleXPosition; + Info.MoleY = (byte)MoleYPosition; + return Info; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs new file mode 100644 index 000000000..2ab294d45 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs @@ -0,0 +1,70 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Mii; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption +{ + public class VirtualAmiiboBinFile + { + public byte[] ApplicationBytes { get; set; } + public byte[] MiiBytes { get; set; } + public string InputBin { get; set; } + public string NickName { get; set; } + public int WriteCounter { get; set; } + public DateTime LastWriteDate { get; set; } + public byte[] TagUuid { get; set; } + + internal RegisterInfo GetRegisterInfo(ITickSource tickSource) + { + RegisterInfo info = new RegisterInfo(); + info.FirstWriteYear = (ushort)LastWriteDate.Year; + info.FirstWriteMonth = (byte)LastWriteDate.Month; + info.FirstWriteDay = (byte)LastWriteDate.Day; + byte[] nicknameBytes = Encoding.UTF8.GetBytes(NickName); + nicknameBytes.CopyTo(info.Nickname.AsSpan()); + byte[] newMiiBytes = new byte[92]; + Array.Copy(MiiBytes, 0, newMiiBytes, 0, 92); + CharInfoBin charInfoBin = CharInfoBin.Parse(newMiiBytes); + CharInfo charInfo = charInfoBin.ConvertToCharInfo(); + info.MiiCharInfo = charInfo; + return info; + } + public void UpdateApplicationArea(byte[] applicationAreaData) + { + ApplicationBytes = applicationAreaData; + WriteCounter++; + LastWriteDate = DateTime.Now; + SaveFile(); + } + + public void UpdateNickName(string newNickName) + { + NickName = newNickName; + SaveFile(); + } + + public bool SaveFile() + { + try + { + AmiiboBinReader.SaveBinFile(InputBin, this); + VirtualAmiibo.VirtualAmiiboBinFile = null; + } + catch (Exception) + { + return false; + } + return true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index 2cb35472f..0087783d7 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -16,9 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp static class VirtualAmiibo { public static uint OpenedApplicationAreaId; - public static byte[] ApplicationBytes = Array.Empty(); - public static string InputBin = string.Empty; - public static string NickName = string.Empty; + public static VirtualAmiiboBinFile VirtualAmiiboBinFile = null; private static readonly AmiiboJsonSerializerContext _serializerContext = AmiiboJsonSerializerContext.Default; public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) { @@ -69,13 +67,12 @@ public static CommonInfo GetCommonInfo(string amiiboId) public static RegisterInfo GetRegisterInfo(ITickSource tickSource, string amiiboId, string userName) { - VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); - string nickname = amiiboFile.NickName ?? "Ryujinx"; - if (NickName != string.Empty) + if (VirtualAmiiboBinFile!=null) { - nickname = NickName; - NickName = string.Empty; + return VirtualAmiiboBinFile.GetRegisterInfo(tickSource); } + VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); + string nickname = amiiboFile.NickName ?? "Ryujinx"; UtilityImpl utilityImpl = new(tickSource); CharInfo charInfo = new(); @@ -103,20 +100,20 @@ public static RegisterInfo GetRegisterInfo(ITickSource tickSource, string amiibo public static void UpdateNickName(string amiiboId, string newNickName) { - VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); - virtualAmiiboFile.NickName = newNickName; - if (InputBin != string.Empty) + if (VirtualAmiiboBinFile != null) { - AmiiboBinReader.SaveBinFile(InputBin, virtualAmiiboFile.NickName); + VirtualAmiiboBinFile.UpdateNickName(newNickName); return; } + VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + virtualAmiiboFile.NickName = newNickName; SaveAmiiboFile(virtualAmiiboFile); } public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId) { VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); - if (ApplicationBytes.Length > 0) + if (VirtualAmiiboBinFile != null) { OpenedApplicationAreaId = applicationAreaId; return true; @@ -134,11 +131,9 @@ public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId) public static byte[] GetApplicationArea(string amiiboId) { - if (ApplicationBytes.Length > 0) + if (VirtualAmiiboBinFile != null) { - byte[] bytes = ApplicationBytes; - ApplicationBytes = Array.Empty(); - return bytes; + return VirtualAmiiboBinFile.ApplicationBytes; } VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); @@ -175,9 +170,9 @@ public static bool CreateApplicationArea(string amiiboId, uint applicationAreaId public static void SetApplicationArea(string amiiboId, byte[] applicationAreaData) { - if (InputBin != string.Empty) + if (VirtualAmiiboBinFile != null) { - AmiiboBinReader.SaveBinFile(InputBin, applicationAreaData); + VirtualAmiiboBinFile.UpdateApplicationArea(applicationAreaData); return; } VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); @@ -241,11 +236,9 @@ public static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile) public static bool SaveFileExists(VirtualAmiiboFile virtualAmiiboFile) { - if (InputBin != string.Empty) + if (VirtualAmiiboBinFile != null) { - SaveAmiiboFile(virtualAmiiboFile); - return true; - + return VirtualAmiiboBinFile.SaveFile(); } return File.Exists(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json")); } From 3af1a2e033339485010f43cba32e0a9eac9bf224 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Mon, 23 Dec 2024 10:52:02 -0600 Subject: [PATCH 2/4] Temp Fix To Load Miis --- .../HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs | 9 +++------ .../Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs | 5 ++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs index 9c3e53519..5d919cd6b 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs @@ -206,13 +206,10 @@ private static string ParseUtf16String(byte[] data, int offset, int maxLength) return Encoding.Unicode.GetString(data, offset, length * 2); } - public CharInfo ConvertToCharInfo() + public CharInfo ConvertToCharInfo(CharInfo Info) { - CharInfo Info = new CharInfo(); - UInt128 CreateId = BitConverter.ToUInt128(CreateID, 0); - Info.CreateId = new CreateId(CreateId); - Info.Build = 64; - Info.Type = 0; + //UInt128 CreateId = BitConverter.ToUInt128(CreateID, 0); + //Info.CreateId = new CreateId(CreateId); Info.Nickname = Nickname.FromString(MiiName); Info.FavoriteColor = (byte)FavoriteColor; Info.Gender = IsMale ? Gender.Male : Gender.Female; diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs index 2ab294d45..83a5697cd 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs @@ -35,7 +35,10 @@ internal RegisterInfo GetRegisterInfo(ITickSource tickSource) byte[] newMiiBytes = new byte[92]; Array.Copy(MiiBytes, 0, newMiiBytes, 0, 92); CharInfoBin charInfoBin = CharInfoBin.Parse(newMiiBytes); - CharInfo charInfo = charInfoBin.ConvertToCharInfo(); + UtilityImpl utilityImpl = new UtilityImpl(tickSource); + CharInfo Info = new(); + Info.SetFromStoreData(StoreData.BuildDefault(utilityImpl, 0)); + CharInfo charInfo = charInfoBin.ConvertToCharInfo(Info); info.MiiCharInfo = charInfo; return info; } From 3c96c341d222c147eefda0075b033c19232ce22e Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Mon, 23 Dec 2024 19:37:42 -0600 Subject: [PATCH 3/4] Remove unnecessary imports --- .../HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs | 5 ----- .../Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs | 5 ----- 2 files changed, 10 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs index 5d919cd6b..0ce490d3e 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs @@ -1,11 +1,6 @@ using Ryujinx.HLE.HOS.Services.Mii.Types; -using Ryujinx.HLE.HOS.SystemState; using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs index 83a5697cd..22acd5665 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs @@ -1,16 +1,11 @@ -using Ryujinx.Common.Logging; using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Mii.Types; using Ryujinx.HLE.HOS.Services.Nfc.Nfp; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using System; using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { From cb1d477e1fc277c67d22388b5f314250d6ee59ce Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Mon, 6 Jan 2025 00:47:04 -0600 Subject: [PATCH 4/4] Make amiibo mii work in miiEdit --- .../Mii/StaticService/DatabaseServiceImpl.cs | 5 ++ .../HOS/Services/Mii/Types/CharInfo.cs | 64 +++++++++++++++++++ .../HOS/Services/Mii/Types/StoreData.cs | 5 ++ .../Nfc/AmiiboDecryption/CharInfoBin.cs | 10 ++- .../AmiiboDecryption/VirtualAmiiboBinFile.cs | 4 +- 5 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs index fc12e2533..931d88e31 100644 --- a/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs +++ b/src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs @@ -1,4 +1,5 @@ using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp; using Ryujinx.HLE.HOS.Services.Settings; using System; @@ -154,6 +155,10 @@ protected override ResultCode Move(CreateId createId, int newIndex) protected override ResultCode AddOrReplace(StoreData storeData) { + if (VirtualAmiibo.VirtualAmiiboBinFile != null) + { + storeData = VirtualAmiibo.VirtualAmiiboBinFile.StoreData; + } if (!_isSystem) { return ResultCode.PermissionDenied; diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs index 63f44694f..e1ad2777b 100644 --- a/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs @@ -335,6 +335,70 @@ public void SetFromStoreData(StoreData storeData) MoleY = storeData.CoreData.MoleY; Reserved = 0; } + public static StoreData BuildFromCharInfo(UtilityImpl utilImpl, CharInfo charInfo) + { + StoreData result = new() + { + CoreData = new CoreData + { + Nickname = charInfo.Nickname, + FontRegion = charInfo.FontRegion, + FavoriteColor = charInfo.FavoriteColor, + Gender = charInfo.Gender, + Height = charInfo.Height, + Build = charInfo.Build, + Type = charInfo.Type, + RegionMove = charInfo.RegionMove, + FacelineType = charInfo.FacelineType, + FacelineColor = charInfo.FacelineColor, + FacelineWrinkle = charInfo.FacelineWrinkle, + FacelineMake = charInfo.FacelineMake, + HairType = charInfo.HairType, + HairColor = charInfo.HairColor, + HairFlip = charInfo.HairFlip, + EyeType = charInfo.EyeType, + EyeColor = charInfo.EyeColor, + EyeScale = charInfo.EyeScale, + EyeAspect = charInfo.EyeAspect, + EyeRotate = charInfo.EyeRotate, + EyeX = charInfo.EyeX, + EyeY = charInfo.EyeY, + EyebrowType = charInfo.EyebrowType, + EyebrowColor = charInfo.EyebrowColor, + EyebrowScale = charInfo.EyebrowScale, + EyebrowAspect = charInfo.EyebrowAspect, + EyebrowRotate = charInfo.EyebrowRotate, + EyebrowX = charInfo.EyebrowX, + EyebrowY = charInfo.EyebrowY, + NoseType = charInfo.NoseType, + NoseScale = charInfo.NoseScale, + NoseY = charInfo.NoseY, + MouthType = charInfo.MouthType, + MouthColor = charInfo.MouthColor, + MouthScale = charInfo.MouthScale, + MouthAspect = charInfo.MouthAspect, + MouthY = charInfo.MouthY, + BeardColor = charInfo.BeardColor, + BeardType = charInfo.BeardType, + MustacheType = charInfo.MustacheType, + MustacheScale = charInfo.MustacheScale, + MustacheY = charInfo.MustacheY, + GlassType = charInfo.GlassType, + GlassColor = charInfo.GlassColor, + GlassScale = charInfo.GlassScale, + GlassY = charInfo.GlassY, + MoleType = charInfo.MoleType, + MoleScale = charInfo.MoleScale, + MoleX = charInfo.MoleX, + MoleY = charInfo.MoleY + } + }; + + result.UpdateCreateID(utilImpl); + result.UpdateCrc(); + + return result; + } public readonly void SetSource(Source source) { diff --git a/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs index 44054e6b8..9d19e4029 100644 --- a/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs +++ b/src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs @@ -30,6 +30,11 @@ private void UpdateDeviceCrc() DeviceCrc = CalculateDeviceCrc(); } + public void UpdateCreateID(UtilityImpl impl) + { + _createId = impl.MakeCreateId(); + } + public void UpdateCrc() { UpdateDataCrc(); diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs index 0ce490d3e..93f44cc5e 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/CharInfoBin.cs @@ -1,3 +1,5 @@ +using Humanizer; +using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Mii.Types; using System; using System.Text; @@ -7,7 +9,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption internal class CharInfoBin { public byte MiiVersion { get; private set; } - public byte[] CreateID { get; private set; } public bool AllowCopying { get; private set; } public bool ProfanityFlag { get; private set; } public int RegionLock { get; private set; } @@ -84,8 +85,6 @@ public static CharInfoBin Parse(byte[] data) mii.ProfanityFlag = (flags1 & 0x2) != 0; mii.RegionLock = (flags1 >> 2) & 0x3; mii.CharacterSet = (flags1 >> 4) & 0x3; - // add the first 0x10 bytes to the create id - mii.CreateID = data[0x0..0x10]; byte position = data[0x2]; mii.PageIndex = position & 0xF; mii.SlotIndex = (position >> 4) & 0xF; @@ -201,10 +200,9 @@ private static string ParseUtf16String(byte[] data, int offset, int maxLength) return Encoding.Unicode.GetString(data, offset, length * 2); } - public CharInfo ConvertToCharInfo(CharInfo Info) + public CharInfo ConvertToCharInfo(UtilityImpl utilImpl, CharInfo Info) { - //UInt128 CreateId = BitConverter.ToUInt128(CreateID, 0); - //Info.CreateId = new CreateId(CreateId); + Info.CreateId = utilImpl.MakeCreateId(); Info.Nickname = Nickname.FromString(MiiName); Info.FavoriteColor = (byte)FavoriteColor; Info.Gender = IsMale ? Gender.Male : Gender.Female; diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs index 22acd5665..286b11604 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/VirtualAmiiboBinFile.cs @@ -18,6 +18,7 @@ public class VirtualAmiiboBinFile public int WriteCounter { get; set; } public DateTime LastWriteDate { get; set; } public byte[] TagUuid { get; set; } + internal StoreData StoreData { get; set; } internal RegisterInfo GetRegisterInfo(ITickSource tickSource) { @@ -33,8 +34,9 @@ internal RegisterInfo GetRegisterInfo(ITickSource tickSource) UtilityImpl utilityImpl = new UtilityImpl(tickSource); CharInfo Info = new(); Info.SetFromStoreData(StoreData.BuildDefault(utilityImpl, 0)); - CharInfo charInfo = charInfoBin.ConvertToCharInfo(Info); + CharInfo charInfo = charInfoBin.ConvertToCharInfo(utilityImpl,Info); info.MiiCharInfo = charInfo; + StoreData = CharInfo.BuildFromCharInfo(utilityImpl, charInfo); return info; } public void UpdateApplicationArea(byte[] applicationAreaData)