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