miijs 2.0.0 → 2.1.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
@@ -1,3 +1,4 @@
1
+ //Imports
1
2
  const fs = require('fs');
2
3
  const nodeCanvas = require('canvas');
3
4
  const { createCanvas, loadImage, ImageData } = nodeCanvas;
@@ -10,47 +11,72 @@ const httpsLib = require('https');
10
11
  const asmCrypto=require("./asmCrypto.js");
11
12
  const path=require("path");
12
13
  const createGL = require('gl');
13
- const {FFLCharModelDescDefault,createCharModel,initCharModelTextures,initializeFFL,parseHexOrB64ToUint8Array}=require("./ffl.js");
14
- const ModuleFFL=require("./ffl-emscripten-single-file.js");
15
- const FFLShaderMaterial=require("./FFLShaderMaterial.js");
16
- function getKeyByValue(object, value) {
17
- for (var key in object) {
18
- if (object[key] === value) {
19
- return key;
20
- }
14
+
15
+ const {
16
+ createCharModel, initCharModelTextures,
17
+ initializeFFL, exitFFL, parseHexOrB64ToUint8Array,
18
+ setIsWebGL1State, getCameraForViewType, ViewType
19
+ } = require("ffl.js/ffl.js");
20
+ const ModuleFFL = require("ffl.js/examples/ffl-emscripten-single-file.js");
21
+ const FFLShaderMaterial = require("ffl.js/FFLShaderMaterial.js");
22
+
23
+ // Typedefs for intellisence
24
+ /** @typedef {import('./types').WiiMii} WiiMii */
25
+
26
+ //Tools
27
+ function Uint8Cat(){
28
+ var destLength = 0
29
+ for(var i = 0;i < arguments.length;i++){
30
+ destLength += arguments[i].length;
21
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;
22
39
  }
23
-
24
- //If FFLResHigh.dat is in the same directory as Node.js is calling the library from, use it by default
25
- var _fflRes=null;
26
- if(fs.existsSync("./FFLResHigh.dat")){
27
- _fflRes=new Uint8Array(fs.readFileSync("./FFLResHigh.dat",""));
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;
28
59
  }
29
60
 
30
- function getBinaryFromAddress(addr, bin){
31
- let byte = bin.readUInt8(addr);
32
- let binaryString = '';
33
- for (let i = 7; i >= 0; i--) {
34
- binaryString += ((byte >> i) & 1) ? '1' : '0';
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
+ return _fflRes = new Uint8Array(fs.readFileSync(path));
35
69
  }
36
- return binaryString;
70
+ // If no file found, mark as null
71
+ return _fflRes = null;
37
72
  }
73
+
74
+ //3DS QR Code (En|De)cryption
38
75
  var NONCE_OFFSET = 0xC;
39
76
  var NONCE_LENGTH = 8;
40
77
  var TAG_LENGTH = 0x10;
41
78
  var aes_key = new Uint8Array([0x59, 0xFC, 0x81, 0x7E, 0x64, 0x46, 0xEA, 0x61, 0x90, 0x34, 0x7B, 0x20, 0xE9, 0xBD, 0xCE, 0x52]);
42
79
  var pad = new Uint8Array([0,0,0,0]);
43
- function Uint8Cat(){
44
- var destLength = 0
45
- for(var i = 0;i < arguments.length;i++)destLength += arguments[i].length;
46
- var dest = new Uint8Array(destLength);
47
- var index = 0;
48
- for(i = 0;i < arguments.length;i++){
49
- dest.set(arguments[i],index);
50
- index += arguments[i].length;
51
- }
52
- return dest;
53
- }
54
80
  function decodeAesCcm(data){
55
81
  var nonce = Uint8Cat(data.subarray(0,NONCE_LENGTH),pad);
56
82
  var ciphertext = data.subarray(NONCE_LENGTH,0x70);
@@ -79,331 +105,457 @@ function encodeAesCcm(data){
79
105
  var ciphertext = asmCrypto.AES_CCM.encrypt(plaintext,aes_key,nonce,undefined,TAG_LENGTH);
80
106
  return Uint8Cat(cfsd.subarray(NONCE_OFFSET,NONCE_OFFSET + NONCE_LENGTH),ciphertext.subarray(0,ciphertext.length - 24),ciphertext.subarray(ciphertext.length - TAG_LENGTH,ciphertext.length))
81
107
  }
82
- async function downloadImage(url) {
83
- return new Promise((resolve, reject) => {
84
- httpsLib.get(url, (res) => {
85
- if (res.statusCode === 200) {
86
- const data = [];
87
- res.on('data', chunk => data.push(chunk));
88
- res.on('end', () => resolve(Buffer.concat(data)));
89
- res.on('error', reject);
90
- } else {
91
- res.resume();
92
- reject(new Error(`Request Failed With a Status Code: ${res.statusCode}`));
93
- }
94
- });
95
- });
96
- }
97
- var favCols=["Red","Orange","Yellow","Lime","Green","Blue","Cyan","Pink","Purple","Brown","White","Black"];
98
- var skinCols=["White","Tanned White","Darker White","Tanned Darker","Mostly Black","Black"];
99
- var hairCols=["Black","Brown","Red","Reddish Brown","Grey","Light Brown","Dark Blonde","Blonde"];
100
- var eyeCols=["Black","Grey","Brown","Lime","Blue","Green"];
101
- var wiiFaceFeatures=["None","Blush","Makeup and Blush","Freckles","Bags","Wrinkles on Cheeks","Wrinkles near Eyes","Chin Wrinkle","Makeup","Stubble","Wrinkles near Mouth","Wrinkles"];
102
- var wiiMouthColors=["Peach","Red","Pink"];
103
- var wiiGlassesCols=["Grey","Brown","Red","Blue","Yellow","White"];
104
- var wiiNoses={
105
- '0': 1,
106
- '1': 10,
107
- '2': 2,
108
- '3': 3,
109
- '4': 6,
110
- '5': 0,
111
- '6': 5,
112
- '7': 4,
113
- '8': 8,
114
- '9': 9,
115
- '10': 7,
116
- '11': 11
117
- };
118
- var mouthTable={
119
- '0': '113',
120
- '1': '121',
121
- '2': '231',
122
- '3': '222',
123
- '4': '232',
124
- '5': '132',
125
- '6': '124',
126
- '7': '211',
127
- '8': '123',
128
- '9': '221',
129
- '10': '133',
130
- '11': '223',
131
- '12': '234',
132
- '13': '134',
133
- '14': '224',
134
- '15': '213',
135
- '16': '114',
136
- '17': '212',
137
- '18': '214',
138
- '19': '131',
139
- '20': '233',
140
- '21': '112',
141
- '22': '122',
142
- '23': '111'
143
- };
144
- var eyebrowTable={
145
- '0': '121',
146
- '1': '112',
147
- '2': '231',
148
- '3': '212',
149
- '4': '134',
150
- '5': '124',
151
- '6': '111',
152
- '7': '113',
153
- '8': '133',
154
- '9': '122',
155
- '10': '221',
156
- '11': '211',
157
- '12': '131',
158
- '13': '223',
159
- '14': '222',
160
- '15': '213',
161
- '16': '224',
162
- '17': '114',
163
- '18': '214',
164
- '19': '132',
165
- '20': '232',
166
- '21': '123',
167
- '22': '233',
168
- '23': '234'
169
- };
170
- var eyeTable={
171
- '0': '131',
172
- '1': '113',
173
- '2': '111',
174
- '3': '413',
175
- '4': '121',
176
- '5': '311',
177
- '6': '332',
178
- '7': '411',
179
- '8': '112',
180
- '9': '222',
181
- '10': '414',
182
- '11': '221',
183
- '12': '232',
184
- '13': '331',
185
- '14': '424',
186
- '15': '114',
187
- '16': '133',
188
- '17': '132',
189
- '18': '314',
190
- '19': '231',
191
- '20': '134',
192
- '21': '233',
193
- '22': '433',
194
- '23': '213',
195
- '24': '313',
196
- '25': '214',
197
- '26': '123',
198
- '27': '124',
199
- '28': '324',
200
- '29': '432',
201
- '30': '323',
202
- '31': '333',
203
- '32': '212',
204
- '33': '211',
205
- '34': '223',
206
- '35': '234',
207
- '36': '312',
208
- '37': '322',
209
- '38': '431',
210
- '39': '122',
211
- '40': '224',
212
- '41': '321',
213
- '42': '412',
214
- '43': '423',
215
- '44': '421',
216
- '45': '422',
217
- '46': '334',
218
- '47': '434'
219
- };
220
- var hairTable={
221
- '0': '534',
222
- '1': '413',
223
- '2': '632',
224
- '3': '521',
225
- '4': '422',
226
- '5': '433',
227
- '6': '522',
228
- '7': '434',
229
- '8': '414',
230
- '9': '612',
231
- '10': '512',
232
- '11': '513',
233
- '12': '411',
234
- '13': '421',
235
- '14': '511',
236
- '15': '624',
237
- '16': '621',
238
- '17': '533',
239
- '18': '622',
240
- '19': '423',
241
- '20': '532',
242
- '21': '524',
243
- '22': '531',
244
- '23': '312',
245
- '24': '614',
246
- '25': '432',
247
- '26': '412',
248
- '27': '424',
249
- '28': '613',
250
- '29': '634',
251
- '30': '314',
252
- '31': '134',
253
- '32': '211',
254
- '33': '111',
255
- '34': '334',
256
- '35': '514',
257
- '36': '313',
258
- '37': '231',
259
- '38': '321',
260
- '39': '122',
261
- '40': '121',
262
- '41': '323',
263
- '42': '331',
264
- '43': '311',
265
- '44': '112',
266
- '45': '113',
267
- '46': '631',
268
- '47': '221',
269
- '48': '212',
270
- '49': '123',
271
- '50': '223',
272
- '51': '131',
273
- '52': '232',
274
- '53': '623',
275
- '54': '332',
276
- '55': '233',
277
- '56': '114',
278
- '57': '324',
279
- '58': '213',
280
- '59': '133',
281
- '60': '224',
282
- '61': '611',
283
- '62': '234',
284
- '63': '523',
285
- '64': '214',
286
- '65': '333',
287
- '66': '222',
288
- '67': '322',
289
- '68': '124',
290
- '69': '431',
291
- '70': '132',
292
- '71': '633'
293
- };
294
- var 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"];
295
- var makeups3DS=["None","Blush","Orange Blush","Blue Eyes","Blush 2","Orange Blush 2","Blue Eyes and Blush","Orange Eyes and Blush","Purple Eyes and Blush 2","Freckles","Beard Stubble","Beard and Mustache Stubble"];
296
- var mouthCols3DS=["Orange","Red","Pink","Peach","Black"];
297
- var glassesCols3DS=["Black","Brown","Red","Blue","Yellow","Grey"];
298
-
299
- var tables={
300
- faces: [
301
- 0x00,0x01,0x08,
302
- 0x02,0x03,0x09,
303
- 0x04,0x05,0x0a,
304
- 0x06,0x07,0x0b
305
- ],
306
- hairs: [
307
- [0x21,0x2f,0x28,
308
- 0x25,0x20,0x6b,
309
- 0x30,0x33,0x37,
310
- 0x46,0x2c,0x42],
311
- [0x34,0x32,0x26,
312
- 0x31,0x2b,0x1f,
313
- 0x38,0x44,0x3e,
314
- 0x73,0x4c,0x77],
315
- [0x40,0x51,0x74,
316
- 0x79,0x16,0x3a,
317
- 0x3c,0x57,0x7d,
318
- 0x75,0x49,0x4b],
319
- [0x2a,0x59,0x39,
320
- 0x36,0x50,0x22,
321
- 0x17,0x56,0x58,
322
- 0x76,0x27,0x24],
323
- [0x2d,0x43,0x3b,
324
- 0x41,0x29,0x1e,
325
- 0x0c,0x10,0x0a,
326
- 0x52,0x80,0x81],
327
- [0x0e,0x5f,0x69,
328
- 0x64,0x06,0x14,
329
- 0x5d,0x66,0x1b,
330
- 0x04,0x11,0x6e],
331
- [0x7b,0x08,0x6a,
332
- 0x48,0x03,0x15,
333
- 0x00,0x62,0x3f,
334
- 0x5a,0x0b,0x78],
335
- [0x05,0x4a,0x6c,
336
- 0x5e,0x7c,0x19,
337
- 0x63,0x45,0x23,
338
- 0x0d,0x7a,0x71],
339
- [0x35,0x18,0x55,
340
- 0x53,0x47,0x83,
341
- 0x60,0x65,0x1d,
342
- 0x07,0x0f,0x70],
343
- [0x4f,0x01,0x6d,
344
- 0x7f,0x5b,0x1a,
345
- 0x3d,0x67,0x02,
346
- 0x4d,0x12,0x5c],
347
- [0x54,0x09,0x13,
348
- 0x82,0x61,0x68,
349
- 0x2e,0x4e,0x1c,
350
- 0x72,0x7e,0x6f]
351
- ],
352
- eyebrows: [
353
- [0x06,0x00,0x0c,
354
- 0x01,0x09,0x13,
355
- 0x07,0x15,0x08,
356
- 0x11,0x05,0x04],
357
- [0x0b,0x0a,0x02,
358
- 0x03,0x0e,0x14,
359
- 0x0f,0x0d,0x16,
360
- 0x12,0x10,0x17]
361
- ],
362
- eyes: [
363
- [0x02,0x04,0x00,
364
- 0x08,0x27,0x11,
365
- 0x01,0x1a,0x10,
366
- 0x0f,0x1b,0x14],
367
- [0x21,0x0b,0x13,
368
- 0x20,0x09,0x0c,
369
- 0x17,0x22,0x15,
370
- 0x19,0x28,0x23],
371
- [0x05,0x29,0x0d,
372
- 0x24,0x25,0x06,
373
- 0x18,0x1e,0x1f,
374
- 0x12,0x1c,0x2e],
375
- [0x07,0x2c,0x26,
376
- 0x2a,0x2d,0x1d,
377
- 0x03,0x2b,0x16,
378
- 0x0a,0x0e,0x2f],
379
- [0x30,0x31,0x32,
380
- 0x35,0x3b,0x38,
381
- 0x36,0x3a,0x39,
382
- 0x37,0x33,0x34]
383
- ],
384
- noses: [
385
- [0x01,0x0a,0x02,
386
- 0x03,0x06,0x00,
387
- 0x05,0x04,0x08,
388
- 0x09,0x07,0x0B],
389
- [0x0d,0x0e,0x0c,
390
- 0x11,0x10,0x0f]
391
- ],
392
- mouths: [
393
- [0x17,0x01,0x13,
394
- 0x15,0x16,0x05,
395
- 0x00,0x08,0x0a,
396
- 0x10,0x06,0x0d],
397
- [0x07,0x09,0x02,
398
- 0x11,0x03,0x04,
399
- 0x0f,0x0b,0x14,
400
- 0x12,0x0e,0x0c],
401
- [0x1b,0x1e,0x18,
402
- 0x19,0x1d,0x1c,
403
- 0x1a,0x23,0x1f,
404
- 0x22,0x21,0x20]
405
- ]
108
+
109
+ //Miscellaneous Tables
110
+ const lookupTables = {
111
+ favCols: ["Red", "Orange", "Yellow", "Lime", "Green", "Blue", "Cyan", "Pink", "Purple", "Brown", "White", "Black"],
112
+ skinCols: ["White", "Tanned White", "Darker White", "Tanned Darker", "Mostly Black", "Black"],
113
+ hairCols: ["Black", "Brown", "Red", "Reddish Brown", "Grey", "Light Brown", "Dark Blonde", "Blonde"],
114
+ eyeCols: ["Black", "Grey", "Brown", "Lime", "Blue", "Green"],
115
+ wiiFaceFeatures: ["None", "Blush", "Makeup and Blush", "Freckles", "Bags", "Wrinkles on Cheeks", "Wrinkles near Eyes", "Chin Wrinkle", "Makeup", "Stubble", "Wrinkles near Mouth", "Wrinkles"],
116
+ wiiMouthColors: ["Peach", "Red", "Pink"],
117
+ wiiGlassesCols: ["Grey", "Brown", "Red", "Blue", "Yellow", "White"],
118
+ wiiNoses: {
119
+ "1": 0,
120
+ "10": 1,
121
+ "2": 2,
122
+ "3": 3,
123
+ "6": 4,
124
+ "0": 5,
125
+ "5": 6,
126
+ "4": 7,
127
+ "8": 8,
128
+ "9": 9,
129
+ "7": 10,
130
+ },
131
+
132
+ pages:{
133
+ mouths: {
134
+ '0': '1',
135
+ '1': '1',
136
+ '2': '2',
137
+ '3': '2',
138
+ '4': '2',
139
+ '5': '1',
140
+ '6': '1',
141
+ '7': '2',
142
+ '8': '1',
143
+ '9': '2',
144
+ '10': '1',
145
+ '11': '2',
146
+ '12': '2',
147
+ '13': '1',
148
+ '14': '2',
149
+ '15': '2',
150
+ '16': '1',
151
+ '17': '2',
152
+ '18': '2',
153
+ '19': '1',
154
+ '20': '2',
155
+ '21': '1',
156
+ '22': '1',
157
+ '23': '1'
158
+ },
159
+ eyebrows:{
160
+ '0': '1',
161
+ '1': '1',
162
+ '2': '2',
163
+ '3': '2',
164
+ '4': '1',
165
+ '5': '1',
166
+ '6': '1',
167
+ '7': '1',
168
+ '8': '1',
169
+ '9': '1',
170
+ '10': '2',
171
+ '11': '2',
172
+ '12': '1',
173
+ '13': '2',
174
+ '14': '2',
175
+ '15': '2',
176
+ '16': '2',
177
+ '17': '1',
178
+ '18': '2',
179
+ '19': '1',
180
+ '20': '2',
181
+ '21': '1',
182
+ '22': '2',
183
+ '23': '2'
184
+ },
185
+ eyes:{
186
+ '0': '1',
187
+ '1': '1',
188
+ '2': '1',
189
+ '3': '4',
190
+ '4': '1',
191
+ '5': '3',
192
+ '6': '3',
193
+ '7': '4',
194
+ '8': '1',
195
+ '9': '2',
196
+ '10': '4',
197
+ '11': '2',
198
+ '12': '2',
199
+ '13': '3',
200
+ '14': '4',
201
+ '15': '1',
202
+ '16': '1',
203
+ '17': '1',
204
+ '18': '3',
205
+ '19': '2',
206
+ '20': '1',
207
+ '21': '2',
208
+ '22': '4',
209
+ '23': '2',
210
+ '24': '3',
211
+ '25': '2',
212
+ '26': '1',
213
+ '27': '1',
214
+ '28': '3',
215
+ '29': '4',
216
+ '30': '3',
217
+ '31': '3',
218
+ '32': '2',
219
+ '33': '2',
220
+ '34': '2',
221
+ '35': '2',
222
+ '36': '3',
223
+ '37': '3',
224
+ '38': '4',
225
+ '39': '1',
226
+ '40': '2',
227
+ '41': '3',
228
+ '42': '4',
229
+ '43': '4',
230
+ '44': '4',
231
+ '45': '4',
232
+ '46': '3',
233
+ '47': '4'
234
+ },
235
+ hairs:{
236
+ '0': '5',
237
+ '1': '4',
238
+ '2': '6',
239
+ '3': '5',
240
+ '4': '4',
241
+ '5': '4',
242
+ '6': '5',
243
+ '7': '4',
244
+ '8': '4',
245
+ '9': '6',
246
+ '10': '5',
247
+ '11': '5',
248
+ '12': '4',
249
+ '13': '4',
250
+ '14': '5',
251
+ '15': '6',
252
+ '16': '6',
253
+ '17': '5',
254
+ '18': '6',
255
+ '19': '4',
256
+ '20': '5',
257
+ '21': '5',
258
+ '22': '5',
259
+ '23': '3',
260
+ '24': '6',
261
+ '25': '4',
262
+ '26': '4',
263
+ '27': '4',
264
+ '28': '6',
265
+ '29': '6',
266
+ '30': '3',
267
+ '31': '1',
268
+ '32': '2',
269
+ '33': '1',
270
+ '34': '3',
271
+ '35': '5',
272
+ '36': '3',
273
+ '37': '2',
274
+ '38': '3',
275
+ '39': '1',
276
+ '40': '1',
277
+ '41': '3',
278
+ '42': '3',
279
+ '43': '3',
280
+ '44': '1',
281
+ '45': '1',
282
+ '46': '6',
283
+ '47': '2',
284
+ '48': '2',
285
+ '49': '1',
286
+ '50': '2',
287
+ '51': '1',
288
+ '52': '2',
289
+ '53': '6',
290
+ '54': '3',
291
+ '55': '2',
292
+ '56': '1',
293
+ '57': '3',
294
+ '58': '2',
295
+ '59': '1',
296
+ '60': '2',
297
+ '61': '6',
298
+ '62': '2',
299
+ '63': '5',
300
+ '64': '2',
301
+ '65': '3',
302
+ '66': '2',
303
+ '67': '3',
304
+ '68': '1',
305
+ '69': '4',
306
+ '70': '1',
307
+ '71': '6'
308
+ }
309
+ },
310
+ types:{
311
+ "mouths": {
312
+ "0": 6,
313
+ "1": 1,
314
+ "2": 2,
315
+ "3": 4,
316
+ "4": 5,
317
+ "5": 5,
318
+ "6": 10,
319
+ "7": 0,
320
+ "8": 7,
321
+ "9": 1,
322
+ "10": 8,
323
+ "11": 7,
324
+ "12": 11,
325
+ "13": 11,
326
+ "14": 10,
327
+ "15": 6,
328
+ "16": 9,
329
+ "17": 3,
330
+ "18": 9,
331
+ "19": 2,
332
+ "20": 8,
333
+ "21": 3,
334
+ "22": 4,
335
+ "23": 0
336
+ },
337
+ "eyebrows": {
338
+ "0": 1,
339
+ "1": 3,
340
+ "2": 2,
341
+ "3": 3,
342
+ "4": 11,
343
+ "5": 10,
344
+ "6": 0,
345
+ "7": 6,
346
+ "8": 8,
347
+ "9": 4,
348
+ "10": 1,
349
+ "11": 0,
350
+ "12": 2,
351
+ "13": 7,
352
+ "14": 4,
353
+ "15": 6,
354
+ "16": 10,
355
+ "17": 9,
356
+ "18": 9,
357
+ "19": 5,
358
+ "20": 5,
359
+ "21": 7,
360
+ "22": 8,
361
+ "23": 11
362
+ },
363
+ "eyes": {
364
+ "0": 2,
365
+ "1": 6,
366
+ "2": 0,
367
+ "3": 6,
368
+ "4": 1,
369
+ "5": 0,
370
+ "6": 5,
371
+ "7": 0,
372
+ "8": 3,
373
+ "9": 4,
374
+ "10": 9,
375
+ "11": 1,
376
+ "12": 5,
377
+ "13": 2,
378
+ "14": 10,
379
+ "15": 9,
380
+ "16": 8,
381
+ "17": 5,
382
+ "18": 9,
383
+ "19": 2,
384
+ "20": 11,
385
+ "21": 8,
386
+ "22": 8,
387
+ "23": 6,
388
+ "24": 6,
389
+ "25": 9,
390
+ "26": 7,
391
+ "27": 10,
392
+ "28": 10,
393
+ "29": 5,
394
+ "30": 7,
395
+ "31": 8,
396
+ "32": 3,
397
+ "33": 0,
398
+ "34": 7,
399
+ "35": 11,
400
+ "36": 3,
401
+ "37": 4,
402
+ "38": 2,
403
+ "39": 4,
404
+ "40": 10,
405
+ "41": 1,
406
+ "42": 3,
407
+ "43": 7,
408
+ "44": 1,
409
+ "45": 4,
410
+ "46": 11,
411
+ "47": 11
412
+ },
413
+ "hairs": {
414
+ "0": 11,
415
+ "1": 6,
416
+ "2": 5,
417
+ "3": 1,
418
+ "4": 4,
419
+ "5": 8,
420
+ "6": 4,
421
+ "7": 11,
422
+ "8": 9,
423
+ "9": 3,
424
+ "10": 3,
425
+ "11": 6,
426
+ "12": 0,
427
+ "13": 1,
428
+ "14": 0,
429
+ "15": 10,
430
+ "16": 1,
431
+ "17": 8,
432
+ "18": 4,
433
+ "19": 7,
434
+ "20": 5,
435
+ "21": 10,
436
+ "22": 2,
437
+ "23": 3,
438
+ "24": 9,
439
+ "25": 5,
440
+ "26": 3,
441
+ "27": 10,
442
+ "28": 6,
443
+ "29": 11,
444
+ "30": 9,
445
+ "31": 11,
446
+ "32": 0,
447
+ "33": 0,
448
+ "34": 11,
449
+ "35": 9,
450
+ "36": 6,
451
+ "37": 2,
452
+ "38": 1,
453
+ "39": 4,
454
+ "40": 1,
455
+ "41": 7,
456
+ "42": 2,
457
+ "43": 0,
458
+ "44": 3,
459
+ "45": 6,
460
+ "46": 2,
461
+ "47": 1,
462
+ "48": 3,
463
+ "49": 7,
464
+ "50": 7,
465
+ "51": 2,
466
+ "52": 5,
467
+ "53": 7,
468
+ "54": 5,
469
+ "55": 8,
470
+ "56": 9,
471
+ "57": 10,
472
+ "58": 6,
473
+ "59": 8,
474
+ "60": 10,
475
+ "61": 0,
476
+ "62": 11,
477
+ "63": 7,
478
+ "64": 9,
479
+ "65": 8,
480
+ "66": 4,
481
+ "67": 4,
482
+ "68": 10,
483
+ "69": 2,
484
+ "70": 5,
485
+ "71": 8
486
+ }
487
+ },
488
+
489
+ // 3DS fields
490
+ 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"],
491
+ makeups3DS: ["None", "Blush", "Orange Blush", "Blue Eyes", "Blush 2", "Orange Blush 2", "Blue Eyes and Blush", "Orange Eyes and Blush", "Purple Eyes and Blush 2", "Freckles", "Beard Stubble", "Beard and Mustache Stubble"],
492
+ mouthCols3DS: ["Orange", "Red", "Pink", "Peach", "Black"],
493
+ glassesCols3DS: ["Black", "Brown", "Red", "Blue", "Yellow", "Grey"],
494
+
495
+ faces: {
496
+ indexLookup: true,
497
+ values: [
498
+ 0x00, 0x01, 0x08,
499
+ 0x02, 0x03, 0x09,
500
+ 0x04, 0x05, 0x0a,
501
+ 0x06, 0x07, 0x0b
502
+ ]
503
+ },
504
+ hairs: {
505
+ paginated: true,
506
+ indexLookup: true,
507
+ values: [
508
+ [0x21, 0x2f, 0x28, 0x25, 0x20, 0x6b, 0x30, 0x33, 0x37, 0x46, 0x2c, 0x42],
509
+ [0x34, 0x32, 0x26, 0x31, 0x2b, 0x1f, 0x38, 0x44, 0x3e, 0x73, 0x4c, 0x77],
510
+ [0x40, 0x51, 0x74, 0x79, 0x16, 0x3a, 0x3c, 0x57, 0x7d, 0x75, 0x49, 0x4b],
511
+ [0x2a, 0x59, 0x39, 0x36, 0x50, 0x22, 0x17, 0x56, 0x58, 0x76, 0x27, 0x24],
512
+ [0x2d, 0x43, 0x3b, 0x41, 0x29, 0x1e, 0x0c, 0x10, 0x0a, 0x52, 0x80, 0x81],
513
+ [0x0e, 0x5f, 0x69, 0x64, 0x06, 0x14, 0x5d, 0x66, 0x1b, 0x04, 0x11, 0x6e],
514
+ [0x7b, 0x08, 0x6a, 0x48, 0x03, 0x15, 0x00, 0x62, 0x3f, 0x5a, 0x0b, 0x78],
515
+ [0x05, 0x4a, 0x6c, 0x5e, 0x7c, 0x19, 0x63, 0x45, 0x23, 0x0d, 0x7a, 0x71],
516
+ [0x35, 0x18, 0x55, 0x53, 0x47, 0x83, 0x60, 0x65, 0x1d, 0x07, 0x0f, 0x70],
517
+ [0x4f, 0x01, 0x6d, 0x7f, 0x5b, 0x1a, 0x3d, 0x67, 0x02, 0x4d, 0x12, 0x5c],
518
+ [0x54, 0x09, 0x13, 0x82, 0x61, 0x68, 0x2e, 0x4e, 0x1c, 0x72, 0x7e, 0x6f]
519
+ ]
520
+ },
521
+ eyebrows: {
522
+ indexLookup: true,
523
+ paginated: true,
524
+ values: [
525
+ [0x06, 0x00, 0x0c, 0x01, 0x09, 0x13, 0x07, 0x15, 0x08, 0x11, 0x05, 0x04],
526
+ [0x0b, 0x0a, 0x02, 0x03, 0x0e, 0x14, 0x0f, 0x0d, 0x16, 0x12, 0x10, 0x17]
527
+ ]
528
+ },
529
+ eyes: {
530
+ indexLookup: true,
531
+ paginated: true,
532
+ values: [
533
+ [0x02, 0x04, 0x00, 0x08, 0x27, 0x11, 0x01, 0x1a, 0x10, 0x0f, 0x1b, 0x14],
534
+ [0x21, 0x0b, 0x13, 0x20, 0x09, 0x0c, 0x17, 0x22, 0x15, 0x19, 0x28, 0x23],
535
+ [0x05, 0x29, 0x0d, 0x24, 0x25, 0x06, 0x18, 0x1e, 0x1f, 0x12, 0x1c, 0x2e],
536
+ [0x07, 0x2c, 0x26, 0x2a, 0x2d, 0x1d, 0x03, 0x2b, 0x16, 0x0a, 0x0e, 0x2f],
537
+ [0x30, 0x31, 0x32, 0x35, 0x3b, 0x38, 0x36, 0x3a, 0x39, 0x37, 0x33, 0x34]
538
+ ]
539
+ },
540
+ noses: {
541
+ indexLookup: true,
542
+ paginated: true,
543
+ values: [
544
+ [0x01, 0x0a, 0x02, 0x03, 0x06, 0x00, 0x05, 0x04, 0x08, 0x09, 0x07, 0x0B],
545
+ [0x0d, 0x0e, 0x0c, 0x11, 0x10, 0x0f]
546
+ ]
547
+ },
548
+ mouths: {
549
+ indexLookup: true,
550
+ paginated: true,
551
+ values: [
552
+ [0x17, 0x01, 0x13, 0x15, 0x16, 0x05, 0x00, 0x08, 0x0a, 0x10, 0x06, 0x0d],
553
+ [0x07, 0x09, 0x02, 0x11, 0x03, 0x04, 0x0f, 0x0b, 0x14, 0x12, 0x0e, 0x0c],
554
+ [0x1b, 0x1e, 0x18, 0x19, 0x1d, 0x1c, 0x1a, 0x23, 0x1f, 0x22, 0x21, 0x20]
555
+ ]
556
+ }
406
557
  };
558
+
407
559
  var convTables={
408
560
  face3DSToWii:[0,1,2,2,3,1,4,5,4,6,7,6],
409
561
  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.
@@ -582,26 +734,8 @@ var convTables={
582
734
  "10",9,11
583
735
  ]
584
736
  };
585
- function lookupTable(table,value,paginated){
586
- if(paginated){
587
- for(var i=0;i<tables[table].length;i++){
588
- for(var j=0;j<tables[table][i].length;j++){
589
- if(tables[table][i][j]===value){
590
- return [i,j];
591
- }
592
- }
593
- }
594
- }
595
- else{
596
- for(var i=0;i<tables[table].length;i++){
597
- if(tables[table][i]===value){
598
- return i;
599
- }
600
- }
601
- }
602
- return undefined;
603
- }
604
- var kidNames={
737
+
738
+ const kidNames={
605
739
  "Male":[
606
740
  "Aaron",
607
741
  "Adam",
@@ -890,7 +1024,9 @@ var kidNames={
890
1024
  "Taylor"
891
1025
  ]
892
1026
  };
893
- var defaultInstrs={
1027
+
1028
+ //Defaults
1029
+ const defaultInstrs={
894
1030
  wii:{
895
1031
  male:{
896
1032
  "col": "On the info page (first tab), set the Favorite Color to Red (1 from the left, top row).",
@@ -1073,370 +1209,718 @@ var defaultInstrs={
1073
1209
  }
1074
1210
  };
1075
1211
 
1076
- async function renderMii(studioMii,fflRes=_fflRes) {
1077
- var width=600,height=600;
1078
- /* ---------- WebGL 1 context ---------- */
1079
- const gl = createGL(width, height, { preserveDrawingBuffer: true });
1080
-
1081
- /* ---------- dummy canvas to keep Three.js happy ---------- */
1082
- const dummyCanvas = {
1083
- width,
1084
- height,
1085
- getContext: () => gl,
1086
- addEventListener () {},
1087
- removeEventListener () {},
1088
- style: {},
1089
- };
1090
-
1091
- /* ---------- Three.js renderer ---------- */
1092
-
1093
-
1094
- const renderer = new THREE.WebGLRenderer({
1095
- canvas: dummyCanvas,
1096
- context: gl,
1097
- });
1098
- renderer.setSize(width, height);
1099
- renderer.setClearColor(0xffffff);
1100
-
1101
- let moduleFFL, currentCharModel;
1102
- /* ---------- simple scene ---------- */
1103
- const scene = new THREE.Scene();
1104
- scene.background = new THREE.Color().setHex(0xffffff, THREE.ColorManagement ? THREE.ColorManagement.workingColorSpace : '');
1105
- let camera = new THREE.PerspectiveCamera(15, width / height, 1, 5000);
1106
- camera.position.set(0, 30, 500);
1107
- function updateCharModelInScene(data, modelDesc) {
1108
- // Decode data.
1109
- if (typeof data === 'string') {
1110
- data = parseHexOrB64ToUint8Array(data);
1111
- }
1112
- // Continue assuming it is Uint8Array.
1113
- // If an existing CharModel exists, update it.
1114
- if (currentCharModel) {
1115
- // Remove current CharModel from the scene, then dispose it.
1116
- currentCharModel.meshes && scene.remove(currentCharModel.meshes);
1117
- currentCharModel.dispose();
1118
- }
1119
-
1120
- // Create a new CharModel.
1121
- currentCharModel = createCharModel(data, modelDesc, FFLShaderMaterial, moduleFFL);
1122
- // Initialize textures for the new CharModel.
1123
- initCharModelTextures(currentCharModel, renderer);
1124
-
1125
- // Add CharModel meshes to scene.
1126
- scene.add(currentCharModel.meshes);
1127
- }
1212
+ const defaultMii={
1213
+ "male":{
1214
+ "general": {
1215
+ "type":3,
1216
+ "birthday": 17,
1217
+ "birthMonth": 4,
1218
+ "height": 0,
1219
+ "weight": 0,
1220
+ "gender": 1,
1221
+ "favoriteColor": 7
1222
+ },
1223
+ "meta":{
1224
+ "name": "Madison",
1225
+ "creatorName": "",
1226
+ "console":"3ds",
1227
+ "miiId":"148",
1228
+ "systemId":"148"
1229
+ },
1230
+ "perms": {
1231
+ "sharing": false,
1232
+ "copying": true,
1233
+ "fromCheckMiiOut": false,
1234
+ "mingle": true
1235
+ },
1236
+ "hair": {
1237
+ "page":0,
1238
+ "type":7,
1239
+ "color": 7,
1240
+ "flipped": false
1241
+ },
1242
+ "face": {
1243
+ "type": 5,
1244
+ "color": 0,
1245
+ "feature": 0,
1246
+ "makeup": 0
1247
+ },
1248
+ "eyes": {
1249
+ "page":0,
1250
+ "type": 9,
1251
+ "col": 4,
1252
+ "size": 1,
1253
+ "squash": 3,
1254
+ "rotation": 4,
1255
+ "distanceApart": 3,
1256
+ "yPosition": 11
1257
+ },
1258
+ "eyebrows": {
1259
+ "page":0,
1260
+ "type":5,
1261
+ "color":7,
1262
+ "size": 2,
1263
+ "squash": 4,
1264
+ "rotation": 4,
1265
+ "distanceApart": 4,
1266
+ "yPosition": 6
1267
+ },
1268
+ "nose": {
1269
+ "page":1,
1270
+ "type":0,
1271
+ "size": 0,
1272
+ "yPosition": 5
1273
+ },
1274
+ "mouth": {
1275
+ "page":1,
1276
+ "type":6,
1277
+ "color": 0,
1278
+ "size": 2,
1279
+ "squash": 3,
1280
+ "yPosition": 10
1281
+ },
1282
+ "beard": {
1283
+ "mustache":{
1284
+ "type": 0,
1285
+ "size": 4,
1286
+ "yPosition": 10
1287
+ },
1288
+ "col": 0,
1289
+ "type": 0
1290
+ },
1291
+ "glasses": {
1292
+ "type": 0,
1293
+ "color":0,
1294
+ "size": 4,
1295
+ "yPosition": 10
1296
+ },
1297
+ "mole": {
1298
+ "on": false,
1299
+ "size": 4,
1300
+ "xPosition": 2,
1301
+ "yPosition": 20
1302
+ },
1303
+ "name": "Madison",
1304
+ "creatorName": ""
1305
+ },
1306
+ "female":{
1307
+ "general": {
1308
+ "type":3,
1309
+ "birthday": 17,
1310
+ "birthMonth": 4,
1311
+ "height": 0,
1312
+ "weight": 0,
1313
+ "gender": 1,
1314
+ "favoriteColor": 7
1315
+ },
1316
+ "meta":{
1317
+ "name": "Madison",
1318
+ "creatorName": "",
1319
+ "console":"3ds",
1320
+ "miiId":"148",
1321
+ "systemId":"148"
1322
+ },
1323
+ "perms": {
1324
+ "sharing": false,
1325
+ "copying": true,
1326
+ "fromCheckMiiOut": false,
1327
+ "mingle": true
1328
+ },
1329
+ "hair": {
1330
+ "page":0,
1331
+ "type":7,
1332
+ "color": 7,
1333
+ "flipped": false
1334
+ },
1335
+ "face": {
1336
+ "type": 5,
1337
+ "color": 0,
1338
+ "feature": 0,
1339
+ "makeup": 0
1340
+ },
1341
+ "eyes": {
1342
+ "page":0,
1343
+ "type": 9,
1344
+ "col": 4,
1345
+ "size": 1,
1346
+ "squash": 3,
1347
+ "rotation": 4,
1348
+ "distanceApart": 3,
1349
+ "yPosition": 11
1350
+ },
1351
+ "eyebrows": {
1352
+ "page":0,
1353
+ "type":5,
1354
+ "color":7,
1355
+ "size": 2,
1356
+ "squash": 4,
1357
+ "rotation": 4,
1358
+ "distanceApart": 4,
1359
+ "yPosition": 6
1360
+ },
1361
+ "nose": {
1362
+ "page":1,
1363
+ "type":0,
1364
+ "size": 0,
1365
+ "yPosition": 5
1366
+ },
1367
+ "mouth": {
1368
+ "page":1,
1369
+ "type":6,
1370
+ "color": 0,
1371
+ "size": 2,
1372
+ "squash": 3,
1373
+ "yPosition": 10
1374
+ },
1375
+ "beard": {
1376
+ "mustache":{
1377
+ "type": 0,
1378
+ "size": 4,
1379
+ "yPosition": 10
1380
+ },
1381
+ "col": 0,
1382
+ "type": 0
1383
+ },
1384
+ "glasses": {
1385
+ "type": 0,
1386
+ "color":0,
1387
+ "size": 4,
1388
+ "yPosition": 10
1389
+ },
1390
+ "mole": {
1391
+ "on": false,
1392
+ "size": 4,
1393
+ "xPosition": 2,
1394
+ "yPosition": 20
1395
+ },
1396
+ "name": "Madison",
1397
+ "creatorName": ""
1398
+ }
1399
+ };
1128
1400
 
1129
- const box = new THREE.Mesh(
1130
- new THREE.BoxGeometry(),
1131
- new THREE.MeshBasicMaterial({ color: 0x00ffff })
1132
- );
1133
- scene.add(box);
1134
- const initResult = await initializeFFL(fflRes, ModuleFFL);
1135
- moduleFFL = initResult.module;
1401
+ // Mii binary helpers
1402
+ const decoders = {
1403
+ number: (value, field) => value + (field.offset || 0),
1404
+ boolean: (value, field) => field.invert ? value === 0 : value === 1,
1405
+ enum: (value, field) => field.values[value],
1406
+ lookup: (value, field, tables) => {
1407
+ const table = getNestedProperty(tables, field.lookupTable)
1408
+ if (!table) return "ERROR: could not find requested lookup table";
1136
1409
 
1137
- updateCharModelInScene(studioMii, FFLCharModelDescDefault); // Use default expression.
1410
+ if (table.indexLookup) {
1411
+ if (table.paginated) {
1412
+ // Handle paginated (2D array) lookup
1413
+ for (let page = 0; page < table.values.length; page++) {
1414
+ for (let index = 0; index < table.values[page].length; index++) {
1415
+ if (table.values[page][index] === value) {
1416
+ return [page, index];
1417
+ }
1418
+ }
1419
+ }
1420
+ return undefined;
1421
+ } else {
1422
+ // Handle non-paginated index lookup
1423
+ return table.values.indexOf(value);
1424
+ }
1425
+ } else if (Array.isArray(table)) {
1426
+ return table[value];
1427
+ } else {
1428
+ return table[value.toString()];
1429
+ }
1430
+ },
1431
+ lookupPage: (value, field, tables, type) => {
1432
+ const table = getNestedProperty(tables, field.lookupTable)
1433
+ if (!table) return "ERROR: could not find requested lookup table";
1138
1434
 
1139
- renderer.render(scene, camera);
1435
+ if (table.indexLookup) {
1436
+ if (table.paginated) {
1437
+ // Handle paginated (2D array) lookup
1438
+ for (let page = 0; page < table.values.length; page++) {
1439
+ for (let index = 0; index < table.values[page].length; index++) {
1440
+ if (table.values[page][index] === value) {
1441
+ return [page, index][0];
1442
+ }
1443
+ }
1444
+ }
1445
+ return undefined;
1446
+ } else {
1447
+ // Handle non-paginated index lookup
1448
+ return table.values.indexOf(value);
1449
+ }
1450
+ } else if (Array.isArray(table)) {
1451
+ return table[value];
1452
+ } else {
1453
+ return table[value.toString()];
1454
+ }
1455
+ },
1456
+ lookupType: (value, field, tables, type) => {
1457
+ const table = getNestedProperty(tables, field.lookupTable)
1458
+ if (!table) return "ERROR: could not find requested lookup table";
1140
1459
 
1460
+ if (table.indexLookup) {
1461
+ if (table.paginated) {
1462
+ // Handle paginated (2D array) lookup
1463
+ for (let page = 0; page < table.values.length; page++) {
1464
+ for (let index = 0; index < table.values[page].length; index++) {
1465
+ if (table.values[page][index] === value) {
1466
+ return [page, index][1];
1467
+ }
1468
+ }
1469
+ }
1470
+ return undefined;
1471
+ } else {
1472
+ // Handle non-paginated index lookup
1473
+ return table.values.indexOf(value);
1474
+ }
1475
+ } else if (Array.isArray(table)) {
1476
+ return table[value];
1477
+ } else {
1478
+ return table[value.toString()];
1479
+ }
1480
+ },
1481
+ color: (value, field, tables) => tables[field.colorArray]?.[value] || value
1482
+ };
1141
1483
 
1484
+ const encoders = {
1485
+ number: (value, field) => value - (field.offset || 0),
1486
+ boolean: (value, field) => field.invert ? (value ? 0 : 1) : (value ? 1 : 0),
1487
+ enum: (value, field) => field.values.indexOf(value),
1488
+ lookup: (decodedValue, field, tables) => {
1489
+ const table = getNestedProperty(tables, field.lookupTable)
1490
+ if (!table) return "ERROR: could not find requested lookup table";
1142
1491
 
1143
- /* ---------- read pixels ---------- */
1144
- const pixels = new Uint8Array(width * height * 4);
1145
- gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
1492
+ if (table.indexLookup){
1493
+ if (table.paginated) {
1494
+ if (!Array.isArray(decodedValue) || decodedValue.length !== 2) {
1495
+ return undefined;
1496
+ }
1497
+ const [page, index] = decodedValue;
1498
+ if (page >= 0 && page < table.values.length && index >= 0 && index < table.values[page].length) {
1499
+ return table.values[page][index];
1500
+ }
1501
+ return undefined;
1502
+ } else {
1503
+ return table.values[decodedValue];
1504
+ }
1505
+ } else if (Array.isArray(table)) {
1506
+ const index = table.indexOf(decodedValue);
1507
+ return index !== -1 ? index : undefined;
1508
+ } else {
1509
+ // Handle object lookup
1510
+ for (const [key, val] of Object.entries(table)) {
1511
+ if (val === decodedValue) return parseInt(key);
1512
+ }
1513
+ return undefined;
1514
+ }
1515
+ },
1516
+ color: (value, field, T) => {
1517
+ const arr = T[field.colorArray];
1518
+ return arr?.indexOf(value) ?? value;
1519
+ },
1520
+ };
1146
1521
 
1147
- /* ---------- flip rows (Uint8Array → Buffer) ---------- */
1148
- const src = Buffer.from(pixels); // <-- Convert here
1149
- const flipped = Buffer.alloc(src.length);
1522
+ // Decoding system
1523
+ function decodeString(data, field) {
1524
+ let result = "";
1525
+ const maxLength = field.maxLength || 10;
1526
+
1527
+ for (let i = 0; i < maxLength; i++) {
1528
+ const charOffset = field.byteOffset + (i * 2);
1529
+ if (charOffset + 1 < data.length) {
1530
+ const char1 = data[charOffset];
1531
+ const char2 = data[charOffset + 1];
1532
+ if (char1 === 0 && char2 === 0) break;
1533
+ result += String.fromCharCode(field.endianness == "little" ? char1 : char2);
1534
+ }
1535
+ }
1536
+ return result.replace(/\x00/g, "");
1537
+ }
1150
1538
 
1151
- const rowBytes = width * 4;
1152
- for (let y = 0; y < height; y++) {
1153
- const srcStart = y * rowBytes;
1154
- const dstStart = (height - y - 1) * rowBytes;
1155
- src.copy(flipped, dstStart, srcStart, srcStart + rowBytes);
1156
- }
1539
+ function encodeString(str, field) {
1540
+ const result = [];
1541
+ const maxLength = field.maxLength || 10;
1157
1542
 
1158
- /* ---------- draw into Node-canvas ---------- */
1159
- const canvas = createCanvas(width, height);
1160
- const ctx = canvas.getContext('2d');
1161
- const img = new ImageData(new Uint8ClampedArray(flipped.buffer), width, height);
1162
- ctx.putImageData(img, 0, 0);
1543
+ for (let i = 0; i < maxLength; i++) {
1544
+ const code = i < str.length ? str.charCodeAt(i) : 0;
1163
1545
 
1164
- return canvas.toBuffer('image/png');
1546
+ if (field.endianness == "little") {
1547
+ result.push(code); // Low byte
1548
+ result.push(0); // High byte
1549
+ } else {
1550
+ result.push(0); // High byte
1551
+ result.push(code); // Low byte
1552
+ }
1553
+ }
1554
+ return result;
1165
1555
  }
1166
1556
 
1167
- var exports={
1168
- readWiiBin:function(binOrPath){
1169
- var thisMii={
1170
- info:{},
1171
- face:{},
1172
- nose:{},
1173
- mouth:{},
1174
- mole:{},
1175
- hair:{},
1176
- eyebrows:{},
1177
- eyes:{},
1178
- glasses:{},
1179
- facialHair:{}
1180
- };
1557
+ function extractMultiBits(data, bitSpecs, isBigEndian = true) {
1558
+ let result = 0;
1559
+ let totalBitsProcessed = 0;
1181
1560
 
1182
- let data;
1183
- if(/[^01]/ig.test(binOrPath)){
1184
- data = fs.readFileSync(binOrPath);
1185
- }
1186
- else{
1187
- data=Buffer.from(binOrPath);
1188
- }
1561
+ // Process bit specs in order (they should be ordered from most significant to least significant)
1562
+ for (const spec of bitSpecs) {
1563
+ const bits = extractBits(data, spec.byteOffset, spec.bitOffset, spec.bitLength, isBigEndian);
1564
+ result = (result << spec.bitLength) | bits;
1565
+ totalBitsProcessed += spec.bitLength;
1566
+ }
1189
1567
 
1190
- const get = address => getBinaryFromAddress(address, data);
1568
+ return result;
1569
+ }
1191
1570
 
1192
- var name="";
1193
- for(var i=0;i<10;i++){
1194
- name+=data.slice(3+i*2, 4+i*2)+"";
1195
- }
1196
- thisMii.name=name.replaceAll("\x00","");
1197
- var cname="";
1198
- for(var i=0;i<10;i++){
1199
- cname+=data.slice(55+i*2, 56+i*2)+"";
1200
- }
1201
- thisMii.creatorName=cname.replaceAll("\x00","");
1202
- thisMii.info.creatorName=thisMii.creatorName;
1203
- thisMii.info.name=thisMii.name;//Up to ten characters
1204
- thisMii.info.gender=get(0x00)[1]==="1"?"Female":"Male";//0 for Male, 1 for Female
1205
- thisMii.info.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);
1206
- thisMii.info.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);
1207
- var temp=get(0x20);
1208
- thisMii.face.shape=parseInt(temp.slice(0,3),2);//0-7
1209
- thisMii.face.col=skinCols[parseInt(temp.slice(3,6),2)];//0-5
1210
- temp=get(0x21);
1211
- thisMii.face.feature=wiiFaceFeatures[parseInt(get(0x20).slice(6,8)+temp.slice(0,2),2)];//0-11
1212
- thisMii.info.mingle=temp[5]==="0";//0 for Mingle, 1 for Don't Mingle
1213
- temp=get(0x2C);
1214
- for(var i=0;i<12;i++){
1215
- if(wiiNoses[i]===parseInt(temp.slice(0,4),2)){
1216
- thisMii.nose.type=i;
1571
+ function setMultiBits(buffer, bitSpecs, value) {
1572
+ let remainingValue = value;
1573
+
1574
+ // Process specs in reverse order (from least significant to most significant)
1575
+ for (let i = bitSpecs.length - 1; i >= 0; i--) {
1576
+ const spec = bitSpecs[i];
1577
+ const mask = (1 << spec.bitLength) - 1;
1578
+ const bitsToSet = remainingValue & mask;
1579
+
1580
+ setBits(buffer, spec.byteOffset, spec.bitOffset, spec.bitLength, bitsToSet);
1581
+ remainingValue >>>= spec.bitLength;
1582
+ }
1583
+ }
1584
+
1585
+ function extractBits(data, byteOffset, bitOffset, bitLength, isBigEndian = true) {
1586
+ const totalBitOffset = byteOffset * 8 + bitOffset;
1587
+ const startByte = Math.floor(totalBitOffset / 8);
1588
+ const endByte = Math.floor((totalBitOffset + bitLength - 1) / 8);
1589
+
1590
+ let value = 0;
1591
+
1592
+ if (isBigEndian) {
1593
+ // Big endian: process bytes left to right (original behavior)
1594
+ for (let i = startByte; i <= endByte; i++) {
1595
+ if (i < data.length) {
1596
+ value = (value << 8) | data[i];
1217
1597
  }
1218
1598
  }
1219
- thisMii.nose.size=parseInt(temp.slice(4,8),2);
1220
- thisMii.nose.yPos=parseInt(get(0x2D).slice(0,5),2);//From top to bottom, 0-18, default 9
1221
- temp=get(0x2E);
1222
- thisMii.mouth.type=mouthTable[""+parseInt(temp.slice(0,5),2)];//0-23, Needs lookup table
1223
- thisMii.mouth.col=wiiMouthColors[parseInt(temp.slice(5,7),2)];//0-2, refer to mouthColors array
1224
- temp2=get(0x2F);
1225
- thisMii.mouth.size=parseInt(temp[7]+temp2.slice(0,3),2);//0-8, default 4
1226
- thisMii.mouth.yPos=parseInt(temp2.slice(3,8),2);//0-18, default 9, from top to bottom
1227
- temp=get(0x00);
1228
- var temp2=get(0x01);
1229
- thisMii.info.birthMonth=parseInt(temp.slice(2,6),2);
1230
- thisMii.info.birthday=parseInt(temp.slice(6,8)+temp2.slice(0,3),2);
1231
- thisMii.info.favColor=favCols[parseInt(temp2.slice(3,7),2)];//0-11, refer to cols array
1232
- thisMii.info.favorited=temp2[7]==="0"?false:true;
1233
- thisMii.info.height=parseInt(get(0x16),2);//0-127
1234
- thisMii.info.weight=parseInt(get(0x17),2);//0-127
1235
- thisMii.info.downloadedFromCheckMiiOut=get(0x21)[7]==="0"?false:true;
1236
- temp=get(0x34);
1237
- temp2=get(0x35);
1238
- thisMii.mole.on=temp[0]==="0"?false:true;//0 for Off, 1 for On
1239
- thisMii.mole.size=parseInt(temp.slice(1,5),2);//0-8, default 4
1240
- thisMii.mole.xPos=parseInt(temp2.slice(2,7),2);//0-16, Default 2
1241
- thisMii.mole.yPos=parseInt(temp.slice(5,8)+temp2.slice(0,2),2);//Top to bottom
1242
- temp=get(0x22);
1243
- temp2=get(0x23);
1244
- thisMii.hair.type=hairTable[""+parseInt(temp.slice(0,7),2)];//0-71, Needs lookup table
1245
- thisMii.hair.col=hairCols[parseInt(temp[7]+temp2.slice(0,2),2)];//0-7, refer to hairCols array
1246
- thisMii.hair.flipped=temp2[2]==="0"?false:true;
1247
- temp=get(0x24);
1248
- temp2=get(0x25);
1249
- thisMii.eyebrows.type=eyebrowTable[""+parseInt(temp.slice(0,5),2)];//0-23, Needs lookup table
1250
- thisMii.eyebrows.rotation=parseInt(temp.slice(6,8)+temp2.slice(0,2),2);//0-11, default varies based on eyebrow type
1251
- temp=get(0x26);
1252
- temp2=get(0x27);
1253
- thisMii.eyebrows.col=hairCols[parseInt(temp.slice(0,3),2)];
1254
- thisMii.eyebrows.size=parseInt(temp.slice(3,7),2);//0-8, default 4
1255
- thisMii.eyebrows.yPos=(parseInt(temp[7]+temp2.slice(0,4),2))-3;//0-15, default 10
1256
- thisMii.eyebrows.distApart=parseInt(temp2.slice(4,8),2);//0-12, default 2
1257
- thisMii.eyes.type=eyeTable[parseInt(get(0x28).slice(0,6),2)];//0-47, needs lookup table
1258
- temp=get(0x29);
1259
- thisMii.eyes.rotation=parseInt(temp.slice(0,3),2);//0-7, default varies based on eye type
1260
- thisMii.eyes.yPos=parseInt(temp.slice(3,8),2);//0-18, default 12, top to bottom
1261
- temp=get(0x2A);
1262
- thisMii.eyes.col=eyeCols[parseInt(temp.slice(0,3),2)];//0-5
1263
- thisMii.eyes.size=parseInt(temp.slice(4,7),2);//0-7, default 4
1264
- temp2=get(0x2B);
1265
- thisMii.eyes.distApart=parseInt(temp[7]+temp2.slice(0,3),2);//0-12, default 2
1266
- temp=get(0x30);
1267
- thisMii.glasses.type=parseInt(temp.slice(0,4),2);//0-8
1268
- thisMii.glasses.col=wiiGlassesCols[parseInt(temp.slice(4,7),2)];//0-5
1269
- temp=get(0x31);
1270
- thisMii.glasses.size=parseInt(temp.slice(0,3),2);//0-7, default 4
1271
- thisMii.glasses.yPos=parseInt(temp.slice(3,8),2);//0-20, default 10
1272
- temp=get(0x32);
1273
- temp2=get(0x33);
1274
- thisMii.facialHair.mustacheType=parseInt(temp.slice(0,2),2);//0-3
1275
- thisMii.facialHair.beardType=parseInt(temp.slice(2,4),2);//0-3
1276
- thisMii.facialHair.col=hairCols[parseInt(temp.slice(4,7),2)];//0-7
1277
- thisMii.facialHair.mustacheSize=parseInt(temp[7]+temp2.slice(0,3),2);//0-30, default 20
1278
- thisMii.facialHair.mustacheYPos=parseInt(temp2.slice(3,8),2);//0-16, default 2
1279
- thisMii.console="Wii";
1280
- return thisMii;
1281
- },
1282
- read3DSQR:async function(binOrPath){
1283
- function readMii(data){
1284
- var miiJson={
1285
- info:{},
1286
- perms:{},
1287
- hair:{},
1288
- face:{},
1289
- eyes:{},
1290
- eyebrows:{},
1291
- nose:{},
1292
- mouth:{},
1293
- facialHair:{},
1294
- glasses:{},
1295
- mole:{}
1296
- };
1297
- const get = address => getBinaryFromAddress(address, data);
1298
- var temp=get(0x18);
1299
- var temp2=get(0x19);
1300
- miiJson.info.birthday=parseInt(temp2.slice(6,8)+temp.slice(0,3),2);
1301
- miiJson.info.birthMonth=parseInt(temp.slice(3,7),2);
1302
- var name="";
1303
- for(var i=0x1A;i<0x2E;i+=2){
1304
- if(get(i)==="00000000"){
1305
- break;
1306
- }
1307
- name+=data.slice(i,i+1);
1599
+ } else {
1600
+ // Little endian: process bytes right to left
1601
+ for (let i = endByte; i >= startByte; i--) {
1602
+ if (i < data.length) {
1603
+ value = (value << 8) | data[i];
1308
1604
  }
1309
- miiJson.name=name.replaceAll("\x00","");
1310
- var cname="";
1311
- for(var i=0x48;i<0x5C;i+=2){
1312
- if(get(i)==="00000000"){
1313
- break;
1314
- }
1315
- cname+=data.slice(i,i+1);
1316
- }
1317
- miiJson.creatorName=cname.replaceAll("\x00","");
1318
- miiJson.info.name=miiJson.name;
1319
- miiJson.info.creatorName=miiJson.creatorName;
1320
- miiJson.info.height=parseInt(get(0x2E),2);
1321
- miiJson.info.weight=parseInt(get(0x2F),2);
1322
- miiJson.info.gender=temp[7]==="1"?"Female":"Male";
1323
- temp=get(0x30);
1324
- miiJson.perms.sharing=temp[7]==="1"?false:true;
1325
- miiJson.info.favColor=favCols[parseInt(temp2.slice(2,6),2)];
1326
- miiJson.perms.copying=get(0x01)[7]==="1"?true:false;
1327
- miiJson.hair.style=lookupTable("hairs",parseInt(get(0x32),2),true);
1328
- miiJson.face.shape=lookupTable("faces",parseInt(temp.slice(3,7),2),false);
1329
- miiJson.face.col=skinCols[parseInt(temp.slice(0,3),2)];
1330
- temp=get(0x31);
1331
- miiJson.face.feature=faceFeatures3DS[parseInt(temp.slice(4,8),2)];
1332
- miiJson.face.makeup=makeups3DS[parseInt(temp.slice(0,4),2)];
1333
- temp=get(0x34);
1334
- miiJson.eyes.type=lookupTable("eyes",parseInt(temp.slice(2,8),2),true);
1335
- temp2=get(0x33);
1336
- miiJson.hair.col=hairCols[parseInt(temp2.slice(5,8),2)];
1337
- miiJson.hair.flipped=temp2[4]==="0"?false:true;
1338
- miiJson.eyes.col=eyeCols[parseInt(get(0x35)[7]+temp.slice(0,2),2)];
1339
- temp=get(0x35);
1340
- miiJson.eyes.size=parseInt(temp.slice(3,7),2);
1341
- miiJson.eyes.squash=parseInt(temp.slice(0,3),2);
1342
- temp=get(0x36);
1343
- temp2=get(0x37);
1344
- miiJson.eyes.rot=parseInt(temp.slice(3,8),2);
1345
- miiJson.eyes.distApart=parseInt(temp2[7]+temp.slice(0,3),2);
1346
- miiJson.eyes.yPos=parseInt(temp2.slice(2,7),2);
1347
- temp=get(0x38);
1348
- miiJson.eyebrows.style=lookupTable("eyebrows",parseInt(temp.slice(3,8),2),true);
1349
- miiJson.eyebrows.col=hairCols[parseInt(temp.slice(0,3),2)];
1350
- temp=get(0x39);
1351
- miiJson.eyebrows.size=parseInt(temp.slice(4,8),2);
1352
- miiJson.eyebrows.squash=parseInt(temp.slice(1,4),2);
1353
- temp=get(0x3A);
1354
- miiJson.eyebrows.rot=parseInt(temp.slice(4,8),2);
1355
- temp2=get(0x3B);
1356
- miiJson.eyebrows.distApart=parseInt(temp2[7]+temp.slice(0,3),2);
1357
- miiJson.eyebrows.yPos=parseInt(temp2.slice(2,7),2)-3;
1358
- temp=get(0x3C);
1359
- miiJson.nose.type=lookupTable("noses",parseInt(temp.slice(3,8),2),true);
1360
- temp2=get(0x3D);
1361
- miiJson.nose.size=parseInt(temp2[7]+temp.slice(0,3),2);
1362
- miiJson.nose.yPos=parseInt(temp2.slice(2,7),2);
1363
- temp=get(0x3E);
1364
- miiJson.mouth.type=lookupTable("mouths",parseInt(temp.slice(2,8),2),true);
1365
- temp2=get(0x3F);
1366
- miiJson.mouth.col=mouthCols3DS[parseInt(temp2[7]+temp.slice(0,2),2)];
1367
- miiJson.mouth.size=parseInt(temp2.slice(3,7),2);
1368
- miiJson.mouth.squash=parseInt(temp2.slice(0,3),2);
1369
- temp=get(0x40);
1370
- miiJson.mouth.yPos=parseInt(temp.slice(3,8),2);
1371
- miiJson.facialHair.mustacheType=parseInt(temp.slice(0,3),2);
1372
- temp=get(0x42);
1373
- miiJson.facialHair.beardType=parseInt(temp.slice(5,8),2);
1374
- miiJson.facialHair.col=hairCols[parseInt(temp.slice(2,5),2)];
1375
- temp2=get(0x43);
1376
- miiJson.facialHair.mustacheSize=parseInt(temp2.slice(6,8)+temp.slice(0,2),2);
1377
- miiJson.facialHair.mustacheYPos=parseInt(temp2.slice(1,6),2);
1378
- temp=get(0x44);
1379
- miiJson.glasses.type=parseInt(temp.slice(4,8),2);
1380
- miiJson.glasses.col=glassesCols3DS[parseInt(temp.slice(1,4),2)];
1381
- temp2=get(0x45);
1382
- miiJson.glasses.size=parseInt(temp2.slice(5,8)+temp[0],2);
1383
- miiJson.glasses.yPos=parseInt(temp2.slice(0,5),2);
1384
- temp=get(0x46);
1385
- miiJson.mole.on=temp[7]==="0"?false:true;
1386
- miiJson.mole.size=parseInt(temp.slice(3,7),2);
1387
- temp2=get(0x47);
1388
- miiJson.mole.xPos=parseInt(temp2.slice(6,8)+temp.slice(0,3),2);
1389
- miiJson.mole.yPos=parseInt(temp2.slice(1,6),2);
1390
- miiJson.console="3DS";
1391
- return miiJson;
1392
1605
  }
1393
- let qrCode;
1394
- if(/[^01]/ig.test(binOrPath)){
1395
- var data=fs.readFileSync(binOrPath);
1396
- var img=await loadImage(data);
1397
- const canvas = createCanvas(img.width, img.height);
1398
- const ctx = canvas.getContext('2d');
1399
- ctx.drawImage(img, 0, 0);
1400
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
1401
- qrCode = jsQR(imageData.data, imageData.width, imageData.height).binaryData;
1606
+ }
1607
+
1608
+ const rightShift = (endByte - startByte + 1) * 8 - (totalBitOffset % 8) - bitLength;
1609
+ value >>>= rightShift;
1610
+
1611
+ const mask = (1 << bitLength) - 1;
1612
+ return value & mask;
1613
+ }
1614
+
1615
+ function setBits(buffer, byteOffset, bitOffset, bitLength, value) {
1616
+ // Calculate the absolute bit position from the start
1617
+ const absoluteBitPos = byteOffset * 8 + bitOffset;
1618
+
1619
+ // Process each bit of the value
1620
+ for (let i = 0; i < bitLength; i++) {
1621
+ const currentBitPos = absoluteBitPos + i;
1622
+ const currentByteIndex = Math.floor(currentBitPos / 8);
1623
+ const currentBitInByte = currentBitPos % 8;
1624
+
1625
+ // Extract the bit from the value (MSB first)
1626
+ const bitValue = (value >> (bitLength - 1 - i)) & 1;
1627
+
1628
+ // Create mask for this specific bit position
1629
+ const mask = 1 << (7 - currentBitInByte);
1630
+
1631
+ if (bitValue) {
1632
+ // Set the bit
1633
+ buffer[currentByteIndex] |= mask;
1634
+ } else {
1635
+ // Clear the bit
1636
+ buffer[currentByteIndex] &= ~mask;
1402
1637
  }
1403
- else{
1404
- var d=binOrPath.match(/(0|1){1,8}/g);
1405
- qrCode=[];
1406
- d.forEach(byte=>{
1407
- qrCode.push(parseInt(byte,2));
1408
- });
1638
+ }
1639
+ }
1640
+
1641
+ function setNestedProperty(obj, path, value) {
1642
+ // Allows to reference nested properties in extraction schemas with "name.subcategory.suboption"
1643
+ const keys = path.split('.');
1644
+ let current = obj;
1645
+
1646
+ for (let i = 0; i < keys.length - 1; i++) {
1647
+ if (!(keys[i] in current)) {
1648
+ current[keys[i]] = {};
1409
1649
  }
1410
- if (qrCode) {
1411
- var data = decodeAesCcm(new Uint8Array(qrCode));
1412
- return Promise.resolve(readMii(Buffer.from(data)));
1650
+ current = current[keys[i]];
1651
+ }
1652
+
1653
+ current[keys[keys.length - 1]] = value;
1654
+ }
1655
+
1656
+ function getNestedProperty(obj, path) {
1657
+ // See `setNestedProperty` comment
1658
+ return path.split('.').reduce((current, key) => current?.[key], obj);
1659
+ }
1660
+
1661
+ function miiBufferToJson(data, schema, lookupTables = {}, isBigEndian = true) {
1662
+ const result = {};
1663
+
1664
+ for (const [fieldPath, fieldDef] of Object.entries(schema)) {
1665
+ let value;
1666
+
1667
+ if (fieldDef.type === 'string') {
1668
+ value = decodeString(data, fieldDef);
1669
+ } else if (fieldDef.bitSpecs) {
1670
+ // Handle multi-byte fields with non-contiguous bits
1671
+ value = extractMultiBits(data, fieldDef.bitSpecs, isBigEndian);
1413
1672
  } else {
1414
- console.error('Failed to decode QR code');
1673
+ // Handle standard contiguous bit fields
1674
+ value = extractBits(data, fieldDef.byteOffset, fieldDef.bitOffset, fieldDef.bitLength, isBigEndian);
1415
1675
  }
1416
- },
1417
- writeWiiBin:function(jsonIn,outPath){
1418
- if(jsonIn.console?.toLowerCase()!=="wii"){
1419
- this.convertMii(jsonIn);
1676
+
1677
+ // Apply decoder
1678
+ if (fieldDef.decoder && typeof decoders !== 'undefined' && decoders[fieldDef.decoder]) {
1679
+ value = decoders[fieldDef.decoder](value, fieldDef, lookupTables);
1420
1680
  }
1421
- var mii=jsonIn;
1422
- var miiBin="0";
1423
- miiBin+=mii.info.gender==="Male"?"0":"1";
1424
- miiBin+=mii.info.birthMonth.toString(2).padStart(4,"0");
1425
- miiBin+=mii.info.birthday.toString(2).padStart(5,"0");
1426
- miiBin+=favCols.indexOf(mii.info.favColor).toString(2).padStart(4,"0");
1427
- miiBin+=mii.info.favorited?1:0;
1428
- for(var i=0;i<10;i++){
1429
- miiBin+="00000000";
1430
- if(i<mii.name.length){
1431
- miiBin+=mii.name.charCodeAt(i).toString(2).padStart(8,"0");
1432
- }
1433
- else{
1434
- miiBin+="00000000";
1681
+
1682
+ setNestedProperty(result, fieldPath, value);
1683
+ }
1684
+
1685
+ return result;
1686
+ }
1687
+
1688
+ function jsonToMiiBuffer(miiData, schema, lookupTables = {}, totalBytes = 74) {
1689
+ const buffer = new Array(totalBytes).fill(0);
1690
+
1691
+ for (const [fieldPath, fieldDef] of Object.entries(schema)) {
1692
+ let raw = getNestedProperty(miiData, fieldPath);
1693
+
1694
+ // Run encoders
1695
+ if (fieldDef.decoder && typeof encoders !== 'undefined' && encoders[fieldDef.decoder]) {
1696
+ raw = encoders[fieldDef.decoder](raw, fieldDef, lookupTables);
1697
+ }
1698
+
1699
+ if (fieldDef.type === 'string') {
1700
+ const bytes = encodeString(raw, fieldDef);
1701
+ for (let i = 0; i < bytes.length; i++) {
1702
+ buffer[fieldDef.byteOffset + i] = bytes[i];
1435
1703
  }
1704
+ } else if (fieldDef.bitSpecs) {
1705
+ // Handle multi-byte fields with non-contiguous bits
1706
+ setMultiBits(buffer, fieldDef.bitSpecs, raw);
1707
+ } else {
1708
+ // Handle standard contiguous bit fields
1709
+ setBits(buffer, fieldDef.byteOffset, fieldDef.bitOffset, fieldDef.bitLength, raw);
1436
1710
  }
1437
- miiBin+=mii.info.height.toString(2).padStart(8,"0");
1438
- miiBin+=mii.info.weight.toString(2).padStart(8,"0");
1439
- let miiId="";
1711
+ }
1712
+
1713
+ return Buffer.from(buffer);
1714
+ }
1715
+
1716
+ const WII_MII_SCHEMA = {
1717
+ 'general.gender': { byteOffset: 0x00, bitOffset: 1, bitLength: 1, decoder: 'number' },
1718
+ 'general.birthMonth': { byteOffset: 0x00, bitOffset: 2, bitLength: 4, decoder: 'number' },
1719
+ 'general.birthday': { byteOffset: 0x00, bitOffset: 6, bitLength: 5, decoder: 'number' },
1720
+ 'general.favoriteColor': { byteOffset: 0x01, bitOffset: 3, bitLength: 4, decoder: 'number' },
1721
+ 'meta.name': { type: 'string', byteOffset: 0x03, maxLength: 10, endianness: "little" },
1722
+ 'meta.creatorName': { type: 'string', byteOffset: 0x37, maxLength: 10, endianness: "little" },
1723
+ 'general.height': { byteOffset: 0x16, bitOffset: 0, bitLength: 8, decoder: 'number' },
1724
+ 'general.weight': { byteOffset: 0x17, bitOffset: 0, bitLength: 8, decoder: 'number' },
1725
+ 'perms.mingle': { byteOffset: 0x21, bitOffset: 5, bitLength: 1, decoder: 'boolean', invert: true },
1726
+ 'perms.fromCheckMiiOut': { byteOffset: 0x21, bitOffset: 7, bitLength: 1, decoder: 'boolean' },
1727
+ 'face.type': { byteOffset: 0x20, bitOffset: 0, bitLength: 3, decoder: 'number' },
1728
+ 'face.color': { byteOffset: 0x20, bitOffset: 3, bitLength: 3, decoder: 'number' },
1729
+ 'face.feature': { byteOffset: 0x20, bitOffset: 6, bitLength: 4, decoder: 'number' },
1730
+ 'hair.page': { byteOffset: 0x22, bitOffset: 0, bitLength: 7, decoder: 'lookup', lookupTable: 'hairs' },
1731
+ 'hair.type': { byteOffset: 0x22, bitOffset: 0, bitLength: 7, decoder: 'lookup', lookupTable: 'types.hairs' },
1732
+ 'hair.color': { byteOffset: 0x22, bitOffset: 7, bitLength: 3, decoder: 'number' },
1733
+ 'hair.flipped': { byteOffset: 0x23, bitOffset: 2, bitLength: 1, decoder: 'boolean' },
1734
+ 'eyebrows.page': { byteOffset: 0x24, bitOffset: 0, bitLength: 5, decoder: 'lookup', lookupTable: 'pages.eyebrows' },
1735
+ 'eyebrows.type': { byteOffset: 0x24, bitOffset: 0, bitLength: 5, decoder: 'lookup', lookupTable: 'types.eyebrows' },
1736
+ 'eyebrows.rotation': { byteOffset: 0x24, bitOffset: 6, bitLength: 4, decoder: 'number' },
1737
+ 'eyebrows.color': { byteOffset: 0x26, bitOffset: 0, bitLength: 3, decoder: 'number' },
1738
+ 'eyebrows.size': { byteOffset: 0x26, bitOffset: 3, bitLength: 4, decoder: 'number' },
1739
+ 'eyebrows.yPosition': { byteOffset: 0x26, bitOffset: 7, bitLength: 5, decoder: 'number', offset: -3 },
1740
+ 'eyebrows.distanceApart': { byteOffset: 0x27, bitOffset: 4, bitLength: 4, decoder: 'number' },
1741
+ 'eyes.page': { byteOffset: 0x28, bitOffset: 0, bitLength: 6, decoder: 'lookup', lookupTable: 'pages.eyes' },
1742
+ 'eyes.type': { byteOffset: 0x28, bitOffset: 0, bitLength: 6, decoder: 'lookup', lookupTable: 'types.eyes' },
1743
+ 'eyes.rotation': { byteOffset: 0x29, bitOffset: 0, bitLength: 3, decoder: 'number' },
1744
+ 'eyes.yPosition': { byteOffset: 0x29, bitOffset: 3, bitLength: 5, decoder: 'number' },
1745
+ 'eyes.color': { byteOffset: 0x2A, bitOffset: 0, bitLength: 3, decoder: 'number' },
1746
+ 'eyes.size': { byteOffset: 0x2A, bitOffset: 4, bitLength: 3 },
1747
+ 'eyes.distanceApart': { byteOffset: 0x2A, bitOffset: 7, bitLength: 4, decoder: 'number' },
1748
+ 'nose.type': { byteOffset: 0x2C, bitOffset: 0, bitLength: 4, decoder: 'lookup', lookupTable: 'wiiNoses' },
1749
+ 'nose.size': { byteOffset: 0x2C, bitOffset: 4, bitLength: 4, decoder: 'number' },
1750
+ 'nose.yPosition': { byteOffset: 0x2D, bitOffset: 0, bitLength: 5, decoder: 'number' },
1751
+ 'mouth.page': { byteOffset: 0x2E, bitOffset: 0, bitLength: 5, decoder: 'lookup', lookupTable: 'pages.mouths' },
1752
+ 'mouth.type': { byteOffset: 0x2E, bitOffset: 0, bitLength: 5, decoder: 'lookup', lookupTable: 'types.mouths' },
1753
+ 'mouth.color': { byteOffset: 0x2E, bitOffset: 5, bitLength: 2, decoder: 'number' },
1754
+ 'mouth.size': { byteOffset: 0x2E, bitOffset: 7, bitLength: 4, decoder: 'number' },
1755
+ 'mouth.yPosition': { byteOffset: 0x2F, bitOffset: 3, bitLength: 5, decoder: 'number' },
1756
+ 'glasses.type': { byteOffset: 0x30, bitOffset: 0, bitLength: 4, decoder: 'number' },
1757
+ 'glasses.color': { byteOffset: 0x30, bitOffset: 4, bitLength: 3, decoder: 'number' },
1758
+ 'glasses.size': { byteOffset: 0x31, bitOffset: 0, bitLength: 3, decoder: 'number' },
1759
+ 'glasses.yPosition': { byteOffset: 0x31, bitOffset: 3, bitLength: 5, decoder: 'number' },
1760
+ 'beard.mustache.type': { byteOffset: 0x32, bitOffset: 0, bitLength: 2, decoder: 'number' },
1761
+ 'beard.type': { byteOffset: 0x32, bitOffset: 2, bitLength: 2, decoder: 'number' },
1762
+ 'beard.color': { byteOffset: 0x32, bitOffset: 4, bitLength: 3, decoder: 'number' },
1763
+ 'beard.mustache.size': { byteOffset: 0x32, bitOffset: 7, bitLength: 4, decoder: 'number' },
1764
+ 'beard.mustache.yPosition': { byteOffset: 0x33, bitOffset: 3, bitLength: 5, decoder: 'number' },
1765
+ 'mole.on': { byteOffset: 0x34, bitOffset: 0, bitLength: 1, decoder: 'boolean' },
1766
+ 'mole.size': { byteOffset: 0x34, bitOffset: 1, bitLength: 4, decoder: 'number' },
1767
+ 'mole.yPosition': { byteOffset: 0x34, bitOffset: 5, bitLength: 5, decoder: 'number' },
1768
+ 'mole.xPosition': { byteOffset: 0x35, bitOffset: 2, bitLength: 5, decoder: 'number' }
1769
+ };
1770
+
1771
+ const THREEDS_MII_SCHEMA = {
1772
+ 'general.birthday': {
1773
+ bitSpecs: [
1774
+ { byteOffset: 0x19, bitOffset: 6, bitLength: 2 },
1775
+ { byteOffset: 0x18, bitOffset: 0, bitLength: 3 }
1776
+ ],
1777
+ decoder: 'number'
1778
+ },
1779
+ 'general.birthMonth': { byteOffset: 0x18, bitOffset: 3, bitLength: 4, decoder: 'number' },
1780
+ 'general.gender': { byteOffset: 0x18, bitOffset: 7, bitLength: 1, decoder: 'number' },
1781
+ 'general.favoriteColor': { byteOffset: 0x19, bitOffset: 2, bitLength: 4, decoder: 'number' },
1782
+ 'meta.name': { type: 'string', byteOffset: 0x1A, maxLength: 10, endianness: "little" },
1783
+ 'meta.creatorName': { type: 'string', byteOffset: 0x48, maxLength: 10, endianness: "little" },
1784
+ 'general.height': { byteOffset: 0x2E, bitOffset: 0, bitLength: 8, decoder: 'number' },
1785
+ 'general.weight': { byteOffset: 0x2F, bitOffset: 0, bitLength: 8, decoder: 'number' },
1786
+ 'perms.sharing': { byteOffset: 0x30, bitOffset: 7, bitLength: 1, decoder: 'boolean', invert: true },
1787
+ 'perms.copying': { byteOffset: 0x01, bitOffset: 7, bitLength: 1, decoder: 'boolean' },
1788
+ 'face.type': { byteOffset: 0x30, bitOffset: 3, bitLength: 4, decoder: 'lookup', lookupTable: 'faces' },
1789
+ 'face.color': { byteOffset: 0x30, bitOffset: 0, bitLength: 3, decoder: 'number' },
1790
+ 'face.feature': { byteOffset: 0x31, bitOffset: 4, bitLength: 4, decoder: 'number' },
1791
+ 'face.makeup': { byteOffset: 0x31, bitOffset: 0, bitLength: 4, decoder: 'number' },
1792
+ 'hair.page': { byteOffset: 0x32, bitOffset: 0, bitLength: 8, decoder: 'lookupPage', lookupTable: 'hairs' },//qk
1793
+ 'hair.type': { byteOffset: 0x32, bitOffset: 0, bitLength: 8, decoder: 'lookupType', lookupTable: 'hairs' },//qk
1794
+ 'hair.color': { byteOffset: 0x33, bitOffset: 5, bitLength: 3, decoder: 'number' },
1795
+ 'hair.flipped': { byteOffset: 0x33, bitOffset: 4, bitLength: 1, decoder: 'boolean' },
1796
+ 'eyes.page': { byteOffset: 0x34, bitOffset: 2, bitLength: 6, decoder: 'lookupPage', lookupTable: 'eyes' },//qk
1797
+ 'eyes.type': { byteOffset: 0x34, bitOffset: 2, bitLength: 6, decoder: 'lookupType', lookupTable: 'eyes' },//qk
1798
+ 'eyes.color': {
1799
+ bitSpecs: [
1800
+ { byteOffset: 0x35, bitOffset: 7, bitLength: 1 },
1801
+ { byteOffset: 0x34, bitOffset: 0, bitLength: 2 }
1802
+ ],
1803
+ decoder: 'number'
1804
+ },
1805
+ 'eyes.size': { byteOffset: 0x35, bitOffset: 3, bitLength: 4, decoder: 'number' },
1806
+ 'eyes.squash': { byteOffset: 0x35, bitOffset: 0, bitLength: 3, decoder: 'number' },
1807
+ 'eyes.rotation': { byteOffset: 0x36, bitOffset: 3, bitLength: 5, decoder: 'number' },
1808
+ 'eyes.distanceApart': {
1809
+ bitSpecs: [
1810
+ { byteOffset: 0x37, bitOffset: 7, bitLength: 1 },
1811
+ { byteOffset: 0x36, bitOffset: 0, bitLength: 3 }
1812
+ ],
1813
+ decoder: 'number'
1814
+ },
1815
+ 'eyes.yPosition': { byteOffset: 0x37, bitOffset: 2, bitLength: 5, decoder: 'number' },
1816
+ 'eyebrows.page': { byteOffset: 0x38, bitOffset: 3, bitLength: 5, decoder: 'lookupPage', lookupTable: 'eyebrows' },//qk
1817
+ 'eyebrows.type': { byteOffset: 0x38, bitOffset: 3, bitLength: 5, decoder: 'lookupType', lookupTable: 'eyebrows' },//qk
1818
+ 'eyebrows.color': { byteOffset: 0x38, bitOffset: 0, bitLength: 3, decoder: 'number' },
1819
+ 'eyebrows.size': { byteOffset: 0x39, bitOffset: 4, bitLength: 4, decoder: 'number' },
1820
+ 'eyebrows.squash': { byteOffset: 0x39, bitOffset: 1, bitLength: 3, decoder: 'number' },
1821
+ 'eyebrows.rotation': { byteOffset: 0x3A, bitOffset: 4, bitLength: 4, decoder: 'number' },
1822
+ 'eyebrows.distanceApart': {
1823
+ bitSpecs: [
1824
+ { byteOffset: 0x3B, bitOffset: 7, bitLength: 1 },
1825
+ { byteOffset: 0x3A, bitOffset: 0, bitLength: 3 }
1826
+ ],
1827
+ decoder: 'number'
1828
+ },
1829
+ 'eyebrows.yPosition': { byteOffset: 0x3B, bitOffset: 2, bitLength: 5, decoder: 'number', offset: -3 },
1830
+ 'nose.page': { byteOffset: 0x3C, bitOffset: 3, bitLength: 5, decoder: 'lookupPage', lookupTable: 'noses' },//qk
1831
+ 'nose.type': { byteOffset: 0x3C, bitOffset: 3, bitLength: 5, decoder: 'lookupType', lookupTable: 'noses' },//qk
1832
+ 'nose.size': {
1833
+ bitSpecs: [
1834
+ { byteOffset: 0x3D, bitOffset: 7, bitLength: 1 },
1835
+ { byteOffset: 0x3C, bitOffset: 0, bitLength: 3 }
1836
+ ],
1837
+ decoder: 'number'
1838
+ },
1839
+ 'nose.yPosition': { byteOffset: 0x3D, bitOffset: 2, bitLength: 5, decoder: 'number' },
1840
+ 'mouth.page': { byteOffset: 0x3E, bitOffset: 2, bitLength: 6, decoder: 'lookupPage', lookupTable: 'mouths' },//qk
1841
+ 'mouth.type': { byteOffset: 0x3E, bitOffset: 2, bitLength: 6, decoder: 'lookupType', lookupTable: 'mouths' },//qk
1842
+ 'mouth.color': {
1843
+ bitSpecs: [
1844
+ { byteOffset: 0x3F, bitOffset: 7, bitLength: 1 },
1845
+ { byteOffset: 0x3E, bitOffset: 0, bitLength: 2 }
1846
+ ],
1847
+ decoder: 'number'
1848
+ },
1849
+ 'mouth.size': { byteOffset: 0x3F, bitOffset: 3, bitLength: 4, decoder: 'number' },
1850
+ 'mouth.squash': { byteOffset: 0x3F, bitOffset: 0, bitLength: 3, decoder: 'number' },
1851
+ 'mouth.yPosition': { byteOffset: 0x40, bitOffset: 3, bitLength: 5, decoder: 'number' },
1852
+ 'beard.mustache.type': { byteOffset: 0x40, bitOffset: 0, bitLength: 3, decoder: 'number' },
1853
+ 'beard.type': { byteOffset: 0x42, bitOffset: 5, bitLength: 3, decoder: 'number' },
1854
+ 'beard.color': { byteOffset: 0x42, bitOffset: 2, bitLength: 3, decoder: 'number' },
1855
+ 'beard.mustache.size': {
1856
+ bitSpecs: [
1857
+ { byteOffset: 0x43, bitOffset: 6, bitLength: 2 },
1858
+ { byteOffset: 0x42, bitOffset: 0, bitLength: 2 }
1859
+ ],
1860
+ decoder: 'number'
1861
+ },
1862
+ 'beard.mustache.yPosition': { byteOffset: 0x43, bitOffset: 1, bitLength: 5, decoder: 'number' },
1863
+ 'glasses.type': { byteOffset: 0x44, bitOffset: 4, bitLength: 4, decoder: 'number' },
1864
+ 'glasses.color': { byteOffset: 0x44, bitOffset: 1, bitLength: 3, decoder: 'number' },
1865
+ 'glasses.size': {
1866
+ bitSpecs: [
1867
+ { byteOffset: 0x45, bitOffset: 5, bitLength: 3 },
1868
+ { byteOffset: 0x44, bitOffset: 0, bitLength: 1 }
1869
+ ],
1870
+ decoder: 'number'
1871
+ },
1872
+ 'glasses.yPosition': { byteOffset: 0x45, bitOffset: 0, bitLength: 5, decoder: 'number' },
1873
+ 'mole.on': { byteOffset: 0x46, bitOffset: 7, bitLength: 1, decoder: 'boolean' },
1874
+ 'mole.size': { byteOffset: 0x46, bitOffset: 3, bitLength: 4, decoder: 'number' },
1875
+ 'mole.xPosition': {
1876
+ bitSpecs: [
1877
+ { byteOffset: 0x47, bitOffset: 6, bitLength: 2 },
1878
+ { byteOffset: 0x46, bitOffset: 0, bitLength: 3 }
1879
+ ],
1880
+ decoder: 'number'
1881
+ },
1882
+ 'mole.yPosition': { byteOffset: 0x47, bitOffset: 1, bitLength: 5, decoder: 'number' },
1883
+ };
1884
+
1885
+ //Functions for working with the Miis
1886
+ function encodeStudio(mii) {
1887
+ var n = 0;
1888
+ var eo;
1889
+ var dest = byteToString(n);
1890
+ for (var i = 0; i < mii.length; i++) {
1891
+ eo = (7 + (mii[i] ^ n)) & 0xFF;
1892
+ n = eo;
1893
+ dest += byteToString(eo);
1894
+ }
1895
+ return dest;
1896
+ }
1897
+ function convertMii(jsonIn,typeTo){
1898
+ typeFrom=jsonIn.console?.toLowerCase();
1899
+ if(typeFrom==null||typeTo===typeFrom){
1900
+ return jsonIn;
1901
+ }
1902
+ let mii=jsonIn;
1903
+ var miiTo={};
1904
+ if(["wii u","3ds"].includes(typeFrom)){
1905
+ miiTo={
1906
+ info:{},
1907
+ face:{},
1908
+ nose:{},
1909
+ mouth:{},
1910
+ mole:{},
1911
+ hair:{},
1912
+ eyebrows:{},
1913
+ eyes:{},
1914
+ glasses:{},
1915
+ facialHair:{}
1916
+ };
1917
+ miiTo.creatorName=mii.creatorName;
1918
+ miiTo.info.creatorName=miiTo.creatorName;
1919
+ miiTo.name=mii.name;
1920
+ miiTo.info.name=miiTo.name;
1921
+ miiTo.info.gender=mii.info.gender;
1922
+ miiTo.info.systemId="ffffffff";
1923
+ let miiId;
1440
1924
  switch(mii.info.type){
1441
1925
  case "Special":
1442
1926
  miiId="01000110";
@@ -1451,770 +1935,463 @@ var exports={
1451
1935
  for(var i=0;i<3;i++){
1452
1936
  miiId+=Math.floor(Math.random()*255).toString(2).padStart(8,"0");
1453
1937
  }
1454
- miiBin+=miiId;
1455
- miiBin+="11111111".repeat(4);//System ID
1456
- miiBin+=mii.face.shape.toString(2).padStart(3,"0");
1457
- miiBin+=skinCols.indexOf(mii.face.col).toString(2).padStart(3,"0");
1458
- miiBin+=wiiFaceFeatures.indexOf(mii.face.feature).toString(2).padStart(4,"0");
1459
- miiBin+="000";
1460
- if(mii.info.mingle&&mii.info.type==="Special"){
1461
- mii.info.mingle=false;
1462
- console.error("A Special Mii cannot have Mingle on and still render on the Wii. Turned Mingle off in the output file.");
1463
- }
1464
- miiBin+=mii.info.mingle?"0":"1";
1465
- miiBin+="0";
1466
- miiBin+=mii.info.downloadedFromCheckMiiOut?"1":"0";
1467
- miiBin+=(+getKeyByValue(hairTable,mii.hair.type)).toString(2).padStart(7,"0");
1468
- miiBin+=hairCols.indexOf(mii.hair.col).toString(2).padStart(3,"0");
1469
- miiBin+=mii.hair.flipped?"1":"0";
1470
- miiBin+="00000";
1471
- miiBin+=(+getKeyByValue(eyebrowTable,mii.eyebrows.type)).toString(2).padStart(5,"0");
1472
- miiBin+="0";
1473
- miiBin+=mii.eyebrows.rotation.toString(2).padStart(4,"0");
1474
- miiBin+="000000";
1475
- miiBin+=hairCols.indexOf(mii.eyebrows.col).toString(2).padStart(3,"0");
1476
- miiBin+=mii.eyebrows.size.toString(2).padStart(4,"0");
1477
- miiBin+=(mii.eyebrows.yPos+3).toString(2).padStart(5,"0");
1478
- miiBin+=mii.eyebrows.distApart.toString(2).padStart(4,"0");
1479
- miiBin+=(+getKeyByValue(eyeTable,mii.eyes.type)).toString(2).padStart(6,"0");
1480
- miiBin+="00";
1481
- miiBin+=mii.eyes.rotation.toString(2).padStart(3,"0");
1482
- miiBin+=mii.eyes.yPos.toString(2).padStart(5,"0");
1483
- miiBin+=eyeCols.indexOf(mii.eyes.col).toString(2).padStart(3,"0");
1484
- miiBin+="0";
1485
- miiBin+=mii.eyes.size.toString(2).padStart(3,"0");
1486
- miiBin+=mii.eyes.distApart.toString(2).padStart(4,"0");
1487
- miiBin+="00000";
1488
- miiBin+=wiiNoses[mii.nose.type].toString(2).padStart(4,"0");
1489
- miiBin+=mii.nose.size.toString(2).padStart(4,"0");
1490
- miiBin+=mii.nose.yPos.toString(2).padStart(5,"0");
1491
- miiBin+="000";
1492
- miiBin+=(+getKeyByValue(mouthTable,mii.mouth.type)).toString(2).padStart(5,"0");
1493
- miiBin+=wiiMouthColors.indexOf(mii.mouth.col).toString(2).padStart(2,"0");
1494
- miiBin+=mii.mouth.size.toString(2).padStart(4,"0");
1495
- miiBin+=mii.mouth.yPos.toString(2).padStart(5,"0");
1496
- miiBin+=mii.glasses.type.toString(2).padStart(4,"0");
1497
- miiBin+=wiiGlassesCols.indexOf(mii.glasses.col).toString(2).padStart(3,"0");
1498
- miiBin+="0";
1499
- miiBin+=mii.glasses.size.toString(2).padStart(3,"0");
1500
- miiBin+=mii.glasses.yPos.toString(2).padStart(5,"0");
1501
- miiBin+=mii.facialHair.mustacheType.toString(2).padStart(2,"0");
1502
- miiBin+=mii.facialHair.beardType.toString(2).padStart(2,"0");
1503
- miiBin+=hairCols.indexOf(mii.facialHair.col).toString(2).padStart(3,"0");
1504
- miiBin+=mii.facialHair.mustacheSize.toString(2).padStart(4,"0");
1505
- miiBin+=mii.facialHair.mustacheYPos.toString(2).padStart(5,"0");
1506
- miiBin+=mii.mole.on?"1":"0";
1507
- miiBin+=mii.mole.size.toString(2).padStart(4,"0");
1508
- miiBin+=mii.mole.yPos.toString(2).padStart(5,"0");
1509
- miiBin+=mii.mole.xPos.toString(2).padStart(5,"0");
1510
- miiBin+="0";
1511
- for(var i=0;i<10;i++){
1512
- miiBin+="00000000";
1513
- if(i<mii.creatorName.length){
1514
- miiBin+=mii.creatorName.charCodeAt(i).toString(2).padStart(8,"0");
1515
- }
1516
- else{
1517
- miiBin+="00000000";
1518
- }
1519
- }
1520
-
1521
- //Writing based on miiBin
1522
- var toWrite=miiBin.match(/.{1,8}/g);
1523
- var buffers=[];
1524
- for(var i=0;i<toWrite.length;i++){
1525
- buffers.push(parseInt(toWrite[i],2));
1938
+ miiTo.info.miiId+=miiId;
1939
+ miiTo.info.mingle=mii.perms.copying;
1940
+ miiTo.info.birthMonth=mii.info.birthMonth;
1941
+ miiTo.info.birthday=mii.info.birthday;
1942
+ miiTo.info.favColor=mii.info.favColor;
1943
+ miiTo.info.favorited=false;
1944
+ miiTo.info.height=mii.info.height;
1945
+ miiTo.info.weight=mii.info.weight;
1946
+ miiTo.info.downloadedFromCheckMiiOut=false;
1947
+ miiTo.face.shape=convTables.face3DSToWii[mii.face.shape];
1948
+ miiTo.face.col=mii.face.col;
1949
+ //We prioritize Facial Features here because the Wii supports more of those than they do Makeup types, and is more likely to apply. The 3DS has two separate fields, so you can have makeup and wrinkles applied at the same time. The Wii only has one that covers both.
1950
+ if(typeof(convTables.features3DSToWii[faceFeatures3DS.indexOf(mii.face.feature)])==='string'){
1951
+ miiTo.face.feature=wiiFaceFeatures[convTables.makeup3DSToWii[makeups3DS.indexOf(mii.face.makeup)]];
1526
1952
  }
1527
- fs.writeFileSync(outPath, Buffer.from(buffers));
1528
- },
1529
- write3DSQR:async function(jsonIn,outPath,fflRes=_fflRes){
1530
- if(!["3ds","wii u"].includes(jsonIn.console?.toLowerCase())){
1531
- jsonIn=this.convertMii(jsonIn);
1532
- }
1533
- return new Promise(async (resolve, reject) => {
1534
- var mii=jsonIn;
1535
- function makeMiiBinary(mii){
1536
- if(mii.perms.sharing&&mii.info.type==="Special"){
1537
- mii.perms.sharing=false;
1538
- console.log("Cannot have Sharing enabled for Special Miis. Disabled Sharing.");
1539
- }
1540
- var miiBin="00000011";
1541
- miiBin+="0000000";
1542
- miiBin+=mii.perms.copying?"1":"0";
1543
- miiBin+="00000000";
1544
- miiBin+="00110000";
1545
- miiBin+="1000101011010010000001101000011100011000110001100100011001100110010101100111111110111100000001110101110001000101011101100000001110100100010000000000000000000000".slice(0,8*8);
1546
- miiBin+=mii.info.type==="Special"?"0":"1";
1547
- miiBin+="0000000";
1548
- for(var i=0;i<3;i++){
1549
- miiBin+=Math.floor(Math.random()*255).toString(2).padStart(8,"0");
1550
- }
1551
- miiBin+="0000000001000101011101100000001110100100010000000000000000000000";
1552
- miiBin+=mii.info.birthday.toString(2).padStart(5,"0").slice(2,5);
1553
- miiBin+=mii.info.birthMonth.toString(2).padStart(4,"0");
1554
- miiBin+=mii.info.gender==="Male"?"0":"1";
1555
- miiBin+="00";
1556
- miiBin+=favCols.indexOf(mii.info.favColor).toString(2).padStart(4,"0");
1557
- miiBin+=mii.info.birthday.toString(2).padStart(5,"0").slice(0,2);
1558
- for(var i=0;i<10;i++){
1559
- if(i<mii.name.length){
1560
- miiBin+=mii.name.charCodeAt(i).toString(2).padStart(8,"0");
1561
- }
1562
- else{
1563
- miiBin+="00000000";
1564
- }
1565
- miiBin+="00000000";
1566
- }
1567
- miiBin+=mii.info.height.toString(2).padStart(8,"0");
1568
- miiBin+=mii.info.weight.toString(2).padStart(8,"0");
1569
- miiBin+=skinCols.indexOf(mii.face.col).toString(2).padStart(3,"0");
1570
- miiBin+=tables.faces[mii.face.shape].toString(2).padStart(4,"0");
1571
- miiBin+=mii.perms.sharing?"0":"1";
1572
- miiBin+=makeups3DS.indexOf(mii.face.makeup).toString(2).padStart(4,"0");
1573
- miiBin+=faceFeatures3DS.indexOf(mii.face.feature).toString(2).padStart(4,"0");
1574
- miiBin+=tables.hairs[mii.hair.style[0]][mii.hair.style[1]].toString(2).padStart(8,"0");
1575
- miiBin+="0000";
1576
- miiBin+=mii.hair.flipped?"1":"0";
1577
- miiBin+=hairCols.indexOf(mii.hair.col).toString(2).padStart(3,"0");
1578
- miiBin+=eyeCols.indexOf(mii.eyes.col).toString(2).padStart(3,"0").slice(1,3);
1579
- miiBin+=tables.eyes[mii.eyes.type[0]][mii.eyes.type[1]].toString(2).padStart(6,"0");
1580
- miiBin+=mii.eyes.squash.toString(2).padStart(3,"0");
1581
- miiBin+=mii.eyes.size.toString(2).padStart(4,"0");
1582
- miiBin+=eyeCols.indexOf(mii.eyes.col).toString(2).padStart(3,"0")[0];
1583
- miiBin+=mii.eyes.distApart.toString(2).padStart(4,"0").slice(1,4);
1584
- miiBin+=mii.eyes.rot.toString(2).padStart(5,"0");
1585
- miiBin+="00";
1586
- miiBin+=mii.eyes.yPos.toString(2).padStart(5,"0");
1587
- miiBin+=mii.eyes.distApart.toString(2).padStart(4,"0")[0];
1588
- miiBin+=hairCols.indexOf(mii.eyebrows.col).toString(2).padStart(3,"0");
1589
- miiBin+=tables.eyebrows[mii.eyebrows.style[0]][mii.eyebrows.style[1]].toString(2).padStart(5,"0");
1590
- miiBin+="0";
1591
- miiBin+=mii.eyebrows.squash.toString(2).padStart(3,"0");
1592
- miiBin+=mii.eyebrows.size.toString(2).padStart(4,"0");
1593
- miiBin+=mii.eyebrows.distApart.toString(2).padStart(4,"0").slice(1,4);
1594
- miiBin+="0";
1595
- miiBin+=mii.eyebrows.rot.toString(2).padStart(4,"0");
1596
- miiBin+="00";
1597
- miiBin+=(mii.eyebrows.yPos+3).toString(2).padStart(5,"0");
1598
- miiBin+=mii.eyebrows.distApart.toString(2).padStart(4,"0")[0];
1599
- miiBin+=mii.nose.size.toString(2).padStart(4,"0").slice(1,4);
1600
- miiBin+=tables.noses[mii.nose.type[0]][mii.nose.type[1]].toString(2).padStart(5,"0");
1601
- miiBin+="00";
1602
- miiBin+=mii.nose.yPos.toString(2).padStart(5,"0");
1603
- miiBin+=mii.nose.size.toString(2).padStart(4,"0")[0];
1604
- miiBin+=mouthCols3DS.indexOf(mii.mouth.col).toString(2).padStart(3,"0").slice(1,3);
1605
- miiBin+=tables.mouths[mii.mouth.type[0]][mii.mouth.type[1]].toString(2).padStart(6,"0");
1606
- miiBin+=mii.mouth.squash.toString(2).padStart(3,"0");
1607
- miiBin+=mii.mouth.size.toString(2).padStart(4,"0");
1608
- miiBin+=mouthCols3DS.indexOf(mii.mouth.col).toString(2).padStart(3,"0")[0];
1609
- miiBin+=mii.facialHair.mustacheType.toString(2).padStart(3,"0");
1610
- miiBin+=mii.mouth.yPos.toString(2).padStart(5,"0");
1611
- miiBin+="00000000";
1612
- miiBin+=mii.facialHair.mustacheSize.toString(2).padStart(4,"0").slice(2,4);
1613
- miiBin+=hairCols.indexOf(mii.facialHair.col).toString(2).padStart(3,"0");
1614
- miiBin+=mii.facialHair.beardType.toString(2).padStart(3,"0");
1615
- miiBin+="0";
1616
- miiBin+=mii.facialHair.mustacheYPos.toString(2).padStart(5,"0");
1617
- miiBin+=mii.facialHair.mustacheSize.toString(2).padStart(4,"0").slice(0,2);
1618
- miiBin+=mii.glasses.size.toString(2).padStart(4,"0")[3];
1619
- miiBin+=glassesCols3DS.indexOf(mii.glasses.col).toString(2).padStart(3,"0");
1620
- miiBin+=mii.glasses.type.toString(2).padStart(4,"0");
1621
- miiBin+="0";
1622
- miiBin+=mii.glasses.yPos.toString(2).padStart(4,"0");
1623
- miiBin+=mii.glasses.size.toString(2).padStart(4,"0").slice(0,3);
1624
- miiBin+=mii.mole.xPos.toString(2).padStart(5,"0").slice(2,5);
1625
- miiBin+=mii.mole.size.toString(2).padStart(4,"0");
1626
- miiBin+=mii.mole.on?"1":"0";
1627
- miiBin+="0";
1628
- miiBin+=mii.mole.yPos.toString(2).padStart(5,"0");
1629
- miiBin+=mii.mole.xPos.toString(2).padStart(5,"0").slice(0,2);
1630
- for(var i=0;i<10;i++){
1631
- if(i<mii.creatorName.length){
1632
- miiBin+=mii.creatorName.charCodeAt(i).toString(2).padStart(8,"0");
1633
- }
1634
- else{
1635
- miiBin+="00000000";
1636
- }
1637
- miiBin+="00000000";
1638
- }
1639
- //Writing based on miiBin
1640
- var toWrite=miiBin.match(/.{1,8}/g);
1641
- var buffers=[];
1642
- for(var i=0;i<toWrite.length;i++){
1643
- buffers.push(parseInt(toWrite[i],2));
1644
- }
1645
- const buffer = Buffer.from(buffers);
1646
- return buffer;
1647
- }
1648
- const miiBinary = makeMiiBinary(mii);
1649
- var encryptedData = Buffer.from(encodeAesCcm(new Uint8Array(miiBinary)));
1650
-
1651
- const options = {
1652
- width: 300,
1653
- height: 300,
1654
- data: encryptedData.toString("latin1"),
1655
- image: "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", // 1x1 gif
1656
- dotsOptions: {
1657
- color: "#000000",
1658
- type: "square"
1659
- },
1660
- backgroundOptions: {
1661
- color: "#ffffff",
1662
- },
1663
- imageOptions: {
1664
- crossOrigin: "anonymous",
1665
- imageSize: 0.4 // Changes how large center area is
1666
- }
1667
- }
1668
- const qrCodeImage = new QRCodeStyling({
1669
- jsdom: JSDOM,
1670
- nodeCanvas,
1671
- ...options
1672
- });
1673
- const qrBuffer = Buffer.from( await qrCodeImage.getRawData("png") )
1674
-
1675
- 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]);
1676
- studioMii[0x16] = mii.info.gender==="Male"?0:1;
1677
- studioMii[0x15] = favCols.indexOf(mii.info.favColor);
1678
- studioMii[0x1E] = mii.info.height;
1679
- studioMii[2] = mii.info.weight;
1680
- studioMii[0x13] = tables.faces[mii.face.shape];
1681
- studioMii[0x11] = skinCols.indexOf(mii.face.col);
1682
- studioMii[0x14] = faceFeatures3DS.indexOf(mii.face.feature);
1683
- studioMii[0x12] = makeups3DS.indexOf(mii.face.makeup);
1684
- studioMii[0x1D] = tables.hairs[mii.hair.style[0]][mii.hair.style[1]];
1685
- studioMii[0x1B] = hairCols.indexOf(mii.hair.col);
1686
- if (!studioMii[0x1B]) studioMii[0x1B] = 8;
1687
- studioMii[0x1C] = mii.hair.flipped?1:0;
1688
- studioMii[7] = tables.eyes[mii.eyes.type[0]][mii.eyes.type[1]];
1689
- studioMii[4] = eyeCols.indexOf(mii.eyes.col) + 8;
1690
- studioMii[6] = mii.eyes.size;
1691
- studioMii[3] = mii.eyes.squash;
1692
- studioMii[5] = mii.eyes.rot;
1693
- studioMii[8] = mii.eyes.distApart;
1694
- studioMii[9] = mii.eyes.yPos;
1695
- studioMii[0xE] = tables.eyebrows[mii.eyebrows.style[0]][mii.eyebrows.style[1]];
1696
- studioMii[0xB] = hairCols.indexOf(mii.eyebrows.col);
1697
- if (!studioMii[0xB]) studioMii[0xB] = 8;
1698
- studioMii[0xD] = mii.eyebrows.size;
1699
- studioMii[0xA] = mii.eyebrows.squash;
1700
- studioMii[0xC] = mii.eyebrows.rot;
1701
- studioMii[0xF] = mii.eyebrows.distApart;
1702
- studioMii[0x10] = mii.eyebrows.yPos+3;
1703
- studioMii[0x2C] = tables.noses[mii.nose.type[0]][mii.nose.type[1]];
1704
- studioMii[0x2B] = mii.nose.size;
1705
- studioMii[0x2D] = mii.nose.yPos;
1706
- studioMii[0x26] = tables.mouths[mii.mouth.type[0]][mii.mouth.type[1]];
1707
- studioMii[0x24] = mouthCols3DS.indexOf(mii.mouth.col);
1708
- if (studioMii[0x24] < 4) {
1709
- studioMii[0x24] += 19;
1710
- } else {
1711
- studioMii[0x24] = 0;
1712
- }
1713
- studioMii[0x25] = mii.mouth.size;
1714
- studioMii[0x23] = mii.mouth.squash;
1715
- studioMii[0x27] = mii.mouth.yPos;
1716
- studioMii[0x29] = mii.facialHair.mustacheType;
1717
- studioMii[1] = mii.facialHair.beardType;
1718
- studioMii[0] = hairCols.indexOf(mii.facialHair.col);
1719
- if (!studioMii[0]) studioMii[0] = 8;
1720
- studioMii[0x28] = mii.facialHair.mustacheSize;
1721
- studioMii[0x2A] = mii.facialHair.mustacheYPos;
1722
- studioMii[0x19] = mii.glasses.type;
1723
- studioMii[0x17] = mii.glasses.col;
1724
- if (!studioMii[0x17]) {
1725
- studioMii[0x17] = 8;
1726
- } else if (studioMii[0x17] < 6) {
1727
- studioMii[0x17] += 13;
1728
- } else {
1729
- studioMii[0x17] = 0;
1730
- }
1731
- studioMii[0x18] = mii.glasses.size;
1732
- studioMii[0x1A] = mii.glasses.yPos;
1733
- studioMii[0x20] = mii.mole.on?1:0;
1734
- studioMii[0x1F] = mii.mole.size;
1735
- studioMii[0x21] = mii.mole.xPos;
1736
- studioMii[0x22] = mii.mole.yPos;
1737
- let miiPNGBuf = null;
1738
- let renderedWithStudio = fflRes===null || fflRes===undefined;
1739
- if(renderedWithStudio){
1740
- miiPNGBuf = await this.render3DSMiiWithStudio(jsonIn);
1741
- }
1742
- else{
1743
- miiPNGBuf = await this.render3DSMii(jsonIn,fflRes);
1744
- }
1745
- const main_img = await Jimp.read(qrBuffer);
1746
- main_img.resize(424, 424, Jimp.RESIZE_NEAREST_NEIGHBOR); // Don't anti-alias the QR code
1747
-
1748
- let miiSize, miiZoomFactor, miiYOffset;
1749
- if (renderedWithStudio) {
1750
- miiSize = 100;
1751
- miiZoomFactor = 1;
1752
- miiYOffset = -15;
1753
-
1754
- } else {
1755
- miiSize = 100;
1756
- miiZoomFactor = 1.25;
1757
- miiYOffset = -5;
1758
- }
1759
- const mii_img = await Jimp.read(miiPNGBuf);
1760
- mii_img.resize(miiSize*miiZoomFactor, miiSize*miiZoomFactor, Jimp.RESIZE_BICUBIC);
1761
- mii_img.crop(
1762
- (miiSize*miiZoomFactor - 100) / 2,
1763
- (miiSize*miiZoomFactor - 100) / 2,
1764
- miiSize,
1765
- miiSize
1766
- );
1767
-
1768
- const canvas = new Jimp(mii_img.bitmap.width, mii_img.bitmap.height, 0xFFFFFFFF);
1769
- canvas.composite(mii_img, 0, miiYOffset);
1770
- main_img.blit(canvas, 212-100/2, 212-100/2);
1771
- const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK)
1772
-
1773
- main_img.print(font, 0, 55, {
1774
- text: mii.name,
1775
- alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
1776
- alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
1777
- }, 424, 395);
1778
-
1779
- if(mii.info.type==="Special"){
1780
- const crown_img = await Jimp.read(path.join(__dirname, 'crown.jpg'));
1781
- crown_img.resize(40,20);
1782
- main_img.blit(crown_img,225,160);
1783
- }
1784
-
1785
- main_img.write(outPath, (err, img) =>
1786
- resolve(img)
1787
- );
1788
- })
1789
- },
1790
- render3DSMiiWithStudio:async function(jsonIn){
1791
- if(!["3ds","wii u"].includes(jsonIn.console)){
1792
- jsonIn=this.convertMii(jsonIn);
1793
- }
1794
- var studioMii=this.convert3DSMiiToStudio(jsonIn);
1795
- return await downloadImage('https://studio.mii.nintendo.com/miis/image.png?data=' + studioMii + "&width=270&type=face");
1796
- },
1797
- convertMii:function(jsonIn){
1798
- typeFrom=jsonIn.console?.toLowerCase();
1799
- if(typeFrom===null||typeFrom===undefined){
1800
- return jsonIn;
1801
- }
1802
- let mii=jsonIn;
1803
- var miiTo={};
1804
- if(["wii u","3ds"].includes(typeFrom)){
1805
- miiTo={
1806
- info:{},
1807
- face:{},
1808
- nose:{},
1809
- mouth:{},
1810
- mole:{},
1811
- hair:{},
1812
- eyebrows:{},
1813
- eyes:{},
1814
- glasses:{},
1815
- facialHair:{}
1816
- };
1817
- miiTo.creatorName=mii.creatorName;
1818
- miiTo.info.creatorName=miiTo.creatorName;
1819
- miiTo.name=mii.name;
1820
- miiTo.info.name=miiTo.name;
1821
- miiTo.info.gender=mii.info.gender;
1822
- miiTo.info.systemId="ffffffff";
1823
- let miiId;
1824
- switch(mii.info.type){
1825
- case "Special":
1826
- miiId="01000110";
1827
- break;
1828
- case "Foreign":
1829
- miiId="11000110";
1830
- break;
1831
- default:
1832
- miiId="10001001";
1833
- break;
1834
- }
1835
- for(var i=0;i<3;i++){
1836
- miiId+=Math.floor(Math.random()*255).toString(2).padStart(8,"0");
1837
- }
1838
- miiTo.info.miiId+=miiId;
1839
- miiTo.info.mingle=mii.perms.copying;
1840
- miiTo.info.birthMonth=mii.info.birthMonth;
1841
- miiTo.info.birthday=mii.info.birthday;
1842
- miiTo.info.favColor=mii.info.favColor;
1843
- miiTo.info.favorited=false;
1844
- miiTo.info.height=mii.info.height;
1845
- miiTo.info.weight=mii.info.weight;
1846
- miiTo.info.downloadedFromCheckMiiOut=false;
1847
- miiTo.face.shape=convTables.face3DSToWii[mii.face.shape];
1848
- miiTo.face.col=mii.face.col;
1849
- //We prioritize Facial Features here because the Wii supports more of those than they do Makeup types, and is more likely to apply. The 3DS has two separate fields, so you can have makeup and wrinkles applied at the same time. The Wii only has one that covers both.
1850
- if(typeof(convTables.features3DSToWii[faceFeatures3DS.indexOf(mii.face.feature)])==='string'){
1851
- miiTo.face.feature=wiiFaceFeatures[convTables.makeup3DSToWii[makeups3DS.indexOf(mii.face.makeup)]];
1852
- }
1853
- else{
1854
- miiTo.face.feature=wiiFaceFeatures[convTables.features3DSToWii[features3DS.indexOf(mii.face.feature)]];
1855
- }
1856
- miiTo.nose.type=convTables.nose3DSToWii[mii.nose.type[0]][mii.nose.type[1]];
1857
- miiTo.nose.size=mii.nose.size;
1858
- miiTo.nose.yPos=mii.nose.yPos;
1859
- miiTo.mouth.type=convTables.mouth3DSToWii[mii.mouth.type[0]][mii.mouth.type[1]];
1860
- miiTo.mouth.col=wiiMouthColors[mouthCols3DS.indexOf(mii.mouth.col)>2?0:mouthCols3DS.indexOf(mii.mouth.col)];
1861
- miiTo.mouth.size=mii.mouth.size;
1862
- miiTo.mouth.yPos=mii.mouth.yPos;
1863
- miiTo.mole=mii.mole;
1864
- miiTo.hair.col=mii.hair.col;
1865
- miiTo.hair.flipped=mii.hair.flipped;
1866
- miiTo.hair.type=convTables.hair3DSToWii[mii.hair.style[0]][mii.hair.style[1]];
1867
- miiTo.eyebrows.type=convTables.eyebrows3DSToWii[mii.eyebrows.style[0]][mii.eyebrows.style[1]];
1868
- miiTo.eyebrows.col=mii.eyebrows.col;
1869
- miiTo.eyebrows.rotation=mii.eyebrows.rot;
1870
- miiTo.eyebrows.size=mii.eyebrows.size;
1871
- miiTo.eyebrows.yPos=mii.eyebrows.yPos;
1872
- miiTo.eyebrows.distApart=mii.eyebrows.distApart;
1873
- miiTo.eyes.type=convTables.eyes3DSToWii[mii.eyes.type[0]][mii.eyes.type[1]];
1874
- miiTo.eyes.rotation=mii.eyes.rot;
1875
- miiTo.eyes.yPos=mii.eyes.yPos;
1876
- miiTo.eyes.col=mii.eyes.col;
1877
- miiTo.eyes.size=mii.eyes.size;
1878
- miiTo.eyes.distApart=mii.eyes.distApart;
1879
- miiTo.glasses=mii.glasses;
1880
- miiTo.glasses.col=wiiGlassesCols[glassesCols3DS.indexOf(mii.glasses.col)];
1881
- miiTo.facialHair=mii.facialHair;
1882
- if(miiTo.facialHair.mustacheType===4){
1883
- miiTo.facialHair.mustacheType=2;
1884
- }
1885
- else if(miiTo.facialHair.mustacheType===5){
1886
- miiTo.facialHair.mustacheType=0;
1887
- miiTo.facialHair.beardType=1;
1888
- }
1889
- if(mii.facialHair.beardType>3){
1890
- mii.facialHair.beardType=3;
1891
- }
1892
- miiTo.console="Wii";
1953
+ else{
1954
+ miiTo.face.feature=wiiFaceFeatures[convTables.features3DSToWii[features3DS.indexOf(mii.face.feature)]];
1893
1955
  }
1894
- else if(typeFrom==="wii"){
1895
- miiTo={
1896
- info:{},
1897
- perms:{},
1898
- hair:{},
1899
- face:{},
1900
- eyes:{},
1901
- eyebrows:{},
1902
- nose:{},
1903
- mouth:{},
1904
- facialHair:{},
1905
- glasses:{},
1906
- mole:{}
1907
- };
1908
- miiTo.info.birthday=mii.info.birthday;
1909
- miiTo.info.birthMonth=mii.info.birthMonth;
1910
- miiTo.name=mii.name;
1911
- miiTo.info.name=miiTo.name;
1912
- miiTo.creatorName=mii.creatorName;
1913
- miiTo.info.creatorName=mii.creatorName;
1914
- miiTo.info.height=mii.info.height;
1915
- miiTo.info.weight=mii.info.weight;
1916
- miiTo.info.favColor=mii.info.favColor;
1917
- miiTo.info.gender=mii.info.gender;
1918
- miiTo.perms.sharing=mii.info.mingle;
1919
- miiTo.perms.copying=mii.info.mingle;
1920
- miiTo.hair.col=hairCols[hairCols.indexOf(mii.hair.col)];
1921
- miiTo.hair.flipped=mii.hair.flipped;
1922
- miiTo.hair.style=convTables.hairWiiTo3DS[+mii.hair.type[0]-1][0+(3*(+mii.hair.type[2]-1))+(+mii.hair.type[1]-1)];
1923
- miiTo.face.shape=convTables.faceWiiTo3DS[mii.face.shape];
1924
- miiTo.face.col=skinCols[skinCols.indexOf(mii.face.col)];
1925
- miiTo.face.makeup="None";
1926
- miiTo.face.feature="None";
1927
- if(typeof(convTables.featureWiiTo3DS[wiiFaceFeatures.indexOf(mii.face.feature)])==='string'){
1928
- miiTo.face.makeup=makeups3DS[+convTables.featureWiiTo3DS[wiiFaceFeatures.indexOf(mii.face.feature)]];
1929
- }
1930
- else{
1931
- miiTo.face.feature=faceFeatures3DS[convTables.featureWiiTo3DS[wiiFaceFeatures.indexOf(mii.face.feature)]];
1932
- }
1933
- miiTo.eyes.col=eyeCols[eyeCols.indexOf(mii.eyes.col)];
1934
- miiTo.eyes.type=[+mii.eyes.type[0]-1,(+mii.eyes.type[1]-1)+(3*(+mii.eyes.type[2]-1))];
1935
- miiTo.eyes.size=mii.eyes.size;
1936
- miiTo.eyes.squash=3;
1937
- miiTo.eyes.rot=mii.eyes.rotation;
1938
- miiTo.eyes.distApart=mii.eyes.distApart;
1939
- miiTo.eyes.yPos=mii.eyes.yPos;
1940
- miiTo.eyebrows.style=[+mii.eyebrows.type[0]-1,(+mii.eyebrows.type[1]-1)+(3*(+mii.eyebrows.type[2]-1))];
1941
- miiTo.eyebrows.col=hairCols[hairCols.indexOf(mii.eyebrows.col)];
1942
- miiTo.eyebrows.size=mii.eyebrows.size;
1943
- miiTo.eyebrows.squash=3;
1944
- miiTo.eyebrows.rot=mii.eyebrows.rotation;
1945
- miiTo.eyebrows.distApart=mii.eyebrows.distApart;
1946
- miiTo.eyebrows.yPos=mii.eyebrows.yPos;
1947
- miiTo.nose.type=[0,mii.nose.type];
1948
- miiTo.nose.size=mii.nose.size;
1949
- miiTo.nose.yPos=mii.nose.yPos;
1950
- miiTo.mouth.type=[+mii.mouth.type[0]-1,(+mii.mouth.type[1]-1)+(3*(+mii.mouth.type[2]-1))];
1951
- miiTo.mouth.col=mouthCols3DS[wiiMouthColors.indexOf(mii.mouth.col)];
1952
- miiTo.mouth.size=mii.mouth.size;
1953
- miiTo.mouth.squash=3;
1954
- miiTo.mouth.yPos=mii.mouth.yPos;
1955
- miiTo.facialHair.mustacheType=mii.facialHair.mustacheType;
1956
- miiTo.facialHair.beardType=mii.facialHair.beardType;
1957
- miiTo.facialHair.col=hairCols[hairCols.indexOf(mii.facialHair.col)];
1958
- miiTo.facialHair.mustacheSize=mii.facialHair.mustacheSize;
1959
- miiTo.facialHair.mustacheYPos=mii.facialHair.mustacheYPos;
1960
- miiTo.glasses.type=mii.glasses.type;
1961
- miiTo.glasses.col=glassesCols3DS[["Grey","Brown","Red","Blue","Yellow","White"].indexOf(mii.glasses.col)];
1962
- miiTo.glasses.size=mii.glasses.size;
1963
- miiTo.glasses.yPos=mii.glasses.yPos;
1964
- miiTo.mole.on=mii.mole.on;
1965
- miiTo.mole.size=mii.mole.size;
1966
- miiTo.mole.xPos=mii.mole.xPos;
1967
- miiTo.mole.yPos=mii.mole.yPos;
1968
- miiTo.console="3DS";
1956
+ miiTo.nose.type=convTables.nose3DSToWii[mii.nose.type[0]][mii.nose.type[1]];
1957
+ miiTo.nose.size=mii.nose.size;
1958
+ miiTo.nose.yPos=mii.nose.yPos;
1959
+ miiTo.mouth.type=convTables.mouth3DSToWii[mii.mouth.type[0]][mii.mouth.type[1]];
1960
+ miiTo.mouth.col=wiiMouthColors[mouthCols3DS.indexOf(mii.mouth.col)>2?0:mouthCols3DS.indexOf(mii.mouth.col)];
1961
+ miiTo.mouth.size=mii.mouth.size;
1962
+ miiTo.mouth.yPos=mii.mouth.yPos;
1963
+ miiTo.mole=mii.mole;
1964
+ miiTo.hair.col=mii.hair.col;
1965
+ miiTo.hair.flipped=mii.hair.flipped;
1966
+ miiTo.hair.type=convTables.hair3DSToWii[mii.hair.style[0]][mii.hair.style[1]];
1967
+ miiTo.eyebrows.type=convTables.eyebrows3DSToWii[mii.eyebrows.style[0]][mii.eyebrows.style[1]];
1968
+ miiTo.eyebrows.col=mii.eyebrows.col;
1969
+ miiTo.eyebrows.rotation=mii.eyebrows.rot;
1970
+ miiTo.eyebrows.size=mii.eyebrows.size;
1971
+ miiTo.eyebrows.yPos=mii.eyebrows.yPos;
1972
+ miiTo.eyebrows.distApart=mii.eyebrows.distApart;
1973
+ miiTo.eyes.type=convTables.eyes3DSToWii[mii.eyes.type[0]][mii.eyes.type[1]];
1974
+ miiTo.eyes.rotation=mii.eyes.rot;
1975
+ miiTo.eyes.yPos=mii.eyes.yPos;
1976
+ miiTo.eyes.col=mii.eyes.col;
1977
+ miiTo.eyes.size=mii.eyes.size;
1978
+ miiTo.eyes.distApart=mii.eyes.distApart;
1979
+ miiTo.glasses=mii.glasses;
1980
+ miiTo.glasses.col=wiiGlassesCols[glassesCols3DS.indexOf(mii.glasses.col)];
1981
+ miiTo.facialHair=mii.facialHair;
1982
+ if(miiTo.facialHair.mustacheType===4){
1983
+ miiTo.facialHair.mustacheType=2;
1969
1984
  }
1970
- return miiTo;
1971
- },
1972
- make3DSChild:function(dad,mom,options={}){
1973
- if(!["3ds","wii u"].includes(dad.console?.toLowerCase())){
1974
- dad=this.convertMii(dad,"wii");
1985
+ else if(miiTo.facialHair.mustacheType===5){
1986
+ miiTo.facialHair.mustacheType=0;
1987
+ miiTo.facialHair.beardType=1;
1975
1988
  }
1976
- if(!["3ds","wii u"].includes(mom.console?.toLowerCase())){
1977
- mom=this.convertMii(dad,"wii");
1989
+ if(mii.facialHair.beardType>3){
1990
+ mii.facialHair.beardType=3;
1978
1991
  }
1979
- var g=options.gender||Math.floor(Math.random()*2)===1?"Male":"Female";
1980
- var child={
1981
- "info":{
1982
- "birthMonth":new Date().getMonth()+1,
1983
- "birthday":new Date().getDay(),
1984
- "height":64,
1985
- "weight":64,
1986
- "creatorName":"",
1987
- "gender":g,
1988
- "name":options.name||kidNames[g][Math.floor(Math.random()*kidNames[g].length)],
1989
- "favColor":options.favColor||favCols[Math.floor(Math.random()*favCols.length)]
1990
- },
1991
- "perms":{
1992
- "sharing":true,
1993
- "copying":true
1994
- },
1995
- "hair":{
1996
- "style":[8,3],//Hardcoded
1997
- "col":Math.floor(Math.random()*2)===1?dad.hair.col:mom.hair.col,
1998
- "flipped":Math.floor(Math.random()*2)===0?true:false
1999
- },
2000
- "face":{
2001
- "shape":Math.floor(Math.random()*2)===1?dad.face.shape:mom.face.shape,
2002
- "feature":Math.floor(Math.random()*2)===1?dad.face.feature:mom.face.feature,
2003
- "makeup":g==="Male"?"None":Math.floor(Math.random()*2)===1?dad.face.makeup:mom.face.makeup
2004
- },
2005
- "eyes":Math.floor(Math.random()*2)===1?dad.eyes:mom.eyes,
2006
- "eyebrows":Math.floor(Math.random()*2)===1?dad.eyebrows:mom.eyebrows,
2007
- "nose":Math.floor(Math.random()*2)===1?dad.nose:mom.nose,
2008
- "mouth":Math.floor(Math.random()*2)===1?dad.mouth:mom.mouth,
2009
- "facialHair":g==="Female"?{
2010
- "mustacheType": 0,
2011
- "beardType": 0,
2012
- "col": "Black",
2013
- "mustacheSize": 4,
2014
- "mustacheYPos": 10
2015
- }:Math.floor(Math.random()*2)===0?dad.facialHair:mom.facialHair,
2016
- "glasses":Math.floor(Math.random()*2)===1?dad.glasses:mom.glasses,
2017
- "mole":Math.floor(Math.random()*2)===1?dad.mole:mom.mole,
2018
- "creatorName":""
1992
+ miiTo.console="wii";
1993
+ }
1994
+ else if(typeFrom==="wii"){
1995
+ miiTo={
1996
+ info:{},
1997
+ perms:{},
1998
+ hair:{},
1999
+ face:{},
2000
+ eyes:{},
2001
+ eyebrows:{},
2002
+ nose:{},
2003
+ mouth:{},
2004
+ facialHair:{},
2005
+ glasses:{},
2006
+ mole:{}
2019
2007
  };
2020
- child.eyebrows.col=child.hair.col;
2021
- var c=[skinCols.indexOf(mom.face.col),skinCols.indexOf(dad.face.col)];
2022
- if(c[0]>c[1]){
2023
- c[1]=c[0];
2024
- c[0]=skinCols.indexOf(dad.face.col);
2025
- }
2026
- child.face.col=skinCols[c[0]+Math.round((c[1]-c[0])/2)];
2027
- child.name=child.info.name;
2028
- child.type="3DS";
2029
- return child;
2030
- },
2031
- generateInstructions:function(mii,full){
2032
- let type=mii.console.toLowerCase();
2033
- if(type.toLowerCase()==="wii"){
2034
- var instrs={
2035
- "base":`Select "${mii.info.gender}", and then "Start from Scratch".`,
2036
- "col":`On the info page (first tab), set the Favorite Color to ${mii.info.favColor} (${favCols.indexOf(mii.info.favColor)<=5?favCols.indexOf(mii.info.favColor)+1:favCols.indexOf(mii.info.favColor)-5} from the left, ${favCols.indexOf(mii.info.favColor)>5?"bottom":"top"} row).`,
2037
- "heightWeight":`On the build page (second tab), set the height to ${Math.round((100/128)*mii.info.height)}%, and the weight to ${Math.round((100/128)*mii.info.weight)}%.`,
2038
- "faceShape":`On the face page (third tab), set the shape to the one ${Math.floor(mii.face.shape/2)+1} from the top, in the ${mii.face.shape%2===0?"left":"right"} column.`,
2039
- "skinCol":`On the face page (third tab), set the color to the one ${skinCols.indexOf(mii.face.col)+skinCols.indexOf(mii.face.col)>2?-2:1} from the left, on the ${skinCols.indexOf(mii.face.col)>2?`bottom`:`top`} row.`,
2040
- "makeup":`On the face page's makeup tab, set the makeup to \"${mii.face.feature}\" (the one ${Math.ceil((wiiFaceFeatures.indexOf(mii.face.feature)+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][wiiFaceFeatures.indexOf(mii.face.feature)]} from the left).`,
2041
- "hairStyle":`On the hair page (fourth tab), set the hair style to the one ${mii.hair.type[1]} from the left, ${mii.hair.type[2]} from the top, on page ${mii.hair.type[0]}.`,
2042
- "hairFlipped":`${mii.hair.flipped?`On the hair page (fourth tab), press the button to flip the hair.`:``}`,
2043
- "hairColor":`On the hair page (fourth tab), set the hair color to the one ${hairCols.indexOf(mii.hair.col)+(hairCols.indexOf(mii.hair.col)>3?-3:1)} from the left, on the ${hairCols.indexOf(mii.hair.col)>3?`bottom`:`top`} row.`,
2044
- "eyebrowStyle":`On the eyebrow page (fifth tab), set the eyebrow style to the one ${mii.eyebrows.type[1]} from the left, ${mii.eyebrows.type[2]} from the top, on page ${mii.eyebrows.type[0]}.`,
2045
- "eyebrowColor":`On the eyebrow page (fifth tab), set the eyebrow color to the one ${hairCols.indexOf(mii.eyebrows.col)+(hairCols.indexOf(mii.eyebrows.col)>3?-3:1)} from the left, on the ${hairCols.indexOf(mii.eyebrows.col)>3?`bottom`:`top`} row.`,
2046
- "eyebrowY":`${mii.eyebrows.yPos!==7?`On the eyebrow page (fifth tab), `:``}${mii.eyebrows.yPos<7?`press the up button ${7-mii.eyebrows.yPos} times.`:mii.eyebrows.yPos>7?`press the down button ${mii.eyebrows.yPos-7} times.`:``}`,
2047
- "eyebrowSize":`${mii.eyebrows.size!==4?`On the eyebrow page (fifth tab), `:``}${mii.eyebrows.size<4?`press the shrink button ${4-mii.eyebrows.size} times.`:mii.eyebrows.size>4?`press the enlarge button ${mii.eyebrows.size-4} times.`:``}`,
2048
- "eyebrowRot":`${mii.eyebrows.rotation!==6?`On the eyebrow page (fifth tab), `:``}${mii.eyebrows.rotation<6?`press the rotate clockwise button ${6-mii.eyebrows.rotation} times.`:mii.eyebrows.rotation>6?`press the rotate counter-clockwise button ${mii.eyebrows.rotation-6} times.`:``}`,
2049
- "eyebrowDist":`${mii.eyebrows.distApart!==2?`On the eyebrow page (fifth tab), `:``}${mii.eyebrows.distApart<2?`press the closer-together button ${2-mii.eyebrows.distApart} times.`:mii.eyebrows.distApart>2?`press the further-apart button ${mii.eyebrows.distApart-2} times.`:``}`,
2050
- "eyeType":`On the eye page (sixth tab), set the eye type to the one ${mii.eyes.type[1]} from the left, ${mii.eyes.type[2]} from the top, on page ${mii.eyes.type[0]}.`,
2051
- "eyeColor":`On the eye page (sixth tab), set the color to the one ${eyeCols.indexOf(mii.eyes.col)+(eyeCols.indexOf(mii.eyes.col)>2?-2:1)} from the left, on the ${eyeCols.indexOf(mii.eyes.col)>2?`bottom`:`top`} row.`,
2052
- "eyeY":`${mii.eyes.yPos!==12?`On the eye page (sixth tab), `:``}${mii.eyes.yPos<12?`press the up button ${12-mii.eyes.yPos} times.`:mii.eyes.yPos>12?`press the down button ${mii.eyes.yPos-12} times.`:``}`,
2053
- "eyeSize":`${mii.eyes.size!==4?`On the eye page (sixth tab), `:``}${mii.eyes.size<4?`press the shrink button ${4-mii.eyes.size} times.`:mii.eyes.size>4?`press the enlarge button ${mii.eyes.size-4} times.`:``}`,
2054
- "eyeRot":`${mii.eyes.rotation!==(mii.info.gender==="Female"?3:4)?`On the eye page (sixth tab), `:``}${mii.eyes.rotation<(mii.info.gender==="Female"?3:4)?`press the rotate clockwise button ${(mii.info.gender==="Female"?3:4)-mii.eyes.rotation} times.`:mii.eyes.rotation>(mii.info.gender==="Female"?3:4)?`press the rotate counter-clockwise button ${mii.eyes.rotation-(mii.info.gender==="Female"?3:4)} times.`:``}`,
2055
- "eyeDist":`${mii.eyes.distApart!==2?`On the eye page (sixth tab), `:``}${mii.eyes.distApart<2?`press the closer-together button ${2-mii.eyes.distApart} times.`:mii.eyes.distApart>2?`press the further-apart button ${mii.eyes.distApart-2} times.`:``}`,
2056
- "noseType":`On the nose page (seventh tab), set the nose to the one ${Math.ceil((mii.nose.type+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.nose.type]} from the left.`,
2057
- "noseY":`${mii.nose.yPos!==9?`On the nose page (seventh tab), `:``}${mii.nose.yPos<9?`press the up button ${9-mii.nose.yPos} times.`:mii.nose.yPos>9?`press the down button ${mii.nose.yPos-9} times.`:``}`,
2058
- "noseSize":`${mii.nose.size!==4?`On the nose page (seventh tab), `:``}${mii.nose.size<4?`press the shrink button ${4-mii.nose.size} times.`:mii.nose.size>4?`press the enlarge button ${mii.nose.size-4} times.`:``}`,
2059
- "mouthType":`On the mouth page (eighth tab), set the mouth type to the one ${mii.mouth.type[1]} from the left, ${mii.mouth.type[2]} from the top, on page ${mii.mouth.type[0]}.`,
2060
- "mouthCol":`On the mouth page (eighth tab), set the color to the one ${wiiMouthColors.indexOf(mii.mouth.col)+1} from the left.`,
2061
- "mouthY":`${mii.mouth.yPos!==13?`On the mouth page (eighth tab), `:``}${mii.mouth.yPos<13?`press the up button ${13-mii.mouth.yPos} times.`:mii.mouth.yPos>13?`press the down button ${mii.mouth.yPos-13} times.`:``}`,
2062
- "mouthSize":`${mii.mouth.size!==4?`On the mouth page (eighth tab), `:``}${mii.mouth.size<4?`press the shrink button ${4-mii.mouth.size} times.`:mii.mouth.size>4?`press the enlarge button ${mii.mouth.size-4} times.`:``}`,
2063
- "glasses":`On the glasses page (within the ninth tab), set the glasses to the one ${Math.ceil((mii.glasses.type+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.glasses.type]} from the left.`,
2064
- "glassesCol":`On the glasses page (within the ninth tab), set the color to the one ${wiiGlassesCols.indexOf(mii.glasses.col)+(wiiGlassesCols.indexOf(mii.glasses.col)>2?-2:1)} from the left, on the ${wiiGlassesCols.indexOf(mii.glasses.col)>2?`bottom`:`top`} row.`,
2065
- "glassesY":`${mii.glasses.yPos!==10?`On the glasses page (within the ninth tab), `:``}${mii.glasses.yPos<10?`press the up button ${10-mii.glasses.yPos} times.`:mii.glasses.yPos>10?`press the down button ${mii.glasses.yPos-10} times.`:``}`,
2066
- "glassesSize":`${mii.glasses.size!==4?`On the glasses page (within the ninth tab), `:``}${mii.glasses.size<4?`press the shrink button ${4-mii.glasses.size} times.`:mii.glasses.size>4?`press the enlarge button ${mii.glasses.size-4} times.`:``}`,
2067
- "stache":`On the mustache page (within the ninth tab), set the mustache to the one on the ${[0,1].includes(mii.facialHair.mustacheType)?`top`:`bottom`}-${[0,2].includes(mii.facialHair.mustacheType)?`left`:`right`}.`,
2068
- "stacheY":`${mii.facialHair.mustacheYPos!==10?`On the mustache page (within the ninth tab), press the `:``}${mii.facialHair.mustacheYPos>10?`down button ${mii.facialHair.mustacheYPos-10} times.`:mii.facialHair.mustacheYPos<10?`up button ${10-mii.facialHair.mustacheYPos} times.`:``}`,
2069
- "stacheSize":`${mii.facialHair.mustacheSize!==4?`On the mustache page (within the ninth tab), `:``}${mii.facialHair.mustacheSize<4?`press the shrink button ${4-mii.facialHair.mustacheSize} times.`:mii.facialHair.mustacheSize>4?`press the enlarge button ${mii.facialHair.mustacheSize-4} times.`:``}`,
2070
- "mole":`${mii.mole.on?`On the mole page (within the ninth tab), turn the mole on.`:``}`,
2071
- "moleX":`${mii.mole.xPos!==2?`On the mole page (within the ninth tab), press the `:``}${mii.mole.xPos>2?`right button ${mii.mole.xPos-2} times.`:mii.mole.xPos<2?`left button ${2-mii.mole.xPos} times.`:``}`,
2072
- "moleY":`${mii.mole.yPos!==20?`On the mole page (within the ninth tab), press the `:``}${mii.mole.yPos>20?`down button ${mii.mole.yPos-20} times.`:mii.mole.yPos<20?`up button ${20-mii.mole.yPos} times.`:``}`,
2073
- "moleSize":`${mii.mole.size!==4?`On the mole page (within the ninth tab), `:``}${mii.mole.size<4?`press the shrink button ${4-mii.mole.size} times.`:mii.mole.size>4?`press the enlarge button ${mii.mole.size-4} times.`:``}`,
2074
- "beard":`On the beard page (within the ninth tab), set the beard to the one on the ${[0,1].includes(mii.facialHair.beardType)?`top`:`bottom`}-${[0,2].includes(mii.facialHair.beardType)?`left`:`right`}.`,
2075
- "beardCol":`On the mustache OR beard pages (within the ninth tab), set the color to the one ${hairCols.indexOf(mii.facialHair.col)+(hairCols.indexOf(mii.facialHair.col)>3?-3:1)} from the left, on the ${hairCols.indexOf(mii.facialHair.col)>3?`bottom`:`top`} row.`,
2076
- "other":`The Nickname of this Mii is ${mii.info.name}.${mii.info.creatorName?` The creator was ${mii.info.creatorName}.`:``} Mingle was turned ${mii.info.mingle?`on`:`off`}.${mii.info.birthday!==0?` Its birthday is ${["","January","February","March","April","May","June","July","August","September","October","November","December"][mii.info.birthMonth]} ${mii.info.birthday}.`:``}`
2077
- };
2078
- if(!full){
2079
- var defaultMiiInstrs=structuredClone(mii.info.gender==="Male"?defaultInstrs.wii.male:defaultInstrs.wii.female);
2080
- Object.keys(instrs).forEach(instr=>{
2081
- if(instrs[instr]===defaultMiiInstrs[instr]){
2082
- delete instrs[instr];
2083
- }
2084
- });
2085
- }
2086
- return instrs;
2008
+ miiTo.info.birthday=mii.info.birthday;
2009
+ miiTo.info.birthMonth=mii.info.birthMonth;
2010
+ miiTo.name=mii.name;
2011
+ miiTo.info.name=miiTo.name;
2012
+ miiTo.creatorName=mii.creatorName;
2013
+ miiTo.info.creatorName=mii.creatorName;
2014
+ miiTo.info.height=mii.info.height;
2015
+ miiTo.info.weight=mii.info.weight;
2016
+ miiTo.info.favColor=mii.info.favColor;
2017
+ miiTo.info.gender=mii.info.gender;
2018
+ miiTo.perms.sharing=mii.info.mingle;
2019
+ miiTo.perms.copying=mii.info.mingle;
2020
+ miiTo.hair.col=hairCols[hairCols.indexOf(mii.hair.col)];
2021
+ miiTo.hair.flipped=mii.hair.flipped;
2022
+ miiTo.hair.style=convTables.hairWiiTo3DS[+mii.hair.type[0]-1][0+(3*(+mii.hair.type[2]-1))+(+mii.hair.type[1]-1)];
2023
+ miiTo.face.shape=convTables.faceWiiTo3DS[mii.face.shape];
2024
+ miiTo.face.col=skinCols[skinCols.indexOf(mii.face.col)];
2025
+ miiTo.face.makeup="None";
2026
+ miiTo.face.feature="None";
2027
+ if(typeof(convTables.featureWiiTo3DS[wiiFaceFeatures.indexOf(mii.face.feature)])==='string'){
2028
+ miiTo.face.makeup=makeups3DS[+convTables.featureWiiTo3DS[wiiFaceFeatures.indexOf(mii.face.feature)]];
2087
2029
  }
2088
2030
  else{
2089
- var instrs={
2090
- "base":`Select "Start from Scratch", and then "${mii.info.gender}".`,
2091
- "faceShape":`On the face page (first tab), set the face shape to the one ${Math.ceil((mii.face.shape+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.face.shape]} from the left.`,
2092
- "skinCol":`On the face page (first tab), set the color to the one ${skinCols.indexOf(mii.face.col)+1} from the top.`,
2093
- "makeup":`On the face page's makeup tab, set the makeup to \"${mii.face.makeup}\" (the one ${Math.ceil((makeups3DS.indexOf(mii.face.makeup)+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][makeups3DS.indexOf(mii.face.makeup)]} from the left).`,
2094
- "feature":`On the face page's wrinkles tab, set the facial feature to \"${mii.face.feature}\" (the one ${Math.ceil((faceFeatures3DS.indexOf(mii.face.feature)+1)/3)+1} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][makeups3DS.indexOf(mii.face.makeup)]} from the left).`,
2095
- "hairStyle":`On the hair page (second tab), set the hair style to the one ${Math.ceil((mii.hair.style[1]+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.hair.style[1]]} from the left, on page ${mii.hair.style[0]+1}.`,
2096
- "hairFlipped":`${mii.hair.flipped?`On the hair page (second tab), press the button to flip the hair.`:``}`,
2097
- "hairColor":`On the hair page (second tab), set the hair color to the one ${hairCols.indexOf(mii.hair.col)+1} from the top.`,
2098
- "eyebrowStyle":`On the eyebrow page (third tab), set the eyebrow style to the one ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.eyebrows.style[1]]} from the left, ${Math.ceil((mii.eyebrows.style[1]+1)/3)} from the top, on page ${mii.eyebrows.style[0]+1}.`,
2099
- "eyebrowColor":`On the eyebrow page (third tab), set the eyebrow color to the one ${hairCols.indexOf(mii.eyebrows.col)+1} from the top.`,
2100
- "eyebrowY":`${mii.eyebrows.yPos!==7?`On the eyebrow page (third tab), `:``}${mii.eyebrows.yPos<7?`press the up button ${7-mii.eyebrows.yPos} times.`:mii.eyebrows.yPos>7?`press the down button ${mii.eyebrows.yPos-7} times.`:``}`,
2101
- "eyebrowSize":`${mii.eyebrows.size!==4?`On the eyebrow page (third tab), `:``}${mii.eyebrows.size<4?`press the shrink button ${4-mii.eyebrows.size} times.`:mii.eyebrows.size>4?`press the enlarge button ${mii.eyebrows.size-4} times.`:``}`,
2102
- "eyebrowRot":`${mii.eyebrows.rot!==6?`On the eyebrow page (third tab), `:``}${mii.eyebrows.rot<6?`press the rotate clockwise button ${6-mii.eyebrows.rot} times.`:mii.eyebrows.rot>6?`press the rotate counter-clockwise button ${mii.eyebrows.rot-6} times.`:``}`,
2103
- "eyebrowDist":`${mii.eyebrows.distApart!==2?`On the eyebrow page (third tab), `:``}${mii.eyebrows.distApart<2?`press the closer-together button ${2-mii.eyebrows.distApart} times.`:mii.eyebrows.distApart>2?`press the further-apart button ${mii.eyebrows.distApart-2} times.`:``}`,
2104
- "eyebrowSquash":`${mii.eyebrows.squash!==3?`On the eyebrow page (third tab), `:``}${mii.eyebrows.squash<3?`press the squish button ${3-mii.eyebrows.squash} times.`:mii.eyebrows.squash>3?`press the un-squish button ${mii.eyebrows.squash-3} times.`:``}`,
2105
- "eyeType":`On the eye page (fourth tab), set the eye type to the one ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.eyes.type[1]]} from the left, ${Math.ceil((mii.eyes.type[1]+1)/3)} from the top, on page ${mii.eyes.type[0]+1}.`,
2106
- "eyeColor":`On the eye page (fourth tab), set the color to the one ${eyeCols.indexOf(mii.eyes.col)+1} from the top.`,
2107
- "eyeY":`${mii.eyes.yPos!==12?`On the eye page (fourth tab), `:``}${mii.eyes.yPos<12?`press the up button ${12-mii.eyes.yPos} times.`:mii.eyes.yPos>12?`press the down button ${mii.eyes.yPos-12} times.`:``}`,
2108
- "eyeSize":`${mii.eyes.size!==4?`On the eye page (fourth tab), `:``}${mii.eyes.size<4?`press the shrink button ${4-mii.eyes.size} times.`:mii.eyes.size>4?`press the enlarge button ${mii.eyes.size-4} times.`:``}`,
2109
- "eyeRot":`${mii.eyes.rot!==(mii.info.gender==="Female"?3:4)?`On the eye page (fourth tab), `:``}${mii.eyes.rot<(mii.info.gender==="Female"?3:4)?`press the rotate clockwise button ${(mii.info.gender==="Female"?3:4)-mii.eyes.rot} times.`:mii.eyes.rot>(mii.info.gender==="Female"?3:4)?`press the rotate counter-clockwise button ${mii.eyes.rot-(mii.info.gender==="Female"?3:4)} times.`:``}`,
2110
- "eyeDist":`${mii.eyes.distApart!==2?`On the eye page (fourth tab), `:``}${mii.eyes.distApart<2?`press the closer-together button ${2-mii.eyes.distApart} times.`:mii.eyes.distApart>2?`press the further-apart button ${mii.eyes.distApart-2} times.`:``}`,
2111
- "eyeSquash":`${mii.eyes.squash!==3?`On the eye page (fourth tab), `:``}${mii.eyes.squash<3?`press the squish button ${3-mii.eyes.squash} times.`:mii.eyes.squash>3?`press the un-squish button ${mii.eyes.squash-3} times.`:``}`,
2112
- "noseType":`On the nose page (fifth tab), set the nose to the one ${Math.ceil((mii.nose.type[1]+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.nose.type[1]]} from the left, on page ${mii.nose.type[0]}.`,
2113
- "noseY":`${mii.nose.yPos!==9?`On the nose page (fifth tab), `:``}${mii.nose.yPos<9?`press the up button ${9-mii.nose.yPos} times.`:mii.nose.yPos>9?`press the down button ${mii.nose.yPos-9} times.`:``}`,
2114
- "noseSize":`${mii.nose.size!==4?`On the nose page (fifth tab), `:``}${mii.nose.size<4?`press the shrink button ${4-mii.nose.size} times.`:mii.nose.size>4?`press the enlarge button ${mii.nose.size-4} times.`:``}`,
2115
- "mouthType":`On the mouth page (sixth tab), set the mouth type to the one ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.mouth.type[1]]} from the left, ${Math.ceil((mii.mouth.type[1]+1)/3)} from the top, on page ${mii.mouth.type[0]+1}.`,
2116
- "mouthCol":`On the mouth page (sixth tab), set the color to the one ${mouthCols3DS.indexOf(mii.mouth.col)+1} from the top.`,
2117
- "mouthY":`${mii.mouth.yPos!==13?`On the mouth page (sixth tab), `:``}${mii.mouth.yPos<13?`press the up button ${13-mii.mouth.yPos} times.`:mii.mouth.yPos>13?`press the down button ${mii.mouth.yPos-13} times.`:``}`,
2118
- "mouthSize":`${mii.mouth.size!==4?`On the mouth page (sixth tab), `:``}${mii.mouth.size<4?`press the shrink button ${4-mii.mouth.size} times.`:mii.mouth.size>4?`press the enlarge button ${mii.mouth.size-4} times.`:``}`,
2119
- "mouthSquash":`${mii.mouth.squash!==3?`On the mouth page (sixth tab), `:``}${mii.mouth.squash<3?`press the squish button ${3-mii.mouth.squash} times.`:mii.mouth.squash>3?`press the un-squish button ${mii.mouth.squash-3} times.`:``}`,
2120
- "glasses":`On the glasses page (within the seventh tab), set the glasses to the one ${Math.ceil((mii.glasses.type+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.glasses.type]} from the left.`,
2121
- "glassesCol":`On the glasses page (within the seventh tab), set the color to the one ${glassesCols3DS.indexOf(mii.glasses.col)+1} from the top.`,
2122
- "glassesY":`${mii.glasses.yPos!==10?`On the glasses page (within the seventh tab), `:``}${mii.glasses.yPos<10?`press the up button ${10-mii.glasses.yPos} times.`:mii.glasses.yPos>10?`press the down button ${mii.glasses.yPos-10} times.`:``}`,
2123
- "glassesSize":`${mii.glasses.size!==4?`On the glasses page (within the seventh tab), `:``}${mii.glasses.size<4?`press the shrink button ${4-mii.glasses.size} times.`:mii.glasses.size>4?`press the enlarge button ${mii.glasses.size-4} times.`:``}`,
2124
- "stache":`On the mustache page (within the seventh tab), set the mustache to the one on the ${[0,1].includes(mii.facialHair.mustacheType)?`top`:[2,3].includes(mii.facialHair.mustacheType)?`middle`:`bottom`}-${[0,2,4].includes(mii.facialHair.mustacheType)?`left`:`right`}.`,
2125
- "stacheY":`${mii.facialHair.mustacheYPos!==10?`On the mustache page (within the seventh tab), press the `:``}${mii.facialHair.mustacheYPos>10?`down button ${mii.facialHair.mustacheYPos-10} times.`:mii.facialHair.mustacheYPos<10?`up button ${10-mii.facialHair.mustacheYPos} times.`:``}`,
2126
- "stacheSize":`${mii.facialHair.mustacheSize!==4?`On the mustache page (within the seventh tab), `:``}${mii.facialHair.mustacheSize<4?`press the shrink button ${4-mii.facialHair.mustacheSize} times.`:mii.facialHair.mustacheSize>4?`press the enlarge button ${mii.facialHair.mustacheSize-4} times.`:``}`,
2127
- "mole":`${mii.mole.on?`On the mole page (within the seventh tab), turn the mole on.`:``}`,
2128
- "moleX":`${mii.mole.xPos!==2?`On the mole page (within the seventh tab), press the `:``}${mii.mole.xPos>2?`right button ${mii.mole.xPos-2} times.`:mii.mole.xPos<2?`left button ${2-mii.mole.xPos} times.`:``}`,
2129
- "moleY":`${mii.mole.yPos!==20?`On the mole page (within the seventh tab), press the `:``}${mii.mole.yPos>20?`down button ${mii.mole.yPos-20} times.`:mii.mole.yPos<20?`up button ${20-mii.mole.yPos} times.`:``}`,
2130
- "moleSize":`${mii.mole.size!==4?`On the mole page (within the seventh tab), `:``}${mii.mole.size<4?`press the shrink button ${4-mii.mole.size} times.`:mii.mole.size>4?`press the enlarge button ${mii.mole.size-4} times.`:``}`,
2131
- "beard":`On the beard page (within the seventh tab), set the beard to the one on the ${[0,1].includes(mii.facialHair.beardType)?`top`:[2,3].includes(mii.facialHair.beardType)?`middle`:`bottom`}-${[0,2].includes(mii.facialHair.beardType)?`left`:`right`}.`,
2132
- "beardCol":`On the mustache OR beard pages (within the seventh tab), set the color to the one ${hairCols.indexOf(mii.facialHair.col)+1} from the top.`,
2133
- "heightWeight":`On the build page (eighth tab), set the height to ${Math.round((100/128)*mii.info.height)}%, and the weight to ${Math.round((100/128)*mii.info.weight)}%.`,
2134
- "col":`On the info page (after pressing "Next"), set the Favorite Color to ${mii.info.favColor} (${favCols.indexOf(mii.info.favColor)<=5?favCols.indexOf(mii.info.favColor)+1:favCols.indexOf(mii.info.favColor)-5} from the left, ${favCols.indexOf(mii.info.favColor)>5?"bottom":"top"} row).`,
2135
- "other":`The Nickname of this Mii is ${mii.info.name}.${mii.info.creatorName?` The creator was ${mii.info.creatorName}.`:``} ${mii.info.birthday!==0?` Its birthday is ${["","January","February","March","April","May","June","July","August","September","October","November","December"][mii.info.birthMonth]} ${mii.info.birthday}.`:``}`
2136
- };
2137
- if(!full){
2138
- var defaultMiiInstrs=structuredClone(mii.info.gender==="Male"?defaultInstrs["3ds"].male:defaultInstrs["3ds"].female);
2139
- Object.keys(instrs).forEach(instr=>{
2140
- if(instrs[instr]===defaultMiiInstrs[instr]){
2141
- delete instrs[instr];
2142
- }
2143
- });
2144
- }
2145
- return instrs;
2031
+ miiTo.face.feature=faceFeatures3DS[convTables.featureWiiTo3DS[wiiFaceFeatures.indexOf(mii.face.feature)]];
2146
2032
  }
2147
- },
2148
- convert3DSMiiToStudio:function(jsonIn){
2149
- if(!["3ds","wii u"].includes(jsonIn.console.toLowerCase())){
2150
- jsonIn=this.convertMii(jsonIn);
2033
+ miiTo.eyes.col=eyeCols[eyeCols.indexOf(mii.eyes.col)];
2034
+ miiTo.eyes.type=[+mii.eyes.type[0]-1,(+mii.eyes.type[1]-1)+(3*(+mii.eyes.type[2]-1))];
2035
+ miiTo.eyes.size=mii.eyes.size;
2036
+ miiTo.eyes.squash=3;
2037
+ miiTo.eyes.rot=mii.eyes.rotation;
2038
+ miiTo.eyes.distApart=mii.eyes.distApart;
2039
+ miiTo.eyes.yPos=mii.eyes.yPos;
2040
+ miiTo.eyebrows.style=[+mii.eyebrows.type[0]-1,(+mii.eyebrows.type[1]-1)+(3*(+mii.eyebrows.type[2]-1))];
2041
+ miiTo.eyebrows.col=hairCols[hairCols.indexOf(mii.eyebrows.col)];
2042
+ miiTo.eyebrows.size=mii.eyebrows.size;
2043
+ miiTo.eyebrows.squash=3;
2044
+ miiTo.eyebrows.rot=mii.eyebrows.rotation;
2045
+ miiTo.eyebrows.distApart=mii.eyebrows.distApart;
2046
+ miiTo.eyebrows.yPos=mii.eyebrows.yPos;
2047
+ miiTo.nose.type=[0,mii.nose.type];
2048
+ miiTo.nose.size=mii.nose.size;
2049
+ miiTo.nose.yPos=mii.nose.yPos;
2050
+ miiTo.mouth.type=[+mii.mouth.type[0]-1,(+mii.mouth.type[1]-1)+(3*(+mii.mouth.type[2]-1))];
2051
+ miiTo.mouth.col=mouthCols3DS[wiiMouthColors.indexOf(mii.mouth.col)];
2052
+ miiTo.mouth.size=mii.mouth.size;
2053
+ miiTo.mouth.squash=3;
2054
+ miiTo.mouth.yPos=mii.mouth.yPos;
2055
+ miiTo.facialHair.mustacheType=mii.facialHair.mustacheType;
2056
+ miiTo.facialHair.beardType=mii.facialHair.beardType;
2057
+ miiTo.facialHair.col=hairCols[hairCols.indexOf(mii.facialHair.col)];
2058
+ miiTo.facialHair.mustacheSize=mii.facialHair.mustacheSize;
2059
+ miiTo.facialHair.mustacheYPos=mii.facialHair.mustacheYPos;
2060
+ miiTo.glasses.type=mii.glasses.type;
2061
+ miiTo.glasses.col=glassesCols3DS[["Grey","Brown","Red","Blue","Yellow","White"].indexOf(mii.glasses.col)];
2062
+ miiTo.glasses.size=mii.glasses.size;
2063
+ miiTo.glasses.yPos=mii.glasses.yPos;
2064
+ miiTo.mole.on=mii.mole.on;
2065
+ miiTo.mole.size=mii.mole.size;
2066
+ miiTo.mole.xPos=mii.mole.xPos;
2067
+ miiTo.mole.yPos=mii.mole.yPos;
2068
+ miiTo.console="3ds";
2069
+ }
2070
+ return miiTo;
2071
+ }
2072
+ function convertMiiToStudio(jsonIn) {
2073
+ if (!["3ds", "wii u"].includes(jsonIn.console?.toLowerCase())) {
2074
+ jsonIn = convertMii(jsonIn);
2075
+ }
2076
+ var mii = jsonIn;
2077
+ 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]);
2078
+ studioMii[0x16] = mii.info.gender === "Male" ? 0 : 1;
2079
+ studioMii[0x15] = lookupTables.favCols.indexOf(mii.info.favColor);
2080
+ studioMii[0x1E] = mii.info.height;
2081
+ studioMii[2] = mii.info.weight;
2082
+ studioMii[0x13] = lookupTables.faces.values[mii.face.shape];
2083
+ studioMii[0x11] = lookupTables.skinCols.indexOf(mii.face.col);
2084
+ studioMii[0x14] = lookupTables.faceFeatures3DS.indexOf(mii.face.feature);
2085
+ studioMii[0x12] = lookupTables.makeups3DS.indexOf(mii.face.makeup);
2086
+ studioMii[0x1D] = lookupTables.hairs.values[mii.hair.style[0]][mii.hair.style[1]];
2087
+ studioMii[0x1B] = lookupTables.hairCols.indexOf(mii.hair.col);
2088
+ if (!studioMii[0x1B]) studioMii[0x1B] = 8;
2089
+ studioMii[0x1C] = mii.hair.flipped ? 1 : 0;
2090
+ studioMii[7] = lookupTables.eyes.values[mii.eyes.type[0]][mii.eyes.type[1]];
2091
+ studioMii[4] = lookupTables.eyeCols.indexOf(mii.eyes.col) + 8;
2092
+ studioMii[6] = mii.eyes.size;
2093
+ studioMii[3] = mii.eyes.squash;
2094
+ studioMii[5] = mii.eyes.rot;
2095
+ studioMii[8] = mii.eyes.distApart;
2096
+ studioMii[9] = mii.eyes.yPos;
2097
+ studioMii[0xE] = lookupTables.eyebrows.values[mii.eyebrows.style[0]][mii.eyebrows.style[1]];
2098
+ studioMii[0xB] = lookupTables.hairCols.indexOf(mii.eyebrows.col);
2099
+ if (!studioMii[0xB]) studioMii[0xB] = 8;
2100
+ studioMii[0xD] = mii.eyebrows.size;
2101
+ studioMii[0xA] = mii.eyebrows.squash;
2102
+ studioMii[0xC] = mii.eyebrows.rot;
2103
+ studioMii[0xF] = mii.eyebrows.distApart;
2104
+ studioMii[0x10] = mii.eyebrows.yPos + 3;
2105
+ studioMii[0x2C] = lookupTables.noses.values[mii.nose.type[0]][mii.nose.type[1]];
2106
+ studioMii[0x2B] = mii.nose.size;
2107
+ studioMii[0x2D] = mii.nose.yPos;
2108
+ studioMii[0x26] = lookupTables.mouths.values[mii.mouth.type[0]][mii.mouth.type[1]];
2109
+ studioMii[0x24] = lookupTables.hairCols.indexOf(mii.mouth.col);
2110
+ if (studioMii[0x24] < 4) {
2111
+ studioMii[0x24] += 19;
2112
+ } else {
2113
+ studioMii[0x24] = 0;
2114
+ }
2115
+ studioMii[0x25] = mii.mouth.size;
2116
+ studioMii[0x23] = mii.mouth.squash;
2117
+ studioMii[0x27] = mii.mouth.yPos;
2118
+ studioMii[0x29] = mii.facialHair.mustacheType;
2119
+ studioMii[1] = mii.facialHair.beardType;
2120
+ studioMii[0] = lookupTables.hairCols.indexOf(mii.facialHair.col);
2121
+ if (!studioMii[0]) studioMii[0] = 8;
2122
+ studioMii[0x28] = mii.facialHair.mustacheSize;
2123
+ studioMii[0x2A] = mii.facialHair.mustacheYPos;
2124
+ studioMii[0x19] = mii.glasses.type;
2125
+ studioMii[0x17] = lookupTables.glassesCols3DS.indexOf(mii.glasses.col);
2126
+ if (!studioMii[0x17]) {
2127
+ studioMii[0x17] = 8;
2128
+ } else if (studioMii[0x17] < 6) {
2129
+ studioMii[0x17] += 13;
2130
+ } else {
2131
+ studioMii[0x17] = 0;
2132
+ }
2133
+ studioMii[0x18] = mii.glasses.size;
2134
+ studioMii[0x1A] = mii.glasses.yPos;
2135
+ studioMii[0x20] = mii.mole.on ? 1 : 0;
2136
+ studioMii[0x1F] = mii.mole.size;
2137
+ studioMii[0x21] = mii.mole.xPos;
2138
+ studioMii[0x22] = mii.mole.yPos;
2139
+ return encodeStudio(studioMii);
2140
+ }
2141
+ async function readWiiBin(binOrPath) {
2142
+ let data;
2143
+ if (/[^01]/ig.test(binOrPath)) {
2144
+ data = await fs.promises.readFile(binOrPath);
2145
+ } else {
2146
+ data = Buffer.from(binOrPath);
2147
+ }
2148
+
2149
+ const thisMii = miiBufferToJson(data, WII_MII_SCHEMA, lookupTables, true);
2150
+ thisMii.console = 'wii';
2151
+
2152
+ return thisMii;
2153
+ }
2154
+ async function read3DSQR(binOrPath,returnDecryptedBin) {
2155
+ let qrCode;
2156
+ if (/[^01]/ig.test(binOrPath)) {
2157
+ var data = await fs.promises.readFile(binOrPath);
2158
+ var img = await loadImage(data);
2159
+ const canvas = createCanvas(img.width, img.height);
2160
+ const ctx = canvas.getContext('2d');
2161
+ ctx.drawImage(img, 0, 0);
2162
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2163
+ qrCode = jsQR(imageData.data, imageData.width, imageData.height).binaryData;
2164
+ }
2165
+ else {
2166
+ var d = binOrPath.match(/(0|1){1,8}/g);
2167
+ qrCode = [];
2168
+ d.forEach(byte => {
2169
+ qrCode.push(parseInt(byte, 2));
2170
+ });
2171
+ }
2172
+ if (qrCode) {
2173
+ var data = Buffer.from(decodeAesCcm(new Uint8Array(qrCode)));
2174
+ if(returnDecryptedBin){
2175
+ return data;
2151
2176
  }
2152
- var mii=jsonIn;
2153
- 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]);
2154
- function encodeStudio(studio) {
2155
- function byteToString(int){
2156
- var str = int.toString(16);
2157
- if(str.length < 2)str = '0' + str;
2158
- return str;
2159
- }
2160
- var n = 0;
2161
- var eo;
2162
- var dest = byteToString(n);
2163
- for (var i = 0; i < studio.length; i++) {
2164
- eo = (7 + (studio[i] ^ n)) & 0xFF;
2165
- n = eo;
2166
- dest += byteToString(eo);
2177
+ const miiJson = miiBufferToJson(data, THREEDS_MII_SCHEMA, lookupTables, false);
2178
+ miiJson.console = '3ds';
2179
+ return miiJson;
2180
+ } else {
2181
+ console.error('Failed to decode QR code');
2182
+ }
2183
+ }
2184
+ async function renderMiiWithStudio(jsonIn){
2185
+ if(!["3ds","wii u"].includes(jsonIn.console?.toLowerCase())){
2186
+ jsonIn=convertMii(jsonIn);
2187
+ }
2188
+ var studioMii=convert3DSMiiToStudio(jsonIn);
2189
+ return await downloadImage('https://studio.mii.nintendo.com/miis/image.png?data=' + studioMii + "&width=270&type=face");
2190
+ }
2191
+ async function createFFLMiiIcon(data, width, height, fflRes) {
2192
+ /**
2193
+ * Creates a Mii face render using FFL.js/Three.js/gl-headless.
2194
+ * @example
2195
+ * const fs = require('fs');
2196
+ * // NOTE: ASSUMES that this function IS EXPORTED in index.js.
2197
+ * const createFFLMiiIcon = require('./index.js').createFFLMiiIcon;
2198
+ * const miiData = '000d142a303f434b717a7b84939ba6b2bbbec5cbc9d0e2ea010d15252b3250535960736f726870757f8289a0a7aeb1';
2199
+ * const outFilePath = 'mii-render.png';
2200
+ * const fflRes = fs.readFileSync('./FFLResHigh.dat');
2201
+ * createFFLMiiIcon(miiData, 512, 512, fflRes)
2202
+ * .then(pngBytes => fs.writeFileSync(outFilePath, pngBytes));
2203
+ */
2204
+
2205
+ // Create WebGL context.
2206
+ const gl = createGL(width, height);
2207
+ if (!gl) {
2208
+ throw new Error('Failed to create WebGL 1 context');
2209
+ }
2210
+
2211
+ // Create a dummy canvas for Three.js to use.
2212
+ const canvas = {
2213
+ width, height, style: {},
2214
+ addEventListener() {},
2215
+ removeEventListener() {},
2216
+ // Return the context for 'webgl' (not webgl2)
2217
+ getContext: (type, _) => type === 'webgl' ? gl : null,
2218
+ };
2219
+
2220
+ // WebGLRenderer constructor sets "self" as the context.
2221
+ // As of r162, it only tries to call cancelAnimationFrame frame on it.
2222
+ globalThis.self ??= {
2223
+ // Mock window functions called by Three.js.
2224
+ cancelAnimationFrame: () => { },
2225
+ };
2226
+ // Create the Three.js renderer and scene.
2227
+ const renderer = new THREE.WebGLRenderer({ canvas, context: gl, alpha: true });
2228
+ setIsWebGL1State(!renderer.capabilities.isWebGL2); // Tell FFL.js we are WebGL1
2229
+
2230
+ const scene = new THREE.Scene();
2231
+ // scene.background = null; // Transparent background.
2232
+ scene.background = new THREE.Color('white');
2233
+ // (You DO NOT need to add any lights to the scene,
2234
+ // unless you are using a Three.js built-in material.
2235
+ // If you are, look at demo-basic.js "addLightsToScene".)
2236
+
2237
+ let ffl, currentCharModel;
2238
+
2239
+ //const _realConsoleDebug = console.debug;
2240
+ //console.debug = () => { };
2241
+ try {
2242
+ // Initialize FFL
2243
+ ffl = await initializeFFL(fflRes, ModuleFFL);
2244
+
2245
+ // Create Mii model and add to the scene.
2246
+ const studioRaw = parseHexOrB64ToUint8Array(data); // Parse studio data
2247
+ currentCharModel = createCharModel(studioRaw, null,
2248
+ FFLShaderMaterial, ffl.module);
2249
+ initCharModelTextures(currentCharModel, renderer); // Initialize fully
2250
+ scene.add(currentCharModel.meshes); // Add to scene
2251
+
2252
+ // Use the camera for an icon pose.
2253
+ const camera = getCameraForViewType(ViewType.MakeIcon);
2254
+
2255
+ // The pixels coming from WebGL are upside down.
2256
+ camera.projectionMatrix.elements[5] *= -1; // Flip the camera Y axis.
2257
+ // When flipping the camera, the triangles are in the wrong direction.
2258
+ scene.traverse(mesh => {
2259
+ if (mesh.isMesh && mesh.material.side === THREE.FrontSide)
2260
+ // Fix triangle winding by changing the culling (side).
2261
+ mesh.material.side = THREE.BackSide;
2262
+ });
2263
+
2264
+ // Render the scene, and read the pixels into a buffer.
2265
+ renderer.render(scene, camera);
2266
+ const pixels = new Uint8Array(width * height * 4);
2267
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
2268
+
2269
+ // Draw the pixels to a new canvas.
2270
+ const canvas = createCanvas(width, height);
2271
+ const img = new ImageData(new Uint8ClampedArray(pixels), width, height);
2272
+ canvas.getContext('2d').putImageData(img, 0, 0);
2273
+
2274
+ return canvas.toBuffer('image/png'); // Encode image to PNG
2275
+
2276
+ } catch (error) {
2277
+ console.error('Error during rendering:', error);
2278
+ throw error;
2279
+ } finally {
2280
+ // Clean up.
2281
+ try {
2282
+ (currentCharModel) && currentCharModel.dispose(); // Mii model
2283
+ exitFFL(ffl.module, ffl.resourceDesc); // Free fflRes from memory.
2284
+ renderer.dispose(); // Dispose Three.js renderer.
2285
+ gl.finish();
2286
+ } catch (error) {
2287
+ console.warn('Error disposing Mii and renderer:', error);
2288
+ }// finally {
2289
+ // console.debug = _realConsoleDebug;
2290
+ //}
2291
+ }
2292
+ }
2293
+ async function renderMii(jsonIn, fflRes=getFFLRes()){
2294
+ if(!["3ds","wii u"].includes(jsonIn.console?.toLowerCase())){
2295
+ jsonIn=convertMii(jsonIn);
2296
+ }
2297
+ const studioMii = convert3DSMiiToStudio(jsonIn);
2298
+ const width = height = 600;
2299
+
2300
+ return createFFLMiiIcon(studioMii, width, height, fflRes);
2301
+ }
2302
+ async function writeWiiBin(jsonIn, outPath) {
2303
+ if (jsonIn.console?.toLowerCase() !== "wii") {
2304
+ convertMii(jsonIn);
2305
+ }
2306
+ const miiBuffer = jsonToMiiBuffer(jsonIn, WII_MII_SCHEMA, lookupTables, 74);
2307
+ if(outPath){
2308
+ await fs.promises.writeFile(outPath, miiBuffer);
2309
+ }
2310
+ else{
2311
+ return miiBuffer;
2312
+ }
2313
+ }
2314
+ async function write3DSQR(miiJson, outPath, fflRes = getFFLRes()) {
2315
+ if (!["3ds", "wii u"].includes(miiJson.console?.toLowerCase())) {
2316
+ miiJson = convertMii(miiJson);
2317
+ }
2318
+ return new Promise(async (resolve, reject) => {
2319
+ const miiBinary = jsonToMiiBuffer(miiJson, THREEDS_MII_SCHEMA, lookupTables, 74);
2320
+ var encryptedData = Buffer.from(encodeAesCcm(new Uint8Array(miiBinary)));
2321
+
2322
+ const options = {
2323
+ width: 300,
2324
+ height: 300,
2325
+ data: encryptedData.toString("latin1"),
2326
+ image: "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", // 1x1 gif
2327
+ dotsOptions: {
2328
+ color: "#000000",
2329
+ type: "square"
2330
+ },
2331
+ backgroundOptions: {
2332
+ color: "#ffffff",
2333
+ },
2334
+ imageOptions: {
2335
+ crossOrigin: "anonymous",
2336
+ imageSize: 0.4 // Changes how large center area is
2167
2337
  }
2168
- return dest;
2169
2338
  }
2170
- studioMii[0x16] = mii.info.gender==="Male"?0:1;
2171
- studioMii[0x15] = favCols.indexOf(mii.info.favColor);
2172
- studioMii[0x1E] = mii.info.height;
2173
- studioMii[2] = mii.info.weight;
2174
- studioMii[0x13] = tables.faces[mii.face.shape];
2175
- studioMii[0x11] = skinCols.indexOf(mii.face.col);
2176
- studioMii[0x14] = faceFeatures3DS.indexOf(mii.face.feature);
2177
- studioMii[0x12] = makeups3DS.indexOf(mii.face.makeup);
2178
- studioMii[0x1D] = tables.hairs[mii.hair.style[0]][mii.hair.style[1]];
2179
- studioMii[0x1B] = hairCols.indexOf(mii.hair.col);
2339
+ const qrCodeImage = new QRCodeStyling({
2340
+ jsdom: JSDOM,
2341
+ nodeCanvas,
2342
+ ...options
2343
+ });
2344
+ const qrBuffer = Buffer.from(await qrCodeImage.getRawData("png"))
2345
+
2346
+ 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]);
2347
+ studioMii[0x16] = miiJson.info.gender === "Male" ? 0 : 1;
2348
+ studioMii[0x15] = lookupTables.favCols.indexOf(miiJson.info.favColor);
2349
+ studioMii[0x1E] = miiJson.info.height;
2350
+ studioMii[2] = miiJson.info.weight;
2351
+ studioMii[0x13] = lookupTables.faces.values[miiJson.face.shape];
2352
+ studioMii[0x11] = lookupTables.skinCols.indexOf(miiJson.face.col);
2353
+ studioMii[0x14] = lookupTables.faceFeatures3DS.indexOf(miiJson.face.feature);
2354
+ studioMii[0x12] = lookupTables.makeups3DS.indexOf(miiJson.face.makeup);
2355
+ studioMii[0x1D] = lookupTables.hairs.values[miiJson.hair.style[0]][miiJson.hair.style[1]];
2356
+ studioMii[0x1B] = lookupTables.hairCols.indexOf(miiJson.hair.col);
2180
2357
  if (!studioMii[0x1B]) studioMii[0x1B] = 8;
2181
- studioMii[0x1C] = mii.hair.flipped?1:0;
2182
- studioMii[7] = tables.eyes[mii.eyes.type[0]][mii.eyes.type[1]];
2183
- studioMii[4] = eyeCols.indexOf(mii.eyes.col) + 8;
2184
- studioMii[6] = mii.eyes.size;
2185
- studioMii[3] = mii.eyes.squash;
2186
- studioMii[5] = mii.eyes.rot;
2187
- studioMii[8] = mii.eyes.distApart;
2188
- studioMii[9] = mii.eyes.yPos;
2189
- studioMii[0xE] = tables.eyebrows[mii.eyebrows.style[0]][mii.eyebrows.style[1]];
2190
- studioMii[0xB] = hairCols.indexOf(mii.eyebrows.col);
2358
+ studioMii[0x1C] = miiJson.hair.flipped ? 1 : 0;
2359
+ studioMii[7] = lookupTables.eyes.values[miiJson.eyes.type[0]][miiJson.eyes.type[1]];
2360
+ studioMii[4] = lookupTables.eyeCols.indexOf(miiJson.eyes.col) + 8;
2361
+ studioMii[6] = miiJson.eyes.size;
2362
+ studioMii[3] = miiJson.eyes.squash;
2363
+ studioMii[5] = miiJson.eyes.rot;
2364
+ studioMii[8] = miiJson.eyes.distApart;
2365
+ studioMii[9] = miiJson.eyes.yPos;
2366
+ studioMii[0xE] = lookupTables.eyebrows.values[miiJson.eyebrows.style[0]][miiJson.eyebrows.style[1]];
2367
+ studioMii[0xB] = lookupTables.hairCols.indexOf(miiJson.eyebrows.col);
2191
2368
  if (!studioMii[0xB]) studioMii[0xB] = 8;
2192
- studioMii[0xD] = mii.eyebrows.size;
2193
- studioMii[0xA] = mii.eyebrows.squash;
2194
- studioMii[0xC] = mii.eyebrows.rot;
2195
- studioMii[0xF] = mii.eyebrows.distApart;
2196
- studioMii[0x10] = mii.eyebrows.yPos+3;
2197
- studioMii[0x2C] = tables.noses[mii.nose.type[0]][mii.nose.type[1]];
2198
- studioMii[0x2B] = mii.nose.size;
2199
- studioMii[0x2D] = mii.nose.yPos;
2200
- studioMii[0x26] = tables.mouths[mii.mouth.type[0]][mii.mouth.type[1]];
2201
- studioMii[0x24] = mouthCols3DS.indexOf(mii.mouth.col);
2369
+ studioMii[0xD] = miiJson.eyebrows.size;
2370
+ studioMii[0xA] = miiJson.eyebrows.squash;
2371
+ studioMii[0xC] = miiJson.eyebrows.rot;
2372
+ studioMii[0xF] = miiJson.eyebrows.distApart;
2373
+ studioMii[0x10] = miiJson.eyebrows.yPos + 3;
2374
+ studioMii[0x2C] = lookupTables.noses.values[miiJson.nose.type[0]][miiJson.nose.type[1]];
2375
+ studioMii[0x2B] = miiJson.nose.size;
2376
+ studioMii[0x2D] = miiJson.nose.yPos;
2377
+ studioMii[0x26] = lookupTables.mouths.values[miiJson.mouth.type[0]][miiJson.mouth.type[1]];
2378
+ studioMii[0x24] = lookupTables.mouthCols3DS.indexOf(miiJson.mouth.col);
2202
2379
  if (studioMii[0x24] < 4) {
2203
2380
  studioMii[0x24] += 19;
2204
2381
  } else {
2205
2382
  studioMii[0x24] = 0;
2206
2383
  }
2207
- studioMii[0x25] = mii.mouth.size;
2208
- studioMii[0x23] = mii.mouth.squash;
2209
- studioMii[0x27] = mii.mouth.yPos;
2210
- studioMii[0x29] = mii.facialHair.mustacheType;
2211
- studioMii[1] = mii.facialHair.beardType;
2212
- studioMii[0] = hairCols.indexOf(mii.facialHair.col);
2384
+ studioMii[0x25] = miiJson.mouth.size;
2385
+ studioMii[0x23] = miiJson.mouth.squash;
2386
+ studioMii[0x27] = miiJson.mouth.yPos;
2387
+ studioMii[0x29] = miiJson.facialHair.mustacheType;
2388
+ studioMii[1] = miiJson.facialHair.beardType;
2389
+ studioMii[0] = lookupTables.hairCols.indexOf(miiJson.facialHair.col);
2213
2390
  if (!studioMii[0]) studioMii[0] = 8;
2214
- studioMii[0x28] = mii.facialHair.mustacheSize;
2215
- studioMii[0x2A] = mii.facialHair.mustacheYPos;
2216
- studioMii[0x19] = mii.glasses.type;
2217
- studioMii[0x17] = glassesCols3DS.indexOf(mii.glasses.col);
2391
+ studioMii[0x28] = miiJson.facialHair.mustacheSize;
2392
+ studioMii[0x2A] = miiJson.facialHair.mustacheYPos;
2393
+ studioMii[0x19] = miiJson.glasses.type;
2394
+ studioMii[0x17] = miiJson.glasses.col;
2218
2395
  if (!studioMii[0x17]) {
2219
2396
  studioMii[0x17] = 8;
2220
2397
  } else if (studioMii[0x17] < 6) {
@@ -2222,19 +2399,262 @@ var exports={
2222
2399
  } else {
2223
2400
  studioMii[0x17] = 0;
2224
2401
  }
2225
- studioMii[0x18] = mii.glasses.size;
2226
- studioMii[0x1A] = mii.glasses.yPos;
2227
- studioMii[0x20] = mii.mole.on?1:0;
2228
- studioMii[0x1F] = mii.mole.size;
2229
- studioMii[0x21] = mii.mole.xPos;
2230
- studioMii[0x22] = mii.mole.yPos;
2231
- return encodeStudio(studioMii);
2232
- },
2233
- render3DSMii:async function(jsonIn,fflRes=_fflRes){
2234
- if(!["3ds","wii u"].includes(jsonIn)){
2235
- jsonIn=this.convertMii(jsonIn);
2402
+ studioMii[0x18] = miiJson.glasses.size;
2403
+ studioMii[0x1A] = miiJson.glasses.yPos;
2404
+ studioMii[0x20] = miiJson.mole.on ? 1 : 0;
2405
+ studioMii[0x1F] = miiJson.mole.size;
2406
+ studioMii[0x21] = miiJson.mole.xPos;
2407
+ studioMii[0x22] = miiJson.mole.yPos;
2408
+ let miiPNGBuf = null;
2409
+ let renderedWithStudio = fflRes === null || fflRes === undefined;
2410
+ if (renderedWithStudio) {
2411
+ miiPNGBuf = await renderMiiWithStudio(miiJson);
2412
+ }
2413
+ else {
2414
+ miiPNGBuf = await renderMii(miiJson, fflRes);
2415
+ }
2416
+ const main_img = await Jimp.read(qrBuffer);
2417
+ main_img.resize(424, 424, Jimp.RESIZE_NEAREST_NEIGHBOR); // Don't anti-alias the QR code
2418
+
2419
+ let miiSize, miiZoomFactor, miiYOffset;
2420
+ if (renderedWithStudio) {
2421
+ miiSize = 100;
2422
+ miiZoomFactor = 1;
2423
+ miiYOffset = -15;
2424
+
2425
+ } else {
2426
+ miiSize = 100;
2427
+ miiZoomFactor = 1.25;
2428
+ miiYOffset = -5;
2429
+ }
2430
+ const mii_img = await Jimp.read(miiPNGBuf);
2431
+ mii_img.resize(miiSize * miiZoomFactor, miiSize * miiZoomFactor, Jimp.RESIZE_BICUBIC);
2432
+ mii_img.crop(
2433
+ (miiSize * miiZoomFactor - 100) / 2,
2434
+ (miiSize * miiZoomFactor - 100) / 2,
2435
+ miiSize,
2436
+ miiSize
2437
+ );
2438
+
2439
+ const canvas = new Jimp(mii_img.bitmap.width, mii_img.bitmap.height, 0xFFFFFFFF);
2440
+ canvas.composite(mii_img, 0, miiYOffset);
2441
+ main_img.blit(canvas, 212 - 100 / 2, 212 - 100 / 2);
2442
+ const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK)
2443
+
2444
+ main_img.print(font, 0, 55, {
2445
+ text: miiJson.name,
2446
+ alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
2447
+ alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
2448
+ }, 424, 395);
2449
+
2450
+ if (miiJson.info.type === "Special") {
2451
+ const crown_img = await Jimp.read(path.join(__dirname, 'crown.jpg'));
2452
+ crown_img.resize(40, 20);
2453
+ main_img.blit(crown_img, 225, 160);
2454
+ }
2455
+
2456
+ main_img.write(outPath, (err, img) =>
2457
+ resolve(img)
2458
+ );
2459
+ })
2460
+ }
2461
+ function make3DSChild(dad,mom,options={}){
2462
+ if(!["3ds","wii u"].includes(dad.console?.toLowerCase())){
2463
+ dad=convertMii(dad,"wii");
2464
+ }
2465
+ if(!["3ds","wii u"].includes(mom.console?.toLowerCase())){
2466
+ mom=convertMii(dad,"wii");
2467
+ }
2468
+ var g=options.gender||Math.floor(Math.random()*2)===1?"Male":"Female";
2469
+ var child={
2470
+ "info":{
2471
+ "birthMonth":new Date().getMonth()+1,
2472
+ "birthday":new Date().getDay(),
2473
+ "height":64,
2474
+ "weight":64,
2475
+ "creatorName":"",
2476
+ "gender":g,
2477
+ "name":options.name||kidNames[g][Math.floor(Math.random()*kidNames[g].length)],
2478
+ "favColor":options.favColor||favCols[Math.floor(Math.random()*favCols.length)]
2479
+ },
2480
+ "perms":{
2481
+ "sharing":true,
2482
+ "copying":true
2483
+ },
2484
+ "hair":{
2485
+ "style":[8,3],//Hardcoded
2486
+ "col":Math.floor(Math.random()*2)===1?dad.hair.col:mom.hair.col,
2487
+ "flipped":Math.floor(Math.random()*2)===0?true:false
2488
+ },
2489
+ "face":{
2490
+ "shape":Math.floor(Math.random()*2)===1?dad.face.shape:mom.face.shape,
2491
+ "feature":Math.floor(Math.random()*2)===1?dad.face.feature:mom.face.feature,
2492
+ "makeup":g==="Male"?"None":Math.floor(Math.random()*2)===1?dad.face.makeup:mom.face.makeup
2493
+ },
2494
+ "eyes":Math.floor(Math.random()*2)===1?dad.eyes:mom.eyes,
2495
+ "eyebrows":Math.floor(Math.random()*2)===1?dad.eyebrows:mom.eyebrows,
2496
+ "nose":Math.floor(Math.random()*2)===1?dad.nose:mom.nose,
2497
+ "mouth":Math.floor(Math.random()*2)===1?dad.mouth:mom.mouth,
2498
+ "facialHair":g==="Female"?{
2499
+ "mustacheType": 0,
2500
+ "beardType": 0,
2501
+ "col": "Black",
2502
+ "mustacheSize": 4,
2503
+ "mustacheYPos": 10
2504
+ }:Math.floor(Math.random()*2)===0?dad.facialHair:mom.facialHair,
2505
+ "glasses":Math.floor(Math.random()*2)===1?dad.glasses:mom.glasses,
2506
+ "mole":Math.floor(Math.random()*2)===1?dad.mole:mom.mole,
2507
+ "creatorName":""
2508
+ };
2509
+ child.eyebrows.col=child.hair.col;
2510
+ var c=[skinCols.indexOf(mom.face.col),skinCols.indexOf(dad.face.col)];
2511
+ if(c[0]>c[1]){
2512
+ c[1]=c[0];
2513
+ c[0]=skinCols.indexOf(dad.face.col);
2514
+ }
2515
+ child.face.col=skinCols[c[0]+Math.round((c[1]-c[0])/2)];
2516
+ child.name=child.info.name;
2517
+ child.type="3DS";
2518
+ return child;
2519
+ }
2520
+ function generateInstructions(mii,full){
2521
+ let type=mii.console?.toLowerCase();
2522
+ if(type.toLowerCase()==="wii"){
2523
+ var instrs={
2524
+ "base":`Select "${mii.info.gender}", and then "Start from Scratch".`,
2525
+ "col":`On the info page (first tab), set the Favorite Color to ${mii.info.favColor} (${favCols.indexOf(mii.info.favColor)<=5?favCols.indexOf(mii.info.favColor)+1:favCols.indexOf(mii.info.favColor)-5} from the left, ${favCols.indexOf(mii.info.favColor)>5?"bottom":"top"} row).`,
2526
+ "heightWeight":`On the build page (second tab), set the height to ${Math.round((100/128)*mii.info.height)}%, and the weight to ${Math.round((100/128)*mii.info.weight)}%.`,
2527
+ "faceShape":`On the face page (third tab), set the shape to the one ${Math.floor(mii.face.shape/2)+1} from the top, in the ${mii.face.shape%2===0?"left":"right"} column.`,
2528
+ "skinCol":`On the face page (third tab), set the color to the one ${skinCols.indexOf(mii.face.col)+skinCols.indexOf(mii.face.col)>2?-2:1} from the left, on the ${skinCols.indexOf(mii.face.col)>2?`bottom`:`top`} row.`,
2529
+ "makeup":`On the face page's makeup tab, set the makeup to \"${mii.face.feature}\" (the one ${Math.ceil((wiiFaceFeatures.indexOf(mii.face.feature)+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][wiiFaceFeatures.indexOf(mii.face.feature)]} from the left).`,
2530
+ "hairStyle":`On the hair page (fourth tab), set the hair style to the one ${mii.hair.type[1]} from the left, ${mii.hair.type[2]} from the top, on page ${mii.hair.type[0]}.`,
2531
+ "hairFlipped":`${mii.hair.flipped?`On the hair page (fourth tab), press the button to flip the hair.`:``}`,
2532
+ "hairColor":`On the hair page (fourth tab), set the hair color to the one ${hairCols.indexOf(mii.hair.col)+(hairCols.indexOf(mii.hair.col)>3?-3:1)} from the left, on the ${hairCols.indexOf(mii.hair.col)>3?`bottom`:`top`} row.`,
2533
+ "eyebrowStyle":`On the eyebrow page (fifth tab), set the eyebrow style to the one ${mii.eyebrows.type[1]} from the left, ${mii.eyebrows.type[2]} from the top, on page ${mii.eyebrows.type[0]}.`,
2534
+ "eyebrowColor":`On the eyebrow page (fifth tab), set the eyebrow color to the one ${hairCols.indexOf(mii.eyebrows.col)+(hairCols.indexOf(mii.eyebrows.col)>3?-3:1)} from the left, on the ${hairCols.indexOf(mii.eyebrows.col)>3?`bottom`:`top`} row.`,
2535
+ "eyebrowY":`${mii.eyebrows.yPos!==7?`On the eyebrow page (fifth tab), `:``}${mii.eyebrows.yPos<7?`press the up button ${7-mii.eyebrows.yPos} times.`:mii.eyebrows.yPos>7?`press the down button ${mii.eyebrows.yPos-7} times.`:``}`,
2536
+ "eyebrowSize":`${mii.eyebrows.size!==4?`On the eyebrow page (fifth tab), `:``}${mii.eyebrows.size<4?`press the shrink button ${4-mii.eyebrows.size} times.`:mii.eyebrows.size>4?`press the enlarge button ${mii.eyebrows.size-4} times.`:``}`,
2537
+ "eyebrowRot":`${mii.eyebrows.rotation!==6?`On the eyebrow page (fifth tab), `:``}${mii.eyebrows.rotation<6?`press the rotate clockwise button ${6-mii.eyebrows.rotation} times.`:mii.eyebrows.rotation>6?`press the rotate counter-clockwise button ${mii.eyebrows.rotation-6} times.`:``}`,
2538
+ "eyebrowDist":`${mii.eyebrows.distApart!==2?`On the eyebrow page (fifth tab), `:``}${mii.eyebrows.distApart<2?`press the closer-together button ${2-mii.eyebrows.distApart} times.`:mii.eyebrows.distApart>2?`press the further-apart button ${mii.eyebrows.distApart-2} times.`:``}`,
2539
+ "eyeType":`On the eye page (sixth tab), set the eye type to the one ${mii.eyes.type[1]} from the left, ${mii.eyes.type[2]} from the top, on page ${mii.eyes.type[0]}.`,
2540
+ "eyeColor":`On the eye page (sixth tab), set the color to the one ${eyeCols.indexOf(mii.eyes.col)+(eyeCols.indexOf(mii.eyes.col)>2?-2:1)} from the left, on the ${eyeCols.indexOf(mii.eyes.col)>2?`bottom`:`top`} row.`,
2541
+ "eyeY":`${mii.eyes.yPos!==12?`On the eye page (sixth tab), `:``}${mii.eyes.yPos<12?`press the up button ${12-mii.eyes.yPos} times.`:mii.eyes.yPos>12?`press the down button ${mii.eyes.yPos-12} times.`:``}`,
2542
+ "eyeSize":`${mii.eyes.size!==4?`On the eye page (sixth tab), `:``}${mii.eyes.size<4?`press the shrink button ${4-mii.eyes.size} times.`:mii.eyes.size>4?`press the enlarge button ${mii.eyes.size-4} times.`:``}`,
2543
+ "eyeRot":`${mii.eyes.rotation!==(mii.info.gender==="Female"?3:4)?`On the eye page (sixth tab), `:``}${mii.eyes.rotation<(mii.info.gender==="Female"?3:4)?`press the rotate clockwise button ${(mii.info.gender==="Female"?3:4)-mii.eyes.rotation} times.`:mii.eyes.rotation>(mii.info.gender==="Female"?3:4)?`press the rotate counter-clockwise button ${mii.eyes.rotation-(mii.info.gender==="Female"?3:4)} times.`:``}`,
2544
+ "eyeDist":`${mii.eyes.distApart!==2?`On the eye page (sixth tab), `:``}${mii.eyes.distApart<2?`press the closer-together button ${2-mii.eyes.distApart} times.`:mii.eyes.distApart>2?`press the further-apart button ${mii.eyes.distApart-2} times.`:``}`,
2545
+ "noseType":`On the nose page (seventh tab), set the nose to the one ${Math.ceil((mii.nose.type+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.nose.type]} from the left.`,
2546
+ "noseY":`${mii.nose.yPos!==9?`On the nose page (seventh tab), `:``}${mii.nose.yPos<9?`press the up button ${9-mii.nose.yPos} times.`:mii.nose.yPos>9?`press the down button ${mii.nose.yPos-9} times.`:``}`,
2547
+ "noseSize":`${mii.nose.size!==4?`On the nose page (seventh tab), `:``}${mii.nose.size<4?`press the shrink button ${4-mii.nose.size} times.`:mii.nose.size>4?`press the enlarge button ${mii.nose.size-4} times.`:``}`,
2548
+ "mouthType":`On the mouth page (eighth tab), set the mouth type to the one ${mii.mouth.type[1]} from the left, ${mii.mouth.type[2]} from the top, on page ${mii.mouth.type[0]}.`,
2549
+ "mouthCol":`On the mouth page (eighth tab), set the color to the one ${wiiMouthColors.indexOf(mii.mouth.col)+1} from the left.`,
2550
+ "mouthY":`${mii.mouth.yPos!==13?`On the mouth page (eighth tab), `:``}${mii.mouth.yPos<13?`press the up button ${13-mii.mouth.yPos} times.`:mii.mouth.yPos>13?`press the down button ${mii.mouth.yPos-13} times.`:``}`,
2551
+ "mouthSize":`${mii.mouth.size!==4?`On the mouth page (eighth tab), `:``}${mii.mouth.size<4?`press the shrink button ${4-mii.mouth.size} times.`:mii.mouth.size>4?`press the enlarge button ${mii.mouth.size-4} times.`:``}`,
2552
+ "glasses":`On the glasses page (within the ninth tab), set the glasses to the one ${Math.ceil((mii.glasses.type+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.glasses.type]} from the left.`,
2553
+ "glassesCol":`On the glasses page (within the ninth tab), set the color to the one ${wiiGlassesCols.indexOf(mii.glasses.col)+(wiiGlassesCols.indexOf(mii.glasses.col)>2?-2:1)} from the left, on the ${wiiGlassesCols.indexOf(mii.glasses.col)>2?`bottom`:`top`} row.`,
2554
+ "glassesY":`${mii.glasses.yPos!==10?`On the glasses page (within the ninth tab), `:``}${mii.glasses.yPos<10?`press the up button ${10-mii.glasses.yPos} times.`:mii.glasses.yPos>10?`press the down button ${mii.glasses.yPos-10} times.`:``}`,
2555
+ "glassesSize":`${mii.glasses.size!==4?`On the glasses page (within the ninth tab), `:``}${mii.glasses.size<4?`press the shrink button ${4-mii.glasses.size} times.`:mii.glasses.size>4?`press the enlarge button ${mii.glasses.size-4} times.`:``}`,
2556
+ "stache":`On the mustache page (within the ninth tab), set the mustache to the one on the ${[0,1].includes(mii.facialHair.mustacheType)?`top`:`bottom`}-${[0,2].includes(mii.facialHair.mustacheType)?`left`:`right`}.`,
2557
+ "stacheY":`${mii.facialHair.mustacheYPos!==10?`On the mustache page (within the ninth tab), press the `:``}${mii.facialHair.mustacheYPos>10?`down button ${mii.facialHair.mustacheYPos-10} times.`:mii.facialHair.mustacheYPos<10?`up button ${10-mii.facialHair.mustacheYPos} times.`:``}`,
2558
+ "stacheSize":`${mii.facialHair.mustacheSize!==4?`On the mustache page (within the ninth tab), `:``}${mii.facialHair.mustacheSize<4?`press the shrink button ${4-mii.facialHair.mustacheSize} times.`:mii.facialHair.mustacheSize>4?`press the enlarge button ${mii.facialHair.mustacheSize-4} times.`:``}`,
2559
+ "mole":`${mii.mole.on?`On the mole page (within the ninth tab), turn the mole on.`:``}`,
2560
+ "moleX":`${mii.mole.xPos!==2?`On the mole page (within the ninth tab), press the `:``}${mii.mole.xPos>2?`right button ${mii.mole.xPos-2} times.`:mii.mole.xPos<2?`left button ${2-mii.mole.xPos} times.`:``}`,
2561
+ "moleY":`${mii.mole.yPos!==20?`On the mole page (within the ninth tab), press the `:``}${mii.mole.yPos>20?`down button ${mii.mole.yPos-20} times.`:mii.mole.yPos<20?`up button ${20-mii.mole.yPos} times.`:``}`,
2562
+ "moleSize":`${mii.mole.size!==4?`On the mole page (within the ninth tab), `:``}${mii.mole.size<4?`press the shrink button ${4-mii.mole.size} times.`:mii.mole.size>4?`press the enlarge button ${mii.mole.size-4} times.`:``}`,
2563
+ "beard":`On the beard page (within the ninth tab), set the beard to the one on the ${[0,1].includes(mii.facialHair.beardType)?`top`:`bottom`}-${[0,2].includes(mii.facialHair.beardType)?`left`:`right`}.`,
2564
+ "beardCol":`On the mustache OR beard pages (within the ninth tab), set the color to the one ${hairCols.indexOf(mii.facialHair.col)+(hairCols.indexOf(mii.facialHair.col)>3?-3:1)} from the left, on the ${hairCols.indexOf(mii.facialHair.col)>3?`bottom`:`top`} row.`,
2565
+ "other":`The Nickname of this Mii is ${mii.info.name}.${mii.info.creatorName?` The creator was ${mii.info.creatorName}.`:``} Mingle was turned ${mii.info.mingle?`on`:`off`}.${mii.info.birthday!==0?` Its birthday is ${["","January","February","March","April","May","June","July","August","September","October","November","December"][mii.info.birthMonth]} ${mii.info.birthday}.`:``}`
2566
+ };
2567
+ if(!full){
2568
+ var defaultMiiInstrs=structuredClone(mii.info.gender==="Male"?defaultInstrs.wii.male:defaultInstrs.wii.female);
2569
+ Object.keys(instrs).forEach(instr=>{
2570
+ if(instrs[instr]===defaultMiiInstrs[instr]){
2571
+ delete instrs[instr];
2572
+ }
2573
+ });
2236
2574
  }
2237
- return await renderMii(this.convert3DSMiiToStudio(jsonIn),fflRes);
2575
+ return instrs;
2238
2576
  }
2577
+ else{
2578
+ var instrs={
2579
+ "base":`Select "Start from Scratch", and then "${mii.info.gender}".`,
2580
+ "faceShape":`On the face page (first tab), set the face shape to the one ${Math.ceil((mii.face.shape+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.face.shape]} from the left.`,
2581
+ "skinCol":`On the face page (first tab), set the color to the one ${skinCols.indexOf(mii.face.col)+1} from the top.`,
2582
+ "makeup":`On the face page's makeup tab, set the makeup to \"${mii.face.makeup}\" (the one ${Math.ceil((makeups3DS.indexOf(mii.face.makeup)+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][makeups3DS.indexOf(mii.face.makeup)]} from the left).`,
2583
+ "feature":`On the face page's wrinkles tab, set the facial feature to \"${mii.face.feature}\" (the one ${Math.ceil((faceFeatures3DS.indexOf(mii.face.feature)+1)/3)+1} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][makeups3DS.indexOf(mii.face.makeup)]} from the left).`,
2584
+ "hairStyle":`On the hair page (second tab), set the hair style to the one ${Math.ceil((mii.hair.style[1]+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.hair.style[1]]} from the left, on page ${mii.hair.style[0]+1}.`,
2585
+ "hairFlipped":`${mii.hair.flipped?`On the hair page (second tab), press the button to flip the hair.`:``}`,
2586
+ "hairColor":`On the hair page (second tab), set the hair color to the one ${hairCols.indexOf(mii.hair.col)+1} from the top.`,
2587
+ "eyebrowStyle":`On the eyebrow page (third tab), set the eyebrow style to the one ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.eyebrows.style[1]]} from the left, ${Math.ceil((mii.eyebrows.style[1]+1)/3)} from the top, on page ${mii.eyebrows.style[0]+1}.`,
2588
+ "eyebrowColor":`On the eyebrow page (third tab), set the eyebrow color to the one ${hairCols.indexOf(mii.eyebrows.col)+1} from the top.`,
2589
+ "eyebrowY":`${mii.eyebrows.yPos!==7?`On the eyebrow page (third tab), `:``}${mii.eyebrows.yPos<7?`press the up button ${7-mii.eyebrows.yPos} times.`:mii.eyebrows.yPos>7?`press the down button ${mii.eyebrows.yPos-7} times.`:``}`,
2590
+ "eyebrowSize":`${mii.eyebrows.size!==4?`On the eyebrow page (third tab), `:``}${mii.eyebrows.size<4?`press the shrink button ${4-mii.eyebrows.size} times.`:mii.eyebrows.size>4?`press the enlarge button ${mii.eyebrows.size-4} times.`:``}`,
2591
+ "eyebrowRot":`${mii.eyebrows.rot!==6?`On the eyebrow page (third tab), `:``}${mii.eyebrows.rot<6?`press the rotate clockwise button ${6-mii.eyebrows.rot} times.`:mii.eyebrows.rot>6?`press the rotate counter-clockwise button ${mii.eyebrows.rot-6} times.`:``}`,
2592
+ "eyebrowDist":`${mii.eyebrows.distApart!==2?`On the eyebrow page (third tab), `:``}${mii.eyebrows.distApart<2?`press the closer-together button ${2-mii.eyebrows.distApart} times.`:mii.eyebrows.distApart>2?`press the further-apart button ${mii.eyebrows.distApart-2} times.`:``}`,
2593
+ "eyebrowSquash":`${mii.eyebrows.squash!==3?`On the eyebrow page (third tab), `:``}${mii.eyebrows.squash<3?`press the squish button ${3-mii.eyebrows.squash} times.`:mii.eyebrows.squash>3?`press the un-squish button ${mii.eyebrows.squash-3} times.`:``}`,
2594
+ "eyeType":`On the eye page (fourth tab), set the eye type to the one ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.eyes.type[1]]} from the left, ${Math.ceil((mii.eyes.type[1]+1)/3)} from the top, on page ${mii.eyes.type[0]+1}.`,
2595
+ "eyeColor":`On the eye page (fourth tab), set the color to the one ${eyeCols.indexOf(mii.eyes.col)+1} from the top.`,
2596
+ "eyeY":`${mii.eyes.yPos!==12?`On the eye page (fourth tab), `:``}${mii.eyes.yPos<12?`press the up button ${12-mii.eyes.yPos} times.`:mii.eyes.yPos>12?`press the down button ${mii.eyes.yPos-12} times.`:``}`,
2597
+ "eyeSize":`${mii.eyes.size!==4?`On the eye page (fourth tab), `:``}${mii.eyes.size<4?`press the shrink button ${4-mii.eyes.size} times.`:mii.eyes.size>4?`press the enlarge button ${mii.eyes.size-4} times.`:``}`,
2598
+ "eyeRot":`${mii.eyes.rot!==(mii.info.gender==="Female"?3:4)?`On the eye page (fourth tab), `:``}${mii.eyes.rot<(mii.info.gender==="Female"?3:4)?`press the rotate clockwise button ${(mii.info.gender==="Female"?3:4)-mii.eyes.rot} times.`:mii.eyes.rot>(mii.info.gender==="Female"?3:4)?`press the rotate counter-clockwise button ${mii.eyes.rot-(mii.info.gender==="Female"?3:4)} times.`:``}`,
2599
+ "eyeDist":`${mii.eyes.distApart!==2?`On the eye page (fourth tab), `:``}${mii.eyes.distApart<2?`press the closer-together button ${2-mii.eyes.distApart} times.`:mii.eyes.distApart>2?`press the further-apart button ${mii.eyes.distApart-2} times.`:``}`,
2600
+ "eyeSquash":`${mii.eyes.squash!==3?`On the eye page (fourth tab), `:``}${mii.eyes.squash<3?`press the squish button ${3-mii.eyes.squash} times.`:mii.eyes.squash>3?`press the un-squish button ${mii.eyes.squash-3} times.`:``}`,
2601
+ "noseType":`On the nose page (fifth tab), set the nose to the one ${Math.ceil((mii.nose.type[1]+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.nose.type[1]]} from the left, on page ${mii.nose.type[0]}.`,
2602
+ "noseY":`${mii.nose.yPos!==9?`On the nose page (fifth tab), `:``}${mii.nose.yPos<9?`press the up button ${9-mii.nose.yPos} times.`:mii.nose.yPos>9?`press the down button ${mii.nose.yPos-9} times.`:``}`,
2603
+ "noseSize":`${mii.nose.size!==4?`On the nose page (fifth tab), `:``}${mii.nose.size<4?`press the shrink button ${4-mii.nose.size} times.`:mii.nose.size>4?`press the enlarge button ${mii.nose.size-4} times.`:``}`,
2604
+ "mouthType":`On the mouth page (sixth tab), set the mouth type to the one ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.mouth.type[1]]} from the left, ${Math.ceil((mii.mouth.type[1]+1)/3)} from the top, on page ${mii.mouth.type[0]+1}.`,
2605
+ "mouthCol":`On the mouth page (sixth tab), set the color to the one ${mouthCols3DS.indexOf(mii.mouth.col)+1} from the top.`,
2606
+ "mouthY":`${mii.mouth.yPos!==13?`On the mouth page (sixth tab), `:``}${mii.mouth.yPos<13?`press the up button ${13-mii.mouth.yPos} times.`:mii.mouth.yPos>13?`press the down button ${mii.mouth.yPos-13} times.`:``}`,
2607
+ "mouthSize":`${mii.mouth.size!==4?`On the mouth page (sixth tab), `:``}${mii.mouth.size<4?`press the shrink button ${4-mii.mouth.size} times.`:mii.mouth.size>4?`press the enlarge button ${mii.mouth.size-4} times.`:``}`,
2608
+ "mouthSquash":`${mii.mouth.squash!==3?`On the mouth page (sixth tab), `:``}${mii.mouth.squash<3?`press the squish button ${3-mii.mouth.squash} times.`:mii.mouth.squash>3?`press the un-squish button ${mii.mouth.squash-3} times.`:``}`,
2609
+ "glasses":`On the glasses page (within the seventh tab), set the glasses to the one ${Math.ceil((mii.glasses.type+1)/3)} from the top, and ${[1,2,3,1,2,3,1,2,3,1,2,3][mii.glasses.type]} from the left.`,
2610
+ "glassesCol":`On the glasses page (within the seventh tab), set the color to the one ${glassesCols3DS.indexOf(mii.glasses.col)+1} from the top.`,
2611
+ "glassesY":`${mii.glasses.yPos!==10?`On the glasses page (within the seventh tab), `:``}${mii.glasses.yPos<10?`press the up button ${10-mii.glasses.yPos} times.`:mii.glasses.yPos>10?`press the down button ${mii.glasses.yPos-10} times.`:``}`,
2612
+ "glassesSize":`${mii.glasses.size!==4?`On the glasses page (within the seventh tab), `:``}${mii.glasses.size<4?`press the shrink button ${4-mii.glasses.size} times.`:mii.glasses.size>4?`press the enlarge button ${mii.glasses.size-4} times.`:``}`,
2613
+ "stache":`On the mustache page (within the seventh tab), set the mustache to the one on the ${[0,1].includes(mii.facialHair.mustacheType)?`top`:[2,3].includes(mii.facialHair.mustacheType)?`middle`:`bottom`}-${[0,2,4].includes(mii.facialHair.mustacheType)?`left`:`right`}.`,
2614
+ "stacheY":`${mii.facialHair.mustacheYPos!==10?`On the mustache page (within the seventh tab), press the `:``}${mii.facialHair.mustacheYPos>10?`down button ${mii.facialHair.mustacheYPos-10} times.`:mii.facialHair.mustacheYPos<10?`up button ${10-mii.facialHair.mustacheYPos} times.`:``}`,
2615
+ "stacheSize":`${mii.facialHair.mustacheSize!==4?`On the mustache page (within the seventh tab), `:``}${mii.facialHair.mustacheSize<4?`press the shrink button ${4-mii.facialHair.mustacheSize} times.`:mii.facialHair.mustacheSize>4?`press the enlarge button ${mii.facialHair.mustacheSize-4} times.`:``}`,
2616
+ "mole":`${mii.mole.on?`On the mole page (within the seventh tab), turn the mole on.`:``}`,
2617
+ "moleX":`${mii.mole.xPos!==2?`On the mole page (within the seventh tab), press the `:``}${mii.mole.xPos>2?`right button ${mii.mole.xPos-2} times.`:mii.mole.xPos<2?`left button ${2-mii.mole.xPos} times.`:``}`,
2618
+ "moleY":`${mii.mole.yPos!==20?`On the mole page (within the seventh tab), press the `:``}${mii.mole.yPos>20?`down button ${mii.mole.yPos-20} times.`:mii.mole.yPos<20?`up button ${20-mii.mole.yPos} times.`:``}`,
2619
+ "moleSize":`${mii.mole.size!==4?`On the mole page (within the seventh tab), `:``}${mii.mole.size<4?`press the shrink button ${4-mii.mole.size} times.`:mii.mole.size>4?`press the enlarge button ${mii.mole.size-4} times.`:``}`,
2620
+ "beard":`On the beard page (within the seventh tab), set the beard to the one on the ${[0,1].includes(mii.facialHair.beardType)?`top`:[2,3].includes(mii.facialHair.beardType)?`middle`:`bottom`}-${[0,2].includes(mii.facialHair.beardType)?`left`:`right`}.`,
2621
+ "beardCol":`On the mustache OR beard pages (within the seventh tab), set the color to the one ${hairCols.indexOf(mii.facialHair.col)+1} from the top.`,
2622
+ "heightWeight":`On the build page (eighth tab), set the height to ${Math.round((100/128)*mii.info.height)}%, and the weight to ${Math.round((100/128)*mii.info.weight)}%.`,
2623
+ "col":`On the info page (after pressing "Next"), set the Favorite Color to ${mii.info.favColor} (${favCols.indexOf(mii.info.favColor)<=5?favCols.indexOf(mii.info.favColor)+1:favCols.indexOf(mii.info.favColor)-5} from the left, ${favCols.indexOf(mii.info.favColor)>5?"bottom":"top"} row).`,
2624
+ "other":`The Nickname of this Mii is ${mii.info.name}.${mii.info.creatorName?` The creator was ${mii.info.creatorName}.`:``} ${mii.info.birthday!==0?` Its birthday is ${["","January","February","March","April","May","June","July","August","September","October","November","December"][mii.info.birthMonth]} ${mii.info.birthday}.`:``}`
2625
+ };
2626
+ if(!full){
2627
+ var defaultMiiInstrs=structuredClone(mii.info.gender==="Male"?defaultInstrs["3ds"].male:defaultInstrs["3ds"].female);
2628
+ Object.keys(instrs).forEach(instr=>{
2629
+ if(instrs[instr]===defaultMiiInstrs[instr]){
2630
+ delete instrs[instr];
2631
+ }
2632
+ });
2633
+ }
2634
+ return instrs;
2635
+ }
2636
+ }
2637
+
2638
+
2639
+
2640
+ module.exports = {
2641
+ // Data
2642
+ Enums: require("./Enums"),
2643
+
2644
+ //Functions
2645
+ convertMii,
2646
+ convertMiiToStudio,
2647
+
2648
+ readWiiBin,
2649
+ read3DSQR,
2650
+
2651
+ renderMiiWithStudio,
2652
+ renderMii,
2653
+
2654
+ writeWiiBin,
2655
+ write3DSQR,
2656
+
2657
+ //make3DSChild, //WIP
2658
+
2659
+ generateInstructions
2239
2660
  }
2240
- module.exports=exports;