Give the game an amiibo's UID and model info in the Switch's layout

TagInfo took the UID as dump bytes 0..6, so the check byte BCC0 (byte 3) replaced the UID's last byte. ModelInfo had
the series and the figure type swapped, because the dump offsets were misread (0x57 is the figure type, 0x5A the
series), and the model number in the tag's big-endian order, while the game reads it little-endian. The game tells
figures of one character apart by that number, so scans gave the wrong rewards. The years of the common and register
info dates go little-endian too. --probe-services checks TagInfo and ModelInfo of a Link (Archer) dump.

The same fixes are in pull requests #519 (genres) and #582 (Gelomban6), which agree on the ModelInfo layout; their
event and feature changes are not part of this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Valiantsin DzerakhandClaude Opus 5.5 committed 2026-11-03 16:39:50 +03:00
1 parent 42d46c1324
commit 3a556c5048
2 files changed
+36 -17

No files matched your search

+22 -17
View File
@@ -56,6 +56,12 @@ static bool amiibo_validate_dump(const uint8_t *data, uint64_t size) {
return true;
}
// The 7-byte UID of an NTAG215 dump: bytes 0..2, then 4..7. Byte 3 is the check byte BCC0.
static void copy_uid(uint8_t out[7], const uint8_t *dump) {
memcpy(out, dump, 3);
memcpy(out + 3, dump + 4, 4);
}
static void parse_dump_info(const char *path) {
memset(&g_info, 0, sizeof(g_info));
if (path) {
@@ -69,15 +75,15 @@ static void parse_dump_info(const char *path) {
snprintf(g_info.filename, sizeof(g_info.filename), "%s", name);
}
// UID: bytes 0..6 (7 bytes) or up to 9
memcpy(g_info.uuid, g_dump, 7);
copy_uid(g_info.uuid, g_dump);
g_info.uuid_len = 7;
// Amiibo ID at offset 0x54 (8 bytes)
// Amiibo ID at offset 0x54 (8 bytes, 3dbrew "Amiibo"): character ID and variant, figure type, model number
// (big-endian), series, format version 02
memcpy(g_info.character_id, g_dump + 0x54, 3);
g_info.series_id = g_dump[0x57];
g_info.nfp_type = g_dump[0x57];
g_info.numbering_id = ((uint16_t)g_dump[0x58] << 8) | g_dump[0x59];
g_info.nfp_type = g_dump[0x5A];
g_info.series_id = g_dump[0x5A];
g_info.version = g_dump[0x5B];
}
@@ -321,7 +327,7 @@ void amiibo_get_tag_info(void *out_tag_info) {
uint8_t *out = (uint8_t *)out_tag_info;
memset(out, 0, 0x58);
// UUID: offset 0x00, 10 bytes max
memcpy(out + 0x00, g_dump, 7);
copy_uid(out, g_dump);
out[0x0A] = 7; // UUID length
// Protocol at offset 0x20: TypeA = 1
*(uint32_t *)(out + 0x20) = 1;
@@ -334,15 +340,14 @@ void amiibo_get_model_info(void *out_model_info) {
plat_mutex_lock(&g_amiibo_lock);
uint8_t *out = (uint8_t *)out_model_info;
memset(out, 0, 0x40);
// Character ID at 0x00 (3 bytes)
memcpy(out + 0x00, g_dump + 0x54, 3);
// Series ID at 0x03 (1 byte)
out[0x03] = g_dump[0x57];
// Numbering ID at 0x04 (2 bytes, big-endian from 0x58)
out[0x04] = g_dump[0x58];
out[0x05] = g_dump[0x59];
// NFP Type at 0x06 (1 byte)
out[0x06] = g_dump[0x5A];
// nn::nfp::ModelInfo from the amiibo ID at 0x54: character ID and variant in the tag's byte order, series, model
// number little-endian (big-endian on the tag), figure type. The game tells figures of one character apart by the
// model number.
memcpy(out + 0x00, g_dump + 0x54, 3); // character ID, variant
out[0x03] = g_dump[0x5A]; // series
out[0x04] = g_dump[0x59]; // model number
out[0x05] = g_dump[0x58];
out[0x06] = g_dump[0x57]; // figure type
plat_mutex_unlock(&g_amiibo_lock);
}
@@ -351,7 +356,7 @@ void amiibo_get_common_info(void *out_common_info) {
uint8_t *out = (uint8_t *)out_common_info;
memset(out, 0, 0x40);
// Last write date: 0x00..0x03
out[0x00] = 0x07; out[0x01] = 0xE7; // 2023
out[0x00] = 0xE7; out[0x01] = 0x07; // 2023, u16 little-endian
out[0x02] = 0x05; out[0x03] = 0x0C; // May 12
// Write counter: 0x04..0x05
out[0x04] = 0x00; out[0x05] = 0x01;
@@ -369,7 +374,7 @@ void amiibo_get_register_info(void *out_register_info) {
// Mii CharInfo at 0x00..0x57 (dummy valid mii)
out[0x00] = 0x03; // version 3
// First write date at 0x58..0x5B
out[0x58] = 0x07; out[0x59] = 0xE7;
out[0x58] = 0xE7; out[0x59] = 0x07; // 2023, u16 little-endian
out[0x5A] = 0x05; out[0x5B] = 0x0C;
// Amiibo Name at 0x5C: copy up to 40 bytes NUL-terminated
const char *name = g_info.filename[0] ? g_info.filename : "Amiibo";
+14
View File
@@ -405,8 +405,22 @@ static void probe_applet(void) {
0x33 ^ 0x44 ^ 0x55 ^ 0x66, 0x48, 0x0F, 0xE0, 0xF1, 0x10, 0xFF, 0xEE};
uint8_t dump[AMIIBO_DUMP_SIZE] = {0};
memcpy(dump, head, sizeof head);
// Link (Archer): character 0x0100, variant 0, figure type 0, model number 0x0353, series 9, format 02
static const uint8_t id[8] = {0x01, 0x00, 0x00, 0x00, 0x03, 0x53, 0x09, 0x02};
memcpy(dump + 0x54, id, sizeof id);
const char *dump_path = "probe-amiibo.bin";
int amiibo_ok = plat_file_write(dump_path, dump, sizeof dump) && amiibo_load_file(dump_path);
// TagInfo carries the 7-byte UID without its check byte; ModelInfo has the series at 3, the model number
// little-endian at 4 and the figure type at 6
uint8_t ti[0x58], mi[0x40];
amiibo_get_tag_info(ti);
amiibo_get_model_info(mi);
static const uint8_t want_uid[7] = {0x04, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
static const uint8_t want_model[7] = {0x01, 0x00, 0x00, 0x09, 0x53, 0x03, 0x00};
CHECK(amiibo_ok && !memcmp(ti, want_uid, 7) && ti[0x0A] == 7, "amiibo TagInfo UID %02x %02x %02x %02x %02x %02x %02x",
ti[0], ti[1], ti[2], ti[3], ti[4], ti[5], ti[6]);
CHECK(amiibo_ok && !memcmp(mi, want_model, 7), "amiibo ModelInfo %02x %02x %02x %02x %02x %02x %02x", mi[0], mi[1],
mi[2], mi[3], mi[4], mi[5], mi[6]);
dump[0x0C] = 0xE1;
amiibo_ok = amiibo_ok && plat_file_write(dump_path, dump, sizeof dump) && !amiibo_load_file(dump_path);
plat_fs_unlink(dump_path);