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