miijs 2.0.1 → 2.1.1

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