miijs 2.4.2 → 2.5.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/README.md +51 -44
- package/index.js +118 -84
- package/package.json +1 -1
- package/mii.js +0 -3346
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
|
-
- **`
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
112
|
-
const miiJson = await miijs.
|
|
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
|
|
123
|
+
const mii3DS = await miijs.read3DSQR('./example3DSQR.jpg');
|
|
131
124
|
|
|
132
125
|
// Convert to Wii format
|
|
133
|
-
const
|
|
126
|
+
const miiWii = miijs.convertMii(mii3DS, 'wii');
|
|
134
127
|
|
|
135
128
|
// Convert back to 3DS
|
|
136
|
-
const backTo3DS = miijs.convertMii(
|
|
129
|
+
const backTo3DS = miijs.convertMii(miiWii, '3ds');
|
|
137
130
|
|
|
138
131
|
// Auto-detect and convert to opposite
|
|
139
|
-
const autoConverted = miijs.convertMii(
|
|
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
|
-
|
|
187
|
-
|
|
177
|
+
let miiData = miijs.extractMiiFromAmiibo(amiiboDump);
|
|
188
178
|
// Convert the raw Mii data to readable JSON
|
|
189
|
-
|
|
190
|
-
const miiJson = miijs.decode3DSMii(miiData); // Note: decode3DSMii not exported, use read3DSQR workflow
|
|
179
|
+
const miiJson = miijs.read3DSQR(miiData);
|
|
191
180
|
|
|
192
|
-
//
|
|
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.
|
|
229
|
-
console.log(`Height: ${heightInfo.feet}'${heightInfo.inches}" (${heightInfo.
|
|
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);
|
|
221
|
+
const miiHeightValue = miijs.inchesToMiiHeight(72);
|
|
233
222
|
console.log('Mii height value for 6\'0":', miiHeightValue);
|
|
234
223
|
|
|
235
|
-
//
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
const
|
|
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
|
-
//
|
|
242
|
-
const
|
|
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 = '
|
|
255
|
-
miiJson.general.favoriteColor = 5;
|
|
256
|
-
miiJson.hair.color = 0;
|
|
257
|
-
miiJson.eyes.color = 2;
|
|
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/index.js
CHANGED
|
@@ -181,7 +181,7 @@ var aes_key = new Uint8Array([0x59, 0xFC, 0x81, 0x7E, 0x64, 0x46, 0xEA, 0x61, 0x
|
|
|
181
181
|
var pad = new Uint8Array([0, 0, 0, 0]);
|
|
182
182
|
function decodeAesCcm(data) {
|
|
183
183
|
var nonce = Uint8Cat(data.subarray(0, NONCE_LENGTH), pad);
|
|
184
|
-
var ciphertext = data.subarray(NONCE_LENGTH,
|
|
184
|
+
var ciphertext = data.subarray(NONCE_LENGTH, data.length);
|
|
185
185
|
var plaintext = asmCrypto.AES_CCM.decrypt(ciphertext, aes_key, nonce, undefined, TAG_LENGTH);
|
|
186
186
|
return Uint8Cat(plaintext.subarray(0, NONCE_OFFSET), data.subarray(0, NONCE_LENGTH), plaintext.subarray(NONCE_OFFSET, plaintext.length - 4));
|
|
187
187
|
}
|
|
@@ -278,10 +278,28 @@ function convertMii(jsonIn, typeTo) {
|
|
|
278
278
|
miiTo.beard.mustache.type = 0;
|
|
279
279
|
miiTo.beard.type = 1;
|
|
280
280
|
}
|
|
281
|
-
if (
|
|
282
|
-
|
|
281
|
+
if (miiTo.beard.type > 3) {
|
|
282
|
+
miiTo.beard.type = 3;
|
|
283
283
|
}
|
|
284
|
-
|
|
284
|
+
|
|
285
|
+
//System IDs are only 4 bytes on the Wii
|
|
286
|
+
if(miiTo.meta.systemId){
|
|
287
|
+
miiTo.meta.systemId=miiTo.meta.systemId.slice(0,8);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (miiTo.meta.miiId) {
|
|
291
|
+
const miiIdInt = parseInt(miiTo.meta.miiId.replaceAll(' ', ''), 16);
|
|
292
|
+
// Extract 28-bit timestamp (bits 0-27), multiply by 2 to get seconds since 2010
|
|
293
|
+
const secsSince2010 = (miiIdInt & 0x0FFFFFFF) * 2;
|
|
294
|
+
// Convert to 4-second intervals since 2006
|
|
295
|
+
const secondsOffset = 126230400; // Seconds between 2006 and 2010
|
|
296
|
+
const intervals = Math.floor((secsSince2010 + secondsOffset) / 4);
|
|
297
|
+
// Combine with type bits
|
|
298
|
+
const typePrefix = miiTo.meta.type === "Special" ? 0b010 : 0b100;
|
|
299
|
+
miiTo.meta.miiId = ((typePrefix << 29) | intervals).toString(16).toUpperCase().padStart(8, '0');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
miiTo.console = "Wii";
|
|
285
303
|
}
|
|
286
304
|
else if (typeFrom === "wii") {
|
|
287
305
|
miiTo.perms.sharing = mii.perms.mingle;
|
|
@@ -291,12 +309,9 @@ function convertMii(jsonIn, typeTo) {
|
|
|
291
309
|
const hairConv = convTables.hairWiiTo3DS[mii.hair.page][mii.hair.type];
|
|
292
310
|
miiTo.hair.page = hairConv[0];
|
|
293
311
|
miiTo.hair.type = hairConv[1];
|
|
294
|
-
miiTo.hair.color = mii.hair.color;
|
|
295
|
-
miiTo.hair.flipped = mii.hair.flipped;
|
|
296
312
|
|
|
297
313
|
// Convert face
|
|
298
314
|
miiTo.face.type = convTables.faceWiiTo3DS[mii.face.type];
|
|
299
|
-
miiTo.face.color = mii.face.color;
|
|
300
315
|
miiTo.face.makeup = 0;
|
|
301
316
|
miiTo.face.feature = 0;
|
|
302
317
|
|
|
@@ -308,62 +323,27 @@ function convertMii(jsonIn, typeTo) {
|
|
|
308
323
|
miiTo.face.feature = +convTables.featureWiiTo3DS[mii.face.feature];
|
|
309
324
|
}
|
|
310
325
|
|
|
311
|
-
// Convert eyes - preserve page/type structure
|
|
312
|
-
miiTo.eyes.page = mii.eyes.page;
|
|
313
|
-
miiTo.eyes.type = mii.eyes.type;
|
|
314
|
-
miiTo.eyes.color = mii.eyes.color;
|
|
315
|
-
miiTo.eyes.size = mii.eyes.size;
|
|
316
326
|
miiTo.eyes.squash = 3; // Default for 3DS
|
|
317
|
-
miiTo.eyes.rotation = mii.eyes.rotation;
|
|
318
|
-
miiTo.eyes.distanceApart = mii.eyes.distanceApart;
|
|
319
|
-
miiTo.eyes.yPosition = mii.eyes.yPosition;
|
|
320
|
-
|
|
321
|
-
// Convert eyebrows - preserve page/type structure
|
|
322
|
-
miiTo.eyebrows.page = mii.eyebrows.page;
|
|
323
|
-
miiTo.eyebrows.type = mii.eyebrows.type;
|
|
324
|
-
miiTo.eyebrows.color = mii.eyebrows.color;
|
|
325
|
-
miiTo.eyebrows.size = mii.eyebrows.size;
|
|
326
327
|
miiTo.eyebrows.squash = 3; // Default for 3DS
|
|
327
|
-
miiTo.eyebrows.rotation = mii.eyebrows.rotation;
|
|
328
|
-
miiTo.eyebrows.distanceApart = mii.eyebrows.distanceApart;
|
|
329
|
-
miiTo.eyebrows.yPosition = mii.eyebrows.yPosition;
|
|
330
|
-
|
|
331
|
-
// Convert nose - preserve page/type structure
|
|
332
328
|
miiTo.nose.page = mii.nose.page || 0;
|
|
333
|
-
miiTo.nose.type = mii.nose.type;
|
|
334
|
-
miiTo.nose.size = mii.nose.size;
|
|
335
|
-
miiTo.nose.yPosition = mii.nose.yPosition;
|
|
336
|
-
|
|
337
|
-
// Convert mouth - preserve page/type structure
|
|
338
|
-
miiTo.mouth.page = mii.mouth.page;
|
|
339
|
-
miiTo.mouth.type = mii.mouth.type;
|
|
340
|
-
miiTo.mouth.color = mii.mouth.color;
|
|
341
|
-
miiTo.mouth.size = mii.mouth.size;
|
|
342
329
|
miiTo.mouth.squash = 3; // Default for 3DS
|
|
343
|
-
miiTo.mouth.yPosition = mii.mouth.yPosition;
|
|
344
330
|
|
|
345
|
-
//
|
|
346
|
-
miiTo.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
miiTo.mole.xPosition = mii.mole.xPosition;
|
|
362
|
-
miiTo.mole.yPosition = mii.mole.yPosition;
|
|
363
|
-
|
|
364
|
-
// Copy general info
|
|
365
|
-
miiTo.general = { ...mii.general };
|
|
366
|
-
miiTo.meta = { ...mii.meta };
|
|
331
|
+
//System IDs are twice as long on 3DS
|
|
332
|
+
if(miiTo.meta.systemId){
|
|
333
|
+
miiTo.meta.systemId=miiTo.meta.systemId.padEnd(16,'0');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (miiTo.meta.miiId) {
|
|
337
|
+
const miiIdInt = parseInt(miiTo.meta.miiId.replaceAll(' ', ''), 16);
|
|
338
|
+
// Extract 29-bit timestamp (bits 0-28), multiply by 4 to get seconds since 2006
|
|
339
|
+
const secsSince2006 = (miiIdInt & 0x1FFFFFFF) * 4;
|
|
340
|
+
// Convert to 2-second intervals since 2010
|
|
341
|
+
const secondsOffset = 126230400; // Seconds between 2006 and 2010
|
|
342
|
+
const intervals = Math.floor((secsSince2006 - secondsOffset) / 2);
|
|
343
|
+
// Combine with flag bits
|
|
344
|
+
const flags = miiTo.meta.type === "Special" ? 0b0001 : 0b1001;
|
|
345
|
+
miiTo.meta.miiId = ((intervals & 0x0FFFFFFF) | (flags << 28)).toString(16).toUpperCase().padStart(8, '0');
|
|
346
|
+
}
|
|
367
347
|
|
|
368
348
|
miiTo.console = "3DS";
|
|
369
349
|
}
|
|
@@ -604,7 +584,7 @@ async function readWiiBin(binOrPath) {
|
|
|
604
584
|
}
|
|
605
585
|
thisMii.meta.creatorName = cname.replaceAll("\x00", "");
|
|
606
586
|
thisMii.general.gender = +get(0x00)[1];//0 for Male, 1 for Female
|
|
607
|
-
thisMii.meta.miiId =
|
|
587
|
+
thisMii.meta.miiId = data.readUInt32BE(0x18).toString(16).padStart(8, '0');
|
|
608
588
|
switch (thisMii.meta.miiId.slice(0, 3)) {
|
|
609
589
|
case "010":
|
|
610
590
|
thisMii.meta.type = "Special";
|
|
@@ -612,11 +592,11 @@ async function readWiiBin(binOrPath) {
|
|
|
612
592
|
case "110":
|
|
613
593
|
thisMii.meta.type = "Foreign";
|
|
614
594
|
break;
|
|
615
|
-
default
|
|
595
|
+
default://100
|
|
616
596
|
thisMii.meta.type = "Default";
|
|
617
597
|
break;
|
|
618
598
|
}
|
|
619
|
-
thisMii.meta.systemId =
|
|
599
|
+
thisMii.meta.systemId = data.readUInt32BE(0x1C).toString(16).padStart(8, '0').toUpperCase();
|
|
620
600
|
var temp = get(0x20);
|
|
621
601
|
thisMii.face.type = parseInt(temp.slice(0, 3), 2);//0-7
|
|
622
602
|
thisMii.face.color = parseInt(temp.slice(3, 6), 2);//0-5
|
|
@@ -639,12 +619,13 @@ async function readWiiBin(binOrPath) {
|
|
|
639
619
|
thisMii.general.birthMonth = parseInt(temp.slice(2, 6), 2);
|
|
640
620
|
thisMii.general.birthday = parseInt(temp.slice(6, 8) + temp2.slice(0, 3), 2);
|
|
641
621
|
thisMii.general.favoriteColor = parseInt(temp2.slice(3, 7), 2);//0-11, refer to cols array
|
|
622
|
+
thisMii.meta.favorited = temp2[7]=="1";
|
|
642
623
|
thisMii.general.height = parseInt(get(0x16), 2);//0-127
|
|
643
624
|
thisMii.general.weight = parseInt(get(0x17), 2);//0-127
|
|
644
|
-
thisMii.perms.fromCheckMiiOut = get(0x21)[7]
|
|
625
|
+
thisMii.perms.fromCheckMiiOut = get(0x21)[7] == "1";
|
|
645
626
|
temp = get(0x34);
|
|
646
627
|
temp2 = get(0x35);
|
|
647
|
-
thisMii.mole.on = temp[0]
|
|
628
|
+
thisMii.mole.on = temp[0] == "1";//0 for Off, 1 for On
|
|
648
629
|
thisMii.mole.size = parseInt(temp.slice(1, 5), 2);//0-8, default 4
|
|
649
630
|
thisMii.mole.xPosition = parseInt(temp2.slice(2, 7), 2);//0-16, Default 2
|
|
650
631
|
thisMii.mole.yPosition = parseInt(temp.slice(5, 8) + temp2.slice(0, 2), 2);//Top to bottom
|
|
@@ -653,7 +634,7 @@ async function readWiiBin(binOrPath) {
|
|
|
653
634
|
thisMii.hair.page = +lookupTables.hairTable["" + parseInt(temp.slice(0, 7), 2)][0] - 1;
|
|
654
635
|
thisMii.hair.type = +convTables.formatTo[lookupTables.hairTable["" + parseInt(temp.slice(0, 7), 2)][2] - 1][lookupTables.hairTable["" + parseInt(temp.slice(0, 7), 2)][1] - 1];//0-71, Needs lookup table
|
|
655
636
|
thisMii.hair.color = parseInt(temp[7] + temp2.slice(0, 2), 2);//0-7, refer to hairCols array
|
|
656
|
-
thisMii.hair.flipped = temp2[2]
|
|
637
|
+
thisMii.hair.flipped = temp2[2] == "1";
|
|
657
638
|
temp = get(0x24);
|
|
658
639
|
temp2 = get(0x25);
|
|
659
640
|
thisMii.eyebrows.page = +lookupTables.eyebrowTable["" + parseInt(temp.slice(0, 5), 2)][0] - 1;
|
|
@@ -709,6 +690,14 @@ function decode3DSMii(data) {
|
|
|
709
690
|
}
|
|
710
691
|
};
|
|
711
692
|
const get = address => getBinaryFromAddress(address, data);
|
|
693
|
+
miiJson.perms.copying = get(0x01)[7] === "1" ? true : false;
|
|
694
|
+
const miiIdValue = data.readUInt32BE(0x0C);
|
|
695
|
+
const systemIdHigh = data.readUInt32BE(0x04);
|
|
696
|
+
const systemIdLow = data.readUInt32BE(0x08);
|
|
697
|
+
|
|
698
|
+
miiJson.meta.type = (miiIdValue & 0x80000000) === 0 ? "Special" : "Default";
|
|
699
|
+
miiJson.meta.systemId = systemIdHigh.toString(16).padStart(8, '0') + systemIdLow.toString(16).padStart(8, '0');
|
|
700
|
+
miiJson.meta.miiId = miiIdValue.toString(16).padStart(8, '0');
|
|
712
701
|
var temp = get(0x18);
|
|
713
702
|
var temp2 = get(0x19);
|
|
714
703
|
miiJson.general.birthday = parseInt(temp2.slice(6, 8) + temp.slice(0, 3), 2);
|
|
@@ -742,7 +731,6 @@ function decode3DSMii(data) {
|
|
|
742
731
|
temp = get(0x30);
|
|
743
732
|
miiJson.perms.sharing = temp[7] === "1" ? false : true;
|
|
744
733
|
miiJson.general.favoriteColor = parseInt(temp2.slice(2, 6), 2);
|
|
745
|
-
miiJson.perms.copying = get(0x01)[7] === "1" ? true : false;
|
|
746
734
|
miiJson.hair.page = lookupTable("hairs", parseInt(get(0x32), 2), true)[0];
|
|
747
735
|
miiJson.hair.type = lookupTable("hairs", parseInt(get(0x32), 2), true)[1];
|
|
748
736
|
miiJson.face.type = lookupTable("faces", parseInt(temp.slice(3, 7), 2), false);
|
|
@@ -811,11 +799,10 @@ function decode3DSMii(data) {
|
|
|
811
799
|
temp2 = get(0x47);
|
|
812
800
|
miiJson.mole.xPosition = parseInt(temp2.slice(6, 8) + temp.slice(0, 3), 2);
|
|
813
801
|
miiJson.mole.yPosition = parseInt(temp2.slice(1, 6), 2);
|
|
814
|
-
miiJson.meta.type = "Default";//qk, Make this actually retrieve MiiID, SystemID, and Mii type
|
|
815
802
|
miiJson.console = "3DS";
|
|
816
803
|
return miiJson;
|
|
817
804
|
}
|
|
818
|
-
async function read3DSQR(binOrPath, returnDecryptedBin) {
|
|
805
|
+
async function read3DSQR(binOrPath, returnDecryptedBin, returnEncryptedBin) {
|
|
819
806
|
let qrCode;
|
|
820
807
|
if (Buffer.isBuffer(binOrPath)) {//Buffer
|
|
821
808
|
qrCode = binOrPath;
|
|
@@ -841,6 +828,9 @@ async function read3DSQR(binOrPath, returnDecryptedBin) {
|
|
|
841
828
|
});
|
|
842
829
|
}
|
|
843
830
|
if (qrCode) {
|
|
831
|
+
if (returnEncryptedBin) {
|
|
832
|
+
return new Uint8Array(qrCode);
|
|
833
|
+
}
|
|
844
834
|
var data;
|
|
845
835
|
data = Buffer.from(decodeAesCcm(new Uint8Array(qrCode)));
|
|
846
836
|
if (returnDecryptedBin) {
|
|
@@ -1245,7 +1235,7 @@ async function writeWiiBin(jsonIn, outPath) {
|
|
|
1245
1235
|
miiBin += mii.general.birthMonth.toString(2).padStart(4, "0");
|
|
1246
1236
|
miiBin += mii.general.birthday.toString(2).padStart(5, "0");
|
|
1247
1237
|
miiBin += mii.general.favoriteColor.toString(2).padStart(4, "0");
|
|
1248
|
-
miiBin += '0';
|
|
1238
|
+
miiBin += mii.perms.favorited?'1':'0';
|
|
1249
1239
|
for (var i = 0; i < 10; i++) {
|
|
1250
1240
|
if (i < mii.meta.name.length) {
|
|
1251
1241
|
miiBin += mii.meta.name.charCodeAt(i).toString(2).padStart(16, "0");
|
|
@@ -1256,23 +1246,33 @@ async function writeWiiBin(jsonIn, outPath) {
|
|
|
1256
1246
|
}
|
|
1257
1247
|
miiBin += mii.general.height.toString(2).padStart(8, "0");
|
|
1258
1248
|
miiBin += mii.general.weight.toString(2).padStart(8, "0");
|
|
1259
|
-
let
|
|
1249
|
+
let miiTypeIdentifier = "";
|
|
1260
1250
|
switch (mii.meta.type) {
|
|
1261
1251
|
case "Special":
|
|
1262
|
-
|
|
1252
|
+
miiTypeIdentifier = "010";
|
|
1263
1253
|
break;
|
|
1264
1254
|
case "Foreign":
|
|
1265
|
-
|
|
1255
|
+
miiTypeIdentifier = "110";
|
|
1266
1256
|
break;
|
|
1267
1257
|
default:
|
|
1268
|
-
|
|
1258
|
+
miiTypeIdentifier = "100";
|
|
1269
1259
|
break;
|
|
1270
1260
|
}
|
|
1271
|
-
|
|
1272
|
-
miiId
|
|
1261
|
+
if (mii.meta.miiId) {
|
|
1262
|
+
let temp = mii.meta.miiId.replaceAll(' ', '').match(/.{1,2}/g).map(b => parseInt(b, 16).toString(2).padStart(8, '0')).join('');
|
|
1263
|
+
miiBin += `${miiTypeIdentifier}${temp.padStart(32, '0').slice(-29)}`; // Take rightmost 29 bits
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
// Calculate the number of 4-second intervals since Jan 1, 2006
|
|
1267
|
+
const miiIdBase = Math.floor((Date.now() - Date.UTC(2006, 0, 1)) / 4000).toString(2).padStart(29, '0');
|
|
1268
|
+
miiBin += `${miiTypeIdentifier}${miiIdBase}`;
|
|
1269
|
+
}
|
|
1270
|
+
if(mii.meta.systemId){
|
|
1271
|
+
miiBin += mii.meta.systemId.replaceAll(' ','').match(/.{1,2}/g).map(b=>parseInt(b,16).toString(2).padStart(8,'0')).join('').padStart(32,'0').slice(-32); // Use slice(-32)
|
|
1272
|
+
}
|
|
1273
|
+
else{
|
|
1274
|
+
miiBin += "11111111".repeat(4);//FF FF FF FF, completely nonsense System ID if none is set
|
|
1273
1275
|
}
|
|
1274
|
-
miiBin += miiId;
|
|
1275
|
-
miiBin += "11111111".repeat(4);//System ID
|
|
1276
1276
|
miiBin += mii.face.type.toString(2).padStart(3, "0");
|
|
1277
1277
|
miiBin += mii.face.color.toString(2).padStart(3, "0");
|
|
1278
1278
|
miiBin += mii.face.feature.toString(2).padStart(4, "0");
|
|
@@ -1315,7 +1315,7 @@ async function writeWiiBin(jsonIn, outPath) {
|
|
|
1315
1315
|
miiBin += mii.mouth.yPosition.toString(2).padStart(5, "0");
|
|
1316
1316
|
miiBin += mii.glasses.type.toString(2).padStart(4, "0");
|
|
1317
1317
|
miiBin += mii.glasses.color.toString(2).padStart(3, "0");
|
|
1318
|
-
miiBin += "0"
|
|
1318
|
+
miiBin += "0";//Invalidates Mii when set to 1
|
|
1319
1319
|
miiBin += mii.glasses.size.toString(2).padStart(3, "0");
|
|
1320
1320
|
miiBin += mii.glasses.yPosition.toString(2).padStart(5, "0");
|
|
1321
1321
|
miiBin += mii.beard.mustache.type.toString(2).padStart(2, "0");
|
|
@@ -1359,26 +1359,39 @@ async function write3DSQR(miiJson, outPath, returnBin, fflRes = getFFLRes()) {
|
|
|
1359
1359
|
|
|
1360
1360
|
//Make the binary
|
|
1361
1361
|
var mii = miiJson;
|
|
1362
|
-
var miiBin = "00000011"
|
|
1362
|
+
var miiBin = "00000011";//Mii version, which for 3DS is 3
|
|
1363
1363
|
//If Special Miis are being used improperly, fix it and warn the user
|
|
1364
1364
|
if (mii.meta.type.toLowerCase() === "special" && (mii.console.toLowerCase() === "wii u" || mii.console.toLowerCase() === "wiiu")) {
|
|
1365
1365
|
mii.meta.type = "Default";
|
|
1366
|
-
console.warn("Wii Us do not work with Special Miis. Reverted to Default Mii.");
|
|
1366
|
+
console.warn("Wii Us do not work with Special Miis. Reverted output to Default Mii.");
|
|
1367
1367
|
}
|
|
1368
1368
|
if (mii.perms.sharing && mii.meta.type === "Special") {
|
|
1369
1369
|
mii.perms.sharing = false;
|
|
1370
1370
|
console.warn("Cannot have Sharing enabled for Special Miis. Disabled Sharing in the output.");
|
|
1371
1371
|
}
|
|
1372
|
-
|
|
1372
|
+
//Revisit this if translating MiiJS out of English ever, for now this is fine
|
|
1373
|
+
miiBin += "0000000";//00 JPN/US/EUR, 01 CHN, 10 KOR, 11 TWN Character Set | Region Lock Off 00 | Profanity Flag 0/1
|
|
1373
1374
|
miiBin += mii.perms.copying ? "1" : "0";
|
|
1374
1375
|
miiBin += "00000000";
|
|
1375
1376
|
miiBin += "00110000";
|
|
1376
|
-
|
|
1377
|
+
if(mii.meta.systemId){
|
|
1378
|
+
miiBin += mii.meta.systemId.replaceAll(' ','').match(/.{1,2}/g).map(b=>parseInt(b,16).toString(2).padStart(8,'0')).join('').padStart(64,'0').slice(-64); // Use slice(-64)
|
|
1379
|
+
}
|
|
1380
|
+
else{
|
|
1381
|
+
//Donor System ID
|
|
1382
|
+
miiBin += "1000101011010010000001101000011100011000110001100100011001100110010101100111111110111100000001110101110001000101011101100000001110100100010000000000000000000000";
|
|
1383
|
+
}
|
|
1377
1384
|
miiBin += mii.meta.type === "Special" ? "0" : "1";
|
|
1378
|
-
miiBin += "
|
|
1379
|
-
|
|
1380
|
-
|
|
1385
|
+
miiBin += "001";
|
|
1386
|
+
let temp = '';
|
|
1387
|
+
if (mii.meta.miiId) {
|
|
1388
|
+
// Convert Mii ID to binary
|
|
1389
|
+
temp += mii.meta.miiId.replaceAll(' ', '').match(/.{1,2}/g).map(b => parseInt(b, 16).toString(2).padStart(8, '0')).join('');
|
|
1390
|
+
} else {
|
|
1391
|
+
// Number of 2-second intervals since Jan 1, 2010
|
|
1392
|
+
temp += Math.floor((Date.now() - Date.UTC(2010, 0, 1)) / 2000).toString(2);
|
|
1381
1393
|
}
|
|
1394
|
+
miiBin += temp.padStart(32, '0').slice(-27); // Take rightmost 27 bits
|
|
1382
1395
|
miiBin += "0000000001000101011101100000001110100100010000000000000000000000";
|
|
1383
1396
|
miiBin += mii.general.birthday.toString(2).padStart(5, "0").slice(2, 5);
|
|
1384
1397
|
miiBin += mii.general.birthMonth.toString(2).padStart(4, "0");
|
|
@@ -1913,6 +1926,25 @@ function metricHeightWeightToMiiWeight(heightCentimeters, weightKilograms) {
|
|
|
1913
1926
|
}
|
|
1914
1927
|
}
|
|
1915
1928
|
|
|
1929
|
+
function miiIdToTimestamp(miiId, mode){
|
|
1930
|
+
miiId = miiId.replaceAll(' ', '');
|
|
1931
|
+
const idBigInt = BigInt('0x' + miiId);
|
|
1932
|
+
|
|
1933
|
+
switch(mode.toLowerCase().replaceAll(' ', '')){
|
|
1934
|
+
case "3ds":
|
|
1935
|
+
case "wiiu":
|
|
1936
|
+
const seconds3ds = (idBigInt & 0x0FFFFFFFn) * 2n;
|
|
1937
|
+
return new Date(Number(BigInt(Date.UTC(2010, 0, 1)) + seconds3ds * 1000n));
|
|
1938
|
+
|
|
1939
|
+
case "wii":
|
|
1940
|
+
// Extract bits 0-27 (28 bits), multiply by 4 for seconds
|
|
1941
|
+
const secondsWii = (idBigInt & 0x0FFFFFFFn) * 4n;
|
|
1942
|
+
return new Date(Number(BigInt(Date.UTC(2006, 0, 1)) + secondsWii * 1000n));
|
|
1943
|
+
|
|
1944
|
+
default:
|
|
1945
|
+
return "No valid mode specified";
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1916
1948
|
|
|
1917
1949
|
|
|
1918
1950
|
module.exports = {
|
|
@@ -1950,6 +1982,8 @@ module.exports = {
|
|
|
1950
1982
|
imperialHeightWeightToMiiWeight,
|
|
1951
1983
|
metricHeightWeightToMiiWeight,
|
|
1952
1984
|
|
|
1985
|
+
miiIdToTimestamp,
|
|
1986
|
+
|
|
1953
1987
|
/*
|
|
1954
1988
|
Handle Amiibo Functions
|
|
1955
1989
|
insertMiiIntoAmiibo(amiiboDump, decrypted3DSMiiBuffer),
|