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")); + } } }