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