Adds the ability to read and write to amiibo bin files #348

Merged
Jacobwasbeast merged 11 commits from feature/amiibo-bin-reading into master 2024-12-20 04:36:47 +00:00
3 changed files with 90 additions and 72 deletions
Showing only changes of commit 46f8b8fa85 - Show all commits

View File

@ -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<VirtualAmiiboApplicationArea> applicationAreasList = ParseAmiiboData(applicationAreas);
List<VirtualAmiiboApplicationArea> applicationAreasList = new List<VirtualAmiiboApplicationArea>();
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<VirtualAmiiboApplicationArea> ParseAmiiboData(byte[] decryptedData)
{
return JsonSerializer.Deserialize<List<VirtualAmiiboApplicationArea>>(decryptedData);
}
}
}

View File

@ -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<object> ParseApplicationAreas(byte[] data, int startOffset, int areaSize)
{
var areas = new List<object>();
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;
}
}
}

View File

@ -13,10 +13,9 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
{
GreemDev commented 2024-12-12 19:18:14 +00:00 (Migrated from github.com)
Review

Please give these fields the proper casing OpenedApplicationAreaId, ApplicationBytes, etc

Please give these fields the proper casing `OpenedApplicationAreaId`, `ApplicationBytes`, etc
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"));
}
}