miijs 2.3.3 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Enums.js +163 -163
- package/README.md +362 -374
- package/amiiboHandler.js +238 -238
- package/data.json +3240 -0
- package/fflWrapper.js +40 -40
- package/ideal.jsonc +90 -90
- package/index.js +242 -1611
- package/package.json +45 -45
- package/patch-ffl.js +52 -52
- package/types.d.ts +156 -156
package/amiiboHandler.js
CHANGED
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
|
-
|
|
3
|
-
/*This constant is provided SOLELY because I cannot find a guide online to retrieve this file from a console or Amiibo on your own that doesn't just tell you to download it from somewhere anyway.
|
|
4
|
-
If someone can find, or make, a guide for this, I will wipe all commits of this key from the repo and instead point to how to get this key for yourself.*/
|
|
5
|
-
const MASTER_KEY_BUFFER = Buffer.from('1D164B375B72A55728B91D64B6A3C205756E666978656420696E666F7300000EDB4B9E3F45278F397EFF9B4FB9930000044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E5450547667F752D2873A20017FEF85C0575904B6D6C6F636B656420736563726574000010FDC8A07694B89E4C47D37DE8CE5C74C1044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E545054766', 'hex');
|
|
6
|
-
|
|
7
|
-
const DATA_HMAC_KEY = MASTER_KEY_BUFFER.slice(0, 16);
|
|
8
|
-
const DATA_TYPE_STRING = MASTER_KEY_BUFFER.slice(16, 30);
|
|
9
|
-
const DATA_MAGIC_BYTES_SIZE = MASTER_KEY_BUFFER[31];
|
|
10
|
-
const DATA_MAGIC_BYTES = MASTER_KEY_BUFFER.slice(32, 48);
|
|
11
|
-
const DATA_XOR_PAD = MASTER_KEY_BUFFER.slice(48, 80);
|
|
12
|
-
|
|
13
|
-
const TAG_HMAC_KEY = MASTER_KEY_BUFFER.slice(80, 96);
|
|
14
|
-
const TAG_TYPE_STRING = MASTER_KEY_BUFFER.slice(96, 110);
|
|
15
|
-
const TAG_MAGIC_BYTES_SIZE = MASTER_KEY_BUFFER[111];
|
|
16
|
-
const TAG_MAGIC_BYTES = MASTER_KEY_BUFFER.slice(112, 128);
|
|
17
|
-
const TAG_XOR_PAD = MASTER_KEY_BUFFER.slice(128, 160);
|
|
18
|
-
|
|
19
|
-
const NFC3D_AMIIBO_SIZE = 520;
|
|
20
|
-
const NTAG215_SIZE = 540;
|
|
21
|
-
const NTAG215_SIZE_ALT = 532;
|
|
22
|
-
|
|
23
|
-
const MII_OFFSET_DECRYPTED = 0x4C;
|
|
24
|
-
const MII_SIZE = 96;
|
|
25
|
-
|
|
26
|
-
//Calculate CRC16 checksum for Mii data (for Amiibo format)
|
|
27
|
-
function calculateMiiChecksum(data) {
|
|
28
|
-
const checksumData = data.slice(0, 94);
|
|
29
|
-
let crc = 0;
|
|
30
|
-
for (let byteIndex = 0; byteIndex < checksumData.length; byteIndex++) {
|
|
31
|
-
for (let bitIndex = 7; bitIndex >= 0; bitIndex--) {
|
|
32
|
-
crc = (((crc << 1) | ((checksumData[byteIndex] >> bitIndex) & 0x1)) ^
|
|
33
|
-
(((crc & 0x8000) !== 0) ? 0x1021 : 0)) & 0xFFFF;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
for (let counter = 16; counter > 0; counter--) {
|
|
37
|
-
crc = ((crc << 1) ^ (((crc & 0x8000) !== 0) ? 0x1021 : 0)) & 0xFFFF;
|
|
38
|
-
}
|
|
39
|
-
return crc & 0xFFFF;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
//Validate and fix Mii checksum - for 96-byte Amiibo format
|
|
43
|
-
function validateAndFixMiiChecksum(miiData) {
|
|
44
|
-
if (miiData.length !== 92 && miiData.length !== MII_SIZE) {
|
|
45
|
-
throw new Error(`Invalid Mii data size: expected 92 or ${MII_SIZE} bytes, got ${miiData.length}`);
|
|
46
|
-
}
|
|
47
|
-
const fullMii = Buffer.alloc(MII_SIZE);
|
|
48
|
-
miiData.slice(0, Math.min(94, miiData.length)).copy(fullMii, 0);
|
|
49
|
-
const newChecksum = calculateMiiChecksum(fullMii);
|
|
50
|
-
fullMii[94] = (newChecksum >> 8) & 0xFF;
|
|
51
|
-
fullMii[95] = newChecksum & 0xFF;
|
|
52
|
-
return fullMii;
|
|
53
|
-
}
|
|
54
|
-
function calcSeed(dump) {
|
|
55
|
-
const seed = Buffer.alloc(64);
|
|
56
|
-
dump.slice(0x029, 0x02B).copy(seed, 0x00);
|
|
57
|
-
seed.fill(0x00, 0x02, 0x10);
|
|
58
|
-
dump.slice(0x1D4, 0x1DC).copy(seed, 0x10);
|
|
59
|
-
dump.slice(0x1D4, 0x1DC).copy(seed, 0x18);
|
|
60
|
-
dump.slice(0x1E8, 0x208).copy(seed, 0x20);
|
|
61
|
-
return seed;
|
|
62
|
-
}
|
|
63
|
-
function prepareSeed(typeString, magicBytes, magicBytesSize, xorPad, baseSeed) {
|
|
64
|
-
const output = Buffer.alloc(480);
|
|
65
|
-
let offset = 0;
|
|
66
|
-
const typeStringEnd = typeString.indexOf(0);
|
|
67
|
-
const typeLen = typeStringEnd >= 0 ? typeStringEnd + 1 : 14;
|
|
68
|
-
typeString.slice(0, typeLen).copy(output, offset);
|
|
69
|
-
offset += typeLen;
|
|
70
|
-
const leadingSeedBytes = 16 - magicBytesSize;
|
|
71
|
-
baseSeed.slice(0, leadingSeedBytes).copy(output, offset);
|
|
72
|
-
offset += leadingSeedBytes;
|
|
73
|
-
magicBytes.slice(0, magicBytesSize).copy(output, offset);
|
|
74
|
-
offset += magicBytesSize;
|
|
75
|
-
baseSeed.slice(0x10, 0x20).copy(output, offset);
|
|
76
|
-
offset += 16;
|
|
77
|
-
for (let i = 0; i < 32; i++) {
|
|
78
|
-
output[offset + i] = baseSeed[0x20 + i] ^ xorPad[i];
|
|
79
|
-
}
|
|
80
|
-
offset += 32;
|
|
81
|
-
return output.slice(0, offset);
|
|
82
|
-
}
|
|
83
|
-
function drbgGenerateBytes(hmacKey, seed, outputSize) {
|
|
84
|
-
const result = Buffer.alloc(outputSize);
|
|
85
|
-
let offset = 0;
|
|
86
|
-
let iteration = 0;
|
|
87
|
-
while (offset < outputSize) {
|
|
88
|
-
const iterBuffer = Buffer.alloc(2 + seed.length);
|
|
89
|
-
iterBuffer[0] = (iteration >> 8) & 0xFF;
|
|
90
|
-
iterBuffer[1] = iteration & 0xFF;
|
|
91
|
-
seed.copy(iterBuffer, 2);
|
|
92
|
-
const hmac = crypto.createHmac('sha256', hmacKey);
|
|
93
|
-
hmac.update(iterBuffer);
|
|
94
|
-
const output = hmac.digest();
|
|
95
|
-
const toCopy = Math.min(32, outputSize - offset);
|
|
96
|
-
output.copy(result, offset, 0, toCopy);
|
|
97
|
-
offset += toCopy;
|
|
98
|
-
iteration++;
|
|
99
|
-
}
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function deriveKeys(typeString, magicBytes, magicBytesSize, xorPad, hmacKey, baseSeed) {
|
|
104
|
-
const preparedSeed = prepareSeed(typeString, magicBytes, magicBytesSize, xorPad, baseSeed);
|
|
105
|
-
const derived = drbgGenerateBytes(hmacKey, preparedSeed, 48);
|
|
106
|
-
return {
|
|
107
|
-
aesKey: derived.slice(0, 16),
|
|
108
|
-
aesIV: derived.slice(16, 32),
|
|
109
|
-
hmacKey: derived.slice(32, 48)
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function tagToInternal(tag) {
|
|
114
|
-
const internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
|
|
115
|
-
tag.slice(0x008, 0x010).copy(internal, 0x000);
|
|
116
|
-
tag.slice(0x080, 0x0A0).copy(internal, 0x008);
|
|
117
|
-
tag.slice(0x010, 0x034).copy(internal, 0x028);
|
|
118
|
-
tag.slice(0x0A0, 0x208).copy(internal, 0x04C);
|
|
119
|
-
tag.slice(0x034, 0x054).copy(internal, 0x1B4);
|
|
120
|
-
tag.slice(0x000, 0x008).copy(internal, 0x1D4);
|
|
121
|
-
tag.slice(0x054, 0x080).copy(internal, 0x1DC);
|
|
122
|
-
return internal;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function internalToTag(internal) {
|
|
126
|
-
const tag = Buffer.alloc(NFC3D_AMIIBO_SIZE);
|
|
127
|
-
internal.slice(0x000, 0x008).copy(tag, 0x008);
|
|
128
|
-
internal.slice(0x008, 0x028).copy(tag, 0x080);
|
|
129
|
-
internal.slice(0x028, 0x04C).copy(tag, 0x010);
|
|
130
|
-
internal.slice(0x04C, 0x1B4).copy(tag, 0x0A0);
|
|
131
|
-
internal.slice(0x1B4, 0x1D4).copy(tag, 0x034);
|
|
132
|
-
internal.slice(0x1D4, 0x1DC).copy(tag, 0x000);
|
|
133
|
-
internal.slice(0x1DC, 0x208).copy(tag, 0x054);
|
|
134
|
-
return tag;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function decryptAmiibo(tag) {
|
|
138
|
-
const internal = tagToInternal(tag);
|
|
139
|
-
const seed = calcSeed(internal);
|
|
140
|
-
const dataKeys = deriveKeys(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
|
|
141
|
-
const tagKeys = deriveKeys(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
|
|
142
|
-
const plain = Buffer.alloc(NFC3D_AMIIBO_SIZE);
|
|
143
|
-
const cipher = crypto.createDecipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
|
|
144
|
-
cipher.setAutoPadding(false);
|
|
145
|
-
const decrypted = cipher.update(internal.slice(0x02C, 0x1B4));
|
|
146
|
-
decrypted.copy(plain, 0x02C);
|
|
147
|
-
internal.slice(0x000, 0x008).copy(plain, 0x000);
|
|
148
|
-
internal.slice(0x028, 0x02C).copy(plain, 0x028);
|
|
149
|
-
internal.slice(0x1D4, 0x208).copy(plain, 0x1D4);
|
|
150
|
-
const tagHmac = crypto.createHmac('sha256', tagKeys.hmacKey);
|
|
151
|
-
tagHmac.update(plain.slice(0x1D4, 0x208));
|
|
152
|
-
const computedTagHmac = tagHmac.digest();
|
|
153
|
-
computedTagHmac.copy(plain, 0x1B4);
|
|
154
|
-
const dataHmac = crypto.createHmac('sha256', dataKeys.hmacKey);
|
|
155
|
-
dataHmac.update(plain.slice(0x029, 0x208));
|
|
156
|
-
const computedDataHmac = dataHmac.digest();
|
|
157
|
-
computedDataHmac.copy(plain, 0x008);
|
|
158
|
-
return plain;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function encryptAmiibo(plain) {
|
|
162
|
-
const seed = calcSeed(plain);
|
|
163
|
-
const dataKeys = deriveKeys(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
|
|
164
|
-
const tagKeys = deriveKeys(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
|
|
165
|
-
const cipher_internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
|
|
166
|
-
const tagHmac = crypto.createHmac('sha256', tagKeys.hmacKey);
|
|
167
|
-
tagHmac.update(plain.slice(0x1D4, 0x208));
|
|
168
|
-
tagHmac.digest().copy(cipher_internal, 0x1B4);
|
|
169
|
-
const dataHmac = crypto.createHmac('sha256', dataKeys.hmacKey);
|
|
170
|
-
dataHmac.update(plain.slice(0x029, 0x1B4));
|
|
171
|
-
dataHmac.update(cipher_internal.slice(0x1B4, 0x1D4));
|
|
172
|
-
dataHmac.update(plain.slice(0x1D4, 0x208));
|
|
173
|
-
dataHmac.digest().copy(cipher_internal, 0x008);
|
|
174
|
-
const aesCipher = crypto.createCipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
|
|
175
|
-
aesCipher.setAutoPadding(false);
|
|
176
|
-
const encrypted = aesCipher.update(plain.slice(0x02C, 0x1B4));
|
|
177
|
-
encrypted.copy(cipher_internal, 0x02C);
|
|
178
|
-
plain.slice(0x000, 0x008).copy(cipher_internal, 0x000);
|
|
179
|
-
plain.slice(0x028, 0x02C).copy(cipher_internal, 0x028);
|
|
180
|
-
plain.slice(0x1D4, 0x208).copy(cipher_internal, 0x1D4);
|
|
181
|
-
return internalToTag(cipher_internal);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
//Extract Mii data from an Amiibo dump
|
|
185
|
-
function extractMiiFromAmiibo(amiiboDump) {
|
|
186
|
-
if (!Buffer.isBuffer(amiiboDump)) {
|
|
187
|
-
throw new Error('Amiibo dump must be a Buffer');
|
|
188
|
-
}
|
|
189
|
-
const size = amiiboDump.length;
|
|
190
|
-
if (size !== NFC3D_AMIIBO_SIZE && size !== NTAG215_SIZE && size !== NTAG215_SIZE_ALT) {
|
|
191
|
-
throw new Error(`Invalid Amiibo dump size: ${size} (expected ${NFC3D_AMIIBO_SIZE}, ${NTAG215_SIZE_ALT}, or ${NTAG215_SIZE})`);
|
|
192
|
-
}
|
|
193
|
-
const tag = amiiboDump.slice(0, NFC3D_AMIIBO_SIZE);
|
|
194
|
-
const decrypted = decryptAmiibo(tag);
|
|
195
|
-
|
|
196
|
-
// Extract only the first 92 bytes (the actual Mii data, without checksum)
|
|
197
|
-
const miiData = decrypted.slice(MII_OFFSET_DECRYPTED, MII_OFFSET_DECRYPTED + 92);
|
|
198
|
-
|
|
199
|
-
return Buffer.from(miiData);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
//Insert Mii data into an Amiibo dump
|
|
203
|
-
function insertMiiIntoAmiibo(amiiboDump, miiData) {
|
|
204
|
-
if (!Buffer.isBuffer(amiiboDump)) {
|
|
205
|
-
throw new Error('Amiibo dump must be a Buffer');
|
|
206
|
-
}
|
|
207
|
-
if (!Buffer.isBuffer(miiData)) {
|
|
208
|
-
throw new Error('Mii data must be a Buffer');
|
|
209
|
-
}
|
|
210
|
-
const size = amiiboDump.length;
|
|
211
|
-
if (size !== NFC3D_AMIIBO_SIZE && size !== NTAG215_SIZE && size !== NTAG215_SIZE_ALT) {
|
|
212
|
-
throw new Error(`Invalid Amiibo dump size: ${size}`);
|
|
213
|
-
}
|
|
214
|
-
if (miiData.length !== 92 && miiData.length !== MII_SIZE) {
|
|
215
|
-
throw new Error(`Mii data must be 92 or ${MII_SIZE} bytes, got ${miiData.length}`);
|
|
216
|
-
}
|
|
217
|
-
const tag = amiiboDump.slice(0, NFC3D_AMIIBO_SIZE);
|
|
218
|
-
const decrypted = decryptAmiibo(tag);
|
|
219
|
-
|
|
220
|
-
// Validate and fix Mii checksum, ensuring it's 96 bytes with correct checksum
|
|
221
|
-
const miiWithChecksum = validateAndFixMiiChecksum(miiData);
|
|
222
|
-
|
|
223
|
-
// Insert Mii data (96 bytes)
|
|
224
|
-
miiWithChecksum.copy(decrypted, MII_OFFSET_DECRYPTED);
|
|
225
|
-
|
|
226
|
-
const encrypted = encryptAmiibo(decrypted);
|
|
227
|
-
const result = Buffer.alloc(size);
|
|
228
|
-
encrypted.copy(result, 0);
|
|
229
|
-
if (size > NFC3D_AMIIBO_SIZE) {
|
|
230
|
-
amiiboDump.slice(NFC3D_AMIIBO_SIZE).copy(result, NFC3D_AMIIBO_SIZE);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return result;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
module.exports = {
|
|
237
|
-
insertMiiIntoAmiibo,
|
|
238
|
-
extractMiiFromAmiibo
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
/*This constant is provided SOLELY because I cannot find a guide online to retrieve this file from a console or Amiibo on your own that doesn't just tell you to download it from somewhere anyway.
|
|
4
|
+
If someone can find, or make, a guide for this, I will wipe all commits of this key from the repo and instead point to how to get this key for yourself.*/
|
|
5
|
+
const MASTER_KEY_BUFFER = Buffer.from('1D164B375B72A55728B91D64B6A3C205756E666978656420696E666F7300000EDB4B9E3F45278F397EFF9B4FB9930000044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E5450547667F752D2873A20017FEF85C0575904B6D6C6F636B656420736563726574000010FDC8A07694B89E4C47D37DE8CE5C74C1044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E545054766', 'hex');
|
|
6
|
+
|
|
7
|
+
const DATA_HMAC_KEY = MASTER_KEY_BUFFER.slice(0, 16);
|
|
8
|
+
const DATA_TYPE_STRING = MASTER_KEY_BUFFER.slice(16, 30);
|
|
9
|
+
const DATA_MAGIC_BYTES_SIZE = MASTER_KEY_BUFFER[31];
|
|
10
|
+
const DATA_MAGIC_BYTES = MASTER_KEY_BUFFER.slice(32, 48);
|
|
11
|
+
const DATA_XOR_PAD = MASTER_KEY_BUFFER.slice(48, 80);
|
|
12
|
+
|
|
13
|
+
const TAG_HMAC_KEY = MASTER_KEY_BUFFER.slice(80, 96);
|
|
14
|
+
const TAG_TYPE_STRING = MASTER_KEY_BUFFER.slice(96, 110);
|
|
15
|
+
const TAG_MAGIC_BYTES_SIZE = MASTER_KEY_BUFFER[111];
|
|
16
|
+
const TAG_MAGIC_BYTES = MASTER_KEY_BUFFER.slice(112, 128);
|
|
17
|
+
const TAG_XOR_PAD = MASTER_KEY_BUFFER.slice(128, 160);
|
|
18
|
+
|
|
19
|
+
const NFC3D_AMIIBO_SIZE = 520;
|
|
20
|
+
const NTAG215_SIZE = 540;
|
|
21
|
+
const NTAG215_SIZE_ALT = 532;
|
|
22
|
+
|
|
23
|
+
const MII_OFFSET_DECRYPTED = 0x4C;
|
|
24
|
+
const MII_SIZE = 96;
|
|
25
|
+
|
|
26
|
+
//Calculate CRC16 checksum for Mii data (for Amiibo format)
|
|
27
|
+
function calculateMiiChecksum(data) {
|
|
28
|
+
const checksumData = data.slice(0, 94);
|
|
29
|
+
let crc = 0;
|
|
30
|
+
for (let byteIndex = 0; byteIndex < checksumData.length; byteIndex++) {
|
|
31
|
+
for (let bitIndex = 7; bitIndex >= 0; bitIndex--) {
|
|
32
|
+
crc = (((crc << 1) | ((checksumData[byteIndex] >> bitIndex) & 0x1)) ^
|
|
33
|
+
(((crc & 0x8000) !== 0) ? 0x1021 : 0)) & 0xFFFF;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (let counter = 16; counter > 0; counter--) {
|
|
37
|
+
crc = ((crc << 1) ^ (((crc & 0x8000) !== 0) ? 0x1021 : 0)) & 0xFFFF;
|
|
38
|
+
}
|
|
39
|
+
return crc & 0xFFFF;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//Validate and fix Mii checksum - for 96-byte Amiibo format
|
|
43
|
+
function validateAndFixMiiChecksum(miiData) {
|
|
44
|
+
if (miiData.length !== 92 && miiData.length !== MII_SIZE) {
|
|
45
|
+
throw new Error(`Invalid Mii data size: expected 92 or ${MII_SIZE} bytes, got ${miiData.length}`);
|
|
46
|
+
}
|
|
47
|
+
const fullMii = Buffer.alloc(MII_SIZE);
|
|
48
|
+
miiData.slice(0, Math.min(94, miiData.length)).copy(fullMii, 0);
|
|
49
|
+
const newChecksum = calculateMiiChecksum(fullMii);
|
|
50
|
+
fullMii[94] = (newChecksum >> 8) & 0xFF;
|
|
51
|
+
fullMii[95] = newChecksum & 0xFF;
|
|
52
|
+
return fullMii;
|
|
53
|
+
}
|
|
54
|
+
function calcSeed(dump) {
|
|
55
|
+
const seed = Buffer.alloc(64);
|
|
56
|
+
dump.slice(0x029, 0x02B).copy(seed, 0x00);
|
|
57
|
+
seed.fill(0x00, 0x02, 0x10);
|
|
58
|
+
dump.slice(0x1D4, 0x1DC).copy(seed, 0x10);
|
|
59
|
+
dump.slice(0x1D4, 0x1DC).copy(seed, 0x18);
|
|
60
|
+
dump.slice(0x1E8, 0x208).copy(seed, 0x20);
|
|
61
|
+
return seed;
|
|
62
|
+
}
|
|
63
|
+
function prepareSeed(typeString, magicBytes, magicBytesSize, xorPad, baseSeed) {
|
|
64
|
+
const output = Buffer.alloc(480);
|
|
65
|
+
let offset = 0;
|
|
66
|
+
const typeStringEnd = typeString.indexOf(0);
|
|
67
|
+
const typeLen = typeStringEnd >= 0 ? typeStringEnd + 1 : 14;
|
|
68
|
+
typeString.slice(0, typeLen).copy(output, offset);
|
|
69
|
+
offset += typeLen;
|
|
70
|
+
const leadingSeedBytes = 16 - magicBytesSize;
|
|
71
|
+
baseSeed.slice(0, leadingSeedBytes).copy(output, offset);
|
|
72
|
+
offset += leadingSeedBytes;
|
|
73
|
+
magicBytes.slice(0, magicBytesSize).copy(output, offset);
|
|
74
|
+
offset += magicBytesSize;
|
|
75
|
+
baseSeed.slice(0x10, 0x20).copy(output, offset);
|
|
76
|
+
offset += 16;
|
|
77
|
+
for (let i = 0; i < 32; i++) {
|
|
78
|
+
output[offset + i] = baseSeed[0x20 + i] ^ xorPad[i];
|
|
79
|
+
}
|
|
80
|
+
offset += 32;
|
|
81
|
+
return output.slice(0, offset);
|
|
82
|
+
}
|
|
83
|
+
function drbgGenerateBytes(hmacKey, seed, outputSize) {
|
|
84
|
+
const result = Buffer.alloc(outputSize);
|
|
85
|
+
let offset = 0;
|
|
86
|
+
let iteration = 0;
|
|
87
|
+
while (offset < outputSize) {
|
|
88
|
+
const iterBuffer = Buffer.alloc(2 + seed.length);
|
|
89
|
+
iterBuffer[0] = (iteration >> 8) & 0xFF;
|
|
90
|
+
iterBuffer[1] = iteration & 0xFF;
|
|
91
|
+
seed.copy(iterBuffer, 2);
|
|
92
|
+
const hmac = crypto.createHmac('sha256', hmacKey);
|
|
93
|
+
hmac.update(iterBuffer);
|
|
94
|
+
const output = hmac.digest();
|
|
95
|
+
const toCopy = Math.min(32, outputSize - offset);
|
|
96
|
+
output.copy(result, offset, 0, toCopy);
|
|
97
|
+
offset += toCopy;
|
|
98
|
+
iteration++;
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function deriveKeys(typeString, magicBytes, magicBytesSize, xorPad, hmacKey, baseSeed) {
|
|
104
|
+
const preparedSeed = prepareSeed(typeString, magicBytes, magicBytesSize, xorPad, baseSeed);
|
|
105
|
+
const derived = drbgGenerateBytes(hmacKey, preparedSeed, 48);
|
|
106
|
+
return {
|
|
107
|
+
aesKey: derived.slice(0, 16),
|
|
108
|
+
aesIV: derived.slice(16, 32),
|
|
109
|
+
hmacKey: derived.slice(32, 48)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function tagToInternal(tag) {
|
|
114
|
+
const internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
|
|
115
|
+
tag.slice(0x008, 0x010).copy(internal, 0x000);
|
|
116
|
+
tag.slice(0x080, 0x0A0).copy(internal, 0x008);
|
|
117
|
+
tag.slice(0x010, 0x034).copy(internal, 0x028);
|
|
118
|
+
tag.slice(0x0A0, 0x208).copy(internal, 0x04C);
|
|
119
|
+
tag.slice(0x034, 0x054).copy(internal, 0x1B4);
|
|
120
|
+
tag.slice(0x000, 0x008).copy(internal, 0x1D4);
|
|
121
|
+
tag.slice(0x054, 0x080).copy(internal, 0x1DC);
|
|
122
|
+
return internal;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function internalToTag(internal) {
|
|
126
|
+
const tag = Buffer.alloc(NFC3D_AMIIBO_SIZE);
|
|
127
|
+
internal.slice(0x000, 0x008).copy(tag, 0x008);
|
|
128
|
+
internal.slice(0x008, 0x028).copy(tag, 0x080);
|
|
129
|
+
internal.slice(0x028, 0x04C).copy(tag, 0x010);
|
|
130
|
+
internal.slice(0x04C, 0x1B4).copy(tag, 0x0A0);
|
|
131
|
+
internal.slice(0x1B4, 0x1D4).copy(tag, 0x034);
|
|
132
|
+
internal.slice(0x1D4, 0x1DC).copy(tag, 0x000);
|
|
133
|
+
internal.slice(0x1DC, 0x208).copy(tag, 0x054);
|
|
134
|
+
return tag;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function decryptAmiibo(tag) {
|
|
138
|
+
const internal = tagToInternal(tag);
|
|
139
|
+
const seed = calcSeed(internal);
|
|
140
|
+
const dataKeys = deriveKeys(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
|
|
141
|
+
const tagKeys = deriveKeys(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
|
|
142
|
+
const plain = Buffer.alloc(NFC3D_AMIIBO_SIZE);
|
|
143
|
+
const cipher = crypto.createDecipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
|
|
144
|
+
cipher.setAutoPadding(false);
|
|
145
|
+
const decrypted = cipher.update(internal.slice(0x02C, 0x1B4));
|
|
146
|
+
decrypted.copy(plain, 0x02C);
|
|
147
|
+
internal.slice(0x000, 0x008).copy(plain, 0x000);
|
|
148
|
+
internal.slice(0x028, 0x02C).copy(plain, 0x028);
|
|
149
|
+
internal.slice(0x1D4, 0x208).copy(plain, 0x1D4);
|
|
150
|
+
const tagHmac = crypto.createHmac('sha256', tagKeys.hmacKey);
|
|
151
|
+
tagHmac.update(plain.slice(0x1D4, 0x208));
|
|
152
|
+
const computedTagHmac = tagHmac.digest();
|
|
153
|
+
computedTagHmac.copy(plain, 0x1B4);
|
|
154
|
+
const dataHmac = crypto.createHmac('sha256', dataKeys.hmacKey);
|
|
155
|
+
dataHmac.update(plain.slice(0x029, 0x208));
|
|
156
|
+
const computedDataHmac = dataHmac.digest();
|
|
157
|
+
computedDataHmac.copy(plain, 0x008);
|
|
158
|
+
return plain;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function encryptAmiibo(plain) {
|
|
162
|
+
const seed = calcSeed(plain);
|
|
163
|
+
const dataKeys = deriveKeys(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
|
|
164
|
+
const tagKeys = deriveKeys(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
|
|
165
|
+
const cipher_internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
|
|
166
|
+
const tagHmac = crypto.createHmac('sha256', tagKeys.hmacKey);
|
|
167
|
+
tagHmac.update(plain.slice(0x1D4, 0x208));
|
|
168
|
+
tagHmac.digest().copy(cipher_internal, 0x1B4);
|
|
169
|
+
const dataHmac = crypto.createHmac('sha256', dataKeys.hmacKey);
|
|
170
|
+
dataHmac.update(plain.slice(0x029, 0x1B4));
|
|
171
|
+
dataHmac.update(cipher_internal.slice(0x1B4, 0x1D4));
|
|
172
|
+
dataHmac.update(plain.slice(0x1D4, 0x208));
|
|
173
|
+
dataHmac.digest().copy(cipher_internal, 0x008);
|
|
174
|
+
const aesCipher = crypto.createCipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
|
|
175
|
+
aesCipher.setAutoPadding(false);
|
|
176
|
+
const encrypted = aesCipher.update(plain.slice(0x02C, 0x1B4));
|
|
177
|
+
encrypted.copy(cipher_internal, 0x02C);
|
|
178
|
+
plain.slice(0x000, 0x008).copy(cipher_internal, 0x000);
|
|
179
|
+
plain.slice(0x028, 0x02C).copy(cipher_internal, 0x028);
|
|
180
|
+
plain.slice(0x1D4, 0x208).copy(cipher_internal, 0x1D4);
|
|
181
|
+
return internalToTag(cipher_internal);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
//Extract Mii data from an Amiibo dump
|
|
185
|
+
function extractMiiFromAmiibo(amiiboDump) {
|
|
186
|
+
if (!Buffer.isBuffer(amiiboDump)) {
|
|
187
|
+
throw new Error('Amiibo dump must be a Buffer');
|
|
188
|
+
}
|
|
189
|
+
const size = amiiboDump.length;
|
|
190
|
+
if (size !== NFC3D_AMIIBO_SIZE && size !== NTAG215_SIZE && size !== NTAG215_SIZE_ALT) {
|
|
191
|
+
throw new Error(`Invalid Amiibo dump size: ${size} (expected ${NFC3D_AMIIBO_SIZE}, ${NTAG215_SIZE_ALT}, or ${NTAG215_SIZE})`);
|
|
192
|
+
}
|
|
193
|
+
const tag = amiiboDump.slice(0, NFC3D_AMIIBO_SIZE);
|
|
194
|
+
const decrypted = decryptAmiibo(tag);
|
|
195
|
+
|
|
196
|
+
// Extract only the first 92 bytes (the actual Mii data, without checksum)
|
|
197
|
+
const miiData = decrypted.slice(MII_OFFSET_DECRYPTED, MII_OFFSET_DECRYPTED + 92);
|
|
198
|
+
|
|
199
|
+
return Buffer.from(miiData);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
//Insert Mii data into an Amiibo dump
|
|
203
|
+
function insertMiiIntoAmiibo(amiiboDump, miiData) {
|
|
204
|
+
if (!Buffer.isBuffer(amiiboDump)) {
|
|
205
|
+
throw new Error('Amiibo dump must be a Buffer');
|
|
206
|
+
}
|
|
207
|
+
if (!Buffer.isBuffer(miiData)) {
|
|
208
|
+
throw new Error('Mii data must be a Buffer');
|
|
209
|
+
}
|
|
210
|
+
const size = amiiboDump.length;
|
|
211
|
+
if (size !== NFC3D_AMIIBO_SIZE && size !== NTAG215_SIZE && size !== NTAG215_SIZE_ALT) {
|
|
212
|
+
throw new Error(`Invalid Amiibo dump size: ${size}`);
|
|
213
|
+
}
|
|
214
|
+
if (miiData.length !== 92 && miiData.length !== MII_SIZE) {
|
|
215
|
+
throw new Error(`Mii data must be 92 or ${MII_SIZE} bytes, got ${miiData.length}`);
|
|
216
|
+
}
|
|
217
|
+
const tag = amiiboDump.slice(0, NFC3D_AMIIBO_SIZE);
|
|
218
|
+
const decrypted = decryptAmiibo(tag);
|
|
219
|
+
|
|
220
|
+
// Validate and fix Mii checksum, ensuring it's 96 bytes with correct checksum
|
|
221
|
+
const miiWithChecksum = validateAndFixMiiChecksum(miiData);
|
|
222
|
+
|
|
223
|
+
// Insert Mii data (96 bytes)
|
|
224
|
+
miiWithChecksum.copy(decrypted, MII_OFFSET_DECRYPTED);
|
|
225
|
+
|
|
226
|
+
const encrypted = encryptAmiibo(decrypted);
|
|
227
|
+
const result = Buffer.alloc(size);
|
|
228
|
+
encrypted.copy(result, 0);
|
|
229
|
+
if (size > NFC3D_AMIIBO_SIZE) {
|
|
230
|
+
amiiboDump.slice(NFC3D_AMIIBO_SIZE).copy(result, NFC3D_AMIIBO_SIZE);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = {
|
|
237
|
+
insertMiiIntoAmiibo,
|
|
238
|
+
extractMiiFromAmiibo
|
|
239
239
|
};
|