miijs 2.5.1 → 2.5.2

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/amiiboHandler.js CHANGED
@@ -1,64 +1,16 @@
1
- const { Buffer } = require('buffer');
2
1
 
2
+ // Takes Amiibo dumps that are either 532 bytes or 540 bytes, and manipulates a 96 byte (C/F)FSD Mii in/out of the dump from offset 0x4C.
3
+
4
+ const { Buffer } = require('buffer');
3
5
  const isBrowser = typeof window !== 'undefined' && typeof window.crypto !== 'undefined';
4
6
  const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
5
-
6
7
  const nodeCrypto = isNode ? require('crypto') : null;
7
8
  const subtleCrypto = (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle)
8
9
  ? globalThis.crypto.subtle
9
10
  : (nodeCrypto && nodeCrypto.webcrypto ? nodeCrypto.webcrypto.subtle : null);
10
11
 
11
- /*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.
12
- 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.*/
13
- const MASTER_KEY_BUFFER = Buffer.from('1D164B375B72A55728B91D64B6A3C205756E666978656420696E666F7300000EDB4B9E3F45278F397EFF9B4FB9930000044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E5450547667F752D2873A20017FEF85C0575904B6D6C6F636B656420736563726574000010FDC8A07694B89E4C47D37DE8CE5C74C1044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E545054766', 'hex');
14
-
15
- const DATA_HMAC_KEY = MASTER_KEY_BUFFER.slice(0, 16);
16
- const DATA_TYPE_STRING = MASTER_KEY_BUFFER.slice(16, 30);
17
- const DATA_MAGIC_BYTES_SIZE = MASTER_KEY_BUFFER[31];
18
- const DATA_MAGIC_BYTES = MASTER_KEY_BUFFER.slice(32, 48);
19
- const DATA_XOR_PAD = MASTER_KEY_BUFFER.slice(48, 80);
20
-
21
- const TAG_HMAC_KEY = MASTER_KEY_BUFFER.slice(80, 96);
22
- const TAG_TYPE_STRING = MASTER_KEY_BUFFER.slice(96, 110);
23
- const TAG_MAGIC_BYTES_SIZE = MASTER_KEY_BUFFER[111];
24
- const TAG_MAGIC_BYTES = MASTER_KEY_BUFFER.slice(112, 128);
25
- const TAG_XOR_PAD = MASTER_KEY_BUFFER.slice(128, 160);
26
-
27
12
  const NFC3D_AMIIBO_SIZE = 520;
28
- const NTAG215_SIZE = 540;
29
- const NTAG215_SIZE_ALT = 532;
30
-
31
- const MII_OFFSET_DECRYPTED = 0x4C;
32
- const MII_SIZE = 96;
33
-
34
- //Calculate CRC16 checksum for Mii data (for Amiibo format)
35
- function calculateMiiChecksum(data) {
36
- const checksumData = data.slice(0, 94);
37
- let crc = 0;
38
- for (let byteIndex = 0; byteIndex < checksumData.length; byteIndex++) {
39
- for (let bitIndex = 7; bitIndex >= 0; bitIndex--) {
40
- crc = (((crc << 1) | ((checksumData[byteIndex] >> bitIndex) & 0x1)) ^
41
- (((crc & 0x8000) !== 0) ? 0x1021 : 0)) & 0xFFFF;
42
- }
43
- }
44
- for (let counter = 16; counter > 0; counter--) {
45
- crc = ((crc << 1) ^ (((crc & 0x8000) !== 0) ? 0x1021 : 0)) & 0xFFFF;
46
- }
47
- return crc & 0xFFFF;
48
- }
49
13
 
50
- //Validate and fix Mii checksum - for 96-byte Amiibo format
51
- function validateAndFixMiiChecksum(miiData) {
52
- if (miiData.length !== 92 && miiData.length !== MII_SIZE) {
53
- throw new Error(`Invalid Mii data size: expected 92 or ${MII_SIZE} bytes, got ${miiData.length}`);
54
- }
55
- const fullMii = Buffer.alloc(MII_SIZE);
56
- miiData.slice(0, Math.min(94, miiData.length)).copy(fullMii, 0);
57
- const newChecksum = calculateMiiChecksum(fullMii);
58
- fullMii[94] = (newChecksum >> 8) & 0xFF;
59
- fullMii[95] = newChecksum & 0xFF;
60
- return fullMii;
61
- }
62
14
  function calcSeed(dump) {
63
15
  const seed = Buffer.alloc(64);
64
16
  dump.slice(0x029, 0x02B).copy(seed, 0x00);
@@ -107,25 +59,6 @@ function drbgGenerateBytes(hmacKey, seed, outputSize) {
107
59
  }
108
60
  return result;
109
61
  }
110
-
111
- async function drbgGenerateBytesAsync(hmacKey, seed, outputSize) {
112
- const result = Buffer.alloc(outputSize);
113
- let offset = 0;
114
- let iteration = 0;
115
- while (offset < outputSize) {
116
- const iterBuffer = Buffer.alloc(2 + seed.length);
117
- iterBuffer[0] = (iteration >> 8) & 0xFF;
118
- iterBuffer[1] = iteration & 0xFF;
119
- seed.copy(iterBuffer, 2);
120
- const output = await hmacSha256Async(hmacKey, iterBuffer);
121
- const toCopy = Math.min(32, outputSize - offset);
122
- output.copy(result, offset, 0, toCopy);
123
- offset += toCopy;
124
- iteration++;
125
- }
126
- return result;
127
- }
128
-
129
62
  function deriveKeys(typeString, magicBytes, magicBytesSize, xorPad, hmacKey, baseSeed) {
130
63
  const preparedSeed = prepareSeed(typeString, magicBytes, magicBytesSize, xorPad, baseSeed);
131
64
  const derived = drbgGenerateBytes(hmacKey, preparedSeed, 48);
@@ -136,66 +69,6 @@ function deriveKeys(typeString, magicBytes, magicBytesSize, xorPad, hmacKey, bas
136
69
  };
137
70
  }
138
71
 
139
- async function deriveKeysAsync(typeString, magicBytes, magicBytesSize, xorPad, hmacKey, baseSeed) {
140
- const preparedSeed = prepareSeed(typeString, magicBytes, magicBytesSize, xorPad, baseSeed);
141
- const derived = await drbgGenerateBytesAsync(hmacKey, preparedSeed, 48);
142
- return {
143
- aesKey: derived.slice(0, 16),
144
- aesIV: derived.slice(16, 32),
145
- hmacKey: derived.slice(32, 48)
146
- };
147
- }
148
-
149
- function ensureBuffer(input, name) {
150
- if (Buffer.isBuffer(input)) {
151
- return input;
152
- }
153
- if (input instanceof Uint8Array) {
154
- return Buffer.from(input);
155
- }
156
- if (input instanceof ArrayBuffer) {
157
- return Buffer.from(new Uint8Array(input));
158
- }
159
- throw new Error(`${name} must be a Buffer or Uint8Array`);
160
- }
161
-
162
- async function hmacSha256Async(key, data) {
163
- if (!subtleCrypto) {
164
- throw new Error('Web Crypto API is not available');
165
- }
166
- const cryptoKey = await subtleCrypto.importKey(
167
- 'raw',
168
- ensureBuffer(key, 'HMAC key'),
169
- { name: 'HMAC', hash: 'SHA-256' },
170
- false,
171
- ['sign']
172
- );
173
- const signature = await subtleCrypto.sign('HMAC', cryptoKey, ensureBuffer(data, 'HMAC data'));
174
- return Buffer.from(new Uint8Array(signature));
175
- }
176
-
177
- async function aesCtrCryptAsync(key, iv, data, encrypt) {
178
- if (!subtleCrypto) {
179
- throw new Error('Web Crypto API is not available');
180
- }
181
- const cryptoKey = await subtleCrypto.importKey(
182
- 'raw',
183
- ensureBuffer(key, 'AES key'),
184
- { name: 'AES-CTR' },
185
- false,
186
- encrypt ? ['encrypt'] : ['decrypt']
187
- );
188
- const algorithm = {
189
- name: 'AES-CTR',
190
- counter: ensureBuffer(iv, 'AES IV'),
191
- length: 128,
192
- };
193
- const result = encrypt
194
- ? await subtleCrypto.encrypt(algorithm, cryptoKey, ensureBuffer(data, 'AES data'))
195
- : await subtleCrypto.decrypt(algorithm, cryptoKey, ensureBuffer(data, 'AES data'));
196
- return Buffer.from(new Uint8Array(result));
197
- }
198
-
199
72
  function tagToInternal(tag) {
200
73
  const internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
201
74
  tag.slice(0x008, 0x010).copy(internal, 0x000);
@@ -207,7 +80,6 @@ function tagToInternal(tag) {
207
80
  tag.slice(0x054, 0x080).copy(internal, 0x1DC);
208
81
  return internal;
209
82
  }
210
-
211
83
  function internalToTag(internal) {
212
84
  const tag = Buffer.alloc(NFC3D_AMIIBO_SIZE);
213
85
  internal.slice(0x000, 0x008).copy(tag, 0x008);
@@ -223,8 +95,8 @@ function internalToTag(internal) {
223
95
  function decryptAmiibo(tag) {
224
96
  const internal = tagToInternal(tag);
225
97
  const seed = calcSeed(internal);
226
- const dataKeys = deriveKeys(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
227
- const tagKeys = deriveKeys(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
98
+ const dataKeys = deriveKeys(Buffer.from("756E666978656420696E666F7300",'hex'), Buffer.from("DB4B9E3F45278F397EFF9B4FB9930000",'hex'), 14, Buffer.from("044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E545054766",'hex'), Buffer.from("1D164B375B72A55728B91D64B6A3C205",'hex'), seed);
99
+ const tagKeys = deriveKeys(Buffer.from("6C6F636B65642073656372657400",'hex'), Buffer.from("FDC8A07694B89E4C47D37DE8CE5C74C1",'hex'), 16, Buffer.from("044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E545054766",'hex'), Buffer.from("7F752D2873A20017FEF85C0575904B6D",'hex'), seed);
228
100
  const plain = Buffer.alloc(NFC3D_AMIIBO_SIZE);
229
101
  const cipher = nodeCrypto.createDecipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
230
102
  cipher.setAutoPadding(false);
@@ -243,29 +115,10 @@ function decryptAmiibo(tag) {
243
115
  computedDataHmac.copy(plain, 0x008);
244
116
  return plain;
245
117
  }
246
-
247
- async function decryptAmiiboAsync(tag) {
248
- const internal = tagToInternal(tag);
249
- const seed = calcSeed(internal);
250
- const dataKeys = await deriveKeysAsync(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
251
- const tagKeys = await deriveKeysAsync(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
252
- const plain = Buffer.alloc(NFC3D_AMIIBO_SIZE);
253
- const decrypted = await aesCtrCryptAsync(dataKeys.aesKey, dataKeys.aesIV, internal.slice(0x02C, 0x1B4), false);
254
- decrypted.copy(plain, 0x02C);
255
- internal.slice(0x000, 0x008).copy(plain, 0x000);
256
- internal.slice(0x028, 0x02C).copy(plain, 0x028);
257
- internal.slice(0x1D4, 0x208).copy(plain, 0x1D4);
258
- const tagHmac = await hmacSha256Async(tagKeys.hmacKey, plain.slice(0x1D4, 0x208));
259
- tagHmac.copy(plain, 0x1B4);
260
- const dataHmac = await hmacSha256Async(dataKeys.hmacKey, plain.slice(0x029, 0x208));
261
- dataHmac.copy(plain, 0x008);
262
- return plain;
263
- }
264
-
265
118
  function encryptAmiibo(plain) {
266
119
  const seed = calcSeed(plain);
267
- const dataKeys = deriveKeys(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
268
- const tagKeys = deriveKeys(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
120
+ const dataKeys = deriveKeys(Buffer.from("756E666978656420696E666F7300",'hex'), Buffer.from("DB4B9E3F45278F397EFF9B4FB9930000",'hex'), 14, Buffer.from("044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E545054766",'hex'), Buffer.from("1D164B375B72A55728B91D64B6A3C205",'hex'), seed);
121
+ const tagKeys = deriveKeys(Buffer.from("6C6F636B65642073656372657400",'hex'), Buffer.from("FDC8A07694B89E4C47D37DE8CE5C74C1",'hex'), 16, Buffer.from("044917DC76B49640D6F83939960FAED4EF392FAAB21428AA21FB54E545054766",'hex'), Buffer.from("7F752D2873A20017FEF85C0575904B6D",'hex'), seed);
269
122
  const cipher_internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
270
123
  const tagHmac = nodeCrypto.createHmac('sha256', tagKeys.hmacKey);
271
124
  tagHmac.update(plain.slice(0x1D4, 0x208));
@@ -285,90 +138,19 @@ function encryptAmiibo(plain) {
285
138
  return internalToTag(cipher_internal);
286
139
  }
287
140
 
288
- async function encryptAmiiboAsync(plain) {
289
- const seed = calcSeed(plain);
290
- const dataKeys = await deriveKeysAsync(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
291
- const tagKeys = await deriveKeysAsync(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
292
- const cipher_internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
293
- const tagHmac = await hmacSha256Async(tagKeys.hmacKey, plain.slice(0x1D4, 0x208));
294
- tagHmac.copy(cipher_internal, 0x1B4);
295
- const dataHmac = await hmacSha256Async(dataKeys.hmacKey, Buffer.concat([
296
- plain.slice(0x029, 0x1B4),
297
- cipher_internal.slice(0x1B4, 0x1D4),
298
- plain.slice(0x1D4, 0x208)
299
- ]));
300
- dataHmac.copy(cipher_internal, 0x008);
301
- const encrypted = await aesCtrCryptAsync(dataKeys.aesKey, dataKeys.aesIV, plain.slice(0x02C, 0x1B4), true);
302
- encrypted.copy(cipher_internal, 0x02C);
303
- plain.slice(0x000, 0x008).copy(cipher_internal, 0x000);
304
- plain.slice(0x028, 0x02C).copy(cipher_internal, 0x028);
305
- plain.slice(0x1D4, 0x208).copy(cipher_internal, 0x1D4);
306
- return internalToTag(cipher_internal);
307
- }
308
-
309
141
  //Extract Mii data from an Amiibo dump
310
- function extractMiiFromAmiibo(amiiboDump) {
311
- const dump = ensureBuffer(amiiboDump, 'Amiibo dump');
312
- const size = dump.length;
313
- if (size !== NFC3D_AMIIBO_SIZE && size !== NTAG215_SIZE && size !== NTAG215_SIZE_ALT) {
314
- throw new Error(`Invalid Amiibo dump size: ${size} (expected ${NFC3D_AMIIBO_SIZE}, ${NTAG215_SIZE_ALT}, or ${NTAG215_SIZE})`);
315
- }
142
+ function extractMiiFromAmiibo(dump) {
316
143
  const tag = dump.slice(0, NFC3D_AMIIBO_SIZE);
317
- if (!isNode) {
318
- return extractMiiFromAmiiboAsync(tag, dump.length);
319
- }
320
144
  const decrypted = decryptAmiibo(tag);
321
-
322
- // Extract only the first 92 bytes (the actual Mii data, without checksum)
323
- const miiData = decrypted.slice(MII_OFFSET_DECRYPTED, MII_OFFSET_DECRYPTED + 92);
324
-
325
- return Buffer.from(miiData);
326
- }
327
-
328
- async function extractMiiFromAmiiboAsync(tag, dumpSize) {
329
- const decrypted = await decryptAmiiboAsync(tag);
330
- const miiData = decrypted.slice(MII_OFFSET_DECRYPTED, MII_OFFSET_DECRYPTED + 92);
145
+ const miiData = decrypted.slice(76, 172);// Extract the 96 Bytes (C/F)FSD Mii
331
146
  return Buffer.from(miiData);
332
147
  }
333
148
 
334
149
  //Insert Mii data into an Amiibo dump
335
- function insertMiiIntoAmiibo(amiiboDump, miiData) {
336
- const dump = ensureBuffer(amiiboDump, 'Amiibo dump');
337
- const miiBuf = ensureBuffer(miiData, 'Mii data');
338
- const size = dump.length;
339
- if (size !== NFC3D_AMIIBO_SIZE && size !== NTAG215_SIZE && size !== NTAG215_SIZE_ALT) {
340
- throw new Error(`Invalid Amiibo dump size: ${size}`);
341
- }
342
- if (miiBuf.length !== 92 && miiBuf.length !== MII_SIZE) {
343
- throw new Error(`Mii data must be 92 or ${MII_SIZE} bytes, got ${miiBuf.length}`);
344
- }
345
- const tag = dump.slice(0, NFC3D_AMIIBO_SIZE);
346
- if (!isNode) {
347
- return insertMiiIntoAmiiboAsync(tag, dump, miiBuf);
348
- }
349
- const decrypted = decryptAmiibo(tag);
350
-
351
- // Validate and fix Mii checksum, ensuring it's 96 bytes with correct checksum
352
- const miiWithChecksum = validateAndFixMiiChecksum(miiBuf);
353
-
354
- // Insert Mii data (96 bytes)
355
- miiWithChecksum.copy(decrypted, MII_OFFSET_DECRYPTED);
356
-
357
- const encrypted = encryptAmiibo(decrypted);
358
- const result = Buffer.alloc(size);
359
- encrypted.copy(result, 0);
360
- if (size > NFC3D_AMIIBO_SIZE) {
361
- dump.slice(NFC3D_AMIIBO_SIZE).copy(result, NFC3D_AMIIBO_SIZE);
362
- }
363
-
364
- return result;
365
- }
366
-
367
- async function insertMiiIntoAmiiboAsync(tag, dump, miiBuf) {
368
- const decrypted = await decryptAmiiboAsync(tag);
369
- const miiWithChecksum = validateAndFixMiiChecksum(miiBuf);
370
- miiWithChecksum.copy(decrypted, MII_OFFSET_DECRYPTED);
371
- const encrypted = await encryptAmiiboAsync(decrypted);
150
+ function insertMiiIntoAmiibo(dump, miiWithChecksum) {
151
+ const decrypted = decryptAmiibo(dump.slice(0, NFC3D_AMIIBO_SIZE));//Decrypt the Amiibo
152
+ miiWithChecksum.copy(decrypted, 76);//Insert the Mii into Amiibo
153
+ const encrypted = encryptAmiibo(decrypted);//Reencrypt the Amiibo
372
154
  const result = Buffer.alloc(dump.length);
373
155
  encrypted.copy(result, 0);
374
156
  if (dump.length > NFC3D_AMIIBO_SIZE) {