miijs 2.4.2 → 2.5.1

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/README.md CHANGED
@@ -47,9 +47,10 @@ MiiJS is a complete and comprehensive Mii library for reading, converting, modif
47
47
  - **`miiHeightToMeasurements(miiHeight)`** - Converts Mii height value (0-127) to real-world feet and inches. Returns `{feet, inches, totalInches, centimeters}`.
48
48
  - **`inchesToMiiHeight(totalInches)`** - Converts real-world height in inches to Mii height value (0-127).
49
49
  - **`centimetersToMiiHeight(totalCentimeters)`** - Converts real-world height in centimeters to Mii height value (0-127).
50
- - **`miiHeightWeightToRealWeight(miiWeight)`** - Converts Mii weight value (0-127) to real-world weight values. Returns `{pounds, kilograms}`.
50
+ - **`miiWeightToRealWeight(miiWeight)`** - Converts Mii weight value (0-127) to real-world weight values. Returns `{pounds, kilograms}`.
51
51
  - **`imperialHeightWeightToMiiWeight(heightInches, weightLbs)`** - Converts real-world imperial measurements to Mii weight values.
52
52
  - **`metricHeightWeightToMiiWeight(heightCentimeters, weightKilograms)`** - Converts real-world metric measurements to Mii weight values.
53
+ - **`miiIdToTimestamp(miiId, consoleMiiIdIsFrom)`** - Converts the Mii ID into a JS Date/Timestamp.
53
54
 
54
55
  ### Other Functions
55
56
  - **`makeChild(miiJson1, miiJson2, options?)`** - Returns an array of 6 different Mii JSONs, which represent a child generated from the two parent Miis passed to the function at different stages of life. This is somewhat experimental, but should be accurate to my current knowledge. You can pass any or none of { name: "The Name", creatorName: "The Name", favoriteColor: 0-11, gender: 0-1/\*0:Male, 1:Female\*/ }
@@ -62,12 +63,11 @@ MiiJS is a complete and comprehensive Mii library for reading, converting, modif
62
63
  ```javascript
63
64
  const miijs = require('miijs');
64
65
 
65
- // Read from file path
66
+ // Read from file path for image, or buffer of the data - encrypted or decrypted
66
67
  const miiJson = await miijs.read3DSQR('./example3DSQR.jpg');
67
68
  console.log('Mii Name:', miiJson.meta.name);
68
- console.log('Favorite Color:', miiJson.general.favoriteColor);
69
69
 
70
- // Or get just the decrypted binary data
70
+ // Get the decrypted binary data
71
71
  const decryptedBin = await miijs.read3DSQR('./example3DSQR.jpg', true);
72
72
  console.log('Decrypted binary length:', decryptedBin.length);
73
73
  ```
@@ -79,7 +79,6 @@ const miijs = require('miijs');
79
79
  // Read from file path
80
80
  const miiJson = await miijs.readWiiBin('./exampleWii.bin');
81
81
  console.log('Mii Name:', miiJson.meta.name);
82
- console.log('Gender:', miiJson.general.gender === 0 ? 'Male' : 'Female');
83
82
 
84
83
  // Or pass binary data directly
85
84
  const fs = require('fs');
@@ -94,13 +93,10 @@ const miijs = require('miijs');
94
93
  // First, read or create a Mii JSON
95
94
  const miiJson = await miijs.read3DSQR('./example3DSQR.jpg');
96
95
 
97
- // Write QR code with Studio rendering (no FFLResHigh.dat needed)
96
+ // Write QR code. If FFLResHigh.dat is in the same directory, will be used automatically. You can pass a buffer containing FFLResHigh.dat as a fourth parameter. Will use Studio rendering instead of local without FFLResHigh.dat.
98
97
  await miijs.write3DSQR(miiJson, './output_qr.jpg');
99
-
100
- // Or with local rendering (requires FFLResHigh.dat in project root or passed as buffer)
101
- const fs = require('fs');
102
- const fflRes = fs.readFileSync('./FFLResHigh.dat');
103
- await miijs.write3DSQR(miiJson, './output_qr_local.jpg', fflRes);
98
+ //The third parameter asks MiiJS to return an encrypted 3DS data instead
99
+ const encryptedData=await miijs.write3DSQR(miiJson,'',true);
104
100
  ```
105
101
 
106
102
  ## Writing a Wii Mii Binary
@@ -108,11 +104,8 @@ await miijs.write3DSQR(miiJson, './output_qr_local.jpg', fflRes);
108
104
  const miijs = require('miijs');
109
105
  const fs = require('fs');
110
106
 
111
- // Read a Mii (from any format)
112
- const miiJson = await miijs.read3DSQR('./example3DSQR.jpg');
113
-
114
- // Convert to Wii format first if needed
115
- const wiiMii = miijs.convertMii(miiJson, 'wii');
107
+ // Read a Mii
108
+ const miiJson = await miijs.readWiiBin('./exampleWii.bin');
116
109
 
117
110
  // Write to file
118
111
  await miijs.writeWiiBin(wiiMii, './output_wii.bin');
@@ -127,16 +120,16 @@ fs.writeFileSync('./manual_write.bin', buffer);
127
120
  const miijs = require('miijs');
128
121
 
129
122
  // Read a 3DS Mii
130
- const ds3Mii = await miijs.read3DSQR('./example3DSQR.jpg');
123
+ const mii3DS = await miijs.read3DSQR('./example3DSQR.jpg');
131
124
 
132
125
  // Convert to Wii format
133
- const wiiMii = miijs.convertMii(ds3Mii, 'wii');
126
+ const miiWii = miijs.convertMii(mii3DS, 'wii');
134
127
 
135
128
  // Convert back to 3DS
136
- const backTo3DS = miijs.convertMii(wiiMii, '3ds');
129
+ const backTo3DS = miijs.convertMii(miiWii, '3ds');
137
130
 
138
131
  // Auto-detect and convert to opposite
139
- const autoConverted = miijs.convertMii(ds3Mii);
132
+ const autoConverted = miijs.convertMii(mii3DS);
140
133
  ```
141
134
 
142
135
  ## Converting to/from Studio Format
@@ -151,7 +144,6 @@ console.log('Studio URL:', `https://studio.mii.nintendo.com/miis/image.png?data=
151
144
  // Convert Studio format back to JSON
152
145
  const studioData = '000d142a303f434b717a7b84939ba6b2bbbec5cbc9d0e2ea...';
153
146
  const miiFromStudio = miijs.convertStudioToMii(studioData);
154
- console.log('Converted Mii:', miiFromStudio.meta.name);
155
147
  ```
156
148
 
157
149
  ## Rendering Miis
@@ -162,16 +154,15 @@ const fs = require('fs');
162
154
  // Read a Mii
163
155
  const miiJson = await miijs.read3DSQR('./example3DSQR.jpg');
164
156
 
165
- // Render using Studio API (simple, no setup needed)
157
+ // Render using Studio API (simple, no setup needed, requires internet access)
166
158
  const studioPng = await miijs.renderMiiWithStudio(miiJson);
167
159
  fs.writeFileSync('./mii_studio_render.png', studioPng);
168
160
 
169
161
  // Render locally with full body (requires FFLResHigh.dat)
162
+ // FFLResHigh.dat can be placed in the project directory to automatically use
170
163
  const fflRes = fs.readFileSync('./FFLResHigh.dat');
171
164
  const localPng = await miijs.renderMii(miiJson, fflRes);
172
165
  fs.writeFileSync('./mii_local_render.png', localPng);
173
-
174
- // Shirt color comes from miiJson.general.favoriteColor
175
166
  ```
176
167
 
177
168
  ## Working with Amiibos
@@ -183,13 +174,11 @@ const fs = require('fs');
183
174
  const amiiboDump = fs.readFileSync('./exampleAmiiboDump.bin');
184
175
 
185
176
  // Extract the Mii from the Amiibo (returns 92 bytes decrypted)
186
- const miiData = miijs.extractMiiFromAmiibo(amiiboDump);
187
-
177
+ let miiData = miijs.extractMiiFromAmiibo(amiiboDump);
188
178
  // Convert the raw Mii data to readable JSON
189
- // (miiData is already decrypted 3DS format)
190
- const miiJson = miijs.decode3DSMii(miiData); // Note: decode3DSMii not exported, use read3DSQR workflow
179
+ const miiJson = miijs.read3DSQR(miiData);
191
180
 
192
- // Better workflow: Read from QR, get decrypted data, insert into Amiibo
181
+ // Read from QR, get decrypted data, insert into Amiibo
193
182
  const qrMiiJson = await miijs.read3DSQR('./example3DSQR.jpg');
194
183
  const decryptedMiiData = await miijs.read3DSQR('./example3DSQR.jpg', true);
195
184
 
@@ -224,23 +213,28 @@ Object.entries(fullInstructions).forEach(([field, instruction]) => {
224
213
  ```javascript
225
214
  const miijs = require('miijs');
226
215
 
227
- // Convert Mii height (0-127) to feet/inches
228
- const heightInfo = miijs.miiHeightToFeetInches(64); // midpoint value
229
- console.log(`Height: ${heightInfo.feet}'${heightInfo.inches}" (${heightInfo.totalInches} inches)`);
216
+ // Convert Mii height (0-127) to feet/inches and centimeters
217
+ const heightInfo = miijs.miiHeightToMeasurements(64); // midpoint value
218
+ console.log(`Height: ${heightInfo.feet}'${heightInfo.inches}" (${heightInfo.centimeters} cm)`);
230
219
 
231
220
  // Convert real height to Mii value
232
- const miiHeightValue = miijs.inchesToMiiHeight(72); // 6'0"
221
+ const miiHeightValue = miijs.inchesToMiiHeight(72);
233
222
  console.log('Mii height value for 6\'0":', miiHeightValue);
234
223
 
235
- // EXPERIMENTAL: Convert real weight to Mii weight
236
- const heightInches = 69; // 5'9"
237
- const weightLbs = 160;
238
- const miiWeightValue = miijs.heightWeightToMiiWeight(heightInches, weightLbs);
224
+ // Convert Mii weight to real weight (requires height)
225
+ const miiHeight = 64;
226
+ const miiWeight = 64;
227
+ const weightInfo = miijs.miiWeightToRealWeight(miiHeight, miiWeight);
228
+ console.log(`Weight: ${weightInfo.pounds.toFixed(1)} lbs (${weightInfo.kilograms} kg)`);
229
+
230
+ // Convert real weight to Mii weight value
231
+ const heightInches=70;
232
+ const weightLbs = 150;
233
+ const miiWeightValue = miijs.imperialHeightWeightToMiiWeight(heightInches, weightLbs);
239
234
  console.log('Mii weight value:', miiWeightValue);
240
235
 
241
- // EXPERIMENTAL: Convert Mii weight to real weight
242
- const weightInfo = miijs.miiWeightToRealWeight(heightInches, 64);
243
- console.log(`Weight: ${weightInfo.pounds.toFixed(1)} lbs, BMI: ${weightInfo.bmi.toFixed(1)}`);
236
+ // Or use metric
237
+ const miiWeightMetric = miijs.metricHeightWeightToMiiWeight(175, 72.5);
244
238
  ```
245
239
 
246
240
  ## Creating and Modifying a Mii
@@ -251,10 +245,10 @@ const miijs = require('miijs');
251
245
  const miiJson = await miijs.read3DSQR('./example3DSQR.jpg');
252
246
 
253
247
  // Modify properties
254
- miiJson.meta.name = 'Custom Name';
255
- miiJson.general.favoriteColor = 5; // Blue
256
- miiJson.hair.color = 0; // Black
257
- miiJson.eyes.color = 2; // Brown
248
+ miiJson.meta.name = 'CustomName';
249
+ miiJson.general.favoriteColor = 5;
250
+ miiJson.hair.color = 0;
251
+ miiJson.eyes.color = 2;
258
252
 
259
253
  // Make it a Special Mii (3DS only)
260
254
  miiJson.meta.type = 'Special';
@@ -287,6 +281,19 @@ for(var i=0;i<child.length;i++){
287
281
  }
288
282
  ```
289
283
 
284
+ ## Getting the Time the Mii Was Created From the Mii ID
285
+ ```javascript
286
+ const miijs=require('miijs');
287
+
288
+ //Read miis into JSON
289
+ const mii3DS=await miijs.read3DSQR('./example_mii.jpg');
290
+ const miiWii=await miijs.readWiiBin('./example_mii.bin');
291
+
292
+ //Same process for both, returns a JS Date/Timestamp
293
+ console.log(miijs.miiIdToTimestamp(mii3DS.meta.miiId,mii3DS.console));
294
+ console.log(miijs.miiIdToTimestamp(miiWii.meta.miiId,miiWii.console));
295
+ ```
296
+
290
297
  <hr>
291
298
 
292
299
  ## Special Miis
package/amiiboHandler.js CHANGED
@@ -1,4 +1,12 @@
1
- const crypto = require('crypto');
1
+ const { Buffer } = require('buffer');
2
+
3
+ const isBrowser = typeof window !== 'undefined' && typeof window.crypto !== 'undefined';
4
+ const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
5
+
6
+ const nodeCrypto = isNode ? require('crypto') : null;
7
+ const subtleCrypto = (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle)
8
+ ? globalThis.crypto.subtle
9
+ : (nodeCrypto && nodeCrypto.webcrypto ? nodeCrypto.webcrypto.subtle : null);
2
10
 
3
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.
4
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.*/
@@ -89,7 +97,7 @@ function drbgGenerateBytes(hmacKey, seed, outputSize) {
89
97
  iterBuffer[0] = (iteration >> 8) & 0xFF;
90
98
  iterBuffer[1] = iteration & 0xFF;
91
99
  seed.copy(iterBuffer, 2);
92
- const hmac = crypto.createHmac('sha256', hmacKey);
100
+ const hmac = nodeCrypto.createHmac('sha256', hmacKey);
93
101
  hmac.update(iterBuffer);
94
102
  const output = hmac.digest();
95
103
  const toCopy = Math.min(32, outputSize - offset);
@@ -100,6 +108,24 @@ function drbgGenerateBytes(hmacKey, seed, outputSize) {
100
108
  return result;
101
109
  }
102
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
+
103
129
  function deriveKeys(typeString, magicBytes, magicBytesSize, xorPad, hmacKey, baseSeed) {
104
130
  const preparedSeed = prepareSeed(typeString, magicBytes, magicBytesSize, xorPad, baseSeed);
105
131
  const derived = drbgGenerateBytes(hmacKey, preparedSeed, 48);
@@ -110,6 +136,66 @@ function deriveKeys(typeString, magicBytes, magicBytesSize, xorPad, hmacKey, bas
110
136
  };
111
137
  }
112
138
 
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
+
113
199
  function tagToInternal(tag) {
114
200
  const internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
115
201
  tag.slice(0x008, 0x010).copy(internal, 0x000);
@@ -140,38 +226,56 @@ function decryptAmiibo(tag) {
140
226
  const dataKeys = deriveKeys(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
141
227
  const tagKeys = deriveKeys(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
142
228
  const plain = Buffer.alloc(NFC3D_AMIIBO_SIZE);
143
- const cipher = crypto.createDecipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
229
+ const cipher = nodeCrypto.createDecipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
144
230
  cipher.setAutoPadding(false);
145
231
  const decrypted = cipher.update(internal.slice(0x02C, 0x1B4));
146
232
  decrypted.copy(plain, 0x02C);
147
233
  internal.slice(0x000, 0x008).copy(plain, 0x000);
148
234
  internal.slice(0x028, 0x02C).copy(plain, 0x028);
149
235
  internal.slice(0x1D4, 0x208).copy(plain, 0x1D4);
150
- const tagHmac = crypto.createHmac('sha256', tagKeys.hmacKey);
236
+ const tagHmac = nodeCrypto.createHmac('sha256', tagKeys.hmacKey);
151
237
  tagHmac.update(plain.slice(0x1D4, 0x208));
152
238
  const computedTagHmac = tagHmac.digest();
153
239
  computedTagHmac.copy(plain, 0x1B4);
154
- const dataHmac = crypto.createHmac('sha256', dataKeys.hmacKey);
240
+ const dataHmac = nodeCrypto.createHmac('sha256', dataKeys.hmacKey);
155
241
  dataHmac.update(plain.slice(0x029, 0x208));
156
242
  const computedDataHmac = dataHmac.digest();
157
243
  computedDataHmac.copy(plain, 0x008);
158
244
  return plain;
159
245
  }
160
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
+
161
265
  function encryptAmiibo(plain) {
162
266
  const seed = calcSeed(plain);
163
267
  const dataKeys = deriveKeys(DATA_TYPE_STRING, DATA_MAGIC_BYTES, DATA_MAGIC_BYTES_SIZE, DATA_XOR_PAD, DATA_HMAC_KEY, seed);
164
268
  const tagKeys = deriveKeys(TAG_TYPE_STRING, TAG_MAGIC_BYTES, TAG_MAGIC_BYTES_SIZE, TAG_XOR_PAD, TAG_HMAC_KEY, seed);
165
269
  const cipher_internal = Buffer.alloc(NFC3D_AMIIBO_SIZE);
166
- const tagHmac = crypto.createHmac('sha256', tagKeys.hmacKey);
270
+ const tagHmac = nodeCrypto.createHmac('sha256', tagKeys.hmacKey);
167
271
  tagHmac.update(plain.slice(0x1D4, 0x208));
168
272
  tagHmac.digest().copy(cipher_internal, 0x1B4);
169
- const dataHmac = crypto.createHmac('sha256', dataKeys.hmacKey);
273
+ const dataHmac = nodeCrypto.createHmac('sha256', dataKeys.hmacKey);
170
274
  dataHmac.update(plain.slice(0x029, 0x1B4));
171
275
  dataHmac.update(cipher_internal.slice(0x1B4, 0x1D4));
172
276
  dataHmac.update(plain.slice(0x1D4, 0x208));
173
277
  dataHmac.digest().copy(cipher_internal, 0x008);
174
- const aesCipher = crypto.createCipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
278
+ const aesCipher = nodeCrypto.createCipheriv('aes-128-ctr', dataKeys.aesKey, dataKeys.aesIV);
175
279
  aesCipher.setAutoPadding(false);
176
280
  const encrypted = aesCipher.update(plain.slice(0x02C, 0x1B4));
177
281
  encrypted.copy(cipher_internal, 0x02C);
@@ -181,16 +285,38 @@ function encryptAmiibo(plain) {
181
285
  return internalToTag(cipher_internal);
182
286
  }
183
287
 
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
+
184
309
  //Extract Mii data from an Amiibo dump
185
310
  function extractMiiFromAmiibo(amiiboDump) {
186
- if (!Buffer.isBuffer(amiiboDump)) {
187
- throw new Error('Amiibo dump must be a Buffer');
188
- }
189
- const size = amiiboDump.length;
311
+ const dump = ensureBuffer(amiiboDump, 'Amiibo dump');
312
+ const size = dump.length;
190
313
  if (size !== NFC3D_AMIIBO_SIZE && size !== NTAG215_SIZE && size !== NTAG215_SIZE_ALT) {
191
314
  throw new Error(`Invalid Amiibo dump size: ${size} (expected ${NFC3D_AMIIBO_SIZE}, ${NTAG215_SIZE_ALT}, or ${NTAG215_SIZE})`);
192
315
  }
193
- const tag = amiiboDump.slice(0, NFC3D_AMIIBO_SIZE);
316
+ const tag = dump.slice(0, NFC3D_AMIIBO_SIZE);
317
+ if (!isNode) {
318
+ return extractMiiFromAmiiboAsync(tag, dump.length);
319
+ }
194
320
  const decrypted = decryptAmiibo(tag);
195
321
 
196
322
  // Extract only the first 92 bytes (the actual Mii data, without checksum)
@@ -199,26 +325,31 @@ function extractMiiFromAmiibo(amiiboDump) {
199
325
  return Buffer.from(miiData);
200
326
  }
201
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);
331
+ return Buffer.from(miiData);
332
+ }
333
+
202
334
  //Insert Mii data into an Amiibo dump
203
335
  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;
336
+ const dump = ensureBuffer(amiiboDump, 'Amiibo dump');
337
+ const miiBuf = ensureBuffer(miiData, 'Mii data');
338
+ const size = dump.length;
211
339
  if (size !== NFC3D_AMIIBO_SIZE && size !== NTAG215_SIZE && size !== NTAG215_SIZE_ALT) {
212
340
  throw new Error(`Invalid Amiibo dump size: ${size}`);
213
341
  }
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}`);
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);
216
348
  }
217
- const tag = amiiboDump.slice(0, NFC3D_AMIIBO_SIZE);
218
349
  const decrypted = decryptAmiibo(tag);
219
350
 
220
351
  // Validate and fix Mii checksum, ensuring it's 96 bytes with correct checksum
221
- const miiWithChecksum = validateAndFixMiiChecksum(miiData);
352
+ const miiWithChecksum = validateAndFixMiiChecksum(miiBuf);
222
353
 
223
354
  // Insert Mii data (96 bytes)
224
355
  miiWithChecksum.copy(decrypted, MII_OFFSET_DECRYPTED);
@@ -227,12 +358,25 @@ function insertMiiIntoAmiibo(amiiboDump, miiData) {
227
358
  const result = Buffer.alloc(size);
228
359
  encrypted.copy(result, 0);
229
360
  if (size > NFC3D_AMIIBO_SIZE) {
230
- amiiboDump.slice(NFC3D_AMIIBO_SIZE).copy(result, NFC3D_AMIIBO_SIZE);
361
+ dump.slice(NFC3D_AMIIBO_SIZE).copy(result, NFC3D_AMIIBO_SIZE);
231
362
  }
232
363
 
233
364
  return result;
234
365
  }
235
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);
372
+ const result = Buffer.alloc(dump.length);
373
+ encrypted.copy(result, 0);
374
+ if (dump.length > NFC3D_AMIIBO_SIZE) {
375
+ dump.slice(NFC3D_AMIIBO_SIZE).copy(result, NFC3D_AMIIBO_SIZE);
376
+ }
377
+ return result;
378
+ }
379
+
236
380
  module.exports = {
237
381
  insertMiiIntoAmiibo,
238
382
  extractMiiFromAmiibo
package/asmCrypto.js CHANGED
@@ -3598,22 +3598,30 @@ function BigNumber_extGCD(a, b) {
3598
3598
  }
3599
3599
 
3600
3600
  function getRandomValues(buf) {
3601
- if (typeof process !== 'undefined') {
3602
- var nodeCrypto = require('crypto');
3603
- var bytes = nodeCrypto.randomBytes(buf.length);
3604
- buf.set(bytes);
3605
- return;
3601
+ if (typeof process !== 'undefined' && typeof require === 'function') {
3602
+ try {
3603
+ var nodeCrypto = eval('require')('crypto');
3604
+ var bytes = nodeCrypto.randomBytes(buf.length);
3605
+ buf.set(bytes);
3606
+ return;
3607
+ } catch (e) {
3608
+ // Fall through to browser methods
3609
+ }
3606
3610
  }
3607
- if (window.crypto && window.crypto.getRandomValues) {
3611
+ if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
3608
3612
  window.crypto.getRandomValues(buf);
3609
3613
  return;
3610
3614
  }
3611
- if (self.crypto && self.crypto.getRandomValues) {
3615
+ if (typeof self !== 'undefined' && self.crypto && self.crypto.getRandomValues) {
3612
3616
  self.crypto.getRandomValues(buf);
3613
3617
  return;
3614
3618
  }
3619
+ if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
3620
+ globalThis.crypto.getRandomValues(buf);
3621
+ return;
3622
+ }
3615
3623
  // @ts-ignore
3616
- if (window.msCrypto && window.msCrypto.getRandomValues) {
3624
+ if (typeof window !== 'undefined' && window.msCrypto && window.msCrypto.getRandomValues) {
3617
3625
  // @ts-ignore
3618
3626
  window.msCrypto.getRandomValues(buf);
3619
3627
  return;