miijs 1.8.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FFLShaderMaterial.js +859 -0
- package/README.md +13 -9
- package/asmCrypto.js +3822 -10183
- package/crown.jpg +0 -0
- package/ffl-emscripten-single-file.js +21 -0
- package/ffl.js +5051 -0
- package/index.js +562 -394
- package/package.json +9 -6
- package/struct-fu.js +1140 -0
package/index.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const
|
|
2
|
+
const nodeCanvas = require('canvas');
|
|
3
|
+
const { createCanvas, loadImage, ImageData } = nodeCanvas;
|
|
3
4
|
const jsQR = require('jsqr');
|
|
4
|
-
|
|
5
|
-
const
|
|
5
|
+
const Jimp = require('jimp');
|
|
6
|
+
const THREE = require('three');
|
|
7
|
+
const QRCodeStyling = require("qr-code-styling");
|
|
8
|
+
const { JSDOM } = require("jsdom");
|
|
6
9
|
const httpsLib = require('https');
|
|
7
10
|
const asmCrypto=require("./asmCrypto.js");
|
|
11
|
+
const path=require("path");
|
|
12
|
+
const createGL = require('gl');
|
|
13
|
+
const {FFLCharModelDescDefault,createCharModel,initCharModelTextures,initializeFFL,parseHexOrB64ToUint8Array}=require("./ffl.js");
|
|
14
|
+
const ModuleFFL=require("./ffl-emscripten-single-file.js");
|
|
15
|
+
const FFLShaderMaterial=require("./FFLShaderMaterial.js");
|
|
8
16
|
function getKeyByValue(object, value) {
|
|
9
17
|
for (var key in object) {
|
|
10
18
|
if (object[key] === value) {
|
|
@@ -13,9 +21,14 @@ function getKeyByValue(object, value) {
|
|
|
13
21
|
}
|
|
14
22
|
}
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
//If FFLResHigh.dat is in the same directory as Node.js is calling the library from, use it by default
|
|
25
|
+
var _fflRes=null;
|
|
26
|
+
if(fs.existsSync("./FFLResHigh.dat")){
|
|
27
|
+
_fflRes=new Uint8Array(fs.readFileSync("./FFLResHigh.dat",""));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getBinaryFromAddress(addr, bin){
|
|
31
|
+
let byte = bin.readUInt8(addr);
|
|
19
32
|
let binaryString = '';
|
|
20
33
|
for (let i = 7; i >= 0; i--) {
|
|
21
34
|
binaryString += ((byte >> i) & 1) ? '1' : '0';
|
|
@@ -66,18 +79,17 @@ function encodeAesCcm(data){
|
|
|
66
79
|
var ciphertext = asmCrypto.AES_CCM.encrypt(plaintext,aes_key,nonce,undefined,TAG_LENGTH);
|
|
67
80
|
return Uint8Cat(cfsd.subarray(NONCE_OFFSET,NONCE_OFFSET + NONCE_LENGTH),ciphertext.subarray(0,ciphertext.length - 24),ciphertext.subarray(ciphertext.length - TAG_LENGTH,ciphertext.length))
|
|
68
81
|
}
|
|
69
|
-
function downloadImage(url
|
|
82
|
+
async function downloadImage(url) {
|
|
70
83
|
return new Promise((resolve, reject) => {
|
|
71
84
|
httpsLib.get(url, (res) => {
|
|
72
85
|
if (res.statusCode === 200) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
const data = [];
|
|
87
|
+
res.on('data', chunk => data.push(chunk));
|
|
88
|
+
res.on('end', () => resolve(Buffer.concat(data)));
|
|
89
|
+
res.on('error', reject);
|
|
76
90
|
} else {
|
|
77
|
-
// Consume response data to free up memory
|
|
78
91
|
res.resume();
|
|
79
92
|
reject(new Error(`Request Failed With a Status Code: ${res.statusCode}`));
|
|
80
|
-
|
|
81
93
|
}
|
|
82
94
|
});
|
|
83
95
|
});
|
|
@@ -1060,7 +1072,99 @@ var defaultInstrs={
|
|
|
1060
1072
|
}
|
|
1061
1073
|
}
|
|
1062
1074
|
};
|
|
1063
|
-
|
|
1075
|
+
|
|
1076
|
+
async function renderMii(studioMii,fflRes=_fflRes) {
|
|
1077
|
+
var width=600,height=600;
|
|
1078
|
+
/* ---------- WebGL 1 context ---------- */
|
|
1079
|
+
const gl = createGL(width, height, { preserveDrawingBuffer: true });
|
|
1080
|
+
|
|
1081
|
+
/* ---------- dummy canvas to keep Three.js happy ---------- */
|
|
1082
|
+
const dummyCanvas = {
|
|
1083
|
+
width,
|
|
1084
|
+
height,
|
|
1085
|
+
getContext: () => gl,
|
|
1086
|
+
addEventListener () {},
|
|
1087
|
+
removeEventListener () {},
|
|
1088
|
+
style: {},
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
/* ---------- Three.js renderer ---------- */
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
const renderer = new THREE.WebGLRenderer({
|
|
1095
|
+
canvas: dummyCanvas,
|
|
1096
|
+
context: gl,
|
|
1097
|
+
});
|
|
1098
|
+
renderer.setSize(width, height);
|
|
1099
|
+
renderer.setClearColor(0xffffff);
|
|
1100
|
+
|
|
1101
|
+
let moduleFFL, currentCharModel;
|
|
1102
|
+
/* ---------- simple scene ---------- */
|
|
1103
|
+
const scene = new THREE.Scene();
|
|
1104
|
+
scene.background = new THREE.Color().setHex(0xffffff, THREE.ColorManagement ? THREE.ColorManagement.workingColorSpace : '');
|
|
1105
|
+
let camera = new THREE.PerspectiveCamera(15, width / height, 1, 5000);
|
|
1106
|
+
camera.position.set(0, 30, 500);
|
|
1107
|
+
function updateCharModelInScene(data, modelDesc) {
|
|
1108
|
+
// Decode data.
|
|
1109
|
+
if (typeof data === 'string') {
|
|
1110
|
+
data = parseHexOrB64ToUint8Array(data);
|
|
1111
|
+
}
|
|
1112
|
+
// Continue assuming it is Uint8Array.
|
|
1113
|
+
// If an existing CharModel exists, update it.
|
|
1114
|
+
if (currentCharModel) {
|
|
1115
|
+
// Remove current CharModel from the scene, then dispose it.
|
|
1116
|
+
currentCharModel.meshes && scene.remove(currentCharModel.meshes);
|
|
1117
|
+
currentCharModel.dispose();
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Create a new CharModel.
|
|
1121
|
+
currentCharModel = createCharModel(data, modelDesc, FFLShaderMaterial, moduleFFL);
|
|
1122
|
+
// Initialize textures for the new CharModel.
|
|
1123
|
+
initCharModelTextures(currentCharModel, renderer);
|
|
1124
|
+
|
|
1125
|
+
// Add CharModel meshes to scene.
|
|
1126
|
+
scene.add(currentCharModel.meshes);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const box = new THREE.Mesh(
|
|
1130
|
+
new THREE.BoxGeometry(),
|
|
1131
|
+
new THREE.MeshBasicMaterial({ color: 0x00ffff })
|
|
1132
|
+
);
|
|
1133
|
+
scene.add(box);
|
|
1134
|
+
const initResult = await initializeFFL(fflRes, ModuleFFL);
|
|
1135
|
+
moduleFFL = initResult.module;
|
|
1136
|
+
|
|
1137
|
+
updateCharModelInScene(studioMii, FFLCharModelDescDefault); // Use default expression.
|
|
1138
|
+
|
|
1139
|
+
renderer.render(scene, camera);
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
/* ---------- read pixels ---------- */
|
|
1144
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
1145
|
+
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
1146
|
+
|
|
1147
|
+
/* ---------- flip rows (Uint8Array → Buffer) ---------- */
|
|
1148
|
+
const src = Buffer.from(pixels); // <-- Convert here
|
|
1149
|
+
const flipped = Buffer.alloc(src.length);
|
|
1150
|
+
|
|
1151
|
+
const rowBytes = width * 4;
|
|
1152
|
+
for (let y = 0; y < height; y++) {
|
|
1153
|
+
const srcStart = y * rowBytes;
|
|
1154
|
+
const dstStart = (height - y - 1) * rowBytes;
|
|
1155
|
+
src.copy(flipped, dstStart, srcStart, srcStart + rowBytes);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/* ---------- draw into Node-canvas ---------- */
|
|
1159
|
+
const canvas = createCanvas(width, height);
|
|
1160
|
+
const ctx = canvas.getContext('2d');
|
|
1161
|
+
const img = new ImageData(new Uint8ClampedArray(flipped.buffer), width, height);
|
|
1162
|
+
ctx.putImageData(img, 0, 0);
|
|
1163
|
+
|
|
1164
|
+
return canvas.toBuffer('image/png');
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
var exports={
|
|
1064
1168
|
readWiiBin:function(binOrPath){
|
|
1065
1169
|
var thisMii={
|
|
1066
1170
|
info:{},
|
|
@@ -1074,103 +1178,109 @@ module.exports={
|
|
|
1074
1178
|
glasses:{},
|
|
1075
1179
|
facialHair:{}
|
|
1076
1180
|
};
|
|
1181
|
+
|
|
1182
|
+
let data;
|
|
1077
1183
|
if(/[^01]/ig.test(binOrPath)){
|
|
1078
|
-
|
|
1184
|
+
data = fs.readFileSync(binOrPath);
|
|
1079
1185
|
}
|
|
1080
1186
|
else{
|
|
1081
|
-
|
|
1187
|
+
data=Buffer.from(binOrPath);
|
|
1082
1188
|
}
|
|
1189
|
+
|
|
1190
|
+
const get = address => getBinaryFromAddress(address, data);
|
|
1191
|
+
|
|
1083
1192
|
var name="";
|
|
1084
1193
|
for(var i=0;i<10;i++){
|
|
1085
|
-
name+=
|
|
1194
|
+
name+=data.slice(3+i*2, 4+i*2)+"";
|
|
1086
1195
|
}
|
|
1087
1196
|
thisMii.name=name.replaceAll("\x00","");
|
|
1088
1197
|
var cname="";
|
|
1089
1198
|
for(var i=0;i<10;i++){
|
|
1090
|
-
cname+=
|
|
1199
|
+
cname+=data.slice(55+i*2, 56+i*2)+"";
|
|
1091
1200
|
}
|
|
1092
1201
|
thisMii.creatorName=cname.replaceAll("\x00","");
|
|
1093
1202
|
thisMii.info.creatorName=thisMii.creatorName;
|
|
1094
1203
|
thisMii.info.name=thisMii.name;//Up to ten characters
|
|
1095
|
-
thisMii.info.gender=
|
|
1096
|
-
thisMii.info.miiId=parseInt(
|
|
1097
|
-
thisMii.info.systemId=parseInt(
|
|
1098
|
-
var temp=
|
|
1204
|
+
thisMii.info.gender=get(0x00)[1]==="1"?"Female":"Male";//0 for Male, 1 for Female
|
|
1205
|
+
thisMii.info.miiId=parseInt(get(0x18),2).toString(16)+parseInt(get(0x19),2).toString(16)+parseInt(get(0x1A),2).toString(16)+parseInt(get(0x1B),2).toString(16);
|
|
1206
|
+
thisMii.info.systemId=parseInt(get(0x1C),2).toString(16)+parseInt(get(0x1D),2).toString(16)+parseInt(get(0x1E),2).toString(16)+parseInt(get(0x1F),2).toString(16);
|
|
1207
|
+
var temp=get(0x20);
|
|
1099
1208
|
thisMii.face.shape=parseInt(temp.slice(0,3),2);//0-7
|
|
1100
1209
|
thisMii.face.col=skinCols[parseInt(temp.slice(3,6),2)];//0-5
|
|
1101
|
-
temp=
|
|
1102
|
-
thisMii.face.feature=wiiFaceFeatures[parseInt(
|
|
1210
|
+
temp=get(0x21);
|
|
1211
|
+
thisMii.face.feature=wiiFaceFeatures[parseInt(get(0x20).slice(6,8)+temp.slice(0,2),2)];//0-11
|
|
1103
1212
|
thisMii.info.mingle=temp[5]==="0";//0 for Mingle, 1 for Don't Mingle
|
|
1104
|
-
temp=
|
|
1213
|
+
temp=get(0x2C);
|
|
1105
1214
|
for(var i=0;i<12;i++){
|
|
1106
1215
|
if(wiiNoses[i]===parseInt(temp.slice(0,4),2)){
|
|
1107
1216
|
thisMii.nose.type=i;
|
|
1108
1217
|
}
|
|
1109
1218
|
}
|
|
1110
1219
|
thisMii.nose.size=parseInt(temp.slice(4,8),2);
|
|
1111
|
-
thisMii.nose.yPos=parseInt(
|
|
1112
|
-
temp=
|
|
1220
|
+
thisMii.nose.yPos=parseInt(get(0x2D).slice(0,5),2);//From top to bottom, 0-18, default 9
|
|
1221
|
+
temp=get(0x2E);
|
|
1113
1222
|
thisMii.mouth.type=mouthTable[""+parseInt(temp.slice(0,5),2)];//0-23, Needs lookup table
|
|
1114
1223
|
thisMii.mouth.col=wiiMouthColors[parseInt(temp.slice(5,7),2)];//0-2, refer to mouthColors array
|
|
1115
|
-
temp2=
|
|
1224
|
+
temp2=get(0x2F);
|
|
1116
1225
|
thisMii.mouth.size=parseInt(temp[7]+temp2.slice(0,3),2);//0-8, default 4
|
|
1117
1226
|
thisMii.mouth.yPos=parseInt(temp2.slice(3,8),2);//0-18, default 9, from top to bottom
|
|
1118
|
-
temp=
|
|
1119
|
-
var temp2=
|
|
1227
|
+
temp=get(0x00);
|
|
1228
|
+
var temp2=get(0x01);
|
|
1120
1229
|
thisMii.info.birthMonth=parseInt(temp.slice(2,6),2);
|
|
1121
1230
|
thisMii.info.birthday=parseInt(temp.slice(6,8)+temp2.slice(0,3),2);
|
|
1122
1231
|
thisMii.info.favColor=favCols[parseInt(temp2.slice(3,7),2)];//0-11, refer to cols array
|
|
1123
1232
|
thisMii.info.favorited=temp2[7]==="0"?false:true;
|
|
1124
|
-
thisMii.info.height=parseInt(
|
|
1125
|
-
thisMii.info.weight=parseInt(
|
|
1126
|
-
thisMii.info.downloadedFromCheckMiiOut=
|
|
1127
|
-
temp=
|
|
1128
|
-
temp2=
|
|
1233
|
+
thisMii.info.height=parseInt(get(0x16),2);//0-127
|
|
1234
|
+
thisMii.info.weight=parseInt(get(0x17),2);//0-127
|
|
1235
|
+
thisMii.info.downloadedFromCheckMiiOut=get(0x21)[7]==="0"?false:true;
|
|
1236
|
+
temp=get(0x34);
|
|
1237
|
+
temp2=get(0x35);
|
|
1129
1238
|
thisMii.mole.on=temp[0]==="0"?false:true;//0 for Off, 1 for On
|
|
1130
1239
|
thisMii.mole.size=parseInt(temp.slice(1,5),2);//0-8, default 4
|
|
1131
1240
|
thisMii.mole.xPos=parseInt(temp2.slice(2,7),2);//0-16, Default 2
|
|
1132
1241
|
thisMii.mole.yPos=parseInt(temp.slice(5,8)+temp2.slice(0,2),2);//Top to bottom
|
|
1133
|
-
temp=
|
|
1134
|
-
temp2=
|
|
1242
|
+
temp=get(0x22);
|
|
1243
|
+
temp2=get(0x23);
|
|
1135
1244
|
thisMii.hair.type=hairTable[""+parseInt(temp.slice(0,7),2)];//0-71, Needs lookup table
|
|
1136
1245
|
thisMii.hair.col=hairCols[parseInt(temp[7]+temp2.slice(0,2),2)];//0-7, refer to hairCols array
|
|
1137
1246
|
thisMii.hair.flipped=temp2[2]==="0"?false:true;
|
|
1138
|
-
temp=
|
|
1139
|
-
temp2=
|
|
1247
|
+
temp=get(0x24);
|
|
1248
|
+
temp2=get(0x25);
|
|
1140
1249
|
thisMii.eyebrows.type=eyebrowTable[""+parseInt(temp.slice(0,5),2)];//0-23, Needs lookup table
|
|
1141
1250
|
thisMii.eyebrows.rotation=parseInt(temp.slice(6,8)+temp2.slice(0,2),2);//0-11, default varies based on eyebrow type
|
|
1142
|
-
temp=
|
|
1143
|
-
temp2=
|
|
1251
|
+
temp=get(0x26);
|
|
1252
|
+
temp2=get(0x27);
|
|
1144
1253
|
thisMii.eyebrows.col=hairCols[parseInt(temp.slice(0,3),2)];
|
|
1145
1254
|
thisMii.eyebrows.size=parseInt(temp.slice(3,7),2);//0-8, default 4
|
|
1146
1255
|
thisMii.eyebrows.yPos=(parseInt(temp[7]+temp2.slice(0,4),2))-3;//0-15, default 10
|
|
1147
1256
|
thisMii.eyebrows.distApart=parseInt(temp2.slice(4,8),2);//0-12, default 2
|
|
1148
|
-
thisMii.eyes.type=eyeTable[parseInt(
|
|
1149
|
-
temp=
|
|
1257
|
+
thisMii.eyes.type=eyeTable[parseInt(get(0x28).slice(0,6),2)];//0-47, needs lookup table
|
|
1258
|
+
temp=get(0x29);
|
|
1150
1259
|
thisMii.eyes.rotation=parseInt(temp.slice(0,3),2);//0-7, default varies based on eye type
|
|
1151
1260
|
thisMii.eyes.yPos=parseInt(temp.slice(3,8),2);//0-18, default 12, top to bottom
|
|
1152
|
-
temp=
|
|
1261
|
+
temp=get(0x2A);
|
|
1153
1262
|
thisMii.eyes.col=eyeCols[parseInt(temp.slice(0,3),2)];//0-5
|
|
1154
1263
|
thisMii.eyes.size=parseInt(temp.slice(4,7),2);//0-7, default 4
|
|
1155
|
-
temp2=
|
|
1264
|
+
temp2=get(0x2B);
|
|
1156
1265
|
thisMii.eyes.distApart=parseInt(temp[7]+temp2.slice(0,3),2);//0-12, default 2
|
|
1157
|
-
temp=
|
|
1266
|
+
temp=get(0x30);
|
|
1158
1267
|
thisMii.glasses.type=parseInt(temp.slice(0,4),2);//0-8
|
|
1159
1268
|
thisMii.glasses.col=wiiGlassesCols[parseInt(temp.slice(4,7),2)];//0-5
|
|
1160
|
-
temp=
|
|
1269
|
+
temp=get(0x31);
|
|
1161
1270
|
thisMii.glasses.size=parseInt(temp.slice(0,3),2);//0-7, default 4
|
|
1162
1271
|
thisMii.glasses.yPos=parseInt(temp.slice(3,8),2);//0-20, default 10
|
|
1163
|
-
temp=
|
|
1164
|
-
temp2=
|
|
1272
|
+
temp=get(0x32);
|
|
1273
|
+
temp2=get(0x33);
|
|
1165
1274
|
thisMii.facialHair.mustacheType=parseInt(temp.slice(0,2),2);//0-3
|
|
1166
1275
|
thisMii.facialHair.beardType=parseInt(temp.slice(2,4),2);//0-3
|
|
1167
1276
|
thisMii.facialHair.col=hairCols[parseInt(temp.slice(4,7),2)];//0-7
|
|
1168
1277
|
thisMii.facialHair.mustacheSize=parseInt(temp[7]+temp2.slice(0,3),2);//0-30, default 20
|
|
1169
1278
|
thisMii.facialHair.mustacheYPos=parseInt(temp2.slice(3,8),2);//0-16, default 2
|
|
1279
|
+
thisMii.console="wii";
|
|
1170
1280
|
return thisMii;
|
|
1171
1281
|
},
|
|
1172
1282
|
read3DSQR:async function(binOrPath){
|
|
1173
|
-
function readMii(){
|
|
1283
|
+
function readMii(data){
|
|
1174
1284
|
var miiJson={
|
|
1175
1285
|
info:{},
|
|
1176
1286
|
perms:{},
|
|
@@ -1184,100 +1294,100 @@ module.exports={
|
|
|
1184
1294
|
glasses:{},
|
|
1185
1295
|
mole:{}
|
|
1186
1296
|
};
|
|
1187
|
-
|
|
1188
|
-
var temp=
|
|
1189
|
-
var temp2=
|
|
1297
|
+
const get = address => getBinaryFromAddress(address, data);
|
|
1298
|
+
var temp=get(0x18);
|
|
1299
|
+
var temp2=get(0x19);
|
|
1190
1300
|
miiJson.info.birthday=parseInt(temp2.slice(6,8)+temp.slice(0,3),2);
|
|
1191
1301
|
miiJson.info.birthMonth=parseInt(temp.slice(3,7),2);
|
|
1192
1302
|
var name="";
|
|
1193
1303
|
for(var i=0x1A;i<0x2E;i+=2){
|
|
1194
|
-
if(
|
|
1304
|
+
if(get(i)==="00000000"){
|
|
1195
1305
|
break;
|
|
1196
1306
|
}
|
|
1197
|
-
name+=
|
|
1307
|
+
name+=data.slice(i,i+1);
|
|
1198
1308
|
}
|
|
1199
1309
|
miiJson.name=name.replaceAll("\x00","");
|
|
1200
1310
|
var cname="";
|
|
1201
1311
|
for(var i=0x48;i<0x5C;i+=2){
|
|
1202
|
-
if(
|
|
1312
|
+
if(get(i)==="00000000"){
|
|
1203
1313
|
break;
|
|
1204
1314
|
}
|
|
1205
|
-
cname+=
|
|
1315
|
+
cname+=data.slice(i,i+1);
|
|
1206
1316
|
}
|
|
1207
1317
|
miiJson.creatorName=cname.replaceAll("\x00","");
|
|
1208
1318
|
miiJson.info.name=miiJson.name;
|
|
1209
1319
|
miiJson.info.creatorName=miiJson.creatorName;
|
|
1210
|
-
miiJson.info.height=parseInt(
|
|
1211
|
-
miiJson.info.weight=parseInt(
|
|
1320
|
+
miiJson.info.height=parseInt(get(0x2E),2);
|
|
1321
|
+
miiJson.info.weight=parseInt(get(0x2F),2);
|
|
1212
1322
|
miiJson.info.gender=temp[7]==="1"?"Female":"Male";
|
|
1213
|
-
temp=
|
|
1323
|
+
temp=get(0x30);
|
|
1214
1324
|
miiJson.perms.sharing=temp[7]==="1"?false:true;
|
|
1215
1325
|
miiJson.info.favColor=favCols[parseInt(temp2.slice(2,6),2)];
|
|
1216
|
-
miiJson.perms.copying=
|
|
1217
|
-
miiJson.hair.style=lookupTable("hairs",parseInt(
|
|
1326
|
+
miiJson.perms.copying=get(0x01)[7]==="1"?true:false;
|
|
1327
|
+
miiJson.hair.style=lookupTable("hairs",parseInt(get(0x32),2),true);
|
|
1218
1328
|
miiJson.face.shape=lookupTable("faces",parseInt(temp.slice(3,7),2),false);
|
|
1219
1329
|
miiJson.face.col=skinCols[parseInt(temp.slice(0,3),2)];
|
|
1220
|
-
temp=
|
|
1330
|
+
temp=get(0x31);
|
|
1221
1331
|
miiJson.face.feature=faceFeatures3DS[parseInt(temp.slice(4,8),2)];
|
|
1222
1332
|
miiJson.face.makeup=makeups3DS[parseInt(temp.slice(0,4),2)];
|
|
1223
|
-
temp=
|
|
1333
|
+
temp=get(0x34);
|
|
1224
1334
|
miiJson.eyes.type=lookupTable("eyes",parseInt(temp.slice(2,8),2),true);
|
|
1225
|
-
temp2=
|
|
1335
|
+
temp2=get(0x33);
|
|
1226
1336
|
miiJson.hair.col=hairCols[parseInt(temp2.slice(5,8),2)];
|
|
1227
1337
|
miiJson.hair.flipped=temp2[4]==="0"?false:true;
|
|
1228
|
-
miiJson.eyes.col=eyeCols[parseInt(
|
|
1229
|
-
temp=
|
|
1338
|
+
miiJson.eyes.col=eyeCols[parseInt(get(0x35)[7]+temp.slice(0,2),2)];
|
|
1339
|
+
temp=get(0x35);
|
|
1230
1340
|
miiJson.eyes.size=parseInt(temp.slice(3,7),2);
|
|
1231
1341
|
miiJson.eyes.squash=parseInt(temp.slice(0,3),2);
|
|
1232
|
-
temp=
|
|
1233
|
-
temp2=
|
|
1342
|
+
temp=get(0x36);
|
|
1343
|
+
temp2=get(0x37);
|
|
1234
1344
|
miiJson.eyes.rot=parseInt(temp.slice(3,8),2);
|
|
1235
1345
|
miiJson.eyes.distApart=parseInt(temp2[7]+temp.slice(0,3),2);
|
|
1236
1346
|
miiJson.eyes.yPos=parseInt(temp2.slice(2,7),2);
|
|
1237
|
-
temp=
|
|
1347
|
+
temp=get(0x38);
|
|
1238
1348
|
miiJson.eyebrows.style=lookupTable("eyebrows",parseInt(temp.slice(3,8),2),true);
|
|
1239
1349
|
miiJson.eyebrows.col=hairCols[parseInt(temp.slice(0,3),2)];
|
|
1240
|
-
temp=
|
|
1350
|
+
temp=get(0x39);
|
|
1241
1351
|
miiJson.eyebrows.size=parseInt(temp.slice(4,8),2);
|
|
1242
1352
|
miiJson.eyebrows.squash=parseInt(temp.slice(1,4),2);
|
|
1243
|
-
temp=
|
|
1353
|
+
temp=get(0x3A);
|
|
1244
1354
|
miiJson.eyebrows.rot=parseInt(temp.slice(4,8),2);
|
|
1245
|
-
temp2=
|
|
1355
|
+
temp2=get(0x3B);
|
|
1246
1356
|
miiJson.eyebrows.distApart=parseInt(temp2[7]+temp.slice(0,3),2);
|
|
1247
1357
|
miiJson.eyebrows.yPos=parseInt(temp2.slice(2,7),2)-3;
|
|
1248
|
-
temp=
|
|
1358
|
+
temp=get(0x3C);
|
|
1249
1359
|
miiJson.nose.type=lookupTable("noses",parseInt(temp.slice(3,8),2),true);
|
|
1250
|
-
temp2=
|
|
1360
|
+
temp2=get(0x3D);
|
|
1251
1361
|
miiJson.nose.size=parseInt(temp2[7]+temp.slice(0,3),2);
|
|
1252
1362
|
miiJson.nose.yPos=parseInt(temp2.slice(2,7),2);
|
|
1253
|
-
temp=
|
|
1363
|
+
temp=get(0x3E);
|
|
1254
1364
|
miiJson.mouth.type=lookupTable("mouths",parseInt(temp.slice(2,8),2),true);
|
|
1255
|
-
temp2=
|
|
1365
|
+
temp2=get(0x3F);
|
|
1256
1366
|
miiJson.mouth.col=mouthCols3DS[parseInt(temp2[7]+temp.slice(0,2),2)];
|
|
1257
1367
|
miiJson.mouth.size=parseInt(temp2.slice(3,7),2);
|
|
1258
1368
|
miiJson.mouth.squash=parseInt(temp2.slice(0,3),2);
|
|
1259
|
-
temp=
|
|
1369
|
+
temp=get(0x40);
|
|
1260
1370
|
miiJson.mouth.yPos=parseInt(temp.slice(3,8),2);
|
|
1261
1371
|
miiJson.facialHair.mustacheType=parseInt(temp.slice(0,3),2);
|
|
1262
|
-
temp=
|
|
1372
|
+
temp=get(0x42);
|
|
1263
1373
|
miiJson.facialHair.beardType=parseInt(temp.slice(5,8),2);
|
|
1264
1374
|
miiJson.facialHair.col=hairCols[parseInt(temp.slice(2,5),2)];
|
|
1265
|
-
temp2=
|
|
1375
|
+
temp2=get(0x43);
|
|
1266
1376
|
miiJson.facialHair.mustacheSize=parseInt(temp2.slice(6,8)+temp.slice(0,2),2);
|
|
1267
1377
|
miiJson.facialHair.mustacheYPos=parseInt(temp2.slice(1,6),2);
|
|
1268
|
-
temp=
|
|
1378
|
+
temp=get(0x44);
|
|
1269
1379
|
miiJson.glasses.type=parseInt(temp.slice(4,8),2);
|
|
1270
1380
|
miiJson.glasses.col=glassesCols3DS[parseInt(temp.slice(1,4),2)];
|
|
1271
|
-
temp2=
|
|
1381
|
+
temp2=get(0x45);
|
|
1272
1382
|
miiJson.glasses.size=parseInt(temp2.slice(5,8)+temp[0],2);
|
|
1273
1383
|
miiJson.glasses.yPos=parseInt(temp2.slice(0,5),2);
|
|
1274
|
-
temp=
|
|
1384
|
+
temp=get(0x46);
|
|
1275
1385
|
miiJson.mole.on=temp[7]==="0"?false:true;
|
|
1276
1386
|
miiJson.mole.size=parseInt(temp.slice(3,7),2);
|
|
1277
|
-
temp2=
|
|
1387
|
+
temp2=get(0x47);
|
|
1278
1388
|
miiJson.mole.xPos=parseInt(temp2.slice(6,8)+temp.slice(0,3),2);
|
|
1279
1389
|
miiJson.mole.yPos=parseInt(temp2.slice(1,6),2);
|
|
1280
|
-
|
|
1390
|
+
miiJson.console="3ds";
|
|
1281
1391
|
return miiJson;
|
|
1282
1392
|
}
|
|
1283
1393
|
let qrCode;
|
|
@@ -1299,13 +1409,15 @@ module.exports={
|
|
|
1299
1409
|
}
|
|
1300
1410
|
if (qrCode) {
|
|
1301
1411
|
var data = decodeAesCcm(new Uint8Array(qrCode));
|
|
1302
|
-
|
|
1303
|
-
return Promise.resolve(readMii());
|
|
1412
|
+
return Promise.resolve(readMii(Buffer.from(data)));
|
|
1304
1413
|
} else {
|
|
1305
1414
|
console.error('Failed to decode QR code');
|
|
1306
1415
|
}
|
|
1307
1416
|
},
|
|
1308
|
-
writeWiiBin:function(jsonIn,outPath){
|
|
1417
|
+
writeWiiBin:function(jsonIn, outPath){
|
|
1418
|
+
if(jsonIn.console?.toLowerCase() !== "wii"){
|
|
1419
|
+
this.convertMii(jsonIn);
|
|
1420
|
+
}
|
|
1309
1421
|
var mii=jsonIn;
|
|
1310
1422
|
var miiBin="0";
|
|
1311
1423
|
miiBin+=mii.info.gender==="Male"?"0":"1";
|
|
@@ -1414,328 +1526,282 @@ module.exports={
|
|
|
1414
1526
|
}
|
|
1415
1527
|
fs.writeFileSync(outPath, Buffer.from(buffers));
|
|
1416
1528
|
},
|
|
1417
|
-
write3DSQR:function(jsonIn,outPath){
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
miiBin+="00000000";
|
|
1428
|
-
miiBin+="00110000";
|
|
1429
|
-
miiBin+="1000101011010010000001101000011100011000110001100100011001100110010101100111111110111100000001110101110001000101011101100000001110100100010000000000000000000000".slice(0,8*8);
|
|
1430
|
-
miiBin+=mii.info.type==="Special"?"0":"1";
|
|
1431
|
-
miiBin+="0000000";
|
|
1432
|
-
for(var i=0;i<3;i++){
|
|
1433
|
-
miiBin+=Math.floor(Math.random()*255).toString(2).padStart(8,"0");
|
|
1434
|
-
}
|
|
1435
|
-
miiBin+="0000000001000101011101100000001110100100010000000000000000000000";
|
|
1436
|
-
miiBin+=mii.info.birthday.toString(2).padStart(5,"0").slice(2,5);
|
|
1437
|
-
miiBin+=mii.info.birthMonth.toString(2).padStart(4,"0");
|
|
1438
|
-
miiBin+=mii.info.gender==="Male"?"0":"1";
|
|
1439
|
-
miiBin+="00";
|
|
1440
|
-
miiBin+=favCols.indexOf(mii.info.favColor).toString(2).padStart(4,"0");
|
|
1441
|
-
miiBin+=mii.info.birthday.toString(2).padStart(5,"0").slice(0,2);
|
|
1442
|
-
for(var i=0;i<10;i++){
|
|
1443
|
-
if(i<mii.name.length){
|
|
1444
|
-
miiBin+=mii.name.charCodeAt(i).toString(2).padStart(8,"0");
|
|
1445
|
-
}
|
|
1446
|
-
else{
|
|
1447
|
-
miiBin+="00000000";
|
|
1529
|
+
write3DSQR:async function(jsonIn,outPath,fflRes=_fflRes){
|
|
1530
|
+
if(!["3ds","wii u"].includes(jsonIn.console?.toLowerCase())){
|
|
1531
|
+
jsonIn=this.convertMii(jsonIn);
|
|
1532
|
+
}
|
|
1533
|
+
return new Promise(async (resolve, reject) => {
|
|
1534
|
+
var mii=jsonIn;
|
|
1535
|
+
function makeMiiBinary(mii){
|
|
1536
|
+
if(mii.perms.sharing&&mii.info.type==="Special"){
|
|
1537
|
+
mii.perms.sharing=false;
|
|
1538
|
+
console.log("Cannot have Sharing enabled for Special Miis. Disabled Sharing.");
|
|
1448
1539
|
}
|
|
1540
|
+
var miiBin="00000011";
|
|
1541
|
+
miiBin+="0000000";
|
|
1542
|
+
miiBin+=mii.perms.copying?"1":"0";
|
|
1449
1543
|
miiBin+="00000000";
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
miiBin+=makeups3DS.indexOf(mii.face.makeup).toString(2).padStart(4,"0");
|
|
1457
|
-
miiBin+=faceFeatures3DS.indexOf(mii.face.feature).toString(2).padStart(4,"0");
|
|
1458
|
-
miiBin+=tables.hairs[mii.hair.style[0]][mii.hair.style[1]].toString(2).padStart(8,"0");
|
|
1459
|
-
miiBin+="0000";
|
|
1460
|
-
miiBin+=mii.hair.flipped?"1":"0";
|
|
1461
|
-
miiBin+=hairCols.indexOf(mii.hair.col).toString(2).padStart(3,"0");
|
|
1462
|
-
miiBin+=eyeCols.indexOf(mii.eyes.col).toString(2).padStart(3,"0").slice(1,3);
|
|
1463
|
-
miiBin+=tables.eyes[mii.eyes.type[0]][mii.eyes.type[1]].toString(2).padStart(6,"0");
|
|
1464
|
-
miiBin+=mii.eyes.squash.toString(2).padStart(3,"0");
|
|
1465
|
-
miiBin+=mii.eyes.size.toString(2).padStart(4,"0");
|
|
1466
|
-
miiBin+=eyeCols.indexOf(mii.eyes.col).toString(2).padStart(3,"0")[0];
|
|
1467
|
-
miiBin+=mii.eyes.distApart.toString(2).padStart(4,"0").slice(1,4);
|
|
1468
|
-
miiBin+=mii.eyes.rot.toString(2).padStart(5,"0");
|
|
1469
|
-
miiBin+="00";
|
|
1470
|
-
miiBin+=mii.eyes.yPos.toString(2).padStart(5,"0");
|
|
1471
|
-
miiBin+=mii.eyes.distApart.toString(2).padStart(4,"0")[0];
|
|
1472
|
-
miiBin+=hairCols.indexOf(mii.eyebrows.col).toString(2).padStart(3,"0");
|
|
1473
|
-
miiBin+=tables.eyebrows[mii.eyebrows.style[0]][mii.eyebrows.style[1]].toString(2).padStart(5,"0");
|
|
1474
|
-
miiBin+="0";
|
|
1475
|
-
miiBin+=mii.eyebrows.squash.toString(2).padStart(3,"0");
|
|
1476
|
-
miiBin+=mii.eyebrows.size.toString(2).padStart(4,"0");
|
|
1477
|
-
miiBin+=mii.eyebrows.distApart.toString(2).padStart(4,"0").slice(1,4);
|
|
1478
|
-
miiBin+="0";
|
|
1479
|
-
miiBin+=mii.eyebrows.rot.toString(2).padStart(4,"0");
|
|
1480
|
-
miiBin+="00";
|
|
1481
|
-
miiBin+=(mii.eyebrows.yPos+3).toString(2).padStart(5,"0");
|
|
1482
|
-
miiBin+=mii.eyebrows.distApart.toString(2).padStart(4,"0")[0];
|
|
1483
|
-
miiBin+=mii.nose.size.toString(2).padStart(4,"0").slice(1,4);
|
|
1484
|
-
miiBin+=tables.noses[mii.nose.type[0]][mii.nose.type[1]].toString(2).padStart(5,"0");
|
|
1485
|
-
miiBin+="00";
|
|
1486
|
-
miiBin+=mii.nose.yPos.toString(2).padStart(5,"0");
|
|
1487
|
-
miiBin+=mii.nose.size.toString(2).padStart(4,"0")[0];
|
|
1488
|
-
miiBin+=mouthCols3DS.indexOf(mii.mouth.col).toString(2).padStart(3,"0").slice(1,3);
|
|
1489
|
-
miiBin+=tables.mouths[mii.mouth.type[0]][mii.mouth.type[1]].toString(2).padStart(6,"0");
|
|
1490
|
-
miiBin+=mii.mouth.squash.toString(2).padStart(3,"0");
|
|
1491
|
-
miiBin+=mii.mouth.size.toString(2).padStart(4,"0");
|
|
1492
|
-
miiBin+=mouthCols3DS.indexOf(mii.mouth.col).toString(2).padStart(3,"0")[0];
|
|
1493
|
-
miiBin+=mii.facialHair.mustacheType.toString(2).padStart(3,"0");
|
|
1494
|
-
miiBin+=mii.mouth.yPos.toString(2).padStart(5,"0");
|
|
1495
|
-
miiBin+="00000000";
|
|
1496
|
-
miiBin+=mii.facialHair.mustacheSize.toString(2).padStart(4,"0").slice(2,4);
|
|
1497
|
-
miiBin+=hairCols.indexOf(mii.facialHair.col).toString(2).padStart(3,"0");
|
|
1498
|
-
miiBin+=mii.facialHair.beardType.toString(2).padStart(3,"0");
|
|
1499
|
-
miiBin+="0";
|
|
1500
|
-
miiBin+=mii.facialHair.mustacheYPos.toString(2).padStart(5,"0");
|
|
1501
|
-
miiBin+=mii.facialHair.mustacheSize.toString(2).padStart(4,"0").slice(0,2);
|
|
1502
|
-
miiBin+=mii.glasses.size.toString(2).padStart(4,"0")[3];
|
|
1503
|
-
miiBin+=glassesCols3DS.indexOf(mii.glasses.col).toString(2).padStart(3,"0");
|
|
1504
|
-
miiBin+=mii.glasses.type.toString(2).padStart(4,"0");
|
|
1505
|
-
miiBin+="0";
|
|
1506
|
-
miiBin+=mii.glasses.yPos.toString(2).padStart(4,"0");
|
|
1507
|
-
miiBin+=mii.glasses.size.toString(2).padStart(4,"0").slice(0,3);
|
|
1508
|
-
miiBin+=mii.mole.xPos.toString(2).padStart(5,"0").slice(2,5);
|
|
1509
|
-
miiBin+=mii.mole.size.toString(2).padStart(4,"0");
|
|
1510
|
-
miiBin+=mii.mole.on?"1":"0";
|
|
1511
|
-
miiBin+="0";
|
|
1512
|
-
miiBin+=mii.mole.yPos.toString(2).padStart(5,"0");
|
|
1513
|
-
miiBin+=mii.mole.xPos.toString(2).padStart(5,"0").slice(0,2);
|
|
1514
|
-
for(var i=0;i<10;i++){
|
|
1515
|
-
if(i<mii.creatorName.length){
|
|
1516
|
-
miiBin+=mii.creatorName.charCodeAt(i).toString(2).padStart(8,"0");
|
|
1544
|
+
miiBin+="00110000";
|
|
1545
|
+
miiBin+="1000101011010010000001101000011100011000110001100100011001100110010101100111111110111100000001110101110001000101011101100000001110100100010000000000000000000000".slice(0,8*8);
|
|
1546
|
+
miiBin+=mii.info.type==="Special"?"0":"1";
|
|
1547
|
+
miiBin+="0000000";
|
|
1548
|
+
for(var i=0;i<3;i++){
|
|
1549
|
+
miiBin+=Math.floor(Math.random()*255).toString(2).padStart(8,"0");
|
|
1517
1550
|
}
|
|
1518
|
-
|
|
1551
|
+
miiBin+="0000000001000101011101100000001110100100010000000000000000000000";
|
|
1552
|
+
miiBin+=mii.info.birthday.toString(2).padStart(5,"0").slice(2,5);
|
|
1553
|
+
miiBin+=mii.info.birthMonth.toString(2).padStart(4,"0");
|
|
1554
|
+
miiBin+=mii.info.gender==="Male"?"0":"1";
|
|
1555
|
+
miiBin+="00";
|
|
1556
|
+
miiBin+=favCols.indexOf(mii.info.favColor).toString(2).padStart(4,"0");
|
|
1557
|
+
miiBin+=mii.info.birthday.toString(2).padStart(5,"0").slice(0,2);
|
|
1558
|
+
for(var i=0;i<10;i++){
|
|
1559
|
+
if(i<mii.name.length){
|
|
1560
|
+
miiBin+=mii.name.charCodeAt(i).toString(2).padStart(8,"0");
|
|
1561
|
+
}
|
|
1562
|
+
else{
|
|
1563
|
+
miiBin+="00000000";
|
|
1564
|
+
}
|
|
1519
1565
|
miiBin+="00000000";
|
|
1520
1566
|
}
|
|
1567
|
+
miiBin+=mii.info.height.toString(2).padStart(8,"0");
|
|
1568
|
+
miiBin+=mii.info.weight.toString(2).padStart(8,"0");
|
|
1569
|
+
miiBin+=skinCols.indexOf(mii.face.col).toString(2).padStart(3,"0");
|
|
1570
|
+
miiBin+=tables.faces[mii.face.shape].toString(2).padStart(4,"0");
|
|
1571
|
+
miiBin+=mii.perms.sharing?"0":"1";
|
|
1572
|
+
miiBin+=makeups3DS.indexOf(mii.face.makeup).toString(2).padStart(4,"0");
|
|
1573
|
+
miiBin+=faceFeatures3DS.indexOf(mii.face.feature).toString(2).padStart(4,"0");
|
|
1574
|
+
miiBin+=tables.hairs[mii.hair.style[0]][mii.hair.style[1]].toString(2).padStart(8,"0");
|
|
1575
|
+
miiBin+="0000";
|
|
1576
|
+
miiBin+=mii.hair.flipped?"1":"0";
|
|
1577
|
+
miiBin+=hairCols.indexOf(mii.hair.col).toString(2).padStart(3,"0");
|
|
1578
|
+
miiBin+=eyeCols.indexOf(mii.eyes.col).toString(2).padStart(3,"0").slice(1,3);
|
|
1579
|
+
miiBin+=tables.eyes[mii.eyes.type[0]][mii.eyes.type[1]].toString(2).padStart(6,"0");
|
|
1580
|
+
miiBin+=mii.eyes.squash.toString(2).padStart(3,"0");
|
|
1581
|
+
miiBin+=mii.eyes.size.toString(2).padStart(4,"0");
|
|
1582
|
+
miiBin+=eyeCols.indexOf(mii.eyes.col).toString(2).padStart(3,"0")[0];
|
|
1583
|
+
miiBin+=mii.eyes.distApart.toString(2).padStart(4,"0").slice(1,4);
|
|
1584
|
+
miiBin+=mii.eyes.rot.toString(2).padStart(5,"0");
|
|
1585
|
+
miiBin+="00";
|
|
1586
|
+
miiBin+=mii.eyes.yPos.toString(2).padStart(5,"0");
|
|
1587
|
+
miiBin+=mii.eyes.distApart.toString(2).padStart(4,"0")[0];
|
|
1588
|
+
miiBin+=hairCols.indexOf(mii.eyebrows.col).toString(2).padStart(3,"0");
|
|
1589
|
+
miiBin+=tables.eyebrows[mii.eyebrows.style[0]][mii.eyebrows.style[1]].toString(2).padStart(5,"0");
|
|
1590
|
+
miiBin+="0";
|
|
1591
|
+
miiBin+=mii.eyebrows.squash.toString(2).padStart(3,"0");
|
|
1592
|
+
miiBin+=mii.eyebrows.size.toString(2).padStart(4,"0");
|
|
1593
|
+
miiBin+=mii.eyebrows.distApart.toString(2).padStart(4,"0").slice(1,4);
|
|
1594
|
+
miiBin+="0";
|
|
1595
|
+
miiBin+=mii.eyebrows.rot.toString(2).padStart(4,"0");
|
|
1596
|
+
miiBin+="00";
|
|
1597
|
+
miiBin+=(mii.eyebrows.yPos+3).toString(2).padStart(5,"0");
|
|
1598
|
+
miiBin+=mii.eyebrows.distApart.toString(2).padStart(4,"0")[0];
|
|
1599
|
+
miiBin+=mii.nose.size.toString(2).padStart(4,"0").slice(1,4);
|
|
1600
|
+
miiBin+=tables.noses[mii.nose.type[0]][mii.nose.type[1]].toString(2).padStart(5,"0");
|
|
1601
|
+
miiBin+="00";
|
|
1602
|
+
miiBin+=mii.nose.yPos.toString(2).padStart(5,"0");
|
|
1603
|
+
miiBin+=mii.nose.size.toString(2).padStart(4,"0")[0];
|
|
1604
|
+
miiBin+=mouthCols3DS.indexOf(mii.mouth.col).toString(2).padStart(3,"0").slice(1,3);
|
|
1605
|
+
miiBin+=tables.mouths[mii.mouth.type[0]][mii.mouth.type[1]].toString(2).padStart(6,"0");
|
|
1606
|
+
miiBin+=mii.mouth.squash.toString(2).padStart(3,"0");
|
|
1607
|
+
miiBin+=mii.mouth.size.toString(2).padStart(4,"0");
|
|
1608
|
+
miiBin+=mouthCols3DS.indexOf(mii.mouth.col).toString(2).padStart(3,"0")[0];
|
|
1609
|
+
miiBin+=mii.facialHair.mustacheType.toString(2).padStart(3,"0");
|
|
1610
|
+
miiBin+=mii.mouth.yPos.toString(2).padStart(5,"0");
|
|
1521
1611
|
miiBin+="00000000";
|
|
1612
|
+
miiBin+=mii.facialHair.mustacheSize.toString(2).padStart(4,"0").slice(2,4);
|
|
1613
|
+
miiBin+=hairCols.indexOf(mii.facialHair.col).toString(2).padStart(3,"0");
|
|
1614
|
+
miiBin+=mii.facialHair.beardType.toString(2).padStart(3,"0");
|
|
1615
|
+
miiBin+="0";
|
|
1616
|
+
miiBin+=mii.facialHair.mustacheYPos.toString(2).padStart(5,"0");
|
|
1617
|
+
miiBin+=mii.facialHair.mustacheSize.toString(2).padStart(4,"0").slice(0,2);
|
|
1618
|
+
miiBin+=mii.glasses.size.toString(2).padStart(4,"0")[3];
|
|
1619
|
+
miiBin+=glassesCols3DS.indexOf(mii.glasses.col).toString(2).padStart(3,"0");
|
|
1620
|
+
miiBin+=mii.glasses.type.toString(2).padStart(4,"0");
|
|
1621
|
+
miiBin+="0";
|
|
1622
|
+
miiBin+=mii.glasses.yPos.toString(2).padStart(4,"0");
|
|
1623
|
+
miiBin+=mii.glasses.size.toString(2).padStart(4,"0").slice(0,3);
|
|
1624
|
+
miiBin+=mii.mole.xPos.toString(2).padStart(5,"0").slice(2,5);
|
|
1625
|
+
miiBin+=mii.mole.size.toString(2).padStart(4,"0");
|
|
1626
|
+
miiBin+=mii.mole.on?"1":"0";
|
|
1627
|
+
miiBin+="0";
|
|
1628
|
+
miiBin+=mii.mole.yPos.toString(2).padStart(5,"0");
|
|
1629
|
+
miiBin+=mii.mole.xPos.toString(2).padStart(5,"0").slice(0,2);
|
|
1630
|
+
for(var i=0;i<10;i++){
|
|
1631
|
+
if(i<mii.creatorName.length){
|
|
1632
|
+
miiBin+=mii.creatorName.charCodeAt(i).toString(2).padStart(8,"0");
|
|
1633
|
+
}
|
|
1634
|
+
else{
|
|
1635
|
+
miiBin+="00000000";
|
|
1636
|
+
}
|
|
1637
|
+
miiBin+="00000000";
|
|
1638
|
+
}
|
|
1639
|
+
//Writing based on miiBin
|
|
1640
|
+
var toWrite=miiBin.match(/.{1,8}/g);
|
|
1641
|
+
var buffers=[];
|
|
1642
|
+
for(var i=0;i<toWrite.length;i++){
|
|
1643
|
+
buffers.push(parseInt(toWrite[i],2));
|
|
1644
|
+
}
|
|
1645
|
+
const buffer = Buffer.from(buffers);
|
|
1646
|
+
return buffer;
|
|
1522
1647
|
}
|
|
1523
|
-
|
|
1524
|
-
var
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1648
|
+
const miiBinary = makeMiiBinary(mii);
|
|
1649
|
+
var encryptedData = Buffer.from(encodeAesCcm(new Uint8Array(miiBinary)));
|
|
1650
|
+
|
|
1651
|
+
const options = {
|
|
1652
|
+
width: 300,
|
|
1653
|
+
height: 300,
|
|
1654
|
+
data: encryptedData.toString("latin1"),
|
|
1655
|
+
image: "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", // 1x1 gif
|
|
1656
|
+
dotsOptions: {
|
|
1657
|
+
color: "#000000",
|
|
1658
|
+
type: "square"
|
|
1659
|
+
},
|
|
1660
|
+
backgroundOptions: {
|
|
1661
|
+
color: "#ffffff",
|
|
1662
|
+
},
|
|
1663
|
+
imageOptions: {
|
|
1664
|
+
crossOrigin: "anonymous",
|
|
1665
|
+
imageSize: 0.4 // Changes how large center area is
|
|
1666
|
+
}
|
|
1528
1667
|
}
|
|
1529
|
-
const
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1668
|
+
const qrCodeImage = new QRCodeStyling({
|
|
1669
|
+
jsdom: JSDOM,
|
|
1670
|
+
nodeCanvas,
|
|
1671
|
+
...options
|
|
1672
|
+
});
|
|
1673
|
+
const qrBuffer = Buffer.from( await qrCodeImage.getRawData("png") )
|
|
1674
|
+
|
|
1675
|
+
var studioMii=new Uint8Array([0x08, 0x00, 0x40, 0x03, 0x08, 0x04, 0x04, 0x02, 0x02, 0x0c, 0x03, 0x01, 0x06, 0x04, 0x06, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x0a, 0x01, 0x00, 0x21, 0x40, 0x04, 0x00, 0x02, 0x14, 0x03, 0x13, 0x04, 0x17, 0x0d, 0x04, 0x00, 0x0a, 0x04, 0x01, 0x09]);
|
|
1676
|
+
studioMii[0x16] = mii.info.gender==="Male"?0:1;
|
|
1677
|
+
studioMii[0x15] = favCols.indexOf(mii.info.favColor);
|
|
1678
|
+
studioMii[0x1E] = mii.info.height;
|
|
1679
|
+
studioMii[2] = mii.info.weight;
|
|
1680
|
+
studioMii[0x13] = tables.faces[mii.face.shape];
|
|
1681
|
+
studioMii[0x11] = skinCols.indexOf(mii.face.col);
|
|
1682
|
+
studioMii[0x14] = faceFeatures3DS.indexOf(mii.face.feature);
|
|
1683
|
+
studioMii[0x12] = makeups3DS.indexOf(mii.face.makeup);
|
|
1684
|
+
studioMii[0x1D] = tables.hairs[mii.hair.style[0]][mii.hair.style[1]];
|
|
1685
|
+
studioMii[0x1B] = hairCols.indexOf(mii.hair.col);
|
|
1686
|
+
if (!studioMii[0x1B]) studioMii[0x1B] = 8;
|
|
1687
|
+
studioMii[0x1C] = mii.hair.flipped?1:0;
|
|
1688
|
+
studioMii[7] = tables.eyes[mii.eyes.type[0]][mii.eyes.type[1]];
|
|
1689
|
+
studioMii[4] = eyeCols.indexOf(mii.eyes.col) + 8;
|
|
1690
|
+
studioMii[6] = mii.eyes.size;
|
|
1691
|
+
studioMii[3] = mii.eyes.squash;
|
|
1692
|
+
studioMii[5] = mii.eyes.rot;
|
|
1693
|
+
studioMii[8] = mii.eyes.distApart;
|
|
1694
|
+
studioMii[9] = mii.eyes.yPos;
|
|
1695
|
+
studioMii[0xE] = tables.eyebrows[mii.eyebrows.style[0]][mii.eyebrows.style[1]];
|
|
1696
|
+
studioMii[0xB] = hairCols.indexOf(mii.eyebrows.col);
|
|
1697
|
+
if (!studioMii[0xB]) studioMii[0xB] = 8;
|
|
1698
|
+
studioMii[0xD] = mii.eyebrows.size;
|
|
1699
|
+
studioMii[0xA] = mii.eyebrows.squash;
|
|
1700
|
+
studioMii[0xC] = mii.eyebrows.rot;
|
|
1701
|
+
studioMii[0xF] = mii.eyebrows.distApart;
|
|
1702
|
+
studioMii[0x10] = mii.eyebrows.yPos+3;
|
|
1703
|
+
studioMii[0x2C] = tables.noses[mii.nose.type[0]][mii.nose.type[1]];
|
|
1704
|
+
studioMii[0x2B] = mii.nose.size;
|
|
1705
|
+
studioMii[0x2D] = mii.nose.yPos;
|
|
1706
|
+
studioMii[0x26] = tables.mouths[mii.mouth.type[0]][mii.mouth.type[1]];
|
|
1707
|
+
studioMii[0x24] = mouthCols3DS.indexOf(mii.mouth.col);
|
|
1708
|
+
if (studioMii[0x24] < 4) {
|
|
1709
|
+
studioMii[0x24] += 19;
|
|
1710
|
+
} else {
|
|
1711
|
+
studioMii[0x24] = 0;
|
|
1545
1712
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1713
|
+
studioMii[0x25] = mii.mouth.size;
|
|
1714
|
+
studioMii[0x23] = mii.mouth.squash;
|
|
1715
|
+
studioMii[0x27] = mii.mouth.yPos;
|
|
1716
|
+
studioMii[0x29] = mii.facialHair.mustacheType;
|
|
1717
|
+
studioMii[1] = mii.facialHair.beardType;
|
|
1718
|
+
studioMii[0] = hairCols.indexOf(mii.facialHair.col);
|
|
1719
|
+
if (!studioMii[0]) studioMii[0] = 8;
|
|
1720
|
+
studioMii[0x28] = mii.facialHair.mustacheSize;
|
|
1721
|
+
studioMii[0x2A] = mii.facialHair.mustacheYPos;
|
|
1722
|
+
studioMii[0x19] = mii.glasses.type;
|
|
1723
|
+
studioMii[0x17] = mii.glasses.col;
|
|
1724
|
+
if (!studioMii[0x17]) {
|
|
1725
|
+
studioMii[0x17] = 8;
|
|
1726
|
+
} else if (studioMii[0x17] < 6) {
|
|
1727
|
+
studioMii[0x17] += 13;
|
|
1728
|
+
} else {
|
|
1729
|
+
studioMii[0x17] = 0;
|
|
1553
1730
|
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
studioMii[0x1D] = tables.hairs[mii.hair.style[0]][mii.hair.style[1]];
|
|
1565
|
-
studioMii[0x1B] = hairCols.indexOf(mii.hair.col);
|
|
1566
|
-
if (!studioMii[0x1B]) studioMii[0x1B] = 8;
|
|
1567
|
-
studioMii[0x1C] = mii.hair.flipped?1:0;
|
|
1568
|
-
studioMii[7] = tables.eyes[mii.eyes.type[0]][mii.eyes.type[1]];
|
|
1569
|
-
studioMii[4] = eyeCols.indexOf(mii.eyes.col) + 8;
|
|
1570
|
-
studioMii[6] = mii.eyes.size;
|
|
1571
|
-
studioMii[3] = mii.eyes.squash;
|
|
1572
|
-
studioMii[5] = mii.eyes.rot;
|
|
1573
|
-
studioMii[8] = mii.eyes.distApart;
|
|
1574
|
-
studioMii[9] = mii.eyes.yPos;
|
|
1575
|
-
studioMii[0xE] = tables.eyebrows[mii.eyebrows.style[0]][mii.eyebrows.style[1]];
|
|
1576
|
-
studioMii[0xB] = hairCols.indexOf(mii.eyebrows.col);
|
|
1577
|
-
if (!studioMii[0xB]) studioMii[0xB] = 8;
|
|
1578
|
-
studioMii[0xD] = mii.eyebrows.size;
|
|
1579
|
-
studioMii[0xA] = mii.eyebrows.squash;
|
|
1580
|
-
studioMii[0xC] = mii.eyebrows.rot;
|
|
1581
|
-
studioMii[0xF] = mii.eyebrows.distApart;
|
|
1582
|
-
studioMii[0x10] = mii.eyebrows.yPos+3;
|
|
1583
|
-
studioMii[0x2C] = tables.noses[mii.nose.type[0]][mii.nose.type[1]];
|
|
1584
|
-
studioMii[0x2B] = mii.nose.size;
|
|
1585
|
-
studioMii[0x2D] = mii.nose.yPos;
|
|
1586
|
-
studioMii[0x26] = tables.mouths[mii.mouth.type[0]][mii.mouth.type[1]];
|
|
1587
|
-
studioMii[0x24] = mouthCols3DS.indexOf(mii.mouth.col);
|
|
1588
|
-
if (studioMii[0x24] < 4) {
|
|
1589
|
-
studioMii[0x24] += 19;
|
|
1590
|
-
} else {
|
|
1591
|
-
studioMii[0x24] = 0;
|
|
1592
|
-
}
|
|
1593
|
-
studioMii[0x25] = mii.mouth.size;
|
|
1594
|
-
studioMii[0x23] = mii.mouth.squash;
|
|
1595
|
-
studioMii[0x27] = mii.mouth.yPos;
|
|
1596
|
-
studioMii[0x29] = mii.facialHair.mustacheType;
|
|
1597
|
-
studioMii[1] = mii.facialHair.beardType;
|
|
1598
|
-
studioMii[0] = hairCols.indexOf(mii.facialHair.col);
|
|
1599
|
-
if (!studioMii[0]) studioMii[0] = 8;
|
|
1600
|
-
studioMii[0x28] = mii.facialHair.mustacheSize;
|
|
1601
|
-
studioMii[0x2A] = mii.facialHair.mustacheYPos;
|
|
1602
|
-
studioMii[0x19] = mii.glasses.type;
|
|
1603
|
-
studioMii[0x17] = mii.glasses.col;
|
|
1604
|
-
if (!studioMii[0x17]) {
|
|
1605
|
-
studioMii[0x17] = 8;
|
|
1606
|
-
} else if (studioMii[0x17] < 6) {
|
|
1607
|
-
studioMii[0x17] += 13;
|
|
1608
|
-
} else {
|
|
1609
|
-
studioMii[0x17] = 0;
|
|
1610
|
-
}
|
|
1611
|
-
studioMii[0x18] = mii.glasses.size;
|
|
1612
|
-
studioMii[0x1A] = mii.glasses.yPos;
|
|
1613
|
-
studioMii[0x20] = mii.mole.on?1:0;
|
|
1614
|
-
studioMii[0x1F] = mii.mole.size;
|
|
1615
|
-
studioMii[0x21] = mii.mole.xPos;
|
|
1616
|
-
studioMii[0x22] = mii.mole.yPos;
|
|
1617
|
-
downloadImage('https://studio.mii.nintendo.com/miis/image.png?data=' + encodeStudio(studioMii) + "&width=270&type=face","./temp.png").then(d=>{
|
|
1618
|
-
Jimp.read(mii.name+'Output.png', (err, fir_img) => {
|
|
1619
|
-
if(err) {
|
|
1620
|
-
console.log(err);
|
|
1621
|
-
} else {
|
|
1622
|
-
Jimp.read('temp.png', (err, sec_img) => {
|
|
1623
|
-
if(err) {
|
|
1624
|
-
console.log(err);
|
|
1625
|
-
} else {
|
|
1626
|
-
fir_img.resize(424, 424);
|
|
1627
|
-
sec_img.resize(130,130);
|
|
1628
|
-
const canvas = new Jimp(sec_img.bitmap.width, sec_img.bitmap.height, 0xFFFFFFFF);
|
|
1629
|
-
canvas.composite(sec_img, 0, 0);
|
|
1630
|
-
fir_img.blit(canvas, 212-130/2,212-130/2);
|
|
1631
|
-
Jimp.loadFont(Jimp.FONT_SANS_16_BLACK).then(font => {
|
|
1632
|
-
fir_img.print(font, 0, 50, {
|
|
1633
|
-
text: mii.name,
|
|
1634
|
-
alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
|
|
1635
|
-
alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
|
|
1636
|
-
}, 424, 424);
|
|
1637
|
-
Jimp.read('./node_modules/miijs/crown.jpg',(err,thi_img)=>{
|
|
1638
|
-
thi_img.resize(40,20);
|
|
1639
|
-
if(mii.info.type==="Special") fir_img.blit(thi_img,232,150);
|
|
1640
|
-
fir_img.write(outPath);
|
|
1641
|
-
fs.unlinkSync("./temp.png");
|
|
1642
|
-
});
|
|
1643
|
-
});
|
|
1644
|
-
}
|
|
1645
|
-
})
|
|
1646
|
-
}
|
|
1647
|
-
fs.unlinkSync(mii.name+"Output.png");
|
|
1648
|
-
});
|
|
1649
|
-
});
|
|
1650
|
-
},
|
|
1651
|
-
render3DSMiiFromJSON:function(jsonIn,outPath){
|
|
1652
|
-
var mii=jsonIn;
|
|
1653
|
-
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]);
|
|
1654
|
-
//miiPreview.src = 'https://studio.mii.nintendo.com/miis/image.png?data=' + mii.previewData + "&width=270&type=face";
|
|
1655
|
-
function encodeStudio(studio) {
|
|
1656
|
-
function byteToString(int){
|
|
1657
|
-
var str = int.toString(16);
|
|
1658
|
-
if(str.length < 2)str = '0' + str;
|
|
1659
|
-
return str;
|
|
1731
|
+
studioMii[0x18] = mii.glasses.size;
|
|
1732
|
+
studioMii[0x1A] = mii.glasses.yPos;
|
|
1733
|
+
studioMii[0x20] = mii.mole.on?1:0;
|
|
1734
|
+
studioMii[0x1F] = mii.mole.size;
|
|
1735
|
+
studioMii[0x21] = mii.mole.xPos;
|
|
1736
|
+
studioMii[0x22] = mii.mole.yPos;
|
|
1737
|
+
let miiPNGBuf = null;
|
|
1738
|
+
let renderedWithStudio = fflRes===null || fflRes===undefined;
|
|
1739
|
+
if(renderedWithStudio){
|
|
1740
|
+
miiPNGBuf = await this.render3DSMiiWithStudio(jsonIn);
|
|
1660
1741
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
var dest = byteToString(n);
|
|
1664
|
-
for (var i = 0; i < studio.length; i++) {
|
|
1665
|
-
eo = (7 + (studio[i] ^ n)) & 0xFF;
|
|
1666
|
-
n = eo;
|
|
1667
|
-
dest += byteToString(eo);
|
|
1742
|
+
else{
|
|
1743
|
+
miiPNGBuf = await this.render3DSMii(jsonIn,fflRes);
|
|
1668
1744
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
studioMii[0x19] = mii.glasses.type;
|
|
1718
|
-
studioMii[0x17] = glassesCols3DS.indexOf(mii.glasses.col);
|
|
1719
|
-
if (!studioMii[0x17]) {
|
|
1720
|
-
studioMii[0x17] = 8;
|
|
1721
|
-
} else if (studioMii[0x17] < 6) {
|
|
1722
|
-
studioMii[0x17] += 13;
|
|
1723
|
-
} else {
|
|
1724
|
-
studioMii[0x17] = 0;
|
|
1745
|
+
const main_img = await Jimp.read(qrBuffer);
|
|
1746
|
+
main_img.resize(424, 424, Jimp.RESIZE_NEAREST_NEIGHBOR); // Don't anti-alias the QR code
|
|
1747
|
+
|
|
1748
|
+
let miiSize, miiZoomFactor, miiYOffset;
|
|
1749
|
+
if (renderedWithStudio) {
|
|
1750
|
+
miiSize = 100;
|
|
1751
|
+
miiZoomFactor = 1;
|
|
1752
|
+
miiYOffset = -15;
|
|
1753
|
+
|
|
1754
|
+
} else {
|
|
1755
|
+
miiSize = 100;
|
|
1756
|
+
miiZoomFactor = 1.25;
|
|
1757
|
+
miiYOffset = -5;
|
|
1758
|
+
}
|
|
1759
|
+
const mii_img = await Jimp.read(miiPNGBuf);
|
|
1760
|
+
mii_img.resize(miiSize*miiZoomFactor, miiSize*miiZoomFactor, Jimp.RESIZE_BICUBIC);
|
|
1761
|
+
mii_img.crop(
|
|
1762
|
+
(miiSize*miiZoomFactor - 100) / 2,
|
|
1763
|
+
(miiSize*miiZoomFactor - 100) / 2,
|
|
1764
|
+
miiSize,
|
|
1765
|
+
miiSize
|
|
1766
|
+
);
|
|
1767
|
+
|
|
1768
|
+
const canvas = new Jimp(mii_img.bitmap.width, mii_img.bitmap.height, 0xFFFFFFFF);
|
|
1769
|
+
canvas.composite(mii_img, 0, miiYOffset);
|
|
1770
|
+
main_img.blit(canvas, 212-100/2, 212-100/2);
|
|
1771
|
+
const font = await Jimp.loadFont(Jimp.FONT_SANS_16_BLACK)
|
|
1772
|
+
|
|
1773
|
+
main_img.print(font, 0, 55, {
|
|
1774
|
+
text: mii.name,
|
|
1775
|
+
alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
|
|
1776
|
+
alignmentY: Jimp.VERTICAL_ALIGN_MIDDLE
|
|
1777
|
+
}, 424, 395);
|
|
1778
|
+
|
|
1779
|
+
if(mii.info.type==="Special"){
|
|
1780
|
+
const crown_img = await Jimp.read(path.join(__dirname, 'crown.jpg'));
|
|
1781
|
+
crown_img.resize(40,20);
|
|
1782
|
+
main_img.blit(crown_img,225,160);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
main_img.write(outPath, (err, img) =>
|
|
1786
|
+
resolve(img)
|
|
1787
|
+
);
|
|
1788
|
+
})
|
|
1789
|
+
},
|
|
1790
|
+
render3DSMiiWithStudio:async function(jsonIn){
|
|
1791
|
+
if(!["3ds","wii u"].includes(jsonIn.console?.toLowerCase())){
|
|
1792
|
+
jsonIn=this.convertMii(jsonIn);
|
|
1725
1793
|
}
|
|
1726
|
-
studioMii
|
|
1727
|
-
|
|
1728
|
-
studioMii[0x20] = mii.mole.on?1:0;
|
|
1729
|
-
studioMii[0x1F] = mii.mole.size;
|
|
1730
|
-
studioMii[0x21] = mii.mole.xPos;
|
|
1731
|
-
studioMii[0x22] = mii.mole.yPos;
|
|
1732
|
-
downloadImage('https://studio.mii.nintendo.com/miis/image.png?data=' + encodeStudio(studioMii) + "&width=270&type=face",outPath);
|
|
1794
|
+
var studioMii=this.convert3DSMiiToStudio(jsonIn);
|
|
1795
|
+
return await downloadImage('https://studio.mii.nintendo.com/miis/image.png?data=' + studioMii + "&width=270&type=face");
|
|
1733
1796
|
},
|
|
1734
|
-
convertMii:function
|
|
1735
|
-
typeFrom=
|
|
1797
|
+
convertMii:function(jsonIn){
|
|
1798
|
+
typeFrom=jsonIn.console?.toLowerCase();
|
|
1799
|
+
if(typeFrom===null||typeFrom===undefined){
|
|
1800
|
+
return jsonIn;
|
|
1801
|
+
}
|
|
1736
1802
|
let mii=jsonIn;
|
|
1737
1803
|
var miiTo={};
|
|
1738
|
-
if(
|
|
1804
|
+
if(["wii u","3ds"].includes(typeFrom)){
|
|
1739
1805
|
miiTo={
|
|
1740
1806
|
info:{},
|
|
1741
1807
|
face:{},
|
|
@@ -1823,6 +1889,7 @@ module.exports={
|
|
|
1823
1889
|
if(mii.facialHair.beardType>3){
|
|
1824
1890
|
mii.facialHair.beardType=3;
|
|
1825
1891
|
}
|
|
1892
|
+
miiTo.console="wii";
|
|
1826
1893
|
}
|
|
1827
1894
|
else if(typeFrom==="wii"){
|
|
1828
1895
|
miiTo={
|
|
@@ -1898,10 +1965,17 @@ module.exports={
|
|
|
1898
1965
|
miiTo.mole.size=mii.mole.size;
|
|
1899
1966
|
miiTo.mole.xPos=mii.mole.xPos;
|
|
1900
1967
|
miiTo.mole.yPos=mii.mole.yPos;
|
|
1968
|
+
miiTo.console="3ds";
|
|
1901
1969
|
}
|
|
1902
1970
|
return miiTo;
|
|
1903
1971
|
},
|
|
1904
1972
|
make3DSChild:function(dad,mom,options={}){
|
|
1973
|
+
if(!["3ds","wii u"].includes(dad.console?.toLowerCase())){
|
|
1974
|
+
dad=this.convertMii(dad,"wii");
|
|
1975
|
+
}
|
|
1976
|
+
if(!["3ds","wii u"].includes(mom.console?.toLowerCase())){
|
|
1977
|
+
mom=this.convertMii(dad,"wii");
|
|
1978
|
+
}
|
|
1905
1979
|
var g=options.gender||Math.floor(Math.random()*2)===1?"Male":"Female";
|
|
1906
1980
|
var child={
|
|
1907
1981
|
"info":{
|
|
@@ -1951,9 +2025,11 @@ module.exports={
|
|
|
1951
2025
|
}
|
|
1952
2026
|
child.face.col=skinCols[c[0]+Math.round((c[1]-c[0])/2)];
|
|
1953
2027
|
child.name=child.info.name;
|
|
2028
|
+
child.type="3DS";
|
|
1954
2029
|
return child;
|
|
1955
2030
|
},
|
|
1956
|
-
generateInstructions:function(mii,
|
|
2031
|
+
generateInstructions:function(mii,full){
|
|
2032
|
+
let type=mii.console?.toLowerCase();
|
|
1957
2033
|
if(type.toLowerCase()==="wii"){
|
|
1958
2034
|
var instrs={
|
|
1959
2035
|
"base":`Select "${mii.info.gender}", and then "Start from Scratch".`,
|
|
@@ -2068,5 +2144,97 @@ module.exports={
|
|
|
2068
2144
|
}
|
|
2069
2145
|
return instrs;
|
|
2070
2146
|
}
|
|
2147
|
+
},
|
|
2148
|
+
convert3DSMiiToStudio:function(jsonIn){
|
|
2149
|
+
if(!["3ds","wii u"].includes(jsonIn.console?.toLowerCase())){
|
|
2150
|
+
jsonIn=this.convertMii(jsonIn);
|
|
2151
|
+
}
|
|
2152
|
+
var mii=jsonIn;
|
|
2153
|
+
var studioMii=new Uint8Array([0x08, 0x00, 0x40, 0x03, 0x08, 0x04, 0x04, 0x02, 0x02, 0x0c, 0x03, 0x01, 0x06, 0x04, 0x06, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x0a, 0x01, 0x00, 0x21, 0x40, 0x04, 0x00, 0x02, 0x14, 0x03, 0x13, 0x04, 0x17, 0x0d, 0x04, 0x00, 0x0a, 0x04, 0x01, 0x09]);
|
|
2154
|
+
function encodeStudio(studio) {
|
|
2155
|
+
function byteToString(int){
|
|
2156
|
+
var str = int.toString(16);
|
|
2157
|
+
if(str.length < 2)str = '0' + str;
|
|
2158
|
+
return str;
|
|
2159
|
+
}
|
|
2160
|
+
var n = 0;
|
|
2161
|
+
var eo;
|
|
2162
|
+
var dest = byteToString(n);
|
|
2163
|
+
for (var i = 0; i < studio.length; i++) {
|
|
2164
|
+
eo = (7 + (studio[i] ^ n)) & 0xFF;
|
|
2165
|
+
n = eo;
|
|
2166
|
+
dest += byteToString(eo);
|
|
2167
|
+
}
|
|
2168
|
+
return dest;
|
|
2169
|
+
}
|
|
2170
|
+
studioMii[0x16] = mii.info.gender==="Male"?0:1;
|
|
2171
|
+
studioMii[0x15] = favCols.indexOf(mii.info.favColor);
|
|
2172
|
+
studioMii[0x1E] = mii.info.height;
|
|
2173
|
+
studioMii[2] = mii.info.weight;
|
|
2174
|
+
studioMii[0x13] = tables.faces[mii.face.shape];
|
|
2175
|
+
studioMii[0x11] = skinCols.indexOf(mii.face.col);
|
|
2176
|
+
studioMii[0x14] = faceFeatures3DS.indexOf(mii.face.feature);
|
|
2177
|
+
studioMii[0x12] = makeups3DS.indexOf(mii.face.makeup);
|
|
2178
|
+
studioMii[0x1D] = tables.hairs[mii.hair.style[0]][mii.hair.style[1]];
|
|
2179
|
+
studioMii[0x1B] = hairCols.indexOf(mii.hair.col);
|
|
2180
|
+
if (!studioMii[0x1B]) studioMii[0x1B] = 8;
|
|
2181
|
+
studioMii[0x1C] = mii.hair.flipped?1:0;
|
|
2182
|
+
studioMii[7] = tables.eyes[mii.eyes.type[0]][mii.eyes.type[1]];
|
|
2183
|
+
studioMii[4] = eyeCols.indexOf(mii.eyes.col) + 8;
|
|
2184
|
+
studioMii[6] = mii.eyes.size;
|
|
2185
|
+
studioMii[3] = mii.eyes.squash;
|
|
2186
|
+
studioMii[5] = mii.eyes.rot;
|
|
2187
|
+
studioMii[8] = mii.eyes.distApart;
|
|
2188
|
+
studioMii[9] = mii.eyes.yPos;
|
|
2189
|
+
studioMii[0xE] = tables.eyebrows[mii.eyebrows.style[0]][mii.eyebrows.style[1]];
|
|
2190
|
+
studioMii[0xB] = hairCols.indexOf(mii.eyebrows.col);
|
|
2191
|
+
if (!studioMii[0xB]) studioMii[0xB] = 8;
|
|
2192
|
+
studioMii[0xD] = mii.eyebrows.size;
|
|
2193
|
+
studioMii[0xA] = mii.eyebrows.squash;
|
|
2194
|
+
studioMii[0xC] = mii.eyebrows.rot;
|
|
2195
|
+
studioMii[0xF] = mii.eyebrows.distApart;
|
|
2196
|
+
studioMii[0x10] = mii.eyebrows.yPos+3;
|
|
2197
|
+
studioMii[0x2C] = tables.noses[mii.nose.type[0]][mii.nose.type[1]];
|
|
2198
|
+
studioMii[0x2B] = mii.nose.size;
|
|
2199
|
+
studioMii[0x2D] = mii.nose.yPos;
|
|
2200
|
+
studioMii[0x26] = tables.mouths[mii.mouth.type[0]][mii.mouth.type[1]];
|
|
2201
|
+
studioMii[0x24] = mouthCols3DS.indexOf(mii.mouth.col);
|
|
2202
|
+
if (studioMii[0x24] < 4) {
|
|
2203
|
+
studioMii[0x24] += 19;
|
|
2204
|
+
} else {
|
|
2205
|
+
studioMii[0x24] = 0;
|
|
2206
|
+
}
|
|
2207
|
+
studioMii[0x25] = mii.mouth.size;
|
|
2208
|
+
studioMii[0x23] = mii.mouth.squash;
|
|
2209
|
+
studioMii[0x27] = mii.mouth.yPos;
|
|
2210
|
+
studioMii[0x29] = mii.facialHair.mustacheType;
|
|
2211
|
+
studioMii[1] = mii.facialHair.beardType;
|
|
2212
|
+
studioMii[0] = hairCols.indexOf(mii.facialHair.col);
|
|
2213
|
+
if (!studioMii[0]) studioMii[0] = 8;
|
|
2214
|
+
studioMii[0x28] = mii.facialHair.mustacheSize;
|
|
2215
|
+
studioMii[0x2A] = mii.facialHair.mustacheYPos;
|
|
2216
|
+
studioMii[0x19] = mii.glasses.type;
|
|
2217
|
+
studioMii[0x17] = glassesCols3DS.indexOf(mii.glasses.col);
|
|
2218
|
+
if (!studioMii[0x17]) {
|
|
2219
|
+
studioMii[0x17] = 8;
|
|
2220
|
+
} else if (studioMii[0x17] < 6) {
|
|
2221
|
+
studioMii[0x17] += 13;
|
|
2222
|
+
} else {
|
|
2223
|
+
studioMii[0x17] = 0;
|
|
2224
|
+
}
|
|
2225
|
+
studioMii[0x18] = mii.glasses.size;
|
|
2226
|
+
studioMii[0x1A] = mii.glasses.yPos;
|
|
2227
|
+
studioMii[0x20] = mii.mole.on?1:0;
|
|
2228
|
+
studioMii[0x1F] = mii.mole.size;
|
|
2229
|
+
studioMii[0x21] = mii.mole.xPos;
|
|
2230
|
+
studioMii[0x22] = mii.mole.yPos;
|
|
2231
|
+
return encodeStudio(studioMii);
|
|
2232
|
+
},
|
|
2233
|
+
render3DSMii:async function(jsonIn,fflRes=_fflRes){
|
|
2234
|
+
if(!["3ds","wii u"].includes(jsonIn.console?.toLowerCase())){
|
|
2235
|
+
jsonIn=this.convertMii(jsonIn);
|
|
2236
|
+
}
|
|
2237
|
+
return await renderMii(this.convert3DSMiiToStudio(jsonIn),fflRes);
|
|
2071
2238
|
}
|
|
2072
2239
|
}
|
|
2240
|
+
module.exports=exports;
|