miijs 2.1.1 → 2.1.3

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