miijs 2.1.2 → 2.2.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/index.js CHANGED
@@ -5,6 +5,7 @@ const { createCanvas, loadImage, ImageData } = nodeCanvas;
5
5
  const jsQR = require('jsqr');
6
6
  const Jimp = require('jimp');
7
7
  const THREE = require('three');
8
+ var GLTFLoader=null;
8
9
  const QRCodeStyling = require("qr-code-styling");
9
10
  const { JSDOM } = require("jsdom");
10
11
  const httpsLib = require('https');
@@ -23,98 +24,15 @@ const FFLShaderMaterial = require("ffl.js/FFLShaderMaterial.js");
23
24
  // Typedefs for intellisence
24
25
  /** @typedef {import('./types').WiiMii} WiiMii */
25
26
 
26
- //Tools
27
- function Uint8Cat(){
28
- var destLength = 0
29
- for(var i = 0;i < arguments.length;i++){
30
- destLength += arguments[i].length;
31
- }
32
- var dest = new Uint8Array(destLength);
33
- var index = 0;
34
- for(var i=0;i<arguments.length;i++){
35
- dest.set(arguments[i],index);
36
- index += arguments[i].length;
37
- }
38
- return dest;
39
- }
40
- async function downloadImage(url) {
41
- return new Promise((resolve, reject) => {
42
- httpsLib.get(url, (res) => {
43
- if (res.statusCode === 200) {
44
- const data = [];
45
- res.on('data', chunk => data.push(chunk));
46
- res.on('end', () => resolve(Buffer.concat(data)));
47
- res.on('error', reject);
48
- } else {
49
- res.resume();
50
- reject(new Error(`Request Failed With a Status Code: ${res.statusCode}`));
51
- }
52
- });
53
- });
54
- }
55
- function byteToString(int){
56
- var str = int.toString(16);
57
- if(str.length < 2)str = '0' + str;
58
- return str;
59
- }
60
-
61
- //If FFLResHigh.dat is in the same directory as Node.js is calling the library from, use it by default
62
- let _fflRes; // undefined initially
63
- function getFFLRes() {
64
- // If we've already tried loading, just return the result
65
- if (_fflRes !== undefined) return _fflRes;
66
- for (const path of [ "./FFLResHigh.dat", "./ffl/FFLResHigh.dat" ]) {
67
- if (fs.existsSync(path)) {
68
- // Convert Buffer to Uint8Array explicitly
69
- const buffer = fs.readFileSync(path);
70
- return _fflRes = new Uint8Array(buffer);
71
- }
72
- }
73
- // If no file found, mark as null
74
- return _fflRes = null;
75
- }
76
-
77
- //3DS QR Code (En|De)cryption
78
- var NONCE_OFFSET = 0xC;
79
- var NONCE_LENGTH = 8;
80
- var TAG_LENGTH = 0x10;
81
- var aes_key = new Uint8Array([0x59, 0xFC, 0x81, 0x7E, 0x64, 0x46, 0xEA, 0x61, 0x90, 0x34, 0x7B, 0x20, 0xE9, 0xBD, 0xCE, 0x52]);
82
- var pad = new Uint8Array([0,0,0,0]);
83
- function decodeAesCcm(data){
84
- var nonce = Uint8Cat(data.subarray(0,NONCE_LENGTH),pad);
85
- var ciphertext = data.subarray(NONCE_LENGTH,0x70);
86
- var plaintext = asmCrypto.AES_CCM.decrypt(ciphertext,aes_key,nonce,undefined,TAG_LENGTH);
87
- return Uint8Cat(plaintext.subarray(0,NONCE_OFFSET),data.subarray(0,NONCE_LENGTH),plaintext.subarray(NONCE_OFFSET,plaintext.length - 4));
88
- }
89
- function crcCalc(data){
90
- var crc = 0;
91
- for (var byteIndex = 0;byteIndex < data.length; byteIndex++){
92
- for (var bitIndex = 7; bitIndex >= 0; bitIndex--){
93
- crc = (((crc << 1) | ((data[byteIndex] >> bitIndex) & 0x1)) ^
94
- (((crc & 0x8000) != 0) ? 0x1021 : 0));
95
- }
96
- }
97
- for(var counter = 16; counter > 0; counter--){
98
- crc = ((crc << 1) ^ (((crc & 0x8000) != 0) ? 0x1021 : 0));
99
- }
100
- return(crc & 0xFFFF);
101
- }
102
- function encodeAesCcm(data){
103
- var nonce = Uint8Cat(data.subarray(NONCE_OFFSET,NONCE_OFFSET + NONCE_LENGTH),pad);
104
- var crcSrc = Uint8Cat(data,new Uint8Array([0,0]));
105
- var crc = crcCalc(crcSrc);
106
- var cfsd = Uint8Cat(crcSrc,new Uint8Array([crc >>> 8,crc & 0xff]));
107
- var plaintext = Uint8Cat(cfsd.subarray(0,NONCE_OFFSET),cfsd.subarray(NONCE_OFFSET + NONCE_LENGTH,cfsd.length),pad,pad);
108
- var ciphertext = asmCrypto.AES_CCM.encrypt(plaintext,aes_key,nonce,undefined,TAG_LENGTH);
109
- return Uint8Cat(cfsd.subarray(NONCE_OFFSET,NONCE_OFFSET + NONCE_LENGTH),ciphertext.subarray(0,ciphertext.length - 24),ciphertext.subarray(ciphertext.length - TAG_LENGTH,ciphertext.length))
110
- }
111
-
112
27
  //Miscellaneous Tables
113
28
  const lookupTables = {
29
+ //Universals
114
30
  favCols: ["Red", "Orange", "Yellow", "Lime", "Green", "Blue", "Cyan", "Pink", "Purple", "Brown", "White", "Black"],
115
31
  skinCols: ["White", "Tanned White", "Darker White", "Tanned Darker", "Mostly Black", "Black"],
116
32
  hairCols: ["Black", "Brown", "Red", "Reddish Brown", "Grey", "Light Brown", "Dark Blonde", "Blonde"],
117
33
  eyeCols: ["Black", "Grey", "Brown", "Lime", "Blue", "Green"],
34
+
35
+ //Wii fields
118
36
  wiiFaceFeatures: ["None", "Blush", "Makeup and Blush", "Freckles", "Bags", "Wrinkles on Cheeks", "Wrinkles near Eyes", "Chin Wrinkle", "Makeup", "Stubble", "Wrinkles near Mouth", "Wrinkles"],
119
37
  wiiMouthColors: ["Peach", "Red", "Pink"],
120
38
  wiiGlassesCols: ["Grey", "Brown", "Red", "Blue", "Yellow", "White"],
@@ -131,183 +49,182 @@ const lookupTables = {
131
49
  "9": 9,
132
50
  "7": 10,
133
51
  },
134
-
135
52
  pages:{
136
53
  mouths: {
137
- '0': '1',
138
- '1': '1',
139
- '2': '2',
140
- '3': '2',
141
- '4': '2',
142
- '5': '1',
143
- '6': '1',
144
- '7': '2',
145
- '8': '1',
146
- '9': '2',
147
- '10': '1',
148
- '11': '2',
149
- '12': '2',
150
- '13': '1',
151
- '14': '2',
152
- '15': '2',
153
- '16': '1',
154
- '17': '2',
155
- '18': '2',
156
- '19': '1',
157
- '20': '2',
158
- '21': '1',
159
- '22': '1',
160
- '23': '1'
54
+ '0': 1,
55
+ '1': 1,
56
+ '2': 2,
57
+ '3': 2,
58
+ '4': 2,
59
+ '5': 1,
60
+ '6': 1,
61
+ '7': 2,
62
+ '8': 1,
63
+ '9': 2,
64
+ '10': 1,
65
+ '11': 2,
66
+ '12': 2,
67
+ '13': 1,
68
+ '14': 2,
69
+ '15': 2,
70
+ '16': 1,
71
+ '17': 2,
72
+ '18': 2,
73
+ '19': 1,
74
+ '20': 2,
75
+ '21': 1,
76
+ '22': 1,
77
+ '23': 1
161
78
  },
162
79
  eyebrows:{
163
- '0': '1',
164
- '1': '1',
165
- '2': '2',
166
- '3': '2',
167
- '4': '1',
168
- '5': '1',
169
- '6': '1',
170
- '7': '1',
171
- '8': '1',
172
- '9': '1',
173
- '10': '2',
174
- '11': '2',
175
- '12': '1',
176
- '13': '2',
177
- '14': '2',
178
- '15': '2',
179
- '16': '2',
180
- '17': '1',
181
- '18': '2',
182
- '19': '1',
183
- '20': '2',
184
- '21': '1',
185
- '22': '2',
186
- '23': '2'
80
+ '0': 1,
81
+ '1': 1,
82
+ '2': 2,
83
+ '3': 2,
84
+ '4': 1,
85
+ '5': 1,
86
+ '6': 1,
87
+ '7': 1,
88
+ '8': 1,
89
+ '9': 1,
90
+ '10': 2,
91
+ '11': 2,
92
+ '12': 1,
93
+ '13': 2,
94
+ '14': 2,
95
+ '15': 2,
96
+ '16': 2,
97
+ '17': 1,
98
+ '18': 2,
99
+ '19': 1,
100
+ '20': 2,
101
+ '21': 1,
102
+ '22': 2,
103
+ '23': 2
187
104
  },
188
105
  eyes:{
189
- '0': '1',
190
- '1': '1',
191
- '2': '1',
192
- '3': '4',
193
- '4': '1',
194
- '5': '3',
195
- '6': '3',
196
- '7': '4',
197
- '8': '1',
198
- '9': '2',
199
- '10': '4',
200
- '11': '2',
201
- '12': '2',
202
- '13': '3',
203
- '14': '4',
204
- '15': '1',
205
- '16': '1',
206
- '17': '1',
207
- '18': '3',
208
- '19': '2',
209
- '20': '1',
210
- '21': '2',
211
- '22': '4',
212
- '23': '2',
213
- '24': '3',
214
- '25': '2',
215
- '26': '1',
216
- '27': '1',
217
- '28': '3',
218
- '29': '4',
219
- '30': '3',
220
- '31': '3',
221
- '32': '2',
222
- '33': '2',
223
- '34': '2',
224
- '35': '2',
225
- '36': '3',
226
- '37': '3',
227
- '38': '4',
228
- '39': '1',
229
- '40': '2',
230
- '41': '3',
231
- '42': '4',
232
- '43': '4',
233
- '44': '4',
234
- '45': '4',
235
- '46': '3',
236
- '47': '4'
106
+ 0: 1,
107
+ 1: 1,
108
+ 2: 1,
109
+ 3: 4,
110
+ 4: 1,
111
+ 5: 3,
112
+ 6: 3,
113
+ 7: 4,
114
+ 8: 1,
115
+ 9: 2,
116
+ 10: 4,
117
+ 11: 2,
118
+ 12: 2,
119
+ 13: 3,
120
+ 14: 4,
121
+ 15: 1,
122
+ 16: 1,
123
+ 17: 1,
124
+ 18: 3,
125
+ 19: 2,
126
+ 20: 1,
127
+ 21: 2,
128
+ 22: 4,
129
+ 23: 2,
130
+ 24: 3,
131
+ 25: 2,
132
+ 26: 1,
133
+ 27: 1,
134
+ 28: 3,
135
+ 29: 4,
136
+ 30: 3,
137
+ 31: 3,
138
+ 32: 2,
139
+ 33: 2,
140
+ 34: 2,
141
+ 35: 2,
142
+ 36: 3,
143
+ 37: 3,
144
+ 38: 4,
145
+ 39: 1,
146
+ 40: 2,
147
+ 41: 3,
148
+ 42: 4,
149
+ 43: 4,
150
+ 44: 4,
151
+ 45: 4,
152
+ 46: 3,
153
+ 47: 4
237
154
  },
238
155
  hairs:{
239
- '0': '5',
240
- '1': '4',
241
- '2': '6',
242
- '3': '5',
243
- '4': '4',
244
- '5': '4',
245
- '6': '5',
246
- '7': '4',
247
- '8': '4',
248
- '9': '6',
249
- '10': '5',
250
- '11': '5',
251
- '12': '4',
252
- '13': '4',
253
- '14': '5',
254
- '15': '6',
255
- '16': '6',
256
- '17': '5',
257
- '18': '6',
258
- '19': '4',
259
- '20': '5',
260
- '21': '5',
261
- '22': '5',
262
- '23': '3',
263
- '24': '6',
264
- '25': '4',
265
- '26': '4',
266
- '27': '4',
267
- '28': '6',
268
- '29': '6',
269
- '30': '3',
270
- '31': '1',
271
- '32': '2',
272
- '33': '1',
273
- '34': '3',
274
- '35': '5',
275
- '36': '3',
276
- '37': '2',
277
- '38': '3',
278
- '39': '1',
279
- '40': '1',
280
- '41': '3',
281
- '42': '3',
282
- '43': '3',
283
- '44': '1',
284
- '45': '1',
285
- '46': '6',
286
- '47': '2',
287
- '48': '2',
288
- '49': '1',
289
- '50': '2',
290
- '51': '1',
291
- '52': '2',
292
- '53': '6',
293
- '54': '3',
294
- '55': '2',
295
- '56': '1',
296
- '57': '3',
297
- '58': '2',
298
- '59': '1',
299
- '60': '2',
300
- '61': '6',
301
- '62': '2',
302
- '63': '5',
303
- '64': '2',
304
- '65': '3',
305
- '66': '2',
306
- '67': '3',
307
- '68': '1',
308
- '69': '4',
309
- '70': '1',
310
- '71': '6'
156
+ '0': 5,
157
+ '1': 4,
158
+ '2': 6,
159
+ '3': 5,
160
+ '4': 4,
161
+ '5': 4,
162
+ '6': 5,
163
+ '7': 4,
164
+ '8': 4,
165
+ '9': 6,
166
+ '10': 5,
167
+ '11': 5,
168
+ '12': 4,
169
+ '13': 4,
170
+ '14': 5,
171
+ '15': 6,
172
+ '16': 6,
173
+ '17': 5,
174
+ '18': 6,
175
+ '19': 4,
176
+ '20': 5,
177
+ '21': 5,
178
+ '22': 5,
179
+ '23': 3,
180
+ '24': 6,
181
+ '25': 4,
182
+ '26': 4,
183
+ '27': 4,
184
+ '28': 6,
185
+ '29': 6,
186
+ '30': 3,
187
+ '31': 1,
188
+ '32': 2,
189
+ '33': 1,
190
+ '34': 3,
191
+ '35': 5,
192
+ '36': 3,
193
+ '37': 2,
194
+ '38': 3,
195
+ '39': 1,
196
+ '40': 1,
197
+ '41': 3,
198
+ '42': 3,
199
+ '43': 3,
200
+ '44': 1,
201
+ '45': 1,
202
+ '46': 6,
203
+ '47': 2,
204
+ '48': 2,
205
+ '49': 1,
206
+ '50': 2,
207
+ '51': 1,
208
+ '52': 2,
209
+ '53': 6,
210
+ '54': 3,
211
+ '55': 2,
212
+ '56': 1,
213
+ '57': 3,
214
+ '58': 2,
215
+ '59': 1,
216
+ '60': 2,
217
+ '61': 6,
218
+ '62': 2,
219
+ '63': 5,
220
+ '64': 2,
221
+ '65': 3,
222
+ '66': 2,
223
+ '67': 3,
224
+ '68': 1,
225
+ '69': 4,
226
+ '70': 1,
227
+ '71': 6
311
228
  }
312
229
  },
313
230
  types:{
@@ -488,6 +405,196 @@ const lookupTables = {
488
405
  "71": 8
489
406
  }
490
407
  },
408
+ wiiNoses:{
409
+ '0': 1,
410
+ '1': 10,
411
+ '2': 2,
412
+ '3': 3,
413
+ '4': 6,
414
+ '5': 0,
415
+ '6': 5,
416
+ '7': 4,
417
+ '8': 8,
418
+ '9': 9,
419
+ '10': 7,
420
+ '11': 11
421
+ },
422
+ mouthTable:{
423
+ '0': '113',
424
+ '1': '121',
425
+ '2': '231',
426
+ '3': '222',
427
+ '4': '232',
428
+ '5': '132',
429
+ '6': '124',
430
+ '7': '211',
431
+ '8': '123',
432
+ '9': '221',
433
+ '10': '133',
434
+ '11': '223',
435
+ '12': '234',
436
+ '13': '134',
437
+ '14': '224',
438
+ '15': '213',
439
+ '16': '114',
440
+ '17': '212',
441
+ '18': '214',
442
+ '19': '131',
443
+ '20': '233',
444
+ '21': '112',
445
+ '22': '122',
446
+ '23': '111'
447
+ },
448
+ eyebrowTable:{
449
+ '0': '121',
450
+ '1': '112',
451
+ '2': '231',
452
+ '3': '212',
453
+ '4': '134',
454
+ '5': '124',
455
+ '6': '111',
456
+ '7': '113',
457
+ '8': '133',
458
+ '9': '122',
459
+ '10': '221',
460
+ '11': '211',
461
+ '12': '131',
462
+ '13': '223',
463
+ '14': '222',
464
+ '15': '213',
465
+ '16': '224',
466
+ '17': '114',
467
+ '18': '214',
468
+ '19': '132',
469
+ '20': '232',
470
+ '21': '123',
471
+ '22': '233',
472
+ '23': '234'
473
+ },
474
+ eyeTable:{
475
+ '0': '131',
476
+ '1': '113',
477
+ '2': '111',
478
+ '3': '413',
479
+ '4': '121',
480
+ '5': '311',
481
+ '6': '332',
482
+ '7': '411',
483
+ '8': '112',
484
+ '9': '222',
485
+ '10': '414',
486
+ '11': '221',
487
+ '12': '232',
488
+ '13': '331',
489
+ '14': '424',
490
+ '15': '114',
491
+ '16': '133',
492
+ '17': '132',
493
+ '18': '314',
494
+ '19': '231',
495
+ '20': '134',
496
+ '21': '233',
497
+ '22': '433',
498
+ '23': '213',
499
+ '24': '313',
500
+ '25': '214',
501
+ '26': '123',
502
+ '27': '124',
503
+ '28': '324',
504
+ '29': '432',
505
+ '30': '323',
506
+ '31': '333',
507
+ '32': '212',
508
+ '33': '211',
509
+ '34': '223',
510
+ '35': '234',
511
+ '36': '312',
512
+ '37': '322',
513
+ '38': '431',
514
+ '39': '122',
515
+ '40': '224',
516
+ '41': '321',
517
+ '42': '412',
518
+ '43': '423',
519
+ '44': '421',
520
+ '45': '422',
521
+ '46': '334',
522
+ '47': '434'
523
+ },
524
+ hairTable:{
525
+ '0': '534',
526
+ '1': '413',
527
+ '2': '632',
528
+ '3': '521',
529
+ '4': '422',
530
+ '5': '433',
531
+ '6': '522',
532
+ '7': '434',
533
+ '8': '414',
534
+ '9': '612',
535
+ '10': '512',
536
+ '11': '513',
537
+ '12': '411',
538
+ '13': '421',
539
+ '14': '511',
540
+ '15': '624',
541
+ '16': '621',
542
+ '17': '533',
543
+ '18': '622',
544
+ '19': '423',
545
+ '20': '532',
546
+ '21': '524',
547
+ '22': '531',
548
+ '23': '312',
549
+ '24': '614',
550
+ '25': '432',
551
+ '26': '412',
552
+ '27': '424',
553
+ '28': '613',
554
+ '29': '634',
555
+ '30': '314',
556
+ '31': '134',
557
+ '32': '211',
558
+ '33': '111',
559
+ '34': '334',
560
+ '35': '514',
561
+ '36': '313',
562
+ '37': '231',
563
+ '38': '321',
564
+ '39': '122',
565
+ '40': '121',
566
+ '41': '323',
567
+ '42': '331',
568
+ '43': '311',
569
+ '44': '112',
570
+ '45': '113',
571
+ '46': '631',
572
+ '47': '221',
573
+ '48': '212',
574
+ '49': '123',
575
+ '50': '223',
576
+ '51': '131',
577
+ '52': '232',
578
+ '53': '623',
579
+ '54': '332',
580
+ '55': '233',
581
+ '56': '114',
582
+ '57': '324',
583
+ '58': '213',
584
+ '59': '133',
585
+ '60': '224',
586
+ '61': '611',
587
+ '62': '234',
588
+ '63': '523',
589
+ '64': '214',
590
+ '65': '333',
591
+ '66': '222',
592
+ '67': '322',
593
+ '68': '124',
594
+ '69': '431',
595
+ '70': '132',
596
+ '71': '633'
597
+ },
491
598
 
492
599
  // 3DS fields
493
600
  faceFeatures3DS: ["None", "Near Eye Creases", "Cheek Creases", "Far Eye Creases", "Near Nose Creases", "Giant Bags", "Cleft Chin", "Chin Crease", "Sunken Eyes", "Far Cheek Creases", "Lines Near Eyes", "Wrinkles"],
@@ -558,7 +665,6 @@ const lookupTables = {
558
665
  ]
559
666
  }
560
667
  };
561
-
562
668
  var convTables={
563
669
  face3DSToWii:[0,1,2,2,3,1,4,5,4,6,7,6],
564
670
  features3DSToWii:["0","6",5,6,"6",4,7,7,8,10,"6",11],//If typeof===String, choose a makeup in that field's place - there is no suitable replacement. Read the discrepancies in the README for more information.
@@ -735,6 +841,18 @@ var convTables={
735
841
  "9",5,2,
736
842
  3,7,8,
737
843
  "10",9,11
844
+ ],
845
+ formatTo:[
846
+ [0,1,2],
847
+ [3,4,5],
848
+ [6,7,8],
849
+ [9,10,11]
850
+ ],
851
+ formatFrom:[
852
+ "11","21","31",
853
+ "12","22","32",
854
+ "13","23","33",
855
+ "14","24","34"
738
856
  ]
739
857
  };
740
858
  const kidNames={
@@ -1027,6 +1145,126 @@ const kidNames={
1027
1145
  ]
1028
1146
  };
1029
1147
 
1148
+ //Tools
1149
+ function Uint8Cat(){
1150
+ var destLength = 0
1151
+ for(var i = 0;i < arguments.length;i++){
1152
+ destLength += arguments[i].length;
1153
+ }
1154
+ var dest = new Uint8Array(destLength);
1155
+ var index = 0;
1156
+ for(var i=0;i<arguments.length;i++){
1157
+ dest.set(arguments[i],index);
1158
+ index += arguments[i].length;
1159
+ }
1160
+ return dest;
1161
+ }
1162
+ async function downloadImage(url) {
1163
+ return new Promise((resolve, reject) => {
1164
+ httpsLib.get(url, (res) => {
1165
+ if (res.statusCode === 200) {
1166
+ const data = [];
1167
+ res.on('data', chunk => data.push(chunk));
1168
+ res.on('end', () => resolve(Buffer.concat(data)));
1169
+ res.on('error', reject);
1170
+ } else {
1171
+ res.resume();
1172
+ reject(new Error(`Request Failed With a Status Code: ${res.statusCode}`));
1173
+ }
1174
+ });
1175
+ });
1176
+ }
1177
+ function byteToString(int){
1178
+ var str = int.toString(16);
1179
+ if(str.length < 2)str = '0' + str;
1180
+ return str;
1181
+ }
1182
+ function getBinaryFromAddress(addr, bin){
1183
+ let byte = bin.readUInt8(addr);
1184
+ let binaryString = '';
1185
+ for (let i = 7; i >= 0; i--) {
1186
+ binaryString += ((byte >> i) & 1) ? '1' : '0';
1187
+ }
1188
+ return binaryString;
1189
+ }
1190
+ function getKeyByValue(object, value) {
1191
+ for (var key in object) {
1192
+ if (object[key] === value) {
1193
+ return key;
1194
+ }
1195
+ }
1196
+ }
1197
+ function lookupTable(table,value,paginated){
1198
+ if(paginated){
1199
+ for(var i=0;i<lookupTables[table].values.length;i++){
1200
+ for(var j=0;j<lookupTables[table].values[i].length;j++){
1201
+ if(lookupTables[table].values[i][j]===value){
1202
+ return [i,j];
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1207
+ else{
1208
+ for(var i=0;i<lookupTables[table].values.length;i++){
1209
+ if(lookupTables[table].values[i]===value){
1210
+ return i;
1211
+ }
1212
+ }
1213
+ }
1214
+ return undefined;
1215
+ }
1216
+
1217
+ //If FFLResHigh.dat is in the same directory as Node.js is calling the library from, use it by default
1218
+ let _fflRes; // undefined initially
1219
+ function getFFLRes() {
1220
+ // If we've already tried loading, just return the result
1221
+ if (_fflRes !== undefined) return _fflRes;
1222
+ for (const path of [ "./FFLResHigh.dat", "./ffl/FFLResHigh.dat" ]) {
1223
+ if (fs.existsSync(path)) {
1224
+ // Convert Buffer to Uint8Array explicitly
1225
+ const buffer = fs.readFileSync(path);
1226
+ return _fflRes = new Uint8Array(buffer);
1227
+ }
1228
+ }
1229
+ // If no file found, mark as null
1230
+ return _fflRes = null;
1231
+ }
1232
+
1233
+ //3DS QR Code (En|De)cryption
1234
+ var NONCE_OFFSET = 0xC;
1235
+ var NONCE_LENGTH = 8;
1236
+ var TAG_LENGTH = 0x10;
1237
+ var aes_key = new Uint8Array([0x59, 0xFC, 0x81, 0x7E, 0x64, 0x46, 0xEA, 0x61, 0x90, 0x34, 0x7B, 0x20, 0xE9, 0xBD, 0xCE, 0x52]);
1238
+ var pad = new Uint8Array([0,0,0,0]);
1239
+ function decodeAesCcm(data){
1240
+ var nonce = Uint8Cat(data.subarray(0,NONCE_LENGTH),pad);
1241
+ var ciphertext = data.subarray(NONCE_LENGTH,0x70);
1242
+ var plaintext = asmCrypto.AES_CCM.decrypt(ciphertext,aes_key,nonce,undefined,TAG_LENGTH);
1243
+ return Uint8Cat(plaintext.subarray(0,NONCE_OFFSET),data.subarray(0,NONCE_LENGTH),plaintext.subarray(NONCE_OFFSET,plaintext.length - 4));
1244
+ }
1245
+ function crcCalc(data){
1246
+ var crc = 0;
1247
+ for (var byteIndex = 0;byteIndex < data.length; byteIndex++){
1248
+ for (var bitIndex = 7; bitIndex >= 0; bitIndex--){
1249
+ crc = (((crc << 1) | ((data[byteIndex] >> bitIndex) & 0x1)) ^
1250
+ (((crc & 0x8000) != 0) ? 0x1021 : 0));
1251
+ }
1252
+ }
1253
+ for(var counter = 16; counter > 0; counter--){
1254
+ crc = ((crc << 1) ^ (((crc & 0x8000) != 0) ? 0x1021 : 0));
1255
+ }
1256
+ return(crc & 0xFFFF);
1257
+ }
1258
+ function encodeAesCcm(data){
1259
+ var nonce = Uint8Cat(data.subarray(NONCE_OFFSET,NONCE_OFFSET + NONCE_LENGTH),pad);
1260
+ var crcSrc = Uint8Cat(data,new Uint8Array([0,0]));
1261
+ var crc = crcCalc(crcSrc);
1262
+ var cfsd = Uint8Cat(crcSrc,new Uint8Array([crc >>> 8,crc & 0xff]));
1263
+ var plaintext = Uint8Cat(cfsd.subarray(0,NONCE_OFFSET),cfsd.subarray(NONCE_OFFSET + NONCE_LENGTH,cfsd.length),pad,pad);
1264
+ var ciphertext = asmCrypto.AES_CCM.encrypt(plaintext,aes_key,nonce,undefined,TAG_LENGTH);
1265
+ return Uint8Cat(cfsd.subarray(NONCE_OFFSET,NONCE_OFFSET + NONCE_LENGTH),ciphertext.subarray(0,ciphertext.length - 24),ciphertext.subarray(ciphertext.length - TAG_LENGTH,ciphertext.length))
1266
+ }
1267
+
1030
1268
  //Defaults
1031
1269
  const defaultInstrs={
1032
1270
  wii:{
@@ -1510,7 +1748,30 @@ const encoders = {
1510
1748
  }
1511
1749
  return undefined;
1512
1750
  }
1513
- },
1751
+ },
1752
+ encodingTable: (decodedValue, field, tables) => {
1753
+ const table = getNestedProperty(tables, field.encodingTable);
1754
+ console.log(table);
1755
+ if (!table) return "ERROR: could not find requested encoding table";
1756
+
1757
+ if (table.indexLookup){
1758
+ if (table.paginated) {
1759
+ if (!Array.isArray(decodedValue) || decodedValue.length !== 2) {
1760
+ return undefined;
1761
+ }
1762
+ const [page, index] = decodedValue;
1763
+ if (page >= 0 && page < table.values.length && index >= 0 && index < table.values[page].length) {
1764
+ return table.values[page][index];
1765
+ }
1766
+ return undefined;
1767
+ } else {
1768
+ return "ERROR";
1769
+ }
1770
+ }
1771
+ else{
1772
+ return "ERROR";
1773
+ }
1774
+ },
1514
1775
  color: (value, field, T) => {
1515
1776
  const arr = T[field.colorArray];
1516
1777
  return arr?.indexOf(value) ?? value;
@@ -1690,7 +1951,13 @@ function jsonToMiiBuffer(miiData, schema, lookupTables = {}, totalBytes = 74) {
1690
1951
  let raw = getNestedProperty(miiData, fieldPath);
1691
1952
 
1692
1953
  // Run encoders
1693
- if (fieldDef.decoder && typeof encoders !== 'undefined' && encoders[fieldDef.decoder]) {
1954
+ if (fieldDef.encodingTable && fieldDef.decoder && typeof encoders !== 'undefined' && encoders[fieldDef.decoder]){
1955
+ if(fieldDef.encodingTable==="NONE"){
1956
+ continue;
1957
+ }
1958
+ raw = encoders.encodingTable([raw,getNestedProperty(miiData,fieldDef.secondaryParameter)], fieldDef, lookupTables);
1959
+ }
1960
+ else if (fieldDef.decoder && typeof encoders !== 'undefined' && encoders[fieldDef.decoder]) {
1694
1961
  raw = encoders[fieldDef.decoder](raw, fieldDef, lookupTables);
1695
1962
  }
1696
1963
 
@@ -1725,7 +1992,7 @@ const WII_MII_SCHEMA = {
1725
1992
  'face.type': { byteOffset: 0x20, bitOffset: 0, bitLength: 3, decoder: 'number' },
1726
1993
  'face.color': { byteOffset: 0x20, bitOffset: 3, bitLength: 3, decoder: 'number' },
1727
1994
  'face.feature': { byteOffset: 0x20, bitOffset: 6, bitLength: 4, decoder: 'number' },
1728
- 'hair.page': { byteOffset: 0x22, bitOffset: 0, bitLength: 7, decoder: 'lookup', lookupTable: 'hairs' },
1995
+ 'hair.page': { byteOffset: 0x22, bitOffset: 0, bitLength: 7, decoder: 'lookup', lookupTable: 'pages.hairs' },//Refuse, marked for destruction.
1729
1996
  'hair.type': { byteOffset: 0x22, bitOffset: 0, bitLength: 7, decoder: 'lookup', lookupTable: 'types.hairs' },
1730
1997
  'hair.color': { byteOffset: 0x22, bitOffset: 7, bitLength: 3, decoder: 'number' },
1731
1998
  'hair.flipped': { byteOffset: 0x23, bitOffset: 2, bitLength: 1, decoder: 'boolean' },
@@ -1736,8 +2003,8 @@ const WII_MII_SCHEMA = {
1736
2003
  'eyebrows.size': { byteOffset: 0x26, bitOffset: 3, bitLength: 4, decoder: 'number' },
1737
2004
  'eyebrows.yPosition': { byteOffset: 0x26, bitOffset: 7, bitLength: 5, decoder: 'number', offset: -3 },
1738
2005
  'eyebrows.distanceApart': { byteOffset: 0x27, bitOffset: 4, bitLength: 4, decoder: 'number' },
1739
- 'eyes.page': { byteOffset: 0x28, bitOffset: 0, bitLength: 6, decoder: 'lookup', lookupTable: 'pages.eyes' },
1740
- 'eyes.type': { byteOffset: 0x28, bitOffset: 0, bitLength: 6, decoder: 'lookup', lookupTable: 'types.eyes' },
2006
+ 'eyes.page': { byteOffset: 0x28, bitOffset: 0, bitLength: 6, decoder: 'lookup', lookupTable: 'pages.eyes', encodingTable: 'eyes', secondaryParameter: 'eyes.type' },//Refuse, marked for destruction.
2007
+ 'eyes.type': { byteOffset: 0x28, bitOffset: 0, bitLength: 6, decoder: 'lookup', lookupTable: 'types.eyes', encodingTable:'NONE' },
1741
2008
  'eyes.rotation': { byteOffset: 0x29, bitOffset: 0, bitLength: 3, decoder: 'number' },
1742
2009
  'eyes.yPosition': { byteOffset: 0x29, bitOffset: 3, bitLength: 5, decoder: 'number' },
1743
2010
  'eyes.color': { byteOffset: 0x2A, bitOffset: 0, bitLength: 3, decoder: 'number' },
@@ -1746,7 +2013,7 @@ const WII_MII_SCHEMA = {
1746
2013
  'nose.type': { byteOffset: 0x2C, bitOffset: 0, bitLength: 4, decoder: 'lookup', lookupTable: 'wiiNoses' },
1747
2014
  'nose.size': { byteOffset: 0x2C, bitOffset: 4, bitLength: 4, decoder: 'number' },
1748
2015
  'nose.yPosition': { byteOffset: 0x2D, bitOffset: 0, bitLength: 5, decoder: 'number' },
1749
- 'mouth.page': { byteOffset: 0x2E, bitOffset: 0, bitLength: 5, decoder: 'lookup', lookupTable: 'pages.mouths' },
2016
+ 'mouth.page': { byteOffset: 0x2E, bitOffset: 0, bitLength: 5, decoder: 'lookup', lookupTable: 'pages.mouths' },//Refuse, marked for destruction.
1750
2017
  'mouth.type': { byteOffset: 0x2E, bitOffset: 0, bitLength: 5, decoder: 'lookup', lookupTable: 'types.mouths' },
1751
2018
  'mouth.color': { byteOffset: 0x2E, bitOffset: 5, bitLength: 2, decoder: 'number' },
1752
2019
  'mouth.size': { byteOffset: 0x2E, bitOffset: 7, bitLength: 4, decoder: 'number' },
@@ -1930,8 +2197,8 @@ function convertMii(jsonIn,typeTo){
1930
2197
  miiTo.console="wii";
1931
2198
  }
1932
2199
  else if(typeFrom==="wii"){
1933
- miiTo.perms.sharing=mii.info.mingle;
1934
- miiTo.perms.copying=mii.info.mingle;
2200
+ miiTo.perms.sharing=mii.general.mingle;
2201
+ miiTo.perms.copying=mii.general.mingle;
1935
2202
  miiTo.hair.style=convTables.hairWiiTo3DS[mii.hair.page][mii.hair.type];
1936
2203
  miiTo.face.shape=convTables.faceWiiTo3DS[mii.face.shape];
1937
2204
  miiTo.face.makeup=0;
@@ -1997,7 +2264,7 @@ function convertMiiToStudio(jsonIn) {
1997
2264
  studioMii[0x23] = mii.mouth.squash;
1998
2265
  studioMii[0x27] = mii.mouth.yPosition;
1999
2266
  studioMii[0x29] = mii.beard.mustache.type;
2000
- studioMii[1] = mii.beard.beardType;
2267
+ studioMii[1] = mii.beard.type;
2001
2268
  studioMii[0] = mii.beard.color;
2002
2269
  if (!studioMii[0]) studioMii[0] = 8;
2003
2270
  studioMii[0x28] = mii.beard.mustache.size;
@@ -2026,10 +2293,121 @@ async function readWiiBin(binOrPath) {
2026
2293
  } else {
2027
2294
  data = Buffer.from(binOrPath);
2028
2295
  }
2296
+ var thisMii={
2297
+ general:{},
2298
+ perms:{},
2299
+ meta:{},
2300
+ face:{},
2301
+ nose:{},
2302
+ mouth:{},
2303
+ mole:{},
2304
+ hair:{},
2305
+ eyebrows:{},
2306
+ eyes:{},
2307
+ glasses:{},
2308
+ beard:{
2309
+ mustache:{}
2310
+ }
2311
+ };
2029
2312
 
2030
- const thisMii = miiBufferToJson(data, WII_MII_SCHEMA, lookupTables, true);
2031
- thisMii.console = 'wii';
2313
+ const get = address => getBinaryFromAddress(address, data);
2032
2314
 
2315
+ var name="";
2316
+ for(var i=0;i<10;i++){
2317
+ name+=data.slice(3+i*2, 4+i*2)+"";
2318
+ }
2319
+ thisMii.meta.name=name.replaceAll("\x00","");
2320
+ var cname="";
2321
+ for(var i=0;i<10;i++){
2322
+ cname+=data.slice(55+i*2, 56+i*2)+"";
2323
+ }
2324
+ thisMii.meta.creatorName=cname.replaceAll("\x00","");
2325
+ thisMii.general.gender=+get(0x00)[1];//0 for Male, 1 for Female
2326
+ thisMii.meta.miiId=parseInt(get(0x18),2).toString(16)+parseInt(get(0x19),2).toString(16)+parseInt(get(0x1A),2).toString(16)+parseInt(get(0x1B),2).toString(16);
2327
+ switch(thisMii.meta.miiId.slice(0,3)){
2328
+ case "010":
2329
+ thisMii.meta.type="Special";
2330
+ break;
2331
+ case "110":
2332
+ thisMii.meta.type="Foreign";
2333
+ break;
2334
+ default:
2335
+ thisMii.meta.type="Default";
2336
+ break;
2337
+ }
2338
+ thisMii.meta.systemId=parseInt(get(0x1C),2).toString(16)+parseInt(get(0x1D),2).toString(16)+parseInt(get(0x1E),2).toString(16)+parseInt(get(0x1F),2).toString(16);
2339
+ var temp=get(0x20);
2340
+ thisMii.face.type=parseInt(temp.slice(0,3),2);//0-7
2341
+ thisMii.face.color=parseInt(temp.slice(3,6),2);//0-5
2342
+ temp=get(0x21);
2343
+ thisMii.face.feature=parseInt(get(0x20).slice(6,8)+temp.slice(0,2),2);//0-11
2344
+ thisMii.perms.mingle=temp[5]==="0";//0 for Mingle, 1 for Don't Mingle
2345
+ temp=get(0x2C);
2346
+ thisMii.nose.type=+getKeyByValue(lookupTables.wiiNoses,parseInt(temp.slice(0,4),2));
2347
+ thisMii.nose.size=parseInt(temp.slice(4,8),2);
2348
+ thisMii.nose.yPosition=parseInt(get(0x2D).slice(0,5),2);//From top to bottom, 0-18, default 9
2349
+ temp=get(0x2E);
2350
+ thisMii.mouth.page=+lookupTables.mouthTable[""+parseInt(temp.slice(0,5),2)][0]-1;
2351
+ thisMii.mouth.type=convTables.formatTo[lookupTables.mouthTable[""+parseInt(temp.slice(0,5),2)][2]-1][lookupTables.mouthTable[""+parseInt(temp.slice(0,5),2)][1]-1];//0-23, Needs lookup table
2352
+ thisMii.mouth.color=parseInt(temp.slice(5,7),2);//0-2, refer to mouthColors array
2353
+ temp2=get(0x2F);
2354
+ thisMii.mouth.size=parseInt(temp[7]+temp2.slice(0,3),2);//0-8, default 4
2355
+ thisMii.mouth.yPosition=parseInt(temp2.slice(3,8),2);//0-18, default 9, from top to bottom
2356
+ temp=get(0x00);
2357
+ var temp2=get(0x01);
2358
+ thisMii.general.birthMonth=parseInt(temp.slice(2,6),2);
2359
+ thisMii.general.birthday=parseInt(temp.slice(6,8)+temp2.slice(0,3),2);
2360
+ thisMii.general.favoriteColor=parseInt(temp2.slice(3,7),2);//0-11, refer to cols array
2361
+ thisMii.general.height=parseInt(get(0x16),2);//0-127
2362
+ thisMii.general.weight=parseInt(get(0x17),2);//0-127
2363
+ thisMii.perms.fromCheckMiiOut=get(0x21)[7]==="0"?false:true;
2364
+ temp=get(0x34);
2365
+ temp2=get(0x35);
2366
+ thisMii.mole.on=temp[0]==="0"?false:true;//0 for Off, 1 for On
2367
+ thisMii.mole.size=parseInt(temp.slice(1,5),2);//0-8, default 4
2368
+ thisMii.mole.xPosition=parseInt(temp2.slice(2,7),2);//0-16, Default 2
2369
+ thisMii.mole.yPosition=parseInt(temp.slice(5,8)+temp2.slice(0,2),2);//Top to bottom
2370
+ temp=get(0x22);
2371
+ temp2=get(0x23);
2372
+ thisMii.hair.page=+lookupTables.hairTable[""+parseInt(temp.slice(0,7),2)][0]-1;
2373
+ 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
2374
+ thisMii.hair.color=parseInt(temp[7]+temp2.slice(0,2),2);//0-7, refer to hairCols array
2375
+ thisMii.hair.flipped=temp2[2]==="0"?false:true;
2376
+ temp=get(0x24);
2377
+ temp2=get(0x25);
2378
+ thisMii.eyebrows.page=+lookupTables.eyebrowTable[""+parseInt(temp.slice(0,5),2)][0]-1;
2379
+ thisMii.eyebrows.type=convTables.formatTo[lookupTables.eyebrowTable[""+parseInt(temp.slice(0,5),2)][2]-1][lookupTables.eyebrowTable[""+parseInt(temp.slice(0,5),2)][1]-1];//0-23, Needs lookup table
2380
+ thisMii.eyebrows.rotation=parseInt(temp.slice(6,8)+temp2.slice(0,2),2);//0-11, default varies based on eyebrow type
2381
+ temp=get(0x26);
2382
+ temp2=get(0x27);
2383
+ thisMii.eyebrows.color=parseInt(temp.slice(0,3),2);
2384
+ thisMii.eyebrows.size=parseInt(temp.slice(3,7),2);//0-8, default 4
2385
+ thisMii.eyebrows.yPosition=(parseInt(temp[7]+temp2.slice(0,4),2))-3;//0-15, default 10
2386
+ thisMii.eyebrows.distanceApart=parseInt(temp2.slice(4,8),2);//0-12, default 2
2387
+ thisMii.eyes.page=+lookupTables.eyeTable[parseInt(get(0x28).slice(0,6),2)][0]-1;//0-47, needs lookup table
2388
+ thisMii.eyes.type=convTables.formatTo[lookupTables.eyeTable[parseInt(get(0x28).slice(0,6),2)][2]-1][lookupTables.eyeTable[parseInt(get(0x28).slice(0,6),2)][1]-1];//0-47, needs lookup table
2389
+ temp=get(0x29);
2390
+ thisMii.eyes.rotation=parseInt(temp.slice(0,3),2);//0-7, default varies based on eye type
2391
+ thisMii.eyes.yPosition=parseInt(temp.slice(3,8),2);//0-18, default 12, top to bottom
2392
+ temp=get(0x2A);
2393
+ thisMii.eyes.color=parseInt(temp.slice(0,3),2);//0-5
2394
+ thisMii.eyes.size=parseInt(temp.slice(4,7),2);//0-7, default 4
2395
+ temp2=get(0x2B);
2396
+ thisMii.eyes.distanceApart=parseInt(temp[7]+temp2.slice(0,3),2);//0-12, default 2
2397
+ temp=get(0x30);
2398
+ thisMii.glasses.type=parseInt(temp.slice(0,4),2);//0-8
2399
+ thisMii.glasses.color=parseInt(temp.slice(4,7),2);//0-5
2400
+ temp=get(0x31);
2401
+ thisMii.glasses.size=parseInt(temp.slice(0,3),2);//0-7, default 4
2402
+ thisMii.glasses.yPosition=parseInt(temp.slice(3,8),2);//0-20, default 10
2403
+ temp=get(0x32);
2404
+ temp2=get(0x33);
2405
+ thisMii.beard.mustache.type=parseInt(temp.slice(0,2),2);//0-3
2406
+ thisMii.beard.type=parseInt(temp.slice(2,4),2);//0-3
2407
+ thisMii.beard.color=parseInt(temp.slice(4,7),2);//0-7
2408
+ thisMii.beard.mustache.size=parseInt(temp[7]+temp2.slice(0,3),2);//0-30, default 20
2409
+ thisMii.beard.mustache.yPosition=parseInt(temp2.slice(3,8),2);//0-16, default 2
2410
+ thisMii.console="Wii";
2033
2411
  return thisMii;
2034
2412
  }
2035
2413
  async function read3DSQR(binOrPath,returnDecryptedBin) {
@@ -2041,7 +2419,11 @@ async function read3DSQR(binOrPath,returnDecryptedBin) {
2041
2419
  const ctx = canvas.getContext('2d');
2042
2420
  ctx.drawImage(img, 0, 0);
2043
2421
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2044
- qrCode = jsQR(imageData.data, imageData.width, imageData.height).binaryData;
2422
+ qrCode = jsQR(imageData.data, imageData.width, imageData.height)?.binaryData;
2423
+ if(!qrCode){
2424
+ console.error("Failed to read QR Code.");
2425
+ return;
2426
+ }
2045
2427
  }
2046
2428
  else {
2047
2429
  var d = binOrPath.match(/(0|1){1,8}/g);
@@ -2055,11 +2437,130 @@ async function read3DSQR(binOrPath,returnDecryptedBin) {
2055
2437
  if(returnDecryptedBin){
2056
2438
  return data;
2057
2439
  }
2058
- const miiJson = miiBufferToJson(data, THREEDS_MII_SCHEMA, lookupTables, false);
2059
- miiJson.console = '3ds';
2440
+ const miiJson = {
2441
+ general:{},
2442
+ perms:{},
2443
+ meta:{},
2444
+ face:{},
2445
+ nose:{},
2446
+ mouth:{},
2447
+ mole:{},
2448
+ hair:{},
2449
+ eyebrows:{},
2450
+ eyes:{},
2451
+ glasses:{},
2452
+ beard:{
2453
+ mustache:{}
2454
+ }
2455
+ };
2456
+ const get = address => getBinaryFromAddress(address, data);
2457
+ var temp=get(0x18);
2458
+ var temp2=get(0x19);
2459
+ miiJson.general.birthday=parseInt(temp2.slice(6,8)+temp.slice(0,3),2);
2460
+ miiJson.general.birthMonth=parseInt(temp.slice(3,7),2);
2461
+ //Handle UTF-16 Names
2462
+ var name = "";
2463
+ for (var i = 0x1A; i < 0x2E; i += 2) {
2464
+ let lo = data[i];
2465
+ let hi = data[i + 1];
2466
+ if (lo === 0x00 && hi === 0x00) {
2467
+ break;
2468
+ }
2469
+ let codeUnit = (hi << 8) | lo;
2470
+ name += String.fromCharCode(codeUnit);
2471
+ }
2472
+ miiJson.meta.name = name.replace(/\u0000/g, "");
2473
+ var cname = "";
2474
+ for (var i = 0x48; i < 0x5C; i += 2) {
2475
+ let lo = data[i];
2476
+ let hi = data[i + 1];
2477
+ if (lo === 0x00 && hi === 0x00) {
2478
+ break;
2479
+ }
2480
+ let codeUnit = (hi << 8) | lo;
2481
+ cname += String.fromCharCode(codeUnit);
2482
+ }
2483
+ miiJson.meta.creatorName = cname.replace(/\u0000/g, "");
2484
+ miiJson.general.height=parseInt(get(0x2E),2);
2485
+ miiJson.general.weight=parseInt(get(0x2F),2);
2486
+ miiJson.general.gender=+temp[7];
2487
+ temp=get(0x30);
2488
+ miiJson.perms.sharing=temp[7]==="1"?false:true;
2489
+ miiJson.general.favoriteColor=parseInt(temp2.slice(2,6),2);
2490
+ miiJson.perms.copying=get(0x01)[7]==="1"?true:false;
2491
+ miiJson.hair.page=lookupTable("hairs",parseInt(get(0x32),2),true)[0];
2492
+ miiJson.hair.type=lookupTable("hairs",parseInt(get(0x32),2),true)[1];
2493
+ miiJson.face.type=lookupTable("faces",parseInt(temp.slice(3,7),2),false);
2494
+ miiJson.face.color=parseInt(temp.slice(0,3),2);
2495
+ temp=get(0x31);
2496
+ miiJson.face.feature=parseInt(temp.slice(4,8),2);
2497
+ miiJson.face.makeup=parseInt(temp.slice(0,4),2);
2498
+ temp=get(0x34);
2499
+ miiJson.eyes.page=lookupTable("eyes",parseInt(temp.slice(2,8),2),true)[0];
2500
+ miiJson.eyes.type=lookupTable("eyes",parseInt(temp.slice(2,8),2),true)[1];
2501
+ temp2=get(0x33);
2502
+ miiJson.hair.color=parseInt(temp2.slice(5,8),2);
2503
+ miiJson.hair.flipped=temp2[4]==="0"?false:true;
2504
+ miiJson.eyes.color=parseInt(get(0x35)[7]+temp.slice(0,2),2);
2505
+ temp=get(0x35);
2506
+ miiJson.eyes.size=parseInt(temp.slice(3,7),2);
2507
+ miiJson.eyes.squash=parseInt(temp.slice(0,3),2);
2508
+ temp=get(0x36);
2509
+ temp2=get(0x37);
2510
+ miiJson.eyes.rotation=parseInt(temp.slice(3,8),2);
2511
+ miiJson.eyes.distanceApart=parseInt(temp2[7]+temp.slice(0,3),2);
2512
+ miiJson.eyes.yPosition=parseInt(temp2.slice(2,7),2);
2513
+ temp=get(0x38);
2514
+ miiJson.eyebrows.page=lookupTable("eyebrows",parseInt(temp.slice(3,8),2),true)[0];
2515
+ miiJson.eyebrows.type=lookupTable("eyebrows",parseInt(temp.slice(3,8),2),true)[1];
2516
+ miiJson.eyebrows.color=parseInt(temp.slice(0,3),2);
2517
+ temp=get(0x39);
2518
+ miiJson.eyebrows.size=parseInt(temp.slice(4,8),2);
2519
+ miiJson.eyebrows.squash=parseInt(temp.slice(1,4),2);
2520
+ temp=get(0x3A);
2521
+ miiJson.eyebrows.rotation=parseInt(temp.slice(4,8),2);
2522
+ temp2=get(0x3B);
2523
+ miiJson.eyebrows.distanceApart=parseInt(temp2[7]+temp.slice(0,3),2);
2524
+ miiJson.eyebrows.yPosition=parseInt(temp2.slice(2,7),2)-3;
2525
+ temp=get(0x3C);
2526
+ miiJson.nose.page=lookupTable("noses",parseInt(temp.slice(3,8),2),true)[0];
2527
+ miiJson.nose.type=lookupTable("noses",parseInt(temp.slice(3,8),2),true)[1];
2528
+ temp2=get(0x3D);
2529
+ miiJson.nose.size=parseInt(temp2[7]+temp.slice(0,3),2);
2530
+ miiJson.nose.yPosition=parseInt(temp2.slice(2,7),2);
2531
+ temp=get(0x3E);
2532
+ miiJson.mouth.page=lookupTable("mouths",parseInt(temp.slice(2,8),2),true)[0];
2533
+ miiJson.mouth.type=lookupTable("mouths",parseInt(temp.slice(2,8),2),true)[1];
2534
+ temp2=get(0x3F);
2535
+ miiJson.mouth.color=parseInt(temp2[7]+temp.slice(0,2),2);
2536
+ miiJson.mouth.size=parseInt(temp2.slice(3,7),2);
2537
+ miiJson.mouth.squash=parseInt(temp2.slice(0,3),2);
2538
+ temp=get(0x40);
2539
+ miiJson.mouth.yPosition=parseInt(temp.slice(3,8),2);
2540
+ miiJson.beard.mustache.type=parseInt(temp.slice(0,3),2);
2541
+ temp=get(0x42);
2542
+ miiJson.beard.type=parseInt(temp.slice(5,8),2);
2543
+ miiJson.beard.color=parseInt(temp.slice(2,5),2);
2544
+ temp2=get(0x43);
2545
+ miiJson.beard.mustache.size=parseInt(temp2.slice(6,8)+temp.slice(0,2),2);
2546
+ miiJson.beard.mustache.yPosition=parseInt(temp2.slice(1,6),2);
2547
+ temp=get(0x44);
2548
+ miiJson.glasses.type=parseInt(temp.slice(4,8),2);
2549
+ miiJson.glasses.color=parseInt(temp.slice(1,4),2);
2550
+ temp2=get(0x45);
2551
+ miiJson.glasses.size=parseInt(temp2.slice(5,8)+temp[0],2);
2552
+ miiJson.glasses.yPosition=parseInt(temp2.slice(0,5),2);
2553
+ temp=get(0x46);
2554
+ miiJson.mole.on=temp[7]==="0"?false:true;
2555
+ miiJson.mole.size=parseInt(temp.slice(3,7),2);
2556
+ temp2=get(0x47);
2557
+ miiJson.mole.xPosition=parseInt(temp2.slice(6,8)+temp.slice(0,3),2);
2558
+ miiJson.mole.yPosition=parseInt(temp2.slice(1,6),2);
2559
+ miiJson.meta.type="Default";//qk, Make this actually retrieve MiiID, SystemID, and Mii type
2560
+ miiJson.console="3DS";
2060
2561
  return miiJson;
2061
2562
  } else {
2062
- console.error('Failed to decode QR code');
2563
+ console.error('Failed to read Mii.');
2063
2564
  }
2064
2565
  }
2065
2566
  async function renderMiiWithStudio(jsonIn){
@@ -2069,7 +2570,7 @@ async function renderMiiWithStudio(jsonIn){
2069
2570
  var studioMii=convertMiiToStudio(jsonIn);
2070
2571
  return await downloadImage('https://studio.mii.nintendo.com/miis/image.png?data=' + studioMii + "&width=270&type=face");
2071
2572
  }
2072
- async function createFFLMiiIcon(data, width, height, fflRes) {
2573
+ async function createFFLMiiIcon(data, width, height, useBody, shirtColor, fflRes) {
2073
2574
  /**
2074
2575
  * Creates a Mii face render using FFL.js/Three.js/gl-headless.
2075
2576
  * @example
@@ -2109,11 +2610,18 @@ async function createFFLMiiIcon(data, width, height, fflRes) {
2109
2610
  setIsWebGL1State(!renderer.capabilities.isWebGL2); // Tell FFL.js we are WebGL1
2110
2611
 
2111
2612
  const scene = new THREE.Scene();
2112
- // scene.background = null; // Transparent background.
2113
- scene.background = new THREE.Color('white');
2114
- // (You DO NOT need to add any lights to the scene,
2115
- // unless you are using a Three.js built-in material.
2116
- // If you are, look at demo-basic.js "addLightsToScene".)
2613
+ scene.background = null; // Transparent background.
2614
+
2615
+ if(useBody){
2616
+ // After: const scene = new THREE.Scene(); scene.background = null;
2617
+ const ambient = new THREE.AmbientLight(0xffffff, 0.15);
2618
+ scene.add(ambient);
2619
+
2620
+ const rim = new THREE.DirectionalLight(0xffffff, 3);
2621
+ rim.position.set(0.5, -7, -1.0);
2622
+ scene.add(rim);
2623
+
2624
+ }
2117
2625
 
2118
2626
  let ffl, currentCharModel;
2119
2627
 
@@ -2129,11 +2637,61 @@ async function createFFLMiiIcon(data, width, height, fflRes) {
2129
2637
  // Convert Uint8Array to Buffer for struct-fu compatibility
2130
2638
  const studioBuffer = Buffer.from(studioRaw);
2131
2639
 
2132
- currentCharModel = createCharModel(studioBuffer, null,
2133
- FFLShaderMaterial, ffl.module);
2640
+ currentCharModel = createCharModel(studioBuffer, null, FFLShaderMaterial, ffl.module);
2134
2641
  initCharModelTextures(currentCharModel, renderer); // Initialize fully
2135
2642
  scene.add(currentCharModel.meshes); // Add to scene
2136
2643
 
2644
+ //Add body
2645
+ if (useBody) {
2646
+ if (typeof GLTFLoader === 'undefined' || !GLTFLoader) {
2647
+ const mod = await import('three/examples/jsm/loaders/GLTFLoader.js');
2648
+ GLTFLoader = mod.GLTFLoader;
2649
+ }
2650
+ //Read GLB from disk and parse (avoids URL/fetch issues)
2651
+ const absPath = path.resolve(__dirname, './mii-body.glb');
2652
+ const buf = fs.readFileSync(absPath);
2653
+ const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
2654
+ const loader = new GLTFLoader();
2655
+ const gltf = await new Promise((resolve, reject) => {
2656
+ loader.parse(
2657
+ arrayBuffer,
2658
+ path.dirname(absPath) + path.sep,
2659
+ resolve,
2660
+ reject
2661
+ );
2662
+ });
2663
+
2664
+ const body = gltf.scene;
2665
+
2666
+ body.position.y-=110;
2667
+
2668
+ //Recolor
2669
+ body.userData.isMiiBody = true;
2670
+ body.traverse(o => {
2671
+ if (o.isMesh) {
2672
+ if (!o.geometry.attributes.normal) {
2673
+ o.geometry.computeVertexNormals();
2674
+ }
2675
+ const isShirt = (o.name === 'mesh_1_');
2676
+ o.material?.dispose?.();
2677
+ o.material = new THREE.MeshLambertMaterial({
2678
+ //["Red", "Orange", "Yellow", "Lime", "Green", "Blue", "Cyan", "Pink", "Purple", "Brown", "White", "Black"]
2679
+ color: isShirt ? [0xFF2400,0xF08000,0xFFD700,0xAAFF00,0x008000,0x0000FF,0x00D7FF,0xFF69B4,0x7F00FF,0x6F4E37,0xFFFFFF,0x303030][shirtColor] : 0x808080,
2680
+ emissive: isShirt ? 0x330000 : 0x222222,
2681
+ emissiveIntensity: 0.0
2682
+ });
2683
+ o.material.side = THREE.DoubleSide;
2684
+ o.material.needsUpdate = true;
2685
+ }
2686
+ });
2687
+
2688
+
2689
+
2690
+ // (6) Add to scene
2691
+ scene.add(body);
2692
+ }
2693
+
2694
+
2137
2695
  // Use the camera for an icon pose.
2138
2696
  const camera = getCameraForViewType(ViewType.MakeIcon);
2139
2697
 
@@ -2141,11 +2699,16 @@ async function createFFLMiiIcon(data, width, height, fflRes) {
2141
2699
  camera.projectionMatrix.elements[5] *= -1; // Flip the camera Y axis.
2142
2700
  // When flipping the camera, the triangles are in the wrong direction.
2143
2701
  scene.traverse(mesh => {
2144
- if (mesh.isMesh && mesh.material.side === THREE.FrontSide)
2145
- // Fix triangle winding by changing the culling (side).
2146
- mesh.material.side = THREE.BackSide;
2702
+ if (
2703
+ mesh.isMesh &&
2704
+ mesh.material.side === THREE.FrontSide &&
2705
+ !mesh.userData.isMiiBody
2706
+ ) {
2707
+ mesh.material.side = THREE.BackSide;
2708
+ }
2147
2709
  });
2148
2710
 
2711
+
2149
2712
  // Render the scene, and read the pixels into a buffer.
2150
2713
  renderer.render(scene, camera);
2151
2714
  const pixels = new Uint8Array(width * height * 4);
@@ -2182,166 +2745,336 @@ async function renderMii(jsonIn, fflRes=getFFLRes()){
2182
2745
  const studioMii = convertMiiToStudio(jsonIn);
2183
2746
  const width = height = 600;
2184
2747
 
2185
- return createFFLMiiIcon(studioMii, width, height, fflRes);
2748
+ return createFFLMiiIcon(studioMii, width, height, true, jsonIn.general.favoriteColor, fflRes);
2186
2749
  }
2187
2750
  async function writeWiiBin(jsonIn, outPath) {
2188
2751
  if (jsonIn.console?.toLowerCase() !== "wii") {
2189
2752
  convertMii(jsonIn);
2190
2753
  }
2191
- const miiBuffer = jsonToMiiBuffer(jsonIn, WII_MII_SCHEMA, lookupTables, 74);
2754
+ var mii=jsonIn;
2755
+ var miiBin="0";
2756
+ miiBin+=mii.general.gender;
2757
+ miiBin+=mii.general.birthMonth.toString(2).padStart(4,"0");
2758
+ miiBin+=mii.general.birthday.toString(2).padStart(5,"0");
2759
+ miiBin+=mii.general.favoriteColor.toString(2).padStart(4,"0");
2760
+ miiBin+='0';
2761
+ for(var i=0;i<10;i++){
2762
+ if(i<mii.meta.name.length){
2763
+ miiBin+=mii.meta.name.charCodeAt(i).toString(2).padStart(16,"0");
2764
+ }
2765
+ else{
2766
+ miiBin+="0000000000000000";
2767
+ }
2768
+ }
2769
+ miiBin+=mii.general.height.toString(2).padStart(8,"0");
2770
+ miiBin+=mii.general.weight.toString(2).padStart(8,"0");
2771
+ let miiId="";
2772
+ switch(mii.meta.type){
2773
+ case "Special":
2774
+ miiId="01000110";
2775
+ break;
2776
+ case "Foreign":
2777
+ miiId="11000110";
2778
+ break;
2779
+ default:
2780
+ miiId="10001001";
2781
+ break;
2782
+ }
2783
+ for(var i=0;i<3;i++){
2784
+ miiId+=Math.floor(Math.random()*255).toString(2).padStart(8,"0");
2785
+ }
2786
+ miiBin+=miiId;
2787
+ miiBin+="11111111".repeat(4);//System ID
2788
+ miiBin+=mii.face.type.toString(2).padStart(3,"0");
2789
+ miiBin+=mii.face.color.toString(2).padStart(3,"0");
2790
+ miiBin+=mii.face.feature.toString(2).padStart(4,"0");
2791
+ miiBin+="000";
2792
+ if(mii.perms.mingle&&mii.meta.type.toLowerCase()==="special"){
2793
+ mii.perms.mingle=false;
2794
+ console.warn("A Special Mii cannot have Mingle on and still render on the Wii. Turned Mingle off in the output.");
2795
+ }
2796
+ miiBin+=mii.perms.mingle?"0":"1";
2797
+ miiBin+="0";
2798
+ miiBin+=mii.perms.fromCheckMiiOut?"1":"0";
2799
+ miiBin+=(+getKeyByValue(lookupTables.hairTable,`${mii.hair.page+1}${convTables.formatFrom[mii.hair.type]}`)).toString(2).padStart(7,"0");
2800
+ miiBin+=mii.hair.color.toString(2).padStart(3,"0");
2801
+ miiBin+=mii.hair.flipped?"1":"0";
2802
+ miiBin+="00000";
2803
+ miiBin+=(+getKeyByValue(lookupTables.eyebrowTable,`${mii.eyebrows.page+1}${convTables.formatFrom[mii.eyebrows.type]}`)).toString(2).padStart(5,"0");
2804
+ miiBin+="0";
2805
+ miiBin+=mii.eyebrows.rotation.toString(2).padStart(4,"0");
2806
+ miiBin+="000000";
2807
+ miiBin+=mii.eyebrows.color.toString(2).padStart(3,"0");
2808
+ miiBin+=mii.eyebrows.size.toString(2).padStart(4,"0");
2809
+ miiBin+=(mii.eyebrows.yPosition+3).toString(2).padStart(5,"0");
2810
+ miiBin+=mii.eyebrows.distanceApart.toString(2).padStart(4,"0");
2811
+ miiBin+=(+getKeyByValue(lookupTables.eyeTable,`${mii.eyes.page+1}${convTables.formatFrom[mii.eyes.type]}`)).toString(2).padStart(6,"0");
2812
+ miiBin+="00";
2813
+ miiBin+=mii.eyes.rotation.toString(2).padStart(3,"0");
2814
+ miiBin+=mii.eyes.yPosition.toString(2).padStart(5,"0");
2815
+ miiBin+=mii.eyes.color.toString(2).padStart(3,"0");
2816
+ miiBin+="0";
2817
+ miiBin+=mii.eyes.size.toString(2).padStart(3,"0");
2818
+ miiBin+=mii.eyes.distanceApart.toString(2).padStart(4,"0");
2819
+ miiBin+="00000";
2820
+ miiBin+=lookupTables.wiiNoses[mii.nose.type].toString(2).padStart(4,"0");
2821
+ miiBin+=mii.nose.size.toString(2).padStart(4,"0");
2822
+ miiBin+=mii.nose.yPosition.toString(2).padStart(5,"0");
2823
+ miiBin+="000";
2824
+ miiBin+=(+getKeyByValue(lookupTables.mouthTable,`${mii.mouth.page+1}${convTables.formatFrom[mii.mouth.type]}`)).toString(2).padStart(5,"0");
2825
+ miiBin+=mii.mouth.color.toString(2).padStart(2,"0");
2826
+ miiBin+=mii.mouth.size.toString(2).padStart(4,"0");
2827
+ miiBin+=mii.mouth.yPosition.toString(2).padStart(5,"0");
2828
+ miiBin+=mii.glasses.type.toString(2).padStart(4,"0");
2829
+ miiBin+=mii.glasses.color.toString(2).padStart(3,"0");
2830
+ miiBin+="0";
2831
+ miiBin+=mii.glasses.size.toString(2).padStart(3,"0");
2832
+ miiBin+=mii.glasses.yPosition.toString(2).padStart(5,"0");
2833
+ miiBin+=mii.beard.mustache.type.toString(2).padStart(2,"0");
2834
+ miiBin+=mii.beard.type.toString(2).padStart(2,"0");
2835
+ miiBin+=mii.beard.color.toString(2).padStart(3,"0");
2836
+ miiBin+=mii.beard.mustache.size.toString(2).padStart(4,"0");
2837
+ miiBin+=mii.beard.mustache.yPosition.toString(2).padStart(5,"0");
2838
+ miiBin+=mii.mole.on?"1":"0";
2839
+ miiBin+=mii.mole.size.toString(2).padStart(4,"0");
2840
+ miiBin+=mii.mole.yPosition.toString(2).padStart(5,"0");
2841
+ miiBin+=mii.mole.xPosition.toString(2).padStart(5,"0");
2842
+ miiBin+="0";
2843
+ for(var i=0;i<10;i++){
2844
+ if(i<mii.meta.creatorName.length){
2845
+ miiBin+=mii.meta.creatorName.charCodeAt(i).toString(2).padStart(16,"0");
2846
+ }
2847
+ else{
2848
+ miiBin+="0000000000000000";
2849
+ }
2850
+ }
2851
+
2852
+ //Writing based on miiBin
2853
+ var toWrite=miiBin.match(/.{1,8}/g);
2854
+ var buffers=[];
2855
+ for(var i=0;i<toWrite.length;i++){
2856
+ buffers.push(parseInt(toWrite[i],2));
2857
+ }
2858
+ toWrite=Buffer.from(buffers);
2192
2859
  if(outPath){
2193
- await fs.promises.writeFile(outPath, miiBuffer);
2860
+ await fs.promises.writeFile(outPath, toWrite);
2194
2861
  }
2195
2862
  else{
2196
- return miiBuffer;
2863
+ return toWrite;
2197
2864
  }
2198
2865
  }
2199
2866
  async function write3DSQR(miiJson, outPath, fflRes = getFFLRes()) {
2867
+ //Convert the Mii if it isn't in 3DS format
2200
2868
  if (!["3ds", "wii u"].includes(miiJson.console?.toLowerCase())) {
2201
2869
  miiJson = convertMii(miiJson);
2202
2870
  }
2203
- return new Promise(async (resolve, reject) => {
2204
- const miiBinary = jsonToMiiBuffer(miiJson, THREEDS_MII_SCHEMA, lookupTables, 74);
2205
- var encryptedData = Buffer.from(encodeAesCcm(new Uint8Array(miiBinary)));
2206
-
2207
- const options = {
2208
- width: 300,
2209
- height: 300,
2210
- data: encryptedData.toString("latin1"),
2211
- image: "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", // 1x1 gif
2212
- dotsOptions: {
2213
- color: "#000000",
2214
- type: "square"
2215
- },
2216
- backgroundOptions: {
2217
- color: "#ffffff",
2218
- },
2219
- imageOptions: {
2220
- crossOrigin: "anonymous",
2221
- imageSize: 0.4 // Changes how large center area is
2222
- }
2223
- }
2224
- const qrCodeImage = new QRCodeStyling({
2225
- jsdom: JSDOM,
2226
- nodeCanvas,
2227
- ...options
2228
- });
2229
- const qrBuffer = Buffer.from(await qrCodeImage.getRawData("png"))
2230
-
2231
- var studioMii = new Uint8Array([0x08, 0x00, 0x40, 0x03, 0x08, 0x04, 0x04, 0x02, 0x02, 0x0c, 0x03, 0x01, 0x06, 0x04, 0x06, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x0a, 0x01, 0x00, 0x21, 0x40, 0x04, 0x00, 0x02, 0x14, 0x03, 0x13, 0x04, 0x17, 0x0d, 0x04, 0x00, 0x0a, 0x04, 0x01, 0x09]);
2232
- studioMii[0x16] = miiJson.info.gender === "Male" ? 0 : 1;
2233
- studioMii[0x15] = lookupTables.favCols.indexOf(miiJson.info.favColor);
2234
- studioMii[0x1E] = miiJson.info.height;
2235
- studioMii[2] = miiJson.info.weight;
2236
- studioMii[0x13] = lookupTables.faces.values[miiJson.face.shape];
2237
- studioMii[0x11] = lookupTables.skinCols.indexOf(miiJson.face.col);
2238
- studioMii[0x14] = lookupTables.faceFeatures3DS.indexOf(miiJson.face.feature);
2239
- studioMii[0x12] = lookupTables.makeups3DS.indexOf(miiJson.face.makeup);
2240
- studioMii[0x1D] = lookupTables.hairs.values[miiJson.hair.style[0]][miiJson.hair.style[1]];
2241
- studioMii[0x1B] = lookupTables.hairCols.indexOf(miiJson.hair.col);
2242
- if (!studioMii[0x1B]) studioMii[0x1B] = 8;
2243
- studioMii[0x1C] = miiJson.hair.flipped ? 1 : 0;
2244
- studioMii[7] = lookupTables.eyes.values[miiJson.eyes.type[0]][miiJson.eyes.type[1]];
2245
- studioMii[4] = lookupTables.eyeCols.indexOf(miiJson.eyes.col) + 8;
2246
- studioMii[6] = miiJson.eyes.size;
2247
- studioMii[3] = miiJson.eyes.squash;
2248
- studioMii[5] = miiJson.eyes.rot;
2249
- studioMii[8] = miiJson.eyes.distApart;
2250
- studioMii[9] = miiJson.eyes.yPos;
2251
- studioMii[0xE] = lookupTables.eyebrows.values[miiJson.eyebrows.style[0]][miiJson.eyebrows.style[1]];
2252
- studioMii[0xB] = lookupTables.hairCols.indexOf(miiJson.eyebrows.col);
2253
- if (!studioMii[0xB]) studioMii[0xB] = 8;
2254
- studioMii[0xD] = miiJson.eyebrows.size;
2255
- studioMii[0xA] = miiJson.eyebrows.squash;
2256
- studioMii[0xC] = miiJson.eyebrows.rot;
2257
- studioMii[0xF] = miiJson.eyebrows.distApart;
2258
- studioMii[0x10] = miiJson.eyebrows.yPos + 3;
2259
- studioMii[0x2C] = lookupTables.noses.values[miiJson.nose.type[0]][miiJson.nose.type[1]];
2260
- studioMii[0x2B] = miiJson.nose.size;
2261
- studioMii[0x2D] = miiJson.nose.yPos;
2262
- studioMii[0x26] = lookupTables.mouths.values[miiJson.mouth.type[0]][miiJson.mouth.type[1]];
2263
- studioMii[0x24] = lookupTables.mouthCols3DS.indexOf(miiJson.mouth.col);
2264
- if (studioMii[0x24] < 4) {
2265
- studioMii[0x24] += 19;
2266
- } else {
2267
- studioMii[0x24] = 0;
2871
+
2872
+ //Make the binary
2873
+ var mii=miiJson;
2874
+ var miiBin = "00000011";
2875
+ //If Special Miis are being used improperly, fix it and warn the user
2876
+ if(mii.meta.type.toLowerCase()==="special"&&(mii.console.toLowerCase()==="wii u"||mii.console.toLowerCase()==="wiiu")){
2877
+ mii.meta.type="Default";
2878
+ console.warn("Wii Us do not work with Special Miis. Reverted to Default Mii.");
2879
+ }
2880
+ if(mii.perms.sharing&&mii.meta.type==="Special"){
2881
+ mii.perms.sharing=false;
2882
+ console.warn("Cannot have Sharing enabled for Special Miis. Disabled Sharing in the output.");
2883
+ }
2884
+ miiBin+="0000000";
2885
+ miiBin+=mii.perms.copying?"1":"0";
2886
+ miiBin+="00000000";
2887
+ miiBin+="00110000";
2888
+ miiBin+="1000101011010010000001101000011100011000110001100100011001100110010101100111111110111100000001110101110001000101011101100000001110100100010000000000000000000000".slice(0,8*8);
2889
+ miiBin+=mii.meta.type==="Special"?"0":"1";
2890
+ miiBin+="0000000";
2891
+ for(var i=0;i<3;i++){
2892
+ miiBin+=Math.floor(Math.random()*255).toString(2).padStart(8,"0");
2893
+ }
2894
+ miiBin+="0000000001000101011101100000001110100100010000000000000000000000";
2895
+ miiBin+=mii.general.birthday.toString(2).padStart(5,"0").slice(2,5);
2896
+ miiBin+=mii.general.birthMonth.toString(2).padStart(4,"0");
2897
+ miiBin+=mii.general.gender;
2898
+ miiBin+="00";
2899
+ miiBin+=mii.general.favoriteColor.toString(2).padStart(4,"0");
2900
+ miiBin+=mii.general.birthday.toString(2).padStart(5,"0").slice(0,2);
2901
+ for (var i = 0; i < 10; i++) {
2902
+ if (i < mii.meta.name.length) {
2903
+ let code = mii.meta.name.charCodeAt(i);
2904
+ miiBin += (code & 0xFF).toString(2).padStart(8, "0");
2905
+ miiBin += ((code >> 8) & 0xFF).toString(2).padStart(8, "0");
2268
2906
  }
2269
- studioMii[0x25] = miiJson.mouth.size;
2270
- studioMii[0x23] = miiJson.mouth.squash;
2271
- studioMii[0x27] = miiJson.mouth.yPos;
2272
- studioMii[0x29] = miiJson.facialHair.mustacheType;
2273
- studioMii[1] = miiJson.facialHair.beardType;
2274
- studioMii[0] = lookupTables.hairCols.indexOf(miiJson.facialHair.col);
2275
- if (!studioMii[0]) studioMii[0] = 8;
2276
- studioMii[0x28] = miiJson.facialHair.mustacheSize;
2277
- studioMii[0x2A] = miiJson.facialHair.mustacheYPos;
2278
- studioMii[0x19] = miiJson.glasses.type;
2279
- studioMii[0x17] = miiJson.glasses.col;
2280
- if (!studioMii[0x17]) {
2281
- studioMii[0x17] = 8;
2282
- } else if (studioMii[0x17] < 6) {
2283
- studioMii[0x17] += 13;
2284
- } else {
2285
- studioMii[0x17] = 0;
2907
+ else {
2908
+ miiBin += "0000000000000000";
2286
2909
  }
2287
- studioMii[0x18] = miiJson.glasses.size;
2288
- studioMii[0x1A] = miiJson.glasses.yPos;
2289
- studioMii[0x20] = miiJson.mole.on ? 1 : 0;
2290
- studioMii[0x1F] = miiJson.mole.size;
2291
- studioMii[0x21] = miiJson.mole.xPos;
2292
- studioMii[0x22] = miiJson.mole.yPos;
2293
- let miiPNGBuf = null;
2294
- let renderedWithStudio = fflRes === null || fflRes === undefined;
2295
- if (renderedWithStudio) {
2296
- miiPNGBuf = await renderMiiWithStudio(miiJson);
2910
+ }
2911
+ miiBin+=mii.general.height.toString(2).padStart(8,"0");
2912
+ miiBin+=mii.general.weight.toString(2).padStart(8,"0");
2913
+ miiBin+=mii.face.color.toString(2).padStart(3,"0");
2914
+ miiBin+=lookupTables.faces.values[mii.face.type].toString(2).padStart(4,"0");
2915
+ miiBin+=mii.perms.sharing?"0":"1";
2916
+ miiBin+=mii.face.makeup.toString(2).padStart(4,"0");
2917
+ miiBin+=mii.face.feature.toString(2).padStart(4,"0");
2918
+ miiBin+=lookupTables.hairs.values[mii.hair.page][mii.hair.type].toString(2).padStart(8,"0");
2919
+ miiBin+="0000";
2920
+ miiBin+=mii.hair.flipped?"1":"0";
2921
+ miiBin+=mii.hair.color.toString(2).padStart(3,"0");
2922
+ miiBin+=mii.eyes.color.toString(2).padStart(3,"0").slice(1,3);
2923
+ miiBin+=lookupTables.eyes.values[mii.eyes.page][mii.eyes.type].toString(2).padStart(6,"0");
2924
+ miiBin+=mii.eyes.squash.toString(2).padStart(3,"0");
2925
+ miiBin+=mii.eyes.size.toString(2).padStart(4,"0");
2926
+ miiBin+=mii.eyes.color.toString(2).padStart(3,"0")[0];
2927
+ miiBin+=mii.eyes.distanceApart.toString(2).padStart(4,"0").slice(1,4);
2928
+ miiBin+=mii.eyes.rotation.toString(2).padStart(5,"0");
2929
+ miiBin+="00";
2930
+ miiBin+=mii.eyes.yPosition.toString(2).padStart(5,"0");
2931
+ miiBin+=mii.eyes.distanceApart.toString(2).padStart(4,"0")[0];
2932
+ miiBin+=mii.eyebrows.color.toString(2).padStart(3,"0");
2933
+ miiBin+=lookupTables.eyebrows.values[mii.eyebrows.page][mii.eyebrows.type].toString(2).padStart(5,"0");
2934
+ miiBin+="0";
2935
+ miiBin+=mii.eyebrows.squash.toString(2).padStart(3,"0");
2936
+ miiBin+=mii.eyebrows.size.toString(2).padStart(4,"0");
2937
+ miiBin+=mii.eyebrows.distanceApart.toString(2).padStart(4,"0").slice(1,4);
2938
+ miiBin+="0";
2939
+ miiBin+=mii.eyebrows.rotation.toString(2).padStart(4,"0");
2940
+ miiBin+="00";
2941
+ miiBin+=(mii.eyebrows.yPosition+3).toString(2).padStart(5,"0");
2942
+ miiBin+=mii.eyebrows.distanceApart.toString(2).padStart(4,"0")[0];
2943
+ miiBin+=mii.nose.size.toString(2).padStart(4,"0").slice(1,4);
2944
+ miiBin+=lookupTables.noses.values[mii.nose.page][mii.nose.type].toString(2).padStart(5,"0");
2945
+ miiBin+="00";
2946
+ miiBin+=mii.nose.yPosition.toString(2).padStart(5,"0");
2947
+ miiBin+=mii.nose.size.toString(2).padStart(4,"0")[0];
2948
+ miiBin+=mii.mouth.color.toString(2).padStart(3,"0").slice(1,3);
2949
+ miiBin+=lookupTables.mouths.values[mii.mouth.page][mii.mouth.type].toString(2).padStart(6,"0");
2950
+ miiBin+=mii.mouth.squash.toString(2).padStart(3,"0");
2951
+ miiBin+=mii.mouth.size.toString(2).padStart(4,"0");
2952
+ miiBin+=mii.mouth.color.toString(2).padStart(3,"0")[0];
2953
+ miiBin+=mii.beard.mustache.type.toString(2).padStart(3,"0");
2954
+ miiBin+=mii.mouth.yPosition.toString(2).padStart(5,"0");
2955
+ miiBin+="00000000";
2956
+ miiBin+=mii.beard.mustache.size.toString(2).padStart(4,"0").slice(2,4);
2957
+ miiBin+=mii.beard.color.toString(2).padStart(3,"0");
2958
+ miiBin+=mii.beard.type.toString(2).padStart(3,"0");
2959
+ miiBin+="0";
2960
+ miiBin+=mii.beard.mustache.yPosition.toString(2).padStart(5,"0");
2961
+ miiBin+=mii.beard.mustache.size.toString(2).padStart(4,"0").slice(0,2);
2962
+ miiBin+=mii.glasses.size.toString(2).padStart(4,"0")[3];
2963
+ miiBin+=mii.glasses.color.toString(2).padStart(3,"0");
2964
+ miiBin+=mii.glasses.type.toString(2).padStart(4,"0");
2965
+ miiBin+="0";
2966
+ miiBin+=mii.glasses.yPosition.toString(2).padStart(4,"0");
2967
+ miiBin+=mii.glasses.size.toString(2).padStart(4,"0").slice(0,3);
2968
+ miiBin+=mii.mole.xPosition.toString(2).padStart(5,"0").slice(2,5);
2969
+ miiBin+=mii.mole.size.toString(2).padStart(4,"0");
2970
+ miiBin+=mii.mole.on?"1":"0";
2971
+ miiBin+="0";
2972
+ miiBin+=mii.mole.yPosition.toString(2).padStart(5,"0");
2973
+ miiBin+=mii.mole.xPosition.toString(2).padStart(5,"0").slice(0,2);
2974
+ for (var i = 0; i < 10; i++) {
2975
+ if (i < mii.meta.creatorName.length) {
2976
+ let code = mii.meta.creatorName.charCodeAt(i);
2977
+ miiBin += (code & 0xFF).toString(2).padStart(8, "0");
2978
+ miiBin += ((code >> 8) & 0xFF).toString(2).padStart(8, "0");
2297
2979
  }
2298
2980
  else {
2299
- miiPNGBuf = await renderMii(miiJson, fflRes);
2981
+ miiBin += "0000000000000000";
2982
+ }
2983
+ }
2984
+ //Writing based on the binary
2985
+ var toWrite=miiBin.match(/.{1,8}/g);
2986
+ var buffers=[];
2987
+ for(var i=0;i<toWrite.length;i++){
2988
+ buffers.push(parseInt(toWrite[i],2));
2989
+ }
2990
+ const buffer = Buffer.from(buffers);
2991
+ var encryptedData = Buffer.from(encodeAesCcm(new Uint8Array(buffer)));
2992
+
2993
+ //Prepare a QR code
2994
+ const options = {
2995
+ width: 300,
2996
+ height: 300,
2997
+ data: encryptedData.toString("latin1"),
2998
+ image: "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", // 1x1 gif
2999
+ dotsOptions: {
3000
+ color: "#000000",
3001
+ type: "square"
3002
+ },
3003
+ backgroundOptions: {
3004
+ color: "#ffffff",
3005
+ },
3006
+ imageOptions: {
3007
+ crossOrigin: "anonymous",
3008
+ imageSize: 0.4 // Changes how large center area is
3009
+ },
3010
+ qrOptions:{
3011
+ errorCorrectionLevel:'H'
2300
3012
  }
2301
- const main_img = await Jimp.read(qrBuffer);
2302
- main_img.resize(424, 424, Jimp.RESIZE_NEAREST_NEIGHBOR); // Don't anti-alias the QR code
3013
+ }
3014
+ const qrCodeImage = new QRCodeStyling({
3015
+ jsdom: JSDOM,
3016
+ nodeCanvas,
3017
+ ...options
3018
+ });
3019
+ const qrBuffer = Buffer.from(await qrCodeImage.getRawData("png"))
2303
3020
 
2304
- let miiSize, miiZoomFactor, miiYOffset;
2305
- if (renderedWithStudio) {
2306
- miiSize = 100;
2307
- miiZoomFactor = 1;
2308
- miiYOffset = -15;
3021
+ let miiPNGBuf = null;
3022
+ let renderedWithStudio = fflRes === null || fflRes === undefined;
3023
+ if (renderedWithStudio) {
3024
+ miiPNGBuf = await renderMiiWithStudio(miiJson);
3025
+ }
3026
+ else {
3027
+ miiPNGBuf = await renderMii(miiJson, fflRes);
3028
+ }
3029
+ const main_img = await Jimp.read(qrBuffer);
3030
+ main_img.resize(424, 424, Jimp.RESIZE_NEAREST_NEIGHBOR); // Don't anti-alias the QR code
2309
3031
 
2310
- } else {
2311
- miiSize = 100;
2312
- miiZoomFactor = 1.25;
2313
- miiYOffset = -5;
2314
- }
2315
- const mii_img = await Jimp.read(miiPNGBuf);
2316
- mii_img.resize(miiSize * miiZoomFactor, miiSize * miiZoomFactor, Jimp.RESIZE_BICUBIC);
2317
- mii_img.crop(
2318
- (miiSize * miiZoomFactor - 100) / 2,
2319
- (miiSize * miiZoomFactor - 100) / 2,
2320
- miiSize,
2321
- miiSize
2322
- );
2323
-
2324
- const canvas = new Jimp(mii_img.bitmap.width, mii_img.bitmap.height, 0xFFFFFFFF);
2325
- canvas.composite(mii_img, 0, miiYOffset);
2326
- main_img.blit(canvas, 212 - 100 / 2, 212 - 100 / 2);
2327
- const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK)
2328
-
2329
- main_img.print(font, 0, 55, {
2330
- text: miiJson.name,
2331
- alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
2332
- alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
2333
- }, 424, 395);
2334
-
2335
- if (miiJson.info.type === "Special") {
2336
- const crown_img = await Jimp.read(path.join(__dirname, 'crown.jpg'));
2337
- crown_img.resize(40, 20);
2338
- main_img.blit(crown_img, 225, 160);
2339
- }
3032
+ let miiSize, miiZoomFactor, miiYOffset;
3033
+ if (renderedWithStudio) {
3034
+ miiSize = 100;
3035
+ miiZoomFactor = 1;
3036
+ miiYOffset = -15;
2340
3037
 
2341
- main_img.write(outPath, (err, img) =>
2342
- resolve(img)
2343
- );
2344
- })
3038
+ } else {
3039
+ miiSize = 100;
3040
+ miiZoomFactor = 1.25;
3041
+ miiYOffset = -5;
3042
+ }
3043
+ const mii_img = await Jimp.read(miiPNGBuf);
3044
+ mii_img.resize(miiSize * miiZoomFactor, miiSize * miiZoomFactor, Jimp.RESIZE_BICUBIC);
3045
+ mii_img.crop(
3046
+ (miiSize * miiZoomFactor - 100) / 2,
3047
+ (miiSize * miiZoomFactor - 100) / 2,
3048
+ miiSize,
3049
+ miiSize
3050
+ );
3051
+
3052
+ const canvas = new Jimp(mii_img.bitmap.width, mii_img.bitmap.height, 0xFFFFFFFF);
3053
+ canvas.composite(mii_img, 0, miiYOffset);
3054
+ main_img.blit(canvas, 212 - 100 / 2, 212 - 100 / 2);
3055
+ const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK)
3056
+
3057
+ main_img.print(font, 0, 55, {
3058
+ text: miiJson.meta.name,
3059
+ alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
3060
+ alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
3061
+ }, 424, 395);
3062
+
3063
+ if (miiJson.meta.type === "Special") {
3064
+ const crown_img = await Jimp.read(path.join(__dirname, 'crown.jpg'));
3065
+ crown_img.resize(40, 20);
3066
+ main_img.blit(crown_img, 225, 160);
3067
+ }
3068
+
3069
+ // Get the buffer
3070
+ const imageBuffer = await main_img.getBufferAsync(Jimp.MIME_PNG);
3071
+
3072
+ // Optionally write to file if outPath is provided
3073
+ if (outPath) {
3074
+ await main_img.writeAsync(outPath);
3075
+ }
3076
+
3077
+ return imageBuffer;
2345
3078
  }
2346
3079
  function make3DSChild(dad,mom,options={}){
2347
3080
  if(!["3ds","wii u"].includes(dad.console?.toLowerCase())){