miijs 2.4.1 → 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/.github/workflows/npm-publish-github-packages.yml +36 -0
- package/README.md +51 -44
- package/index.js +122 -110
- package/package.json +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
+
|
|
4
|
+
name: Node.js Package
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [created]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npm test
|
|
20
|
+
|
|
21
|
+
publish-gpr:
|
|
22
|
+
needs: build
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
packages: write
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-node@v4
|
|
30
|
+
with:
|
|
31
|
+
node-version: 20
|
|
32
|
+
registry-url: https://npm.pkg.github.com/
|
|
33
|
+
- run: npm ci
|
|
34
|
+
- run: npm publish
|
|
35
|
+
env:
|
|
36
|
+
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ MiiJS is a complete and comprehensive Mii library for reading, converting, modif
|
|
|
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
|
|
@@ -360,4 +367,4 @@ You can find FFLResHigh using a Wii U with an FTP program installed at `sys/titl
|
|
|
360
367
|
- **[kazuki-4ys' MiiInfoEditorCTR](https://github.com/kazuki-4ys/kazuki-4ys.github.io/tree/master/web_apps/MiiInfoEditorCTR)** - I repurposed how to decrypt and reencrypt the QR codes from here, including repurposing the asmCrypto.js file in its entirety with very small modifications (it has since been stripped down to only include the functions this library uses). I believe I also modified the code for rendering the Mii using Nintendo's Mii Studio from here as well, though I do not remember for certain.
|
|
361
368
|
- **[ariankordi's FFL.js](https://github.com/ariankordi/FFL.js/)** - Rendering Miis locally would not be possible without this library. Instructions for finding FFLResHigh are also learned from [ariankordi's FFL-Testing repository](https://github.com/ariankordi/FFL-Testing).
|
|
362
369
|
- **[Models Resource](https://models.spriters-resource.com/3ds/systembios/asset/306260/)** - For the bodies used in Mii rendering
|
|
363
|
-
- **[socram8888's Amiitools](https://github.com/socram8888/amiitool)** - I _think_, for the code reverse engineered to help with aspects of Amiibo dump processing. I went through so many iterations in research and coding, there may be other credits due as well but I _think_ this was the only repo actually used for the reverse engineering in the final working code.
|
|
370
|
+
- **[socram8888's Amiitools](https://github.com/socram8888/amiitool)** - I _think_, for the code reverse engineered to help with aspects of Amiibo dump processing. I went through so many iterations in research and coding, there may be other credits due as well but I _think_ this was the only repo actually used for the reverse engineering in the final working code.
|
package/index.js
CHANGED
|
@@ -12,7 +12,6 @@ const httpsLib = require('https');
|
|
|
12
12
|
const asmCrypto = require("./asmCrypto.js");
|
|
13
13
|
const path = require("path");
|
|
14
14
|
const createGL = require('gl');
|
|
15
|
-
const typeCheat = [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3];
|
|
16
15
|
const {
|
|
17
16
|
createCharModel, initCharModelTextures,
|
|
18
17
|
initializeFFL, exitFFL, parseHexOrB64ToUint8Array,
|
|
@@ -20,6 +19,7 @@ const {
|
|
|
20
19
|
} = require("./fflWrapper.js");
|
|
21
20
|
const ModuleFFL = require("ffl.js/examples/ffl-emscripten-single-file.js");
|
|
22
21
|
const FFLShaderMaterial = require("ffl.js/FFLShaderMaterial.js");
|
|
22
|
+
const typeCheat = [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3];
|
|
23
23
|
|
|
24
24
|
// Typedefs for intellisence
|
|
25
25
|
/** @typedef {import('./types').WiiMii} WiiMii */
|
|
@@ -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
|
+
}
|
|
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');
|
|
283
300
|
}
|
|
284
|
-
|
|
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");
|
|
@@ -1686,7 +1699,7 @@ function makeChild(parent0, parent1, options) {
|
|
|
1686
1699
|
delete child.stages[iStage].stages;//Because we're just cloning the baseline object repeatedly to make the stages a little bit cleaner, we need to clear this on subsequent clones
|
|
1687
1700
|
}
|
|
1688
1701
|
|
|
1689
|
-
//
|
|
1702
|
+
//Basically there's a random chance for a hairstyle to not advance throughout the years, so it's possible to end up with a hairstyle from a younger stage. This is slightly more likely for boys than girls.
|
|
1690
1703
|
let ageGroup = 0;
|
|
1691
1704
|
for (let iHairStage = 0; iHairStage < 4; iHairStage++) {
|
|
1692
1705
|
const subgroup = childGenTables.hairStyleGroups[hairGroupIndex][ageGroup];
|
|
@@ -1716,25 +1729,8 @@ function makeChild(parent0, parent1, options) {
|
|
|
1716
1729
|
child.stages[5].hair.type = hairType;
|
|
1717
1730
|
break;
|
|
1718
1731
|
}
|
|
1719
|
-
if (child.stages[0].general.gender === 0) {
|
|
1720
|
-
|
|
1721
|
-
ageGroup = Math.min(ageGroup + 1, 3);
|
|
1722
|
-
}
|
|
1723
|
-
else {
|
|
1724
|
-
if (Math.floor(Math.random() * 3) !== 0) {
|
|
1725
|
-
ageGroup = Math.min(ageGroup + 1, 3);
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
else {
|
|
1730
|
-
if (iHairStage === 0) {
|
|
1731
|
-
ageGroup = Math.min(ageGroup + 1, 3);
|
|
1732
|
-
}
|
|
1733
|
-
else {
|
|
1734
|
-
if (Math.floor(Math.random() * 4) !== 0) {
|
|
1735
|
-
ageGroup = Math.min(ageGroup + 1, 3);
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1732
|
+
if (iHairStage === 0 || Math.floor(Math.random() * (child.stages[0].general.gender === 0 ? 3 : 4)) !== 0) {//For each stage of life there is a 33% chance for boys, and a 25% chance for girls, of staying on the same hairstyle as they had already. However, they are guaranteed to never have the same hairstyle stage as their newborn stage.
|
|
1733
|
+
ageGroup = Math.min(ageGroup + 1, 3);
|
|
1738
1734
|
}
|
|
1739
1735
|
}
|
|
1740
1736
|
return child.stages;
|
|
@@ -1898,7 +1894,6 @@ function miiWeightToRealWeight(heightInches, miiWeight) {
|
|
|
1898
1894
|
|
|
1899
1895
|
This is approximate, not guaranteed accurate nor intended to be taken that way. This is for entertainment value only.
|
|
1900
1896
|
*/
|
|
1901
|
-
if (!heightInches || heightInches < 0) throw new Error("heightInches must be >= 0");
|
|
1902
1897
|
const H = miiHeightToMeasurements(heightInches).totalInches;
|
|
1903
1898
|
const BMI = bmiFromWeightSlider(miiWeight);
|
|
1904
1899
|
return {
|
|
@@ -1907,8 +1902,6 @@ function miiWeightToRealWeight(heightInches, miiWeight) {
|
|
|
1907
1902
|
};
|
|
1908
1903
|
}
|
|
1909
1904
|
function imperialHeightWeightToMiiWeight(heightInches, weightLbs) {
|
|
1910
|
-
if (!heightInches || heightInches < 0) throw new Error("heightInches must be >= 0");
|
|
1911
|
-
|
|
1912
1905
|
const H = miiHeightToMeasurements(heightInches).totalInches;
|
|
1913
1906
|
const BMI = weightLbs * 703 / (H * H);
|
|
1914
1907
|
|
|
@@ -1922,8 +1915,6 @@ function imperialHeightWeightToMiiWeight(heightInches, weightLbs) {
|
|
|
1922
1915
|
function metricHeightWeightToMiiWeight(heightCentimeters, weightKilograms) {
|
|
1923
1916
|
const heightInches = Math.round(heightCentimeters / 2.54);
|
|
1924
1917
|
const weightLbs = Math.round(weightKilograms / 0.4535924);
|
|
1925
|
-
if (!heightInches || heightInches < 0) throw new Error("heightCentimeters must be >= 0");
|
|
1926
|
-
|
|
1927
1918
|
const H = miiHeightToMeasurements(heightInches).totalInches;
|
|
1928
1919
|
const BMI = weightLbs * 703 / (H * H);
|
|
1929
1920
|
|
|
@@ -1935,6 +1926,25 @@ function metricHeightWeightToMiiWeight(heightCentimeters, weightKilograms) {
|
|
|
1935
1926
|
}
|
|
1936
1927
|
}
|
|
1937
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
|
+
}
|
|
1938
1948
|
|
|
1939
1949
|
|
|
1940
1950
|
module.exports = {
|
|
@@ -1972,6 +1982,8 @@ module.exports = {
|
|
|
1972
1982
|
imperialHeightWeightToMiiWeight,
|
|
1973
1983
|
metricHeightWeightToMiiWeight,
|
|
1974
1984
|
|
|
1985
|
+
miiIdToTimestamp,
|
|
1986
|
+
|
|
1975
1987
|
/*
|
|
1976
1988
|
Handle Amiibo Functions
|
|
1977
1989
|
insertMiiIntoAmiibo(amiiboDump, decrypted3DSMiiBuffer),
|