From 6b155f5fbe00312b0d4ce785ba36e1cc9b12f99b Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Tue, 3 Dec 2024 21:50:33 -0600 Subject: [PATCH 1/9] First attempt Currently DateTime is incorrect, Still trying to figure out application data. --- src/Ryujinx.HLE/HOS/Horizon.cs | 12 + .../Nfc/AmiiboDecryption/AmiiboBinReader.cs | 233 ++++++++++++++++++ .../Nfc/AmiiboDecryption/AmiiboDecrypter.cs | 113 +++++++++ .../Nfc/AmiiboDecryption/AmiiboMasterKey.cs | 69 ++++++ .../Nfp/NfpManager/Types/VirtualAmiiboFile.cs | 4 +- .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 2 +- src/Ryujinx/Assets/Locales/en_US.json | 1 + .../UI/ViewModels/MainWindowViewModel.cs | 28 +++ .../UI/Views/Main/MainMenuBarView.axaml | 7 + .../UI/Views/Main/MainMenuBarView.axaml.cs | 3 + 10 files changed, 469 insertions(+), 3 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 64b08e309..d961e1e6a 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -16,6 +16,7 @@ using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemA using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.HLE.HOS.Services.Caps; using Ryujinx.HLE.HOS.Services.Mii; +using Ryujinx.HLE.HOS.Services.Nfc.Bin; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; @@ -344,6 +345,17 @@ namespace Ryujinx.HLE.HOS NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid; } } + public void ScanAmiiboFromBin(string path) + { + byte[] encryptedData = File.ReadAllBytes(path); + VirtualAmiiboFile newFile = AmiiboBinReader.ReadBinFile(encryptedData); + if (SearchingForAmiibo(out int nfpDeviceId)) + { + NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound; + NfpDevices[nfpDeviceId].AmiiboId = newFile.AmiiboId; + NfpDevices[nfpDeviceId].UseRandomUuid = false; + } + } public bool SearchingForAmiibo(out int nfpDeviceId) { diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs new file mode 100644 index 000000000..8e32db885 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -0,0 +1,233 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using Ryujinx.HLE.HOS.Tamper; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Bin +{ + public class AmiiboBinReader + { + // Method to calculate BCC (XOR checksum) from UID bytes + private static byte CalculateBCC(byte[] uid, int startIdx) + { + return (byte)(uid[startIdx] ^ uid[startIdx + 1] ^ uid[startIdx + 2] ^ 0x88); + } + + // Method to read and process a .bin file + public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes) + { + string keyRetailBinPath = GetKeyRetailBinPath(); + if (string.IsNullOrEmpty(keyRetailBinPath)) + { + Console.WriteLine("Key retail bin path not found."); + return new VirtualAmiiboFile(); + } + + byte[] initialCounter = new byte[16]; + + // Ensure the file is long enough + if (fileBytes.Length < 128 * 4) // Each page is 4 bytes, total 512 bytes + { + Console.WriteLine("File is too short to process."); + return new VirtualAmiiboFile(); + } + + // Decrypt the Amiibo data + AmiiboDecrypter amiiboDecryptor = new AmiiboDecrypter(keyRetailBinPath); + byte[] decryptedFileBytes = amiiboDecryptor.DecryptAmiiboData(fileBytes, initialCounter); + + // Assuming the UID is stored in the first 7 bytes (NTAG215 UID length) + byte[] uid = new byte[7]; + Array.Copy(fileBytes, 0, uid, 0, 7); + + // Calculate BCC values + byte bcc0 = CalculateBCC(uid, 0); // BCC0 = UID0 ^ UID1 ^ UID2 ^ 0x88 + byte bcc1 = CalculateBCC(uid, 3); // BCC1 = UID3 ^ UID4 ^ UID5 ^ 0x00 + + Console.WriteLine($"UID: {BitConverter.ToString(uid)}"); + Console.WriteLine($"BCC0: 0x{bcc0:X2}, BCC1: 0x{bcc1:X2}"); + + // Initialize byte arrays for data extraction + byte[] nickNameBytes = new byte[20]; // Amiibo nickname is 20 bytes + byte[] titleId = new byte[8]; + byte[] usedCharacter = new byte[2]; + byte[] variation = new byte[2]; + byte[] amiiboID = new byte[2]; + byte[] setID = new byte[1]; + byte[] initDate = new byte[2]; + byte[] writeDate = new byte[2]; + byte[] writeCounter = new byte[2]; + byte formData = 0; + byte[] applicationAreas = new byte[212]; + + // Reading specific pages and parsing bytes + for (int page = 0; page < 128; page++) // NTAG215 has 128 pages + { + int pageStartIdx = page * 4; // Each page is 4 bytes + byte[] pageData = new byte[4]; + bool isEncrypted = IsPageEncrypted(page); + byte[] sourceBytes = isEncrypted ? decryptedFileBytes : fileBytes; + Array.Copy(sourceBytes, pageStartIdx, pageData, 0, 4); + + // Special handling for specific pages + switch (page) + { + case 0: // Page 0 (UID + BCC0) + Console.WriteLine("Page 0: UID and BCC0."); + break; + + case 2: // Page 2 (BCC1 + Internal Value) + byte internalValue = pageData[1]; + Console.WriteLine($"Page 2: BCC1 + Internal Value 0x{internalValue:X2} (Expected 0x48)."); + break; + + case 6: + // Bytes 0 and 1 are init date, bytes 2 and 3 are write date + Array.Copy(pageData, 0, initDate, 0, 2); + Array.Copy(pageData, 2, writeDate, 0, 2); + break; + + case 8: + case 9: + case 10: + case 11: + case 12: + // Extract nickname bytes + int nickNameOffset = (page - 8) * 4; + Array.Copy(pageData, 0, nickNameBytes, nickNameOffset, 4); + break; + + case 21: + // Bytes 0 and 1 are used character, bytes 2 and 3 are variation + Array.Copy(pageData, 0, usedCharacter, 0, 2); + Array.Copy(pageData, 2, variation, 0, 2); + break; + + case 22: + // Bytes 0 and 1 are amiibo ID, byte 2 is set ID, byte 3 is form data + Array.Copy(pageData, 0, amiiboID, 0, 2); + setID[0] = pageData[2]; + formData = pageData[3]; + break; + + case 64: + case 65: + // Extract title ID + int titleIdOffset = (page - 64) * 4; + Array.Copy(pageData, 0, titleId, titleIdOffset, 4); + break; + + case 66: + // Bytes 0 and 1 are write counter + Array.Copy(pageData, 0, writeCounter, 0, 2); + break; + + // Pages 76 to 127 are application areas + case >= 76 and <= 127: + int appAreaOffset = (page - 76) * 4; + Array.Copy(pageData, 0, applicationAreas, appAreaOffset, 4); + break; + } + } + // Debugging + string titleIdStr = BitConverter.ToString(titleId).Replace("-", ""); + string usedCharacterStr = BitConverter.ToString(usedCharacter).Replace("-", ""); + string variationStr = BitConverter.ToString(variation).Replace("-", ""); + string amiiboIDStr = BitConverter.ToString(amiiboID).Replace("-", ""); + string formDataStr = formData.ToString("X2"); + string setIDStr = BitConverter.ToString(setID).Replace("-", ""); + string nickName = Encoding.BigEndianUnicode.GetString(nickNameBytes).TrimEnd('\0'); + string head = usedCharacterStr + variationStr; + string tail = amiiboIDStr + setIDStr + "02"; + string finalID = head + tail; + string initDateStr = BitConverter.ToString(initDate).Replace("-", ""); + string writeDateStr = BitConverter.ToString(writeDate).Replace("-", ""); + + Console.WriteLine($"Title ID: {titleIdStr}"); + Console.WriteLine($"Head: {head}"); + Console.WriteLine($"Tail: {tail}"); + Console.WriteLine($"Used Character: {usedCharacterStr}"); + Console.WriteLine($"Form Data: {formDataStr}"); + Console.WriteLine($"Variation: {variationStr}"); + Console.WriteLine($"Amiibo ID: {amiiboIDStr}"); + Console.WriteLine($"Set ID: {setIDStr}"); + Console.WriteLine($"Final ID: {finalID}"); + Console.WriteLine($"Nickname: {nickName}"); + Console.WriteLine($"Init Date: {initDateStr}"); + Console.WriteLine($"Write Date: {writeDateStr}"); + + VirtualAmiiboFile virtualAmiiboFile = new VirtualAmiiboFile + { + FileVersion = 1, + TagUuid = uid, + AmiiboId = finalID + }; + + DateTime initDateTime = DateTimeFromBytes(initDate); + DateTime writeDateTime = DateTimeFromBytes(writeDate); + + Console.WriteLine($"Parsed Init Date: {initDateTime}"); + Console.WriteLine($"Parsed Write Date: {writeDateTime}"); + + virtualAmiiboFile.FirstWriteDate = initDateTime; + virtualAmiiboFile.LastWriteDate = writeDateTime; + virtualAmiiboFile.WriteCounter = BitConverter.ToUInt16(writeCounter, 0); + + // Parse application areas + //List applicationAreasList = ParseAmiiboData(applicationAreas); + List applicationAreasList = new List(); + virtualAmiiboFile.ApplicationAreas = applicationAreasList; + + // Save the virtual Amiibo file + VirtualAmiibo.SaveAmiiboFile(virtualAmiiboFile); + + return virtualAmiiboFile; + } + + private static string GetKeyRetailBinPath() + { + return Path.Combine(AppDataManager.KeysDirPath, "key_retail.bin"); + } + + public static bool IsPageEncrypted(int page) + { + return (page >= 6 && page <= 9) || (page >= 43 && page <= 84); + } + + public static DateTime DateTimeFromBytes(byte[] date) + { + if (date == null || date.Length != 2) + { + Console.WriteLine("Invalid date bytes."); + return DateTime.MinValue; + } + + ushort value = BitConverter.ToUInt16(date, 0); + + int day = value & 0x1F; + int month = (value >> 5) & 0x0F; + int year = (value >> 9) & 0x7F; + + try + { + return new DateTime(2000 + year, month, day); + } + catch (ArgumentOutOfRangeException) + { + Console.WriteLine("Invalid date values extracted."); + return DateTime.MinValue; + } + } + + public static List ParseAmiiboData(byte[] decryptedData) + { + return JsonSerializer.Deserialize>(decryptedData); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs new file mode 100644 index 000000000..b2b309828 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs @@ -0,0 +1,113 @@ +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Bin +{ + public class AmiiboDecrypter + { + public readonly byte[] _hmacKey; // HMAC key + public readonly byte[] _aesKey; // AES key + + public AmiiboDecrypter(string keyRetailBinPath) + { + var keys = AmiiboMasterKey.FromCombinedBin(File.ReadAllBytes(keyRetailBinPath)); + _hmacKey = keys.DataKey.HmacKey; + _aesKey = keys.DataKey.XorPad; + } + + public byte[] DecryptAmiiboData(byte[] encryptedData, byte[] counter) + { + // Ensure the counter length matches the block size + if (counter.Length != 16) + { + throw new ArgumentException("Counter must be 16 bytes long for AES block size."); + } + + byte[] decryptedData = new byte[encryptedData.Length]; + + using (Aes aesAlg = Aes.Create()) + { + aesAlg.Key = _aesKey; + aesAlg.Mode = CipherMode.ECB; // Use ECB mode to handle the counter encryption + aesAlg.Padding = PaddingMode.None; + + using (var encryptor = aesAlg.CreateEncryptor()) + { + int blockSize = 16; + byte[] encryptedCounter = new byte[blockSize]; + byte[] currentCounter = (byte[])counter.Clone(); + + for (int i = 0; i < encryptedData.Length; i += blockSize) + { + // Encrypt the current counter block + encryptor.TransformBlock(currentCounter, 0, blockSize, encryptedCounter, 0); + + // XOR the encrypted counter with the ciphertext to get the decrypted data + for (int j = 0; j < blockSize && i + j < encryptedData.Length; j++) + { + decryptedData[i + j] = (byte)(encryptedData[i + j] ^ encryptedCounter[j]); + } + + // Increment the counter for the next block + IncrementCounter(currentCounter); + } + } + } + + return decryptedData; + } + + public byte[] CalculateHMAC(byte[] data) + { + using (var hmac = new HMACSHA256(_hmacKey)) + { + return hmac.ComputeHash(data); + } + } + + public void IncrementCounter(byte[] counter) + { + for (int i = counter.Length - 1; i >= 0; i--) + { + if (++counter[i] != 0) + break; // Stop if no overflow + } + } + + public DateTime ParseDate(byte[] data, int offset) + { + ushort year = BitConverter.ToUInt16(data, offset); + byte month = data[offset + 2]; + byte day = data[offset + 3]; + byte hour = data[offset + 4]; + byte minute = data[offset + 5]; + byte second = data[offset + 6]; + + return new DateTime(year, month, day, hour, minute, second); + } + + public List ParseApplicationAreas(byte[] data, int startOffset, int areaSize) + { + var areas = new List(); + for (int i = 0; i < 8; i++) // Assuming 8 areas + { + int offset = startOffset + (i * areaSize); + string applicationId = BitConverter.ToString(data[offset..(offset + 4)]).Replace("-", ""); + byte[] areaData = data[(offset + 4)..(offset + areaSize)]; + areas.Add(new VirtualAmiiboApplicationArea + { + ApplicationAreaId = uint.Parse(applicationId), + ApplicationArea = areaData + }); + } + + return areas; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs new file mode 100644 index 000000000..9ec2d0b80 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Bin +{ + public class AmiiboMasterKey + { + private const int DataLength = 80; + private const int CombinedLength = 160; + public byte[] HmacKey { get; private set; } // 16 bytes + public byte[] TypeString { get; private set; } // 14 bytes + public byte Rfu { get; private set; } // 1 byte reserved + public byte MagicSize { get; private set; } // 1 byte + public byte[] MagicBytes { get; private set; } // 16 bytes + public byte[] XorPad { get; private set; } // 32 bytes + + private AmiiboMasterKey(byte[] data) + { + if (data.Length != DataLength) + throw new ArgumentException($"Data is {data.Length} bytes (should be {DataLength})."); + + + // Unpack the data + HmacKey = data[..16]; + TypeString = data[16..30]; + Rfu = data[30]; + MagicSize = data[31]; + MagicBytes = data[32..48]; + XorPad = data[48..]; + } + + public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromSeparateBin(byte[] dataBin, byte[] tagBin) + { + var dataKey = new AmiiboMasterKey(dataBin); + var tagKey = new AmiiboMasterKey(tagBin); + return (dataKey, tagKey); + } + + public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromSeparateHex(string dataHex, string tagHex) + { + return FromSeparateBin(HexToBytes(dataHex), HexToBytes(tagHex)); + } + + public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromCombinedBin(byte[] combinedBin) + { + if (combinedBin.Length != CombinedLength) + throw new ArgumentException($"Data is {combinedBin.Length} bytes (should be {CombinedLength})."); + + byte[] dataBin = combinedBin[..DataLength]; + byte[] tagBin = combinedBin[DataLength..]; + return FromSeparateBin(dataBin, tagBin); + } + + private static byte[] HexToBytes(string hex) + { + int length = hex.Length / 2; + byte[] bytes = new byte[length]; + for (int i = 0; i < length; i++) + { + bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + } + return bytes; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs index e1db98e5f..9450e1db5 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager { - struct VirtualAmiiboFile + public struct VirtualAmiiboFile { public uint FileVersion { get; set; } public byte[] TagUuid { get; set; } @@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager public List ApplicationAreas { get; set; } } - struct VirtualAmiiboApplicationArea + public struct VirtualAmiiboApplicationArea { public uint ApplicationAreaId { get; set; } public byte[] ApplicationArea { get; set; } diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index 0c685471c..579c9157e 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -204,7 +204,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp return virtualAmiiboFile; } - private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile) + public static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile) { string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json"); JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, _serializerContext.VirtualAmiiboFile); diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json index ee0d03171..f67b83c7a 100644 --- a/src/Ryujinx/Assets/Locales/en_US.json +++ b/src/Ryujinx/Assets/Locales/en_US.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Actions", "MenuBarOptionsSimulateWakeUpMessage": "Simulate Wake-up message", "MenuBarActionsScanAmiibo": "Scan An Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Tools", "MenuBarToolsInstallFirmware": "Install Firmware", "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 3672f8c71..83443c6d8 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -28,12 +28,14 @@ using Ryujinx.HLE; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.Services.Nfc.Bin; using Ryujinx.HLE.UI; using Ryujinx.Input.HLE; using Ryujinx.UI.App.Common; using Ryujinx.UI.Common; using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Helper; +using Silk.NET.Vulkan; using SkiaSharp; using System; using System.Collections.Generic; @@ -2059,6 +2061,32 @@ namespace Ryujinx.Ava.UI.ViewModels } } } + public async Task OpenBinFile() + { + if (!IsAmiiboRequested) + return; + + if (AppHost.Device.System.SearchingForAmiibo(out int deviceId)) + { + var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle], + AllowMultiple = false, + FileTypeFilter = new List + { + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = new[] { "*.bin" }, + } + } + }); + if (result.Count > 0) + { + AppHost.Device.System.ScanAmiiboFromBin(result[0].Path.LocalPath); + } + } + } + public void ToggleFullscreen() { diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index c5e794da2..153cfd379 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -241,6 +241,13 @@ Icon="{ext:Icon mdi-cube-scan}" InputGesture="Ctrl + A" IsEnabled="{Binding IsAmiiboRequested}" /> + await ViewModel.OpenAmiiboWindow(); + public async void OpenBinFile(object sender, RoutedEventArgs e) + => await ViewModel.OpenBinFile(); + public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e) { if (!ViewModel.IsGameRunning) From 46f8b8fa85f2d8ae2421c2d67cf794db01420bf3 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Wed, 4 Dec 2024 08:34:58 -0600 Subject: [PATCH 2/9] Some small fixes The data now loads but SMBU says it can't be loaded The date time is for some reason in the future idk why still working on it --- .../Nfc/AmiiboDecryption/AmiiboBinReader.cs | 114 ++++++++++++------ .../Nfc/AmiiboDecryption/AmiiboDecrypter.cs | 30 ----- .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 18 ++- 3 files changed, 90 insertions(+), 72 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs index 8e32db885..8ba3fcf3f 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -3,9 +3,11 @@ using Ryujinx.HLE.HOS.Services.Nfc.Nfp; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using Ryujinx.HLE.HOS.Tamper; using System; +using System.CodeDom; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Intrinsics.Arm; using System.Text; using System.Text.Json; @@ -64,17 +66,24 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin byte[] writeDate = new byte[2]; byte[] writeCounter = new byte[2]; byte formData = 0; - byte[] applicationAreas = new byte[212]; + byte[] applicationAreas = new byte[216]; + byte[] appid = new byte[2]; + byte[] SettingsBytes = new byte[2]; + //// apply to decrypt bytes self.data[304:308] = self._calculate_crc32(self.data[308:520]).to_bytes(4, "little") + //byte[] crc32Bytes = new byte[212]; + //Array.Copy(decryptedFileBytes, 308, crc32Bytes, 0, 212); + //byte[] toApply = BitConverter.GetBytes(CalculateCRC32(crc32Bytes)); + //Array.Reverse(crc32Bytes); + //Array.Copy(toApply, 0, decryptedFileBytes, 304, 4); // Reading specific pages and parsing bytes - for (int page = 0; page < 128; page++) // NTAG215 has 128 pages + for (int page = 0; page < 134; page++) // NTAG215 has 128 pages { int pageStartIdx = page * 4; // Each page is 4 bytes byte[] pageData = new byte[4]; bool isEncrypted = IsPageEncrypted(page); byte[] sourceBytes = isEncrypted ? decryptedFileBytes : fileBytes; Array.Copy(sourceBytes, pageStartIdx, pageData, 0, 4); - // Special handling for specific pages switch (page) { @@ -86,6 +95,10 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin byte internalValue = pageData[1]; Console.WriteLine($"Page 2: BCC1 + Internal Value 0x{internalValue:X2} (Expected 0x48)."); break; + case 5: + // byte 0 amd 1 are settings + Array.Copy(pageData, 0, SettingsBytes, 0, 2); + break; case 6: // Bytes 0 and 1 are init date, bytes 2 and 3 are write date @@ -126,22 +139,28 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin case 66: // Bytes 0 and 1 are write counter Array.Copy(pageData, 0, writeCounter, 0, 2); + // bytes 2 and 3 are appid + Array.Copy(pageData, 2, appid, 0, 2); break; // Pages 76 to 127 are application areas - case >= 76 and <= 127: + case >= 76 and <= 129: int appAreaOffset = (page - 76) * 4; Array.Copy(pageData, 0, applicationAreas, appAreaOffset, 4); break; } + } + // Debugging - string titleIdStr = BitConverter.ToString(titleId).Replace("-", ""); + uint titleIdStr = BitConverter.ToUInt32(titleId, 0); string usedCharacterStr = BitConverter.ToString(usedCharacter).Replace("-", ""); string variationStr = BitConverter.ToString(variation).Replace("-", ""); string amiiboIDStr = BitConverter.ToString(amiiboID).Replace("-", ""); string formDataStr = formData.ToString("X2"); string setIDStr = BitConverter.ToString(setID).Replace("-", ""); + uint settingsStr = BitConverter.ToUInt16(SettingsBytes, 0); + string nickName = Encoding.BigEndianUnicode.GetString(nickNameBytes).TrimEnd('\0'); string head = usedCharacterStr + variationStr; string tail = amiiboIDStr + setIDStr + "02"; @@ -161,6 +180,8 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin Console.WriteLine($"Nickname: {nickName}"); Console.WriteLine($"Init Date: {initDateStr}"); Console.WriteLine($"Write Date: {writeDateStr}"); + Console.WriteLine($"Settings: {settingsStr}"); + Console.WriteLine("Length of Application Areas: " + applicationAreas.Length); VirtualAmiiboFile virtualAmiiboFile = new VirtualAmiiboFile { @@ -168,9 +189,10 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin TagUuid = uid, AmiiboId = finalID }; - - DateTime initDateTime = DateTimeFromBytes(initDate); - DateTime writeDateTime = DateTimeFromBytes(writeDate); + ushort initDateValue = BitConverter.ToUInt16(initDate, 0); + ushort writeDateValue = BitConverter.ToUInt16(writeDate, 0); + DateTime initDateTime = DateTimeFromTag(initDateValue); + DateTime writeDateTime = DateTimeFromTag(writeDateValue); Console.WriteLine($"Parsed Init Date: {initDateTime}"); Console.WriteLine($"Parsed Write Date: {writeDateTime}"); @@ -178,18 +200,44 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin virtualAmiiboFile.FirstWriteDate = initDateTime; virtualAmiiboFile.LastWriteDate = writeDateTime; virtualAmiiboFile.WriteCounter = BitConverter.ToUInt16(writeCounter, 0); - - // Parse application areas - //List applicationAreasList = ParseAmiiboData(applicationAreas); - List applicationAreasList = new List(); - virtualAmiiboFile.ApplicationAreas = applicationAreasList; - - // Save the virtual Amiibo file VirtualAmiibo.SaveAmiiboFile(virtualAmiiboFile); - + VirtualAmiibo.applicationBytes = applicationAreas; return virtualAmiiboFile; } + public static uint CalculateCRC32(byte[] input) + { + // Setup CRC 32 table + uint p0 = 0xEDB88320u | 0x80000000u; + uint[] u0 = new uint[0x100]; + + for (uint i = 1; i < 0x100; i++) + { + uint t0 = i; + for (int j = 0; j < 8; j++) + { + if ((t0 & 1) != 0) + { + t0 = (t0 >> 1) ^ p0; + } + else + { + t0 >>= 1; + } + } + u0[i] = t0; + } + + // Calculate CRC32 from table + uint t = 0x0; + foreach (byte k in input) + { + t = (t >> 8) ^ u0[(k ^ t) & 0xFF]; + } + + return t ^ 0xFFFFFFFFu; + } + private static string GetKeyRetailBinPath() { return Path.Combine(AppDataManager.KeysDirPath, "key_retail.bin"); @@ -197,37 +245,25 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin public static bool IsPageEncrypted(int page) { - return (page >= 6 && page <= 9) || (page >= 43 && page <= 84); + // 0-4 are not encrypted, 5-12 is encrypted, 13-39 is not encrypted, + // 40-129 is encrypted, and 130-134 is not encrypted. + + return (page >= 5 && page <= 12) || (page >= 40 && page <= 129); } - public static DateTime DateTimeFromBytes(byte[] date) + public static DateTime DateTimeFromTag(ushort value) { - if (date == null || date.Length != 2) - { - Console.WriteLine("Invalid date bytes."); - return DateTime.MinValue; - } - - ushort value = BitConverter.ToUInt16(date, 0); - - int day = value & 0x1F; - int month = (value >> 5) & 0x0F; - int year = (value >> 9) & 0x7F; - try { - return new DateTime(2000 + year, month, day); + var day = value & 0x1F; + var month = (value >> 5) & 0x0F; + var year = (value >> 9) & 0x7F; + return new DateTime(1970 + year, month, day); } - catch (ArgumentOutOfRangeException) + catch { - Console.WriteLine("Invalid date values extracted."); - return DateTime.MinValue; + return DateTime.Now; } } - - public static List ParseAmiiboData(byte[] decryptedData) - { - return JsonSerializer.Deserialize>(decryptedData); - } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs index b2b309828..ffaef6558 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs @@ -79,35 +79,5 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin break; // Stop if no overflow } } - - public DateTime ParseDate(byte[] data, int offset) - { - ushort year = BitConverter.ToUInt16(data, offset); - byte month = data[offset + 2]; - byte day = data[offset + 3]; - byte hour = data[offset + 4]; - byte minute = data[offset + 5]; - byte second = data[offset + 6]; - - return new DateTime(year, month, day, hour, minute, second); - } - - public List ParseApplicationAreas(byte[] data, int startOffset, int areaSize) - { - var areas = new List(); - for (int i = 0; i < 8; i++) // Assuming 8 areas - { - int offset = startOffset + (i * areaSize); - string applicationId = BitConverter.ToString(data[offset..(offset + 4)]).Replace("-", ""); - byte[] areaData = data[(offset + 4)..(offset + areaSize)]; - areas.Add(new VirtualAmiiboApplicationArea - { - ApplicationAreaId = uint.Parse(applicationId), - ApplicationArea = areaData - }); - } - - return areas; - } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index 579c9157e..0d72757a7 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -13,10 +13,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { static class VirtualAmiibo { - private static uint _openedApplicationAreaId; - + public static uint _openedApplicationAreaId; + public static byte[] applicationBytes = new byte[0]; private static readonly AmiiboJsonSerializerContext _serializerContext = AmiiboJsonSerializerContext.Default; - public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) { if (useRandomUuid) @@ -103,6 +102,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId) { VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); + if (applicationBytes.Length > 0) + { + _openedApplicationAreaId = applicationAreaId; + return true; + } if (virtualAmiiboFile.ApplicationAreas.Exists(item => item.ApplicationAreaId == applicationAreaId)) { @@ -116,6 +120,12 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp public static byte[] GetApplicationArea(string amiiboId) { + if (applicationBytes.Length > 0) + { + byte[] bytes = applicationBytes; + applicationBytes = new byte[0]; + return bytes; + } VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); foreach (VirtualAmiiboApplicationArea applicationArea in virtualAmiiboFile.ApplicationAreas) @@ -209,5 +219,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json"); JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, _serializerContext.VirtualAmiiboFile); } + + public static bool SaveFileExists(VirtualAmiiboFile virtualAmiiboFile) => File.Exists(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json")); } } From 334e71528e747f503bb5fdaa6ea0a53e3c4853b3 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Wed, 4 Dec 2024 20:47:04 -0600 Subject: [PATCH 3/9] Simplify The Logic --- .../Nfc/AmiiboDecryption/AmiiboBinReader.cs | 223 ++++++++---------- 1 file changed, 103 insertions(+), 120 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs index 8ba3fcf3f..9bdf9c481 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -3,60 +3,59 @@ using Ryujinx.HLE.HOS.Services.Nfc.Nfp; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using Ryujinx.HLE.HOS.Tamper; using System; -using System.CodeDom; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Runtime.Intrinsics.Arm; using System.Text; -using System.Text.Json; namespace Ryujinx.HLE.HOS.Services.Nfc.Bin { public class AmiiboBinReader { - // Method to calculate BCC (XOR checksum) from UID bytes - private static byte CalculateBCC(byte[] uid, int startIdx) + private static byte CalculateBCC0(byte[] uid) { - return (byte)(uid[startIdx] ^ uid[startIdx + 1] ^ uid[startIdx + 2] ^ 0x88); + return (byte)(uid[0] ^ uid[1] ^ uid[2] ^ 0x88); + } + + private static byte CalculateBCC1(byte[] uid) + { + return (byte)(uid[3] ^ uid[4] ^ uid[5] ^ uid[6]); } - // Method to read and process a .bin file public static VirtualAmiiboFile ReadBinFile(byte[] fileBytes) { string keyRetailBinPath = GetKeyRetailBinPath(); if (string.IsNullOrEmpty(keyRetailBinPath)) { - Console.WriteLine("Key retail bin path not found."); return new VirtualAmiiboFile(); } byte[] initialCounter = new byte[16]; - // Ensure the file is long enough - if (fileBytes.Length < 128 * 4) // Each page is 4 bytes, total 512 bytes + const int totalPages = 135; + const int pageSize = 4; + const int totalBytes = totalPages * pageSize; + + if (fileBytes.Length < totalBytes) { - Console.WriteLine("File is too short to process."); return new VirtualAmiiboFile(); } - // Decrypt the Amiibo data AmiiboDecrypter amiiboDecryptor = new AmiiboDecrypter(keyRetailBinPath); byte[] decryptedFileBytes = amiiboDecryptor.DecryptAmiiboData(fileBytes, initialCounter); - // Assuming the UID is stored in the first 7 bytes (NTAG215 UID length) + if (decryptedFileBytes.Length != totalBytes) + { + Array.Resize(ref decryptedFileBytes, totalBytes); + } + byte[] uid = new byte[7]; Array.Copy(fileBytes, 0, uid, 0, 7); - // Calculate BCC values - byte bcc0 = CalculateBCC(uid, 0); // BCC0 = UID0 ^ UID1 ^ UID2 ^ 0x88 - byte bcc1 = CalculateBCC(uid, 3); // BCC1 = UID3 ^ UID4 ^ UID5 ^ 0x00 + byte bcc0 = CalculateBCC0(uid); + byte bcc1 = CalculateBCC1(uid); - Console.WriteLine($"UID: {BitConverter.ToString(uid)}"); - Console.WriteLine($"BCC0: 0x{bcc0:X2}, BCC1: 0x{bcc1:X2}"); + LogDebugData(uid, bcc0, bcc1); - // Initialize byte arrays for data extraction - byte[] nickNameBytes = new byte[20]; // Amiibo nickname is 20 bytes + byte[] nickNameBytes = new byte[20]; byte[] titleId = new byte[8]; byte[] usedCharacter = new byte[2]; byte[] variation = new byte[2]; @@ -65,177 +64,160 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin byte[] initDate = new byte[2]; byte[] writeDate = new byte[2]; byte[] writeCounter = new byte[2]; + byte[] appId = new byte[8]; + byte[] settingsBytes = new byte[2]; byte formData = 0; byte[] applicationAreas = new byte[216]; - byte[] appid = new byte[2]; - byte[] SettingsBytes = new byte[2]; - //// apply to decrypt bytes self.data[304:308] = self._calculate_crc32(self.data[308:520]).to_bytes(4, "little") - //byte[] crc32Bytes = new byte[212]; - //Array.Copy(decryptedFileBytes, 308, crc32Bytes, 0, 212); - //byte[] toApply = BitConverter.GetBytes(CalculateCRC32(crc32Bytes)); - //Array.Reverse(crc32Bytes); - //Array.Copy(toApply, 0, decryptedFileBytes, 304, 4); - // Reading specific pages and parsing bytes - for (int page = 0; page < 134; page++) // NTAG215 has 128 pages + for (int page = 0; page < totalPages; page++) { - int pageStartIdx = page * 4; // Each page is 4 bytes + int pageStartIdx = page * pageSize; byte[] pageData = new byte[4]; bool isEncrypted = IsPageEncrypted(page); byte[] sourceBytes = isEncrypted ? decryptedFileBytes : fileBytes; + if (pageStartIdx + pageSize > sourceBytes.Length) + { + break; + } Array.Copy(sourceBytes, pageStartIdx, pageData, 0, 4); - // Special handling for specific pages + switch (page) { - case 0: // Page 0 (UID + BCC0) - Console.WriteLine("Page 0: UID and BCC0."); + case 0: break; - case 2: // Page 2 (BCC1 + Internal Value) + case 2: byte internalValue = pageData[1]; - Console.WriteLine($"Page 2: BCC1 + Internal Value 0x{internalValue:X2} (Expected 0x48)."); break; + case 5: - // byte 0 amd 1 are settings - Array.Copy(pageData, 0, SettingsBytes, 0, 2); + Array.Copy(pageData, 0, settingsBytes, 0, 2); break; case 6: - // Bytes 0 and 1 are init date, bytes 2 and 3 are write date Array.Copy(pageData, 0, initDate, 0, 2); Array.Copy(pageData, 2, writeDate, 0, 2); break; - case 8: - case 9: - case 10: - case 11: - case 12: - // Extract nickname bytes + case >= 8 and <= 12: int nickNameOffset = (page - 8) * 4; Array.Copy(pageData, 0, nickNameBytes, nickNameOffset, 4); break; case 21: - // Bytes 0 and 1 are used character, bytes 2 and 3 are variation Array.Copy(pageData, 0, usedCharacter, 0, 2); Array.Copy(pageData, 2, variation, 0, 2); break; case 22: - // Bytes 0 and 1 are amiibo ID, byte 2 is set ID, byte 3 is form data Array.Copy(pageData, 0, amiiboID, 0, 2); setID[0] = pageData[2]; formData = pageData[3]; break; + case 40: + case 41: + int appIdOffset = (page - 40) * 4; + Array.Copy(decryptedFileBytes, pageStartIdx, appId, appIdOffset, 4); + break; + case 64: case 65: - // Extract title ID int titleIdOffset = (page - 64) * 4; - Array.Copy(pageData, 0, titleId, titleIdOffset, 4); + Array.Copy(sourceBytes, pageStartIdx, titleId, titleIdOffset, 4); break; case 66: - // Bytes 0 and 1 are write counter Array.Copy(pageData, 0, writeCounter, 0, 2); - // bytes 2 and 3 are appid - Array.Copy(pageData, 2, appid, 0, 2); break; - // Pages 76 to 127 are application areas case >= 76 and <= 129: int appAreaOffset = (page - 76) * 4; - Array.Copy(pageData, 0, applicationAreas, appAreaOffset, 4); + if (appAreaOffset + 4 <= applicationAreas.Length) + { + Array.Copy(pageData, 0, applicationAreas, appAreaOffset, 4); + } break; } - } - // Debugging - uint titleIdStr = BitConverter.ToUInt32(titleId, 0); string usedCharacterStr = BitConverter.ToString(usedCharacter).Replace("-", ""); string variationStr = BitConverter.ToString(variation).Replace("-", ""); string amiiboIDStr = BitConverter.ToString(amiiboID).Replace("-", ""); - string formDataStr = formData.ToString("X2"); string setIDStr = BitConverter.ToString(setID).Replace("-", ""); - uint settingsStr = BitConverter.ToUInt16(SettingsBytes, 0); - - string nickName = Encoding.BigEndianUnicode.GetString(nickNameBytes).TrimEnd('\0'); string head = usedCharacterStr + variationStr; string tail = amiiboIDStr + setIDStr + "02"; string finalID = head + tail; - string initDateStr = BitConverter.ToString(initDate).Replace("-", ""); - string writeDateStr = BitConverter.ToString(writeDate).Replace("-", ""); - Console.WriteLine($"Title ID: {titleIdStr}"); - Console.WriteLine($"Head: {head}"); - Console.WriteLine($"Tail: {tail}"); - Console.WriteLine($"Used Character: {usedCharacterStr}"); - Console.WriteLine($"Form Data: {formDataStr}"); - Console.WriteLine($"Variation: {variationStr}"); - Console.WriteLine($"Amiibo ID: {amiiboIDStr}"); - Console.WriteLine($"Set ID: {setIDStr}"); - Console.WriteLine($"Final ID: {finalID}"); - Console.WriteLine($"Nickname: {nickName}"); - Console.WriteLine($"Init Date: {initDateStr}"); - Console.WriteLine($"Write Date: {writeDateStr}"); - Console.WriteLine($"Settings: {settingsStr}"); - Console.WriteLine("Length of Application Areas: " + applicationAreas.Length); + ushort settingsValue = BitConverter.ToUInt16(settingsBytes, 0); + string nickName = Encoding.BigEndianUnicode.GetString(nickNameBytes).TrimEnd('\0'); + ushort initDateValue = BitConverter.ToUInt16(initDate, 0); + ushort writeDateValue = BitConverter.ToUInt16(writeDate, 0); + DateTime initDateTime = DateTimeFromTag(initDateValue); + DateTime writeDateTime = DateTimeFromTag(writeDateValue); + ushort writeCounterValue = BitConverter.ToUInt16(writeCounter, 0); + + LogFinalData(titleId, appId, head, tail, finalID, nickName, initDateTime, writeDateTime, settingsValue, writeCounterValue, applicationAreas); VirtualAmiiboFile virtualAmiiboFile = new VirtualAmiiboFile { FileVersion = 1, TagUuid = uid, - AmiiboId = finalID + AmiiboId = finalID, + FirstWriteDate = initDateTime, + LastWriteDate = writeDateTime, + WriteCounter = writeCounterValue, }; - ushort initDateValue = BitConverter.ToUInt16(initDate, 0); - ushort writeDateValue = BitConverter.ToUInt16(writeDate, 0); - DateTime initDateTime = DateTimeFromTag(initDateValue); - DateTime writeDateTime = DateTimeFromTag(writeDateValue); - - Console.WriteLine($"Parsed Init Date: {initDateTime}"); - Console.WriteLine($"Parsed Write Date: {writeDateTime}"); - - virtualAmiiboFile.FirstWriteDate = initDateTime; - virtualAmiiboFile.LastWriteDate = writeDateTime; - virtualAmiiboFile.WriteCounter = BitConverter.ToUInt16(writeCounter, 0); - VirtualAmiibo.SaveAmiiboFile(virtualAmiiboFile); VirtualAmiibo.applicationBytes = applicationAreas; + return virtualAmiiboFile; } - public static uint CalculateCRC32(byte[] input) + private static void LogDebugData(byte[] uid, byte bcc0, byte bcc1) { - // Setup CRC 32 table - uint p0 = 0xEDB88320u | 0x80000000u; - uint[] u0 = new uint[0x100]; + Console.WriteLine($"UID: {BitConverter.ToString(uid)}"); + Console.WriteLine($"BCC0: 0x{bcc0:X2}, BCC1: 0x{bcc1:X2}"); + } - for (uint i = 1; i < 0x100; i++) + private static void LogFinalData(byte[] titleId, byte[] appId, string head, string tail, string finalID, string nickName, DateTime initDateTime, DateTime writeDateTime, ushort settingsValue, ushort writeCounterValue, byte[] applicationAreas) + { + Console.WriteLine($"Title ID: 0x{BitConverter.ToString(titleId).Replace("-", "")}"); + Console.WriteLine($"Application Program ID: 0x{BitConverter.ToString(appId).Replace("-", "")}"); + Console.WriteLine($"Head: {head}"); + Console.WriteLine($"Tail: {tail}"); + Console.WriteLine($"Final ID: {finalID}"); + Console.WriteLine($"Nickname: {nickName}"); + Console.WriteLine($"Init Date: {initDateTime}"); + Console.WriteLine($"Write Date: {writeDateTime}"); + Console.WriteLine($"Settings: 0x{settingsValue:X4}"); + Console.WriteLine($"Write Counter: {writeCounterValue}"); + Console.WriteLine("Length of Application Areas: " + applicationAreas.Length); + } + + private static uint CalculateCRC32(byte[] input) + { + uint[] table = new uint[256]; + uint polynomial = 0xEDB88320; + for (uint i = 0; i < table.Length; ++i) { - uint t0 = i; - for (int j = 0; j < 8; j++) + uint crc = i; + for (int j = 0; j < 8; ++j) { - if ((t0 & 1) != 0) - { - t0 = (t0 >> 1) ^ p0; - } + if ((crc & 1) != 0) + crc = (crc >> 1) ^ polynomial; else - { - t0 >>= 1; - } + crc >>= 1; } - u0[i] = t0; + table[i] = crc; } - // Calculate CRC32 from table - uint t = 0x0; - foreach (byte k in input) + uint result = 0xFFFFFFFF; + foreach (byte b in input) { - t = (t >> 8) ^ u0[(k ^ t) & 0xFF]; + byte index = (byte)((result & 0xFF) ^ b); + result = (result >> 8) ^ table[index]; } - - return t ^ 0xFFFFFFFFu; + return ~result; } private static string GetKeyRetailBinPath() @@ -245,9 +227,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin public static bool IsPageEncrypted(int page) { - // 0-4 are not encrypted, 5-12 is encrypted, 13-39 is not encrypted, - // 40-129 is encrypted, and 130-134 is not encrypted. - return (page >= 5 && page <= 12) || (page >= 40 && page <= 129); } @@ -255,10 +234,14 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin { try { - var day = value & 0x1F; - var month = (value >> 5) & 0x0F; - var year = (value >> 9) & 0x7F; - return new DateTime(1970 + year, month, day); + int day = value & 0x1F; + int month = (value >> 5) & 0x0F; + int year = (value >> 9) & 0x7F; + + if (day == 0 || month == 0 || month > 12 || day > DateTime.DaysInMonth(2000 + year, month)) + throw new ArgumentOutOfRangeException(); + + return new DateTime(2000 + year, month, day); } catch { From e6b11ad76d2bf8868644cf2f312e24f017e4f933 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Thu, 5 Dec 2024 01:33:51 -0600 Subject: [PATCH 4/9] Actually got it working Nickname changing and saving does not work, if you try to save with a bin amiibo it will just save over your previous virtual amiibo. --- src/Ryujinx.HLE/HOS/Horizon.cs | 11 +- .../Nfc/AmiiboDecryption/AmiiboBinReader.cs | 97 ++--- .../Nfc/AmiiboDecryption/AmiiboDecrypter.cs | 81 +--- .../Nfc/AmiiboDecryption/AmiiboDump.cs | 389 ++++++++++++++++++ .../Nfc/AmiiboDecryption/AmiiboMasterKey.cs | 60 +-- .../HOS/Services/Nfc/Nfp/NfpManager/INfp.cs | 8 - .../UI/ViewModels/MainWindowViewModel.cs | 12 +- .../UI/Views/Main/MainMenuBarView.axaml | 4 +- .../UI/Views/Main/MainMenuBarView.axaml.cs | 7 + 9 files changed, 490 insertions(+), 179 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDump.cs diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index d961e1e6a..3b314daf8 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -16,7 +16,8 @@ using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemA using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.HLE.HOS.Services.Caps; using Ryujinx.HLE.HOS.Services.Mii; -using Ryujinx.HLE.HOS.Services.Nfc.Bin; +using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; @@ -338,6 +339,10 @@ namespace Ryujinx.HLE.HOS public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid) { + if (VirtualAmiibo.applicationBytes.Length > 0) + { + VirtualAmiibo.applicationBytes = new byte[0]; + } if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag) { NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound; @@ -347,6 +352,10 @@ namespace Ryujinx.HLE.HOS } public void ScanAmiiboFromBin(string path) { + if (VirtualAmiibo.applicationBytes.Length > 0) + { + VirtualAmiibo.applicationBytes = new byte[0]; + } byte[] encryptedData = File.ReadAllBytes(path); VirtualAmiiboFile newFile = AmiiboBinReader.ReadBinFile(encryptedData); if (SearchingForAmiibo(out int nfpDeviceId)) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs index 9bdf9c481..f701c2a30 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -5,8 +5,9 @@ using Ryujinx.HLE.HOS.Tamper; using System; using System.IO; using System.Text; +using static LibHac.FsSystem.AesCtrCounterExtendedStorage; -namespace Ryujinx.HLE.HOS.Services.Nfc.Bin +namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { public class AmiiboBinReader { @@ -40,22 +41,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin } AmiiboDecrypter amiiboDecryptor = new AmiiboDecrypter(keyRetailBinPath); - byte[] decryptedFileBytes = amiiboDecryptor.DecryptAmiiboData(fileBytes, initialCounter); + AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(fileBytes); - if (decryptedFileBytes.Length != totalBytes) - { - Array.Resize(ref decryptedFileBytes, totalBytes); - } - - byte[] uid = new byte[7]; - Array.Copy(fileBytes, 0, uid, 0, 7); - - byte bcc0 = CalculateBCC0(uid); - byte bcc1 = CalculateBCC1(uid); - - LogDebugData(uid, bcc0, bcc1); - - byte[] nickNameBytes = new byte[20]; + byte[] titleId = new byte[8]; byte[] usedCharacter = new byte[2]; byte[] variation = new byte[2]; @@ -68,75 +56,60 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin byte[] settingsBytes = new byte[2]; byte formData = 0; byte[] applicationAreas = new byte[216]; + byte[] dataFull = amiiboDump.GetData(); + Console.WriteLine("Data Full Length: " + dataFull.Length); + byte[] uid = new byte[7]; + Array.Copy(dataFull, 0, uid, 0, 7); - for (int page = 0; page < totalPages; page++) + byte bcc0 = CalculateBCC0(uid); + byte bcc1 = CalculateBCC1(uid); + LogDebugData(uid, bcc0, bcc1); + for (int page = 0; page < 128; page++) // NTAG215 has 128 pages { - int pageStartIdx = page * pageSize; + int pageStartIdx = page * 4; // Each page is 4 bytes byte[] pageData = new byte[4]; - bool isEncrypted = IsPageEncrypted(page); - byte[] sourceBytes = isEncrypted ? decryptedFileBytes : fileBytes; - if (pageStartIdx + pageSize > sourceBytes.Length) - { - break; - } + byte[] sourceBytes = dataFull; Array.Copy(sourceBytes, pageStartIdx, pageData, 0, 4); - + // Special handling for specific pages switch (page) { - case 0: + case 0: // Page 0 (UID + BCC0) + Console.WriteLine("Page 0: UID and BCC0."); break; - - case 2: + case 2: // Page 2 (BCC1 + Internal Value) byte internalValue = pageData[1]; + Console.WriteLine($"Page 2: BCC1 + Internal Value 0x{internalValue:X2} (Expected 0x48)."); break; - - case 5: - Array.Copy(pageData, 0, settingsBytes, 0, 2); - break; - case 6: + // Bytes 0 and 1 are init date, bytes 2 and 3 are write date Array.Copy(pageData, 0, initDate, 0, 2); Array.Copy(pageData, 2, writeDate, 0, 2); break; - - case >= 8 and <= 12: - int nickNameOffset = (page - 8) * 4; - Array.Copy(pageData, 0, nickNameBytes, nickNameOffset, 4); - break; - case 21: + // Bytes 0 and 1 are used character, bytes 2 and 3 are variation Array.Copy(pageData, 0, usedCharacter, 0, 2); Array.Copy(pageData, 2, variation, 0, 2); break; - case 22: + // Bytes 0 and 1 are amiibo ID, byte 2 is set ID, byte 3 is form data Array.Copy(pageData, 0, amiiboID, 0, 2); setID[0] = pageData[2]; formData = pageData[3]; break; - - case 40: - case 41: - int appIdOffset = (page - 40) * 4; - Array.Copy(decryptedFileBytes, pageStartIdx, appId, appIdOffset, 4); - break; - case 64: case 65: + // Extract title ID int titleIdOffset = (page - 64) * 4; - Array.Copy(sourceBytes, pageStartIdx, titleId, titleIdOffset, 4); + Array.Copy(pageData, 0, titleId, titleIdOffset, 4); break; - case 66: + // Bytes 0 and 1 are write counter Array.Copy(pageData, 0, writeCounter, 0, 2); break; - - case >= 76 and <= 129: + // Pages 76 to 127 are application areas + case >= 76 and <= 127: int appAreaOffset = (page - 76) * 4; - if (appAreaOffset + 4 <= applicationAreas.Length) - { - Array.Copy(pageData, 0, applicationAreas, appAreaOffset, 4); - } + Array.Copy(pageData, 0, applicationAreas, appAreaOffset, 4); break; } } @@ -150,13 +123,12 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin string finalID = head + tail; ushort settingsValue = BitConverter.ToUInt16(settingsBytes, 0); - string nickName = Encoding.BigEndianUnicode.GetString(nickNameBytes).TrimEnd('\0'); ushort initDateValue = BitConverter.ToUInt16(initDate, 0); ushort writeDateValue = BitConverter.ToUInt16(writeDate, 0); DateTime initDateTime = DateTimeFromTag(initDateValue); DateTime writeDateTime = DateTimeFromTag(writeDateValue); ushort writeCounterValue = BitConverter.ToUInt16(writeCounter, 0); - + string nickName = amiiboDump.AmiiboNickname; LogFinalData(titleId, appId, head, tail, finalID, nickName, initDateTime, writeDateTime, settingsValue, writeCounterValue, applicationAreas); VirtualAmiiboFile virtualAmiiboFile = new VirtualAmiiboFile @@ -164,11 +136,15 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin FileVersion = 1, TagUuid = uid, AmiiboId = finalID, + NickName = nickName, FirstWriteDate = initDateTime, LastWriteDate = writeDateTime, WriteCounter = writeCounterValue, }; - VirtualAmiibo.applicationBytes = applicationAreas; + if (writeCounterValue>0) + { + VirtualAmiibo.applicationBytes = applicationAreas; + } return virtualAmiiboFile; } @@ -225,11 +201,10 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Bin return Path.Combine(AppDataManager.KeysDirPath, "key_retail.bin"); } - public static bool IsPageEncrypted(int page) + public static bool HasKeyRetailBinPath() { - return (page >= 5 && page <= 12) || (page >= 40 && page <= 129); + return File.Exists(GetKeyRetailBinPath()); } - public static DateTime DateTimeFromTag(ushort value) { try diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs index ffaef6558..8e63c4149 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs @@ -1,83 +1,36 @@ -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.HLE.HOS.Services.Mii.Types; -using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using System; -using System.Collections.Generic; +using System.Linq; using System.IO; +using System.Collections.Generic; using System.Security.Cryptography; -using System.Text; -namespace Ryujinx.HLE.HOS.Services.Nfc.Bin +namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { public class AmiiboDecrypter { - public readonly byte[] _hmacKey; // HMAC key - public readonly byte[] _aesKey; // AES key + public AmiiboMasterKey DataKey { get; private set; } + public AmiiboMasterKey TagKey { get; private set; } public AmiiboDecrypter(string keyRetailBinPath) { - var keys = AmiiboMasterKey.FromCombinedBin(File.ReadAllBytes(keyRetailBinPath)); - _hmacKey = keys.DataKey.HmacKey; - _aesKey = keys.DataKey.XorPad; + var combinedKeys = File.ReadAllBytes(keyRetailBinPath); + var keys = AmiiboMasterKey.FromCombinedBin(combinedKeys); + DataKey = keys.DataKey; + TagKey = keys.TagKey; } - public byte[] DecryptAmiiboData(byte[] encryptedData, byte[] counter) + public AmiiboDump DecryptAmiiboDump(byte[] encryptedDumpData) { - // Ensure the counter length matches the block size - if (counter.Length != 16) - { - throw new ArgumentException("Counter must be 16 bytes long for AES block size."); - } + // Initialize AmiiboDump with encrypted data + AmiiboDump amiiboDump = new AmiiboDump(encryptedDumpData, DataKey, TagKey, isLocked: true); - byte[] decryptedData = new byte[encryptedData.Length]; + // Unlock (decrypt) the dump + amiiboDump.Unlock(); - using (Aes aesAlg = Aes.Create()) - { - aesAlg.Key = _aesKey; - aesAlg.Mode = CipherMode.ECB; // Use ECB mode to handle the counter encryption - aesAlg.Padding = PaddingMode.None; + // Optional: Verify HMACs + amiiboDump.VerifyHMACs(); - using (var encryptor = aesAlg.CreateEncryptor()) - { - int blockSize = 16; - byte[] encryptedCounter = new byte[blockSize]; - byte[] currentCounter = (byte[])counter.Clone(); - - for (int i = 0; i < encryptedData.Length; i += blockSize) - { - // Encrypt the current counter block - encryptor.TransformBlock(currentCounter, 0, blockSize, encryptedCounter, 0); - - // XOR the encrypted counter with the ciphertext to get the decrypted data - for (int j = 0; j < blockSize && i + j < encryptedData.Length; j++) - { - decryptedData[i + j] = (byte)(encryptedData[i + j] ^ encryptedCounter[j]); - } - - // Increment the counter for the next block - IncrementCounter(currentCounter); - } - } - } - - return decryptedData; - } - - public byte[] CalculateHMAC(byte[] data) - { - using (var hmac = new HMACSHA256(_hmacKey)) - { - return hmac.ComputeHash(data); - } - } - - public void IncrementCounter(byte[] counter) - { - for (int i = counter.Length - 1; i >= 0; i--) - { - if (++counter[i] != 0) - break; // Stop if no overflow - } + return amiiboDump; } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDump.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDump.cs new file mode 100644 index 000000000..16c1e72d3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDump.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption +{ + public class AmiiboDump + { + private AmiiboMasterKey dataMasterKey; + private AmiiboMasterKey tagMasterKey; + + private bool isLocked; + private byte[] data; + private byte[] hmacTagKey; + private byte[] hmacDataKey; + private byte[] aesKey; + private byte[] aesIv; + + public AmiiboDump(byte[] dumpData, AmiiboMasterKey dataKey, AmiiboMasterKey tagKey, bool isLocked = true) + { + if (dumpData.Length < 540) + throw new ArgumentException("Incomplete dump. Amiibo data is at least 540 bytes."); + + this.data = new byte[540]; + Array.Copy(dumpData, this.data, dumpData.Length); + this.dataMasterKey = dataKey; + this.tagMasterKey = tagKey; + this.isLocked = isLocked; + + if (!isLocked) + { + DeriveKeysAndCipher(); + } + } + + private byte[] DeriveKey(AmiiboMasterKey key, bool deriveAes, out byte[] derivedAesKey, out byte[] derivedAesIv) + { + List seed = new List(); + + // Start with the type string (14 bytes) + seed.AddRange(key.TypeString); + + // Append data based on magic size + int append = 16 - key.MagicSize; + byte[] extract = new byte[16]; + Array.Copy(this.data, 0x011, extract, 0, 2); // Extract two bytes from user data section + for (int i = 2; i < 16; i++) + { + extract[i] = 0x00; + } + seed.AddRange(extract.Take(append)); + + // Add the magic bytes + seed.AddRange(key.MagicBytes.Take(key.MagicSize)); + + // Extract the UID (UID is 8 bytes) + byte[] uid = new byte[8]; + Array.Copy(this.data, 0x000, uid, 0, 8); + seed.AddRange(uid); + seed.AddRange(uid); + + // Extract some tag data (pages 0x20 - 0x28) + byte[] user = new byte[32]; + Array.Copy(this.data, 0x060, user, 0, 32); + + // XOR it with the key padding (XorPad) + byte[] paddedUser = new byte[32]; + for (int i = 0; i < user.Length; i++) + { + paddedUser[i] = (byte)(user[i] ^ key.XorPad[i]); + } + seed.AddRange(paddedUser); + + byte[] seedBytes = seed.ToArray(); + if (seedBytes.Length != 78) + { + throw new Exception("Size check for key derived seed failed"); + } + + byte[] hmacKey; + derivedAesKey = null; + derivedAesIv = null; + + if (deriveAes) + { + // Derive AES Key and IV + var dataForAes = new byte[2 + seedBytes.Length]; + dataForAes[0] = 0x00; + dataForAes[1] = 0x00; // Counter (0) + Array.Copy(seedBytes, 0, dataForAes, 2, seedBytes.Length); + + byte[] derivedBytes; + using (var hmac = new HMACSHA256(key.HmacKey)) + { + derivedBytes = hmac.ComputeHash(dataForAes); + } + + derivedAesKey = derivedBytes.Take(16).ToArray(); + derivedAesIv = derivedBytes.Skip(16).Take(16).ToArray(); + + // Derive HMAC Key + var dataForHmacKey = new byte[2 + seedBytes.Length]; + dataForHmacKey[0] = 0x00; + dataForHmacKey[1] = 0x01; // Counter (1) + Array.Copy(seedBytes, 0, dataForHmacKey, 2, seedBytes.Length); + + using (var hmac = new HMACSHA256(key.HmacKey)) + { + derivedBytes = hmac.ComputeHash(dataForHmacKey); + } + + hmacKey = derivedBytes.Take(16).ToArray(); + } + else + { + // Derive HMAC Key only + var dataForHmacKey = new byte[2 + seedBytes.Length]; + dataForHmacKey[0] = 0x00; + dataForHmacKey[1] = 0x01; // Counter (1) + Array.Copy(seedBytes, 0, dataForHmacKey, 2, seedBytes.Length); + + byte[] derivedBytes; + using (var hmac = new HMACSHA256(key.HmacKey)) + { + derivedBytes = hmac.ComputeHash(dataForHmacKey); + } + + hmacKey = derivedBytes.Take(16).ToArray(); + } + + return hmacKey; + } + + private void DeriveKeysAndCipher() + { + byte[] discard; + // Derive HMAC Tag Key + this.hmacTagKey = DeriveKey(this.tagMasterKey, false, out discard, out discard); + + // Derive HMAC Data Key and AES Key/IV + this.hmacDataKey = DeriveKey(this.dataMasterKey, true, out aesKey, out aesIv); + } + + private void DecryptData() + { + byte[] encryptedBlock = new byte[0x020 + 0x168]; + Array.Copy(data, 0x014, encryptedBlock, 0, 0x020); // data[0x014:0x034] + Array.Copy(data, 0x0A0, encryptedBlock, 0x020, 0x168); // data[0x0A0:0x208] + + byte[] decryptedBlock = AES_CTR_Transform(encryptedBlock, aesKey, aesIv); + + // Copy decrypted data back + Array.Copy(decryptedBlock, 0, data, 0x014, 0x020); + Array.Copy(decryptedBlock, 0x020, data, 0x0A0, 0x168); + } + + private void EncryptData() + { + byte[] plainBlock = new byte[0x020 + 0x168]; + Array.Copy(data, 0x014, plainBlock, 0, 0x020); // data[0x014:0x034] + Array.Copy(data, 0x0A0, plainBlock, 0x020, 0x168); // data[0x0A0:0x208] + + byte[] encryptedBlock = AES_CTR_Transform(plainBlock, aesKey, aesIv); + + // Copy encrypted data back + Array.Copy(encryptedBlock, 0, data, 0x014, 0x020); + Array.Copy(encryptedBlock, 0x020, data, 0x0A0, 0x168); + } + + private byte[] AES_CTR_Transform(byte[] data, byte[] key, byte[] iv) + { + byte[] output = new byte[data.Length]; + + using (Aes aes = Aes.Create()) + { + aes.Key = key; + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + + int blockSize = aes.BlockSize / 8; // in bytes, should be 16 + byte[] counter = new byte[blockSize]; + Array.Copy(iv, counter, blockSize); + + using (ICryptoTransform encryptor = aes.CreateEncryptor()) + { + byte[] encryptedCounter = new byte[blockSize]; + + for (int i = 0; i < data.Length; i += blockSize) + { + // Encrypt the counter + encryptor.TransformBlock(counter, 0, blockSize, encryptedCounter, 0); + + // Determine the number of bytes to process in this block + int blockLength = Math.Min(blockSize, data.Length - i); + + // XOR the encrypted counter with the plaintext/ciphertext block + for (int j = 0; j < blockLength; j++) + { + output[i + j] = (byte)(data[i + j] ^ encryptedCounter[j]); + } + + // Increment the counter + IncrementCounter(counter); + } + } + } + + return output; + } + + private void IncrementCounter(byte[] counter) + { + for (int i = counter.Length - 1; i >= 0; i--) + { + if (++counter[i] != 0) + break; + } + } + + private void DeriveHMACs() + { + if (isLocked) + throw new InvalidOperationException("Cannot derive HMACs when data is locked."); + + // Calculate tag HMAC + byte[] tagHmacData = new byte[8 + 44]; + Array.Copy(data, 0x000, tagHmacData, 0, 8); + Array.Copy(data, 0x054, tagHmacData, 8, 44); + + byte[] tagHmac; + using (var hmac = new HMACSHA256(hmacTagKey)) + { + tagHmac = hmac.ComputeHash(tagHmacData); + } + + // Overwrite the stored tag HMAC + Array.Copy(tagHmac, 0, data, 0x034, 32); + + // Prepare data for data HMAC + int len1 = 0x023; // 0x011 to 0x034 (0x034 - 0x011) + int len2 = 0x168; // 0x0A0 to 0x208 (0x208 - 0x0A0) + int len3 = tagHmac.Length; // 32 bytes + int len4 = 0x008; // 0x000 to 0x008 (0x008 - 0x000) + int len5 = 0x02C; // 0x054 to 0x080 (0x080 - 0x054) + int totalLength = len1 + len2 + len3 + len4 + len5; + byte[] dataHmacData = new byte[totalLength]; + + int offset = 0; + Array.Copy(data, 0x011, dataHmacData, offset, len1); + offset += len1; + Array.Copy(data, 0x0A0, dataHmacData, offset, len2); + offset += len2; + Array.Copy(tagHmac, 0, dataHmacData, offset, len3); + offset += len3; + Array.Copy(data, 0x000, dataHmacData, offset, len4); + offset += len4; + Array.Copy(data, 0x054, dataHmacData, offset, len5); + + byte[] dataHmac; + using (var hmac = new HMACSHA256(hmacDataKey)) + { + dataHmac = hmac.ComputeHash(dataHmacData); + } + + // Overwrite the stored data HMAC + Array.Copy(dataHmac, 0, data, 0x080, 32); + } + + public void VerifyHMACs() + { + if (isLocked) + throw new InvalidOperationException("Cannot verify HMACs when data is locked."); + + // Calculate tag HMAC + byte[] tagHmacData = new byte[8 + 44]; + Array.Copy(data, 0x000, tagHmacData, 0, 8); + Array.Copy(data, 0x054, tagHmacData, 8, 44); + + byte[] calculatedTagHmac; + using (var hmac = new HMACSHA256(hmacTagKey)) + { + calculatedTagHmac = hmac.ComputeHash(tagHmacData); + } + + byte[] storedTagHmac = new byte[32]; + Array.Copy(data, 0x034, storedTagHmac, 0, 32); + + if (!calculatedTagHmac.SequenceEqual(storedTagHmac)) + { + throw new Exception("Tag HMAC verification failed."); + } + + // Prepare data for data HMAC + int len1 = 0x023; // 0x011 to 0x034 + int len2 = 0x168; // 0x0A0 to 0x208 + int len3 = calculatedTagHmac.Length; // 32 bytes + int len4 = 0x008; // 0x000 to 0x008 + int len5 = 0x02C; // 0x054 to 0x080 + int totalLength = len1 + len2 + len3 + len4 + len5; + byte[] dataHmacData = new byte[totalLength]; + + int offset = 0; + Array.Copy(data, 0x011, dataHmacData, offset, len1); + offset += len1; + Array.Copy(data, 0x0A0, dataHmacData, offset, len2); + offset += len2; + Array.Copy(calculatedTagHmac, 0, dataHmacData, offset, len3); + offset += len3; + Array.Copy(data, 0x000, dataHmacData, offset, len4); + offset += len4; + Array.Copy(data, 0x054, dataHmacData, offset, len5); + + byte[] calculatedDataHmac; + using (var hmac = new HMACSHA256(hmacDataKey)) + { + calculatedDataHmac = hmac.ComputeHash(dataHmacData); + } + + byte[] storedDataHmac = new byte[32]; + Array.Copy(data, 0x080, storedDataHmac, 0, 32); + + if (!calculatedDataHmac.SequenceEqual(storedDataHmac)) + { + throw new Exception("Data HMAC verification failed."); + } + } + + public void Unlock() + { + if (!isLocked) + throw new InvalidOperationException("Data is already unlocked."); + + // Derive keys and cipher + DeriveKeysAndCipher(); + + // Decrypt the encrypted data + DecryptData(); + + isLocked = false; + } + + public void Lock() + { + if (isLocked) + throw new InvalidOperationException("Data is already locked."); + + // Recalculate HMACs + DeriveHMACs(); + + // Encrypt the data + EncryptData(); + + isLocked = true; + } + + public byte[] GetData() + { + return data; + } + + // Property to get or set Amiibo nickname + public string AmiiboNickname + { + get + { + // data[0x020:0x034], big endian UTF-16 + byte[] nicknameBytes = new byte[0x014]; + Array.Copy(data, 0x020, nicknameBytes, 0, 0x014); + string nickname = System.Text.Encoding.BigEndianUnicode.GetString(nicknameBytes).TrimEnd('\0'); + return nickname; + } + set + { + byte[] nicknameBytes = System.Text.Encoding.BigEndianUnicode.GetBytes(value.PadRight(10, '\0')); + if (nicknameBytes.Length > 20) + throw new ArgumentException("Nickname too long."); + Array.Copy(nicknameBytes, 0, data, 0x020, nicknameBytes.Length); + // Pad remaining bytes with zeros + for (int i = 0x020 + nicknameBytes.Length; i < 0x034; i++) + { + data[i] = 0x00; + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs index 9ec2d0b80..e8eb0eb7a 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs @@ -1,69 +1,45 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -namespace Ryujinx.HLE.HOS.Services.Nfc.Bin +namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { public class AmiiboMasterKey { - private const int DataLength = 80; - private const int CombinedLength = 160; - public byte[] HmacKey { get; private set; } // 16 bytes + public byte[] HmacKey { get; private set; } // 16 bytes public byte[] TypeString { get; private set; } // 14 bytes - public byte Rfu { get; private set; } // 1 byte reserved + public byte Rfu { get; private set; } // 1 byte public byte MagicSize { get; private set; } // 1 byte public byte[] MagicBytes { get; private set; } // 16 bytes public byte[] XorPad { get; private set; } // 32 bytes - private AmiiboMasterKey(byte[] data) + public AmiiboMasterKey(byte[] data) { - if (data.Length != DataLength) - throw new ArgumentException($"Data is {data.Length} bytes (should be {DataLength})."); + if (data.Length != 80) + throw new ArgumentException("Master key data must be 80 bytes."); - - // Unpack the data - HmacKey = data[..16]; - TypeString = data[16..30]; + HmacKey = data.Take(16).ToArray(); + TypeString = data.Skip(16).Take(14).ToArray(); Rfu = data[30]; MagicSize = data[31]; - MagicBytes = data[32..48]; - XorPad = data[48..]; - } - - public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromSeparateBin(byte[] dataBin, byte[] tagBin) - { - var dataKey = new AmiiboMasterKey(dataBin); - var tagKey = new AmiiboMasterKey(tagBin); - return (dataKey, tagKey); - } - - public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromSeparateHex(string dataHex, string tagHex) - { - return FromSeparateBin(HexToBytes(dataHex), HexToBytes(tagHex)); + MagicBytes = data.Skip(32).Take(16).ToArray(); + XorPad = data.Skip(48).Take(32).ToArray(); } public static (AmiiboMasterKey DataKey, AmiiboMasterKey TagKey) FromCombinedBin(byte[] combinedBin) { - if (combinedBin.Length != CombinedLength) - throw new ArgumentException($"Data is {combinedBin.Length} bytes (should be {CombinedLength})."); + if (combinedBin.Length != 160) + throw new ArgumentException($"Data is {combinedBin.Length} bytes (should be 160)."); - byte[] dataBin = combinedBin[..DataLength]; - byte[] tagBin = combinedBin[DataLength..]; - return FromSeparateBin(dataBin, tagBin); - } + byte[] dataBin = combinedBin.Take(80).ToArray(); + byte[] tagBin = combinedBin.Skip(80).Take(80).ToArray(); - private static byte[] HexToBytes(string hex) - { - int length = hex.Length / 2; - byte[] bytes = new byte[length]; - for (int i = 0; i < length; i++) - { - bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); - } - return bytes; + AmiiboMasterKey dataKey = new AmiiboMasterKey(dataBin); + AmiiboMasterKey tagKey = new AmiiboMasterKey(tagBin); + + return (dataKey, tagKey); } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs index 20f67a4ef..3256684f4 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs @@ -78,7 +78,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp if (_state == State.Initialized) { _cancelTokenSource?.Cancel(); - // NOTE: All events are destroyed here. context.Device.System.NfpDevices.Clear(); @@ -146,9 +145,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp break; } } - _cancelTokenSource = new CancellationTokenSource(); - Task.Run(() => { while (true) @@ -199,7 +196,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp break; } } - return ResultCode.Success; } @@ -229,7 +225,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp } // TODO: Found how the MountTarget is handled. - for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) { if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle) @@ -488,14 +483,12 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp #pragma warning disable IDE0059 // Remove unnecessary value assignment uint deviceHandle = (uint)context.RequestData.ReadUInt64(); #pragma warning restore IDE0059 - if (context.Device.System.NfpDevices.Count == 0) { return ResultCode.DeviceNotFound; } // NOTE: Since we handle amiibo through VirtualAmiibo, we don't have to flush anything in our case. - return ResultCode.Success; } @@ -884,7 +877,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp return ResultCode.Success; } } - return ResultCode.DeviceNotFound; } diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 83443c6d8..6acbbe230 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -28,7 +28,7 @@ using Ryujinx.HLE; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; -using Ryujinx.HLE.HOS.Services.Nfc.Bin; +using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption; using Ryujinx.HLE.UI; using Ryujinx.Input.HLE; using Ryujinx.UI.App.Common; @@ -318,6 +318,16 @@ namespace Ryujinx.Ava.UI.ViewModels OnPropertyChanged(); } } + public bool IsBinAmiiboRequested + { + get => IsAmiiboRequested && AmiiboBinReader.HasKeyRetailBinPath(); + set + { + _isAmiiboRequested = value; + + OnPropertyChanged(); + } + } public bool ShowLoadProgress { diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index 153cfd379..e3fc3d33d 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -243,11 +243,11 @@ IsEnabled="{Binding IsAmiiboRequested}" /> + IsEnabled="{Binding IsBinAmiiboRequested}" /> Date: Thu, 5 Dec 2024 02:26:11 -0600 Subject: [PATCH 5/9] Added Saving --- src/Ryujinx.HLE/HOS/Horizon.cs | 2 + .../Nfc/AmiiboDecryption/AmiiboBinReader.cs | 66 +++++++++++++++++++ .../Nfc/AmiiboDecryption/AmiiboDecrypter.cs | 11 ++++ .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 18 ++++- 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 3b314daf8..15187ef3b 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -342,6 +342,7 @@ namespace Ryujinx.HLE.HOS if (VirtualAmiibo.applicationBytes.Length > 0) { VirtualAmiibo.applicationBytes = new byte[0]; + VirtualAmiibo.inputBin = string.Empty; } if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag) { @@ -352,6 +353,7 @@ namespace Ryujinx.HLE.HOS } public void ScanAmiiboFromBin(string path) { + VirtualAmiibo.inputBin = path; if (VirtualAmiibo.applicationBytes.Length > 0) { VirtualAmiibo.applicationBytes = new byte[0]; diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs index f701c2a30..9744f5c36 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -148,6 +148,72 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption return virtualAmiiboFile; } + public static bool SaveBinFile(string inputFile, byte[] appData) + { + Console.WriteLine("Saving bin file."); + byte[] readBytes; + try + { + readBytes = File.ReadAllBytes(inputFile); + } + catch (Exception ex) + { + Console.WriteLine($"Error reading file: {ex.Message}"); + return false; + } + string keyRetailBinPath = GetKeyRetailBinPath(); + if (string.IsNullOrEmpty(keyRetailBinPath)) + { + Console.WriteLine("Key retail path is empty."); + return false; + } + + if (appData.Length != 216) // Ensure application area size is valid + { + Console.WriteLine("Invalid application data length. Expected 216 bytes."); + return false; + } + + AmiiboDecrypter amiiboDecryptor = new AmiiboDecrypter(keyRetailBinPath); + AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(readBytes); + + byte[] oldData = amiiboDump.GetData(); + if (oldData.Length != 540) // Verify the expected length for NTAG215 tags + { + Console.WriteLine("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) + { + Console.WriteLine("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); + Console.WriteLine($"Modified Amiibo data saved to {outputFilePath}."); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error saving file: {ex.Message}"); + return false; + } + } private static void LogDebugData(byte[] uid, byte bcc0, byte bcc1) { diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs index 8e63c4149..de38a5609 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs @@ -32,5 +32,16 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption return amiiboDump; } + + public AmiiboDump EncryptAmiiboDump(byte[] decryptedDumpData) + { + // Initialize AmiiboDump with decrypted data + AmiiboDump amiiboDump = new AmiiboDump(decryptedDumpData, DataKey, TagKey, isLocked: false); + + // Lock (encrypt) the dump + amiiboDump.Lock(); + + return amiiboDump; + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index 0d72757a7..3bbbf122f 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -4,6 +4,7 @@ using Ryujinx.Common.Utilities; using Ryujinx.Cpu; using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using System; using System.Collections.Generic; @@ -15,6 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { public static uint _openedApplicationAreaId; public static byte[] applicationBytes = new byte[0]; + public static string inputBin = string.Empty; private static readonly AmiiboJsonSerializerContext _serializerContext = AmiiboJsonSerializerContext.Default; public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) { @@ -161,6 +163,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp public static void SetApplicationArea(string amiiboId, byte[] applicationAreaData) { + if (inputBin != string.Empty) + { + AmiiboBinReader.SaveBinFile(inputBin, applicationAreaData); + return; + } VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); if (virtualAmiiboFile.ApplicationAreas.Exists(item => item.ApplicationAreaId == _openedApplicationAreaId)) @@ -220,6 +227,15 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, _serializerContext.VirtualAmiiboFile); } - public static bool SaveFileExists(VirtualAmiiboFile virtualAmiiboFile) => File.Exists(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json")); + public static bool SaveFileExists(VirtualAmiiboFile virtualAmiiboFile) + { + if (inputBin != string.Empty) + { + SaveAmiiboFile(virtualAmiiboFile); + return true; + + } + return File.Exists(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json")); + } } } From 6051c3555370594cb8735818c6c7c839c9d1e35c Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Thu, 5 Dec 2024 02:38:30 -0600 Subject: [PATCH 6/9] Added nickname support --- .../HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs | 2 +- src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs index 9744f5c36..a2664a2b6 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -145,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { VirtualAmiibo.applicationBytes = applicationAreas; } - + VirtualAmiibo.nickName = nickName; return virtualAmiiboFile; } public static bool SaveBinFile(string inputFile, byte[] appData) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index 3bbbf122f..ac9eef5e9 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -17,6 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp public static uint _openedApplicationAreaId; public static byte[] applicationBytes = new byte[0]; public static string inputBin = string.Empty; + public static string nickName = string.Empty; private static readonly AmiiboJsonSerializerContext _serializerContext = AmiiboJsonSerializerContext.Default; public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) { @@ -69,6 +70,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); string nickname = amiiboFile.NickName ?? "Ryujinx"; + if (nickName != string.Empty) + { + nickname = nickName; + nickName = string.Empty; + } UtilityImpl utilityImpl = new(tickSource); CharInfo charInfo = new(); From bc79830b3b4aa96e43709547459704524b2a2f66 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Thu, 5 Dec 2024 03:00:10 -0600 Subject: [PATCH 7/9] Finishing touches --- .../Nfc/AmiiboDecryption/AmiiboBinReader.cs | 105 +++++++++++++----- .../Nfc/AmiiboDecryption/AmiiboDecrypter.cs | 4 - .../Nfc/AmiiboDecryption/AmiiboDump.cs | 2 - .../Nfc/AmiiboDecryption/AmiiboMasterKey.cs | 3 - .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 5 + src/Ryujinx/Assets/Locales/ar_SA.json | 1 + src/Ryujinx/Assets/Locales/de_DE.json | 1 + src/Ryujinx/Assets/Locales/el_GR.json | 1 + src/Ryujinx/Assets/Locales/es_ES.json | 1 + src/Ryujinx/Assets/Locales/fr_FR.json | 1 + src/Ryujinx/Assets/Locales/he_IL.json | 1 + src/Ryujinx/Assets/Locales/it_IT.json | 1 + src/Ryujinx/Assets/Locales/ja_JP.json | 1 + src/Ryujinx/Assets/Locales/ko_KR.json | 1 + src/Ryujinx/Assets/Locales/pl_PL.json | 1 + src/Ryujinx/Assets/Locales/pt_BR.json | 1 + src/Ryujinx/Assets/Locales/ru_RU.json | 1 + src/Ryujinx/Assets/Locales/th_TH.json | 1 + src/Ryujinx/Assets/Locales/tr_TR.json | 1 + src/Ryujinx/Assets/Locales/uk_UA.json | 1 + src/Ryujinx/Assets/Locales/zh_CN.json | 1 + src/Ryujinx/Assets/Locales/zh_TW.json | 1 + 22 files changed, 98 insertions(+), 38 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs index a2664a2b6..68bc978fc 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -1,11 +1,9 @@ using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Nfc.Nfp; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; -using Ryujinx.HLE.HOS.Tamper; using System; using System.IO; -using System.Text; -using static LibHac.FsSystem.AesCtrCounterExtendedStorage; namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { @@ -43,7 +41,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption AmiiboDecrypter amiiboDecryptor = new AmiiboDecrypter(keyRetailBinPath); AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(fileBytes); - byte[] titleId = new byte[8]; byte[] usedCharacter = new byte[2]; byte[] variation = new byte[2]; @@ -57,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption byte formData = 0; byte[] applicationAreas = new byte[216]; byte[] dataFull = amiiboDump.GetData(); - Console.WriteLine("Data Full Length: " + dataFull.Length); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Data Full Length: {dataFull.Length}"); byte[] uid = new byte[7]; Array.Copy(dataFull, 0, uid, 0, 7); @@ -74,11 +71,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption switch (page) { case 0: // Page 0 (UID + BCC0) - Console.WriteLine("Page 0: UID and BCC0."); + Logger.Debug?.Print(LogClass.ServiceNfp, "Page 0: UID and BCC0."); break; case 2: // Page 2 (BCC1 + Internal Value) byte internalValue = pageData[1]; - Console.WriteLine($"Page 2: BCC1 + Internal Value 0x{internalValue:X2} (Expected 0x48)."); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Page 2: BCC1 + Internal Value 0x{internalValue:X2} (Expected 0x48)."); break; case 6: // Bytes 0 and 1 are init date, bytes 2 and 3 are write date @@ -141,7 +138,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption LastWriteDate = writeDateTime, WriteCounter = writeCounterValue, }; - if (writeCounterValue>0) + if (writeCounterValue > 0) { VirtualAmiibo.applicationBytes = applicationAreas; } @@ -150,7 +147,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption } public static bool SaveBinFile(string inputFile, byte[] appData) { - Console.WriteLine("Saving bin file."); + Logger.Info?.Print(LogClass.ServiceNfp, "Saving bin file."); byte[] readBytes; try { @@ -158,19 +155,19 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption } catch (Exception ex) { - Console.WriteLine($"Error reading file: {ex.Message}"); + Logger.Error?.Print(LogClass.ServiceNfp, $"Error reading file: {ex.Message}"); return false; } string keyRetailBinPath = GetKeyRetailBinPath(); if (string.IsNullOrEmpty(keyRetailBinPath)) { - Console.WriteLine("Key retail path is empty."); + Logger.Error?.Print(LogClass.ServiceNfp, "Key retail path is empty."); return false; } if (appData.Length != 216) // Ensure application area size is valid { - Console.WriteLine("Invalid application data length. Expected 216 bytes."); + Logger.Error?.Print(LogClass.ServiceNfp, "Invalid application data length. Expected 216 bytes."); return false; } @@ -180,7 +177,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption byte[] oldData = amiiboDump.GetData(); if (oldData.Length != 540) // Verify the expected length for NTAG215 tags { - Console.WriteLine("Invalid tag data length. Expected 540 bytes."); + Logger.Error?.Print(LogClass.ServiceNfp, "Invalid tag data length. Expected 540 bytes."); return false; } @@ -196,7 +193,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption if (encryptedData == null || encryptedData.Length != readBytes.Length) { - Console.WriteLine("Failed to encrypt data correctly."); + Logger.Error?.Print(LogClass.ServiceNfp, "Failed to encrypt data correctly."); return false; } inputFile = inputFile.Replace("_modified", string.Empty); @@ -205,35 +202,85 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption try { File.WriteAllBytes(outputFilePath, encryptedData); - Console.WriteLine($"Modified Amiibo data saved to {outputFilePath}."); + Logger.Info?.Print(LogClass.ServiceNfp, $"Modified Amiibo data saved to {outputFilePath}."); return true; } catch (Exception ex) { - Console.WriteLine($"Error saving file: {ex.Message}"); + 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) + { + 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."); + return false; + } + AmiiboDecrypter amiiboDecryptor = new AmiiboDecrypter(keyRetailBinPath); + AmiiboDump amiiboDump = amiiboDecryptor.DecryptAmiiboDump(readBytes); + amiiboDump.AmiiboNickname = newNickName; + 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[] encryptedData = amiiboDecryptor.EncryptAmiiboDump(oldData).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; + } + } private static void LogDebugData(byte[] uid, byte bcc0, byte bcc1) { - Console.WriteLine($"UID: {BitConverter.ToString(uid)}"); - Console.WriteLine($"BCC0: 0x{bcc0:X2}, BCC1: 0x{bcc1:X2}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"UID: {BitConverter.ToString(uid)}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"BCC0: 0x{bcc0:X2}, BCC1: 0x{bcc1:X2}"); } private static void LogFinalData(byte[] titleId, byte[] appId, string head, string tail, string finalID, string nickName, DateTime initDateTime, DateTime writeDateTime, ushort settingsValue, ushort writeCounterValue, byte[] applicationAreas) { - Console.WriteLine($"Title ID: 0x{BitConverter.ToString(titleId).Replace("-", "")}"); - Console.WriteLine($"Application Program ID: 0x{BitConverter.ToString(appId).Replace("-", "")}"); - Console.WriteLine($"Head: {head}"); - Console.WriteLine($"Tail: {tail}"); - Console.WriteLine($"Final ID: {finalID}"); - Console.WriteLine($"Nickname: {nickName}"); - Console.WriteLine($"Init Date: {initDateTime}"); - Console.WriteLine($"Write Date: {writeDateTime}"); - Console.WriteLine($"Settings: 0x{settingsValue:X4}"); - Console.WriteLine($"Write Counter: {writeCounterValue}"); - Console.WriteLine("Length of Application Areas: " + applicationAreas.Length); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Title ID: 0x{BitConverter.ToString(titleId).Replace("-", "")}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Application Program ID: 0x{BitConverter.ToString(appId).Replace("-", "")}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Head: {head}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Tail: {tail}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Final ID: {finalID}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Nickname: {nickName}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Init Date: {initDateTime}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Write Date: {writeDateTime}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Settings: 0x{settingsValue:X4}"); + Logger.Debug?.Print(LogClass.ServiceNfp, $"Write Counter: {writeCounterValue}"); + Logger.Debug?.Print(LogClass.ServiceNfp, "Length of Application Areas: " + applicationAreas.Length); } private static uint CalculateCRC32(byte[] input) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs index de38a5609..71758c947 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDecrypter.cs @@ -1,8 +1,4 @@ -using System; -using System.Linq; using System.IO; -using System.Collections.Generic; -using System.Security.Cryptography; namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDump.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDump.cs index 16c1e72d3..7343a40ca 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDump.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboDump.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs index e8eb0eb7a..f61f8c84d 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboMasterKey.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption { diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index ac9eef5e9..06570cfbf 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -104,6 +104,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); virtualAmiiboFile.NickName = newNickName; + if (inputBin != string.Empty) + { + AmiiboBinReader.SaveBinFile(inputBin, virtualAmiiboFile.NickName); + return; + } SaveAmiiboFile(virtualAmiiboFile); } diff --git a/src/Ryujinx/Assets/Locales/ar_SA.json b/src/Ryujinx/Assets/Locales/ar_SA.json index c1ee30f19..865c2e503 100644 --- a/src/Ryujinx/Assets/Locales/ar_SA.json +++ b/src/Ryujinx/Assets/Locales/ar_SA.json @@ -27,6 +27,7 @@ "MenuBarActions": "_الإجراءات", "MenuBarOptionsSimulateWakeUpMessage": "محاكاة رسالة الاستيقاظ", "MenuBarActionsScanAmiibo": "‫فحص Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_الأدوات", "MenuBarToolsInstallFirmware": "تثبيت البرنامج الثابت", "MenuBarFileToolsInstallFirmwareFromFile": "تثبيت برنامج ثابت من XCI أو ZIP", diff --git a/src/Ryujinx/Assets/Locales/de_DE.json b/src/Ryujinx/Assets/Locales/de_DE.json index e3f6b1be1..8c9e3694a 100644 --- a/src/Ryujinx/Assets/Locales/de_DE.json +++ b/src/Ryujinx/Assets/Locales/de_DE.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Aktionen", "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht simulieren", "MenuBarActionsScanAmiibo": "Amiibo scannen", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Tools", "MenuBarToolsInstallFirmware": "Firmware installieren", "MenuBarFileToolsInstallFirmwareFromFile": "Firmware von einer XCI- oder einer ZIP-Datei installieren", diff --git a/src/Ryujinx/Assets/Locales/el_GR.json b/src/Ryujinx/Assets/Locales/el_GR.json index e93e9310a..f4f4d169b 100644 --- a/src/Ryujinx/Assets/Locales/el_GR.json +++ b/src/Ryujinx/Assets/Locales/el_GR.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Δράσεις", "MenuBarOptionsSimulateWakeUpMessage": "Προσομοίωση Μηνύματος Αφύπνισης", "MenuBarActionsScanAmiibo": "Σάρωση Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Εργαλεία", "MenuBarToolsInstallFirmware": "Εγκατάσταση Firmware", "MenuBarFileToolsInstallFirmwareFromFile": "Εγκατάσταση Firmware από XCI ή ZIP", diff --git a/src/Ryujinx/Assets/Locales/es_ES.json b/src/Ryujinx/Assets/Locales/es_ES.json index 0a68d44c6..4bebaf8a0 100644 --- a/src/Ryujinx/Assets/Locales/es_ES.json +++ b/src/Ryujinx/Assets/Locales/es_ES.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Acciones", "MenuBarOptionsSimulateWakeUpMessage": "Simular mensaje de reactivación", "MenuBarActionsScanAmiibo": "Escanear Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Herramientas", "MenuBarToolsInstallFirmware": "Instalar firmware", "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware desde un archivo XCI o ZIP", diff --git a/src/Ryujinx/Assets/Locales/fr_FR.json b/src/Ryujinx/Assets/Locales/fr_FR.json index 471dfbe5e..503efc08d 100644 --- a/src/Ryujinx/Assets/Locales/fr_FR.json +++ b/src/Ryujinx/Assets/Locales/fr_FR.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Actions", "MenuBarOptionsSimulateWakeUpMessage": "Simuler un message de réveil", "MenuBarActionsScanAmiibo": "Scanner un Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Outils", "MenuBarToolsInstallFirmware": "Installer un firmware", "MenuBarFileToolsInstallFirmwareFromFile": "Installer un firmware depuis un fichier XCI ou ZIP", diff --git a/src/Ryujinx/Assets/Locales/he_IL.json b/src/Ryujinx/Assets/Locales/he_IL.json index dbacf5ea1..292c2310d 100644 --- a/src/Ryujinx/Assets/Locales/he_IL.json +++ b/src/Ryujinx/Assets/Locales/he_IL.json @@ -27,6 +27,7 @@ "MenuBarActions": "_פעולות", "MenuBarOptionsSimulateWakeUpMessage": "דמה הודעת השכמה", "MenuBarActionsScanAmiibo": "סרוק אמיבו", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_כלים", "MenuBarToolsInstallFirmware": "התקן קושחה", "MenuBarFileToolsInstallFirmwareFromFile": "התקן קושחה מקובץ- ZIP/XCI", diff --git a/src/Ryujinx/Assets/Locales/it_IT.json b/src/Ryujinx/Assets/Locales/it_IT.json index 61ea2a355..60a1a7d23 100644 --- a/src/Ryujinx/Assets/Locales/it_IT.json +++ b/src/Ryujinx/Assets/Locales/it_IT.json @@ -24,6 +24,7 @@ "MenuBarActions": "_Azioni", "MenuBarOptionsSimulateWakeUpMessage": "Simula messaggio Wake-up", "MenuBarActionsScanAmiibo": "Scansiona un Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Strumenti", "MenuBarToolsInstallFirmware": "Installa firmware", "MenuBarFileToolsInstallFirmwareFromFile": "Installa un firmware da file XCI o ZIP", diff --git a/src/Ryujinx/Assets/Locales/ja_JP.json b/src/Ryujinx/Assets/Locales/ja_JP.json index 9acd1c486..8d6c500f6 100644 --- a/src/Ryujinx/Assets/Locales/ja_JP.json +++ b/src/Ryujinx/Assets/Locales/ja_JP.json @@ -27,6 +27,7 @@ "MenuBarActions": "アクション(_A)", "MenuBarOptionsSimulateWakeUpMessage": "スリープ復帰メッセージをシミュレート", "MenuBarActionsScanAmiibo": "Amiibo をスキャン", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "ツール(_T)", "MenuBarToolsInstallFirmware": "ファームウェアをインストール", "MenuBarFileToolsInstallFirmwareFromFile": "XCI または ZIP からファームウェアをインストール", diff --git a/src/Ryujinx/Assets/Locales/ko_KR.json b/src/Ryujinx/Assets/Locales/ko_KR.json index 86592aa69..10afa5277 100644 --- a/src/Ryujinx/Assets/Locales/ko_KR.json +++ b/src/Ryujinx/Assets/Locales/ko_KR.json @@ -27,6 +27,7 @@ "MenuBarActions": "동작(_A)", "MenuBarOptionsSimulateWakeUpMessage": "웨이크업 메시지 시뮬레이션", "MenuBarActionsScanAmiibo": "Amiibo 스캔", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "도구(_T)", "MenuBarToolsInstallFirmware": "펌웨어 설치", "MenuBarFileToolsInstallFirmwareFromFile": "XCI 또는 ZIP으로 펌웨어 설치", diff --git a/src/Ryujinx/Assets/Locales/pl_PL.json b/src/Ryujinx/Assets/Locales/pl_PL.json index 1ed0988f9..b9f3c12ef 100644 --- a/src/Ryujinx/Assets/Locales/pl_PL.json +++ b/src/Ryujinx/Assets/Locales/pl_PL.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Akcje", "MenuBarOptionsSimulateWakeUpMessage": "Symuluj wiadomość wybudzania", "MenuBarActionsScanAmiibo": "Skanuj Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Narzędzia", "MenuBarToolsInstallFirmware": "Zainstaluj oprogramowanie", "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj oprogramowanie z XCI lub ZIP", diff --git a/src/Ryujinx/Assets/Locales/pt_BR.json b/src/Ryujinx/Assets/Locales/pt_BR.json index 676d89d96..68fef5840 100644 --- a/src/Ryujinx/Assets/Locales/pt_BR.json +++ b/src/Ryujinx/Assets/Locales/pt_BR.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Ações", "MenuBarOptionsSimulateWakeUpMessage": "_Simular mensagem de acordar console", "MenuBarActionsScanAmiibo": "Escanear um Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Ferramentas", "MenuBarToolsInstallFirmware": "_Instalar firmware", "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware a partir de um arquivo ZIP/XCI", diff --git a/src/Ryujinx/Assets/Locales/ru_RU.json b/src/Ryujinx/Assets/Locales/ru_RU.json index ea4dcc8c8..d6ce6a9c5 100644 --- a/src/Ryujinx/Assets/Locales/ru_RU.json +++ b/src/Ryujinx/Assets/Locales/ru_RU.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Действия", "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", "MenuBarActionsScanAmiibo": "Сканировать Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Инструменты", "MenuBarToolsInstallFirmware": "Установка прошивки", "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", diff --git a/src/Ryujinx/Assets/Locales/th_TH.json b/src/Ryujinx/Assets/Locales/th_TH.json index fa4c1d334..95c6e113f 100644 --- a/src/Ryujinx/Assets/Locales/th_TH.json +++ b/src/Ryujinx/Assets/Locales/th_TH.json @@ -27,6 +27,7 @@ "MenuBarActions": "การดำเนินการ", "MenuBarOptionsSimulateWakeUpMessage": "จำลองข้อความปลุก", "MenuBarActionsScanAmiibo": "สแกนหา Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_เครื่องมือ", "MenuBarToolsInstallFirmware": "ติดตั้งเฟิร์มแวร์", "MenuBarFileToolsInstallFirmwareFromFile": "ติดตั้งเฟิร์มแวร์จาก ไฟล์ XCI หรือ ไฟล์ ZIP", diff --git a/src/Ryujinx/Assets/Locales/tr_TR.json b/src/Ryujinx/Assets/Locales/tr_TR.json index 475086e44..61c5b2e06 100644 --- a/src/Ryujinx/Assets/Locales/tr_TR.json +++ b/src/Ryujinx/Assets/Locales/tr_TR.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Eylemler", "MenuBarOptionsSimulateWakeUpMessage": "Uyandırma Mesajı Simüle Et", "MenuBarActionsScanAmiibo": "Bir Amiibo Tara", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Araçlar", "MenuBarToolsInstallFirmware": "Yazılım Yükle", "MenuBarFileToolsInstallFirmwareFromFile": "XCI veya ZIP'ten Yazılım Yükle", diff --git a/src/Ryujinx/Assets/Locales/uk_UA.json b/src/Ryujinx/Assets/Locales/uk_UA.json index 68679a9b2..613e5bb0b 100644 --- a/src/Ryujinx/Assets/Locales/uk_UA.json +++ b/src/Ryujinx/Assets/Locales/uk_UA.json @@ -27,6 +27,7 @@ "MenuBarActions": "_Дії", "MenuBarOptionsSimulateWakeUpMessage": "Симулювати повідомлення про пробудження", "MenuBarActionsScanAmiibo": "Сканувати Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "_Інструменти", "MenuBarToolsInstallFirmware": "Установити прошивку", "MenuBarFileToolsInstallFirmwareFromFile": "Установити прошивку з XCI або ZIP", diff --git a/src/Ryujinx/Assets/Locales/zh_CN.json b/src/Ryujinx/Assets/Locales/zh_CN.json index 741b5b370..73b1a077a 100644 --- a/src/Ryujinx/Assets/Locales/zh_CN.json +++ b/src/Ryujinx/Assets/Locales/zh_CN.json @@ -27,6 +27,7 @@ "MenuBarActions": "操作(_A)", "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", "MenuBarActionsScanAmiibo": "扫描 Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "工具(_T)", "MenuBarToolsInstallFirmware": "安装系统固件", "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 文件中安装系统固件", diff --git a/src/Ryujinx/Assets/Locales/zh_TW.json b/src/Ryujinx/Assets/Locales/zh_TW.json index aaf8170c0..c72691b49 100644 --- a/src/Ryujinx/Assets/Locales/zh_TW.json +++ b/src/Ryujinx/Assets/Locales/zh_TW.json @@ -27,6 +27,7 @@ "MenuBarActions": "動作(_A)", "MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息", "MenuBarActionsScanAmiibo": "掃描 Amiibo", + "MenuBarActionsScanAmiiboBin": "Scan An Amiibo (From Bin)", "MenuBarTools": "工具(_T)", "MenuBarToolsInstallFirmware": "安裝韌體", "MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體", From a93b42d218cadffa1422a9b2c70398187b9b6a4b Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Tue, 10 Dec 2024 10:29:44 -0600 Subject: [PATCH 8/9] Fix Logical Inconsistencies --- src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs | 8 ++++---- src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml | 2 +- src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 6acbbe230..d1b70b32b 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -72,6 +72,7 @@ namespace Ryujinx.Ava.UI.ViewModels private string _gpuStatusText; private string _shaderCountText; private bool _isAmiiboRequested; + private bool _isAmiiboBinRequested; private bool _showRightmostSeparator; private bool _isGameRunning; private bool _isFullScreen; @@ -318,17 +319,16 @@ namespace Ryujinx.Ava.UI.ViewModels OnPropertyChanged(); } } - public bool IsBinAmiiboRequested + public bool IsAmiiboBinRequested { - get => IsAmiiboRequested && AmiiboBinReader.HasKeyRetailBinPath(); + get => _isAmiiboBinRequested && _isGameRunning; set { - _isAmiiboRequested = value; + _isAmiiboBinRequested = value; OnPropertyChanged(); } } - public bool ShowLoadProgress { get => _showLoadProgress; diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index e3fc3d33d..c3d16529d 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -247,7 +247,7 @@ Click="OpenBinFile" Header="{ext:Locale MenuBarActionsScanAmiiboBin}" Icon="{ext:Icon mdi-cube-scan}" - IsEnabled="{Binding IsBinAmiiboRequested}" /> + IsEnabled="{Binding IsAmiiboBinRequested}" /> Date: Thu, 12 Dec 2024 13:33:11 -0600 Subject: [PATCH 9/9] Fix casing inconsistencies and remove unused showRightmostSeparator property --- src/Ryujinx.HLE/HOS/Horizon.cs | 12 ++--- .../Nfc/AmiiboDecryption/AmiiboBinReader.cs | 4 +- .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 44 +++++++++---------- .../UI/ViewModels/MainWindowViewModel.cs | 1 - 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 15187ef3b..c585aed54 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -339,10 +339,10 @@ namespace Ryujinx.HLE.HOS public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid) { - if (VirtualAmiibo.applicationBytes.Length > 0) + if (VirtualAmiibo.ApplicationBytes.Length > 0) { - VirtualAmiibo.applicationBytes = new byte[0]; - VirtualAmiibo.inputBin = string.Empty; + VirtualAmiibo.ApplicationBytes = new byte[0]; + VirtualAmiibo.InputBin = string.Empty; } if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag) { @@ -353,10 +353,10 @@ namespace Ryujinx.HLE.HOS } public void ScanAmiiboFromBin(string path) { - VirtualAmiibo.inputBin = path; - if (VirtualAmiibo.applicationBytes.Length > 0) + VirtualAmiibo.InputBin = path; + if (VirtualAmiibo.ApplicationBytes.Length > 0) { - VirtualAmiibo.applicationBytes = new byte[0]; + VirtualAmiibo.ApplicationBytes = new byte[0]; } byte[] encryptedData = File.ReadAllBytes(path); VirtualAmiiboFile newFile = AmiiboBinReader.ReadBinFile(encryptedData); diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs index 68bc978fc..13a5ef998 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/AmiiboDecryption/AmiiboBinReader.cs @@ -140,9 +140,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption }; if (writeCounterValue > 0) { - VirtualAmiibo.applicationBytes = applicationAreas; + VirtualAmiibo.ApplicationBytes = applicationAreas; } - VirtualAmiibo.nickName = nickName; + VirtualAmiibo.NickName = nickName; return virtualAmiiboFile; } public static bool SaveBinFile(string inputFile, byte[] appData) diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index 06570cfbf..fa39cb030 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -14,10 +14,10 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { static class VirtualAmiibo { - public static uint _openedApplicationAreaId; - public static byte[] applicationBytes = new byte[0]; - public static string inputBin = string.Empty; - public static string nickName = string.Empty; + public static uint OpenedApplicationAreaId; + public static byte[] ApplicationBytes = new byte[0]; + public static string InputBin = string.Empty; + public static string NickName = string.Empty; private static readonly AmiiboJsonSerializerContext _serializerContext = AmiiboJsonSerializerContext.Default; public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) { @@ -70,10 +70,10 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); string nickname = amiiboFile.NickName ?? "Ryujinx"; - if (nickName != string.Empty) + if (NickName != string.Empty) { - nickname = nickName; - nickName = string.Empty; + nickname = NickName; + NickName = string.Empty; } UtilityImpl utilityImpl = new(tickSource); CharInfo charInfo = new(); @@ -104,9 +104,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); virtualAmiiboFile.NickName = newNickName; - if (inputBin != string.Empty) + if (InputBin != string.Empty) { - AmiiboBinReader.SaveBinFile(inputBin, virtualAmiiboFile.NickName); + AmiiboBinReader.SaveBinFile(InputBin, virtualAmiiboFile.NickName); return; } SaveAmiiboFile(virtualAmiiboFile); @@ -115,15 +115,15 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId) { VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); - if (applicationBytes.Length > 0) + if (ApplicationBytes.Length > 0) { - _openedApplicationAreaId = applicationAreaId; + OpenedApplicationAreaId = applicationAreaId; return true; } if (virtualAmiiboFile.ApplicationAreas.Exists(item => item.ApplicationAreaId == applicationAreaId)) { - _openedApplicationAreaId = applicationAreaId; + OpenedApplicationAreaId = applicationAreaId; return true; } @@ -133,17 +133,17 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp public static byte[] GetApplicationArea(string amiiboId) { - if (applicationBytes.Length > 0) + if (ApplicationBytes.Length > 0) { - byte[] bytes = applicationBytes; - applicationBytes = new byte[0]; + byte[] bytes = ApplicationBytes; + ApplicationBytes = new byte[0]; return bytes; } VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); foreach (VirtualAmiiboApplicationArea applicationArea in virtualAmiiboFile.ApplicationAreas) { - if (applicationArea.ApplicationAreaId == _openedApplicationAreaId) + if (applicationArea.ApplicationAreaId == OpenedApplicationAreaId) { return applicationArea.ApplicationArea; } @@ -174,22 +174,22 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp public static void SetApplicationArea(string amiiboId, byte[] applicationAreaData) { - if (inputBin != string.Empty) + if (InputBin != string.Empty) { - AmiiboBinReader.SaveBinFile(inputBin, applicationAreaData); + AmiiboBinReader.SaveBinFile(InputBin, applicationAreaData); return; } VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId); - if (virtualAmiiboFile.ApplicationAreas.Exists(item => item.ApplicationAreaId == _openedApplicationAreaId)) + if (virtualAmiiboFile.ApplicationAreas.Exists(item => item.ApplicationAreaId == OpenedApplicationAreaId)) { for (int i = 0; i < virtualAmiiboFile.ApplicationAreas.Count; i++) { - if (virtualAmiiboFile.ApplicationAreas[i].ApplicationAreaId == _openedApplicationAreaId) + if (virtualAmiiboFile.ApplicationAreas[i].ApplicationAreaId == OpenedApplicationAreaId) { virtualAmiiboFile.ApplicationAreas[i] = new VirtualAmiiboApplicationArea() { - ApplicationAreaId = _openedApplicationAreaId, + ApplicationAreaId = OpenedApplicationAreaId, ApplicationArea = applicationAreaData, }; @@ -240,7 +240,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp public static bool SaveFileExists(VirtualAmiiboFile virtualAmiiboFile) { - if (inputBin != string.Empty) + if (InputBin != string.Empty) { SaveAmiiboFile(virtualAmiiboFile); return true; diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index b1d4a3b37..0496b161e 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -74,7 +74,6 @@ namespace Ryujinx.Ava.UI.ViewModels private string _shaderCountText; private bool _isAmiiboRequested; private bool _isAmiiboBinRequested; - private bool _showRightmostSeparator; private bool _showShaderCompilationHint; private bool _isGameRunning; private bool _isFullScreen;