miijs
Version:
Work with Mii characters in every possible way needed for your project.
1,574 lines (1,546 loc) • 110 kB
JavaScript
//Imports
const fs = require('fs');
const nodeCanvas = require('canvas');
const { createCanvas, loadImage, ImageData } = nodeCanvas;
const jsQR = require('jsqr');
const Jimp = require('jimp');
const THREE = require('three');
const QRCodeStyling = require("qr-code-styling");
const { JSDOM } = require("jsdom");
const httpsLib = require('https');
const asmCrypto=require("./asmCrypto.js");
const path=require("path");
const createGL = require('gl');
const req=require("require-esm-in-cjs");
const {
createCharModel, initCharModelTextures,
initializeFFL, exitFFL, parseHexOrB64ToUint8Array,
setIsWebGL1State, getCameraForViewType, ViewType
} = req("ffl.js/ffl.js");
const ModuleFFL = require("ffl.js/examples/ffl-emscripten-single-file.js");
const FFLShaderMaterial = require("ffl.js/FFLShaderMaterial.js");
// Typedefs for intellisence
/** @typedef {import('./types').WiiMii} WiiMii */
//Tools
function Uint8Cat(){
var destLength = 0
for(var i = 0;i < arguments.length;i++){
destLength += arguments[i].length;
}
var dest = new Uint8Array(destLength);
var index = 0;
for(var i=0;i<arguments.length;i++){
dest.set(arguments[i],index);
index += arguments[i].length;
}
return dest;
}
async function downloadImage(url) {
return new Promise((resolve, reject) => {
httpsLib.get(url, (res) => {
if (res.statusCode === 200) {
const data = [];
res.on('data', chunk => data.push(chunk));
res.on('end', () => resolve(Buffer.concat(data)));
res.on('error', reject);
} else {
res.resume();
reject(new Error(`Request Failed With a Status Code: ${res.statusCode}`));
}
});
});
}
function byteToString(int){
var str = int.toString(16);
if(str.length < 2)str = '0' + str;
return str;
}
//If FFLResHigh.dat is in the same directory as Node.js is calling the library from, use it by default
let _fflRes; // undefined initially
function getFFLRes() {
// If we've already tried loading, just return the result
if (_fflRes !== undefined) return _fflRes;
for (const path of [ "./FFLResHigh.dat", "./ffl/FFLResHigh.dat" ]) {
if (fs.existsSync(path))
return _fflRes = new Uint8Array(fs.readFileSync(path));
}
// If no file found, mark as null
return _fflRes = null;
}
//3DS QR Code (En|De)cryption
var NONCE_OFFSET = 0xC;
var NONCE_LENGTH = 8;
var TAG_LENGTH = 0x10;
var aes_key = new Uint8Array([0x59, 0xFC, 0x81, 0x7E, 0x64, 0x46, 0xEA, 0x61, 0x90, 0x34, 0x7B, 0x20, 0xE9, 0xBD, 0xCE, 0x52]);
var pad = new Uint8Array([0,0,0,0]);
function decodeAesCcm(data){
var nonce = Uint8Cat(data.subarray(0,NONCE_LENGTH),pad);
var ciphertext = data.subarray(NONCE_LENGTH,0x70);
var plaintext = asmCrypto.AES_CCM.decrypt(ciphertext,aes_key,nonce,undefined,TAG_LENGTH);
return Uint8Cat(plaintext.subarray(0,NONCE_OFFSET),data.subarray(0,NONCE_LENGTH),plaintext.subarray(NONCE_OFFSET,plaintext.length - 4));
}
function crcCalc(data){
var crc = 0;
for (var byteIndex = 0;byteIndex < data.length; byteIndex++){
for (var bitIndex = 7; bitIndex >= 0; bitIndex--){
crc = (((crc << 1) | ((data[byteIndex] >> bitIndex) & 0x1)) ^
(((crc & 0x8000) != 0) ? 0x1021 : 0));
}
}
for(var counter = 16; counter > 0; counter--){
crc = ((crc << 1) ^ (((crc & 0x8000) != 0) ? 0x1021 : 0));
}
return(crc & 0xFFFF);
}
function encodeAesCcm(data){
var nonce = Uint8Cat(data.subarray(NONCE_OFFSET,NONCE_OFFSET + NONCE_LENGTH),pad);
var crcSrc = Uint8Cat(data,new Uint8Array([0,0]));
var crc = crcCalc(crcSrc);
var cfsd = Uint8Cat(crcSrc,new Uint8Array([crc >>> 8,crc & 0xff]));
var plaintext = Uint8Cat(cfsd.subarray(0,NONCE_OFFSET),cfsd.subarray(NONCE_OFFSET + NONCE_LENGTH,cfsd.length),pad,pad);
var ciphertext = asmCrypto.AES_CCM.encrypt(plaintext,aes_key,nonce,undefined,TAG_LENGTH);
return Uint8Cat(cfsd.subarray(NONCE_OFFSET,NONCE_OFFSET + NONCE_LENGTH),ciphertext.subarray(0,ciphertext.length - 24),ciphertext.subarray(ciphertext.length - TAG_LENGTH,ciphertext.length))
}
//Miscellaneous Tables
const lookupTables = {
favCols: ["Red", "Orange", "Yellow", "Lime", "Green", "Blue", "Cyan", "Pink", "Purple", "Brown", "White", "Black"],
skinCols: ["White", "Tanned White", "Darker White", "Tanned Darker", "Mostly Black", "Black"],
hairCols: ["Black", "Brown", "Red", "Reddish Brown", "Grey", "Light Brown", "Dark Blonde", "Blonde"],
eyeCols: ["Black", "Grey", "Brown", "Lime", "Blue", "Green"],
wiiFaceFeatures: ["None", "Blush", "Makeup and Blush", "Freckles", "Bags", "Wrinkles on Cheeks", "Wrinkles near Eyes", "Chin Wrinkle", "Makeup", "Stubble", "Wrinkles near Mouth", "Wrinkles"],
wiiMouthColors: ["Peach", "Red", "Pink"],
wiiGlassesCols: ["Grey", "Brown", "Red", "Blue", "Yellow", "White"],
wiiNoses: {
"1": 0,
"10": 1,
"2": 2,
"3": 3,
"6": 4,
"0": 5,
"5": 6,
"4": 7,
"8": 8,
"9": 9,
"7": 10,
},
pages:{
mouths: {
'0': '1',
'1': '1',
'2': '2',
'3': '2',
'4': '2',
'5': '1',
'6': '1',
'7': '2',
'8': '1',
'9': '2',
'10': '1',
'11': '2',
'12': '2',
'13': '1',
'14': '2',
'15': '2',
'16': '1',
'17': '2',
'18': '2',
'19': '1',
'20': '2',
'21': '1',
'22': '1',
'23': '1'
},
eyebrows:{
'0': '1',
'1': '1',
'2': '2',
'3': '2',
'4': '1',
'5': '1',
'6': '1',
'7': '1',
'8': '1',
'9': '1',
'10': '2',
'11': '2',
'12': '1',
'13': '2',
'14': '2',
'15': '2',
'16': '2',
'17': '1',
'18': '2',
'19': '1',
'20': '2',
'21': '1',
'22': '2',
'23': '2'
},
eyes:{
'0': '1',
'1': '1',
'2': '1',
'3': '4',
'4': '1',
'5': '3',
'6': '3',
'7': '4',
'8': '1',
'9': '2',
'10': '4',
'11': '2',
'12': '2',
'13': '3',
'14': '4',
'15': '1',
'16': '1',
'17': '1',
'18': '3',
'19': '2',
'20': '1',
'21': '2',
'22': '4',
'23': '2',
'24': '3',
'25': '2',
'26': '1',
'27': '1',
'28': '3',
'29': '4',
'30': '3',
'31': '3',
'32': '2',
'33': '2',
'34': '2',
'35': '2',
'36': '3',
'37': '3',
'38': '4',
'39': '1',
'40': '2',
'41': '3',
'42': '4',
'43': '4',
'44': '4',
'45': '4',
'46': '3',
'47': '4'
},
hairs:{
'0': '5',
'1': '4',
'2': '6',
'3': '5',
'4': '4',
'5': '4',
'6': '5',
'7': '4',
'8': '4',
'9': '6',
'10': '5',
'11': '5',
'12': '4',
'13': '4',
'14': '5',
'15': '6',
'16': '6',
'17': '5',
'18': '6',
'19': '4',
'20': '5',
'21': '5',
'22': '5',
'23': '3',
'24': '6',
'25': '4',
'26': '4',
'27': '4',
'28': '6',
'29': '6',
'30': '3',
'31': '1',
'32': '2',
'33': '1',
'34': '3',
'35': '5',
'36': '3',
'37': '2',
'38': '3',
'39': '1',
'40': '1',
'41': '3',
'42': '3',
'43': '3',
'44': '1',
'45': '1',
'46': '6',
'47': '2',
'48': '2',
'49': '1',
'50': '2',
'51': '1',
'52': '2',
'53': '6',
'54': '3',
'55': '2',
'56': '1',
'57': '3',
'58': '2',
'59': '1',
'60': '2',
'61': '6',
'62': '2',
'63': '5',
'64': '2',
'65': '3',
'66': '2',
'67': '3',
'68': '1',
'69': '4',
'70': '1',
'71': '6'
}
},
types:{
"mouths": {
"0": 6,
"1": 1,
"2": 2,
"3": 4,
"4": 5,
"5": 5,
"6": 10,
"7": 0,
"8": 7,
"9": 1,
"10": 8,
"11": 7,
"12": 11,
"13": 11,
"14": 10,
"15": 6,
"16": 9,
"17": 3,
"18": 9,
"19": 2,
"20": 8,
"21": 3,
"22": 4,
"23": 0
},
"eyebrows": {
"0": 1,
"1": 3,
"2": 2,
"3": 3,
"4": 11,
"5": 10,
"6": 0,
"7": 6,
"8": 8,
"9": 4,
"10": 1,
"11": 0,
"12": 2,
"13": 7,
"14": 4,
"15": 6,
"16": 10,
"17": 9,
"18": 9,
"19": 5,
"20": 5,
"21": 7,
"22": 8,
"23": 11
},
"eyes": {
"0": 2,
"1": 6,
"2": 0,
"3": 6,
"4": 1,
"5": 0,
"6": 5,
"7": 0,
"8": 3,
"9": 4,
"10": 9,
"11": 1,
"12": 5,
"13": 2,
"14": 10,
"15": 9,
"16": 8,
"17": 5,
"18": 9,
"19": 2,
"20": 11,
"21": 8,
"22": 8,
"23": 6,
"24": 6,
"25": 9,
"26": 7,
"27": 10,
"28": 10,
"29": 5,
"30": 7,
"31": 8,
"32": 3,
"33": 0,
"34": 7,
"35": 11,
"36": 3,
"37": 4,
"38": 2,
"39": 4,
"40": 10,
"41": 1,
"42": 3,
"43": 7,
"44": 1,
"45": 4,
"46": 11,
"47": 11
},
"hairs": {
"0": 11,
"1": 6,
"2": 5,
"3": 1,
"4": 4,
"5": 8,
"6": 4,
"7": 11,
"8": 9,
"9": 3,
"10": 3,
"11": 6,
"12": 0,
"13": 1,
"14": 0,
"15": 10,
"16": 1,
"17": 8,
"18": 4,
"19": 7,
"20": 5,
"21": 10,
"22": 2,
"23": 3,
"24": 9,
"25": 5,
"26": 3,
"27": 10,
"28": 6,
"29": 11,
"30": 9,
"31": 11,
"32": 0,
"33": 0,
"34": 11,
"35": 9,
"36": 6,
"37": 2,
"38": 1,
"39": 4,
"40": 1,
"41": 7,
"42": 2,
"43": 0,
"44": 3,
"45": 6,
"46": 2,
"47": 1,
"48": 3,
"49": 7,
"50": 7,
"51": 2,
"52": 5,
"53": 7,
"54": 5,
"55": 8,
"56": 9,
"57": 10,
"58": 6,
"59": 8,
"60": 10,
"61": 0,
"62": 11,
"63": 7,
"64": 9,
"65": 8,
"66": 4,
"67": 4,
"68": 10,
"69": 2,
"70": 5,
"71": 8
}
},
// 3DS fields
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"],
makeups3DS: ["None", "Blush", "Orange Blush", "Blue Eyes", "Blush 2", "Orange Blush 2", "Blue Eyes and Blush", "Orange Eyes and Blush", "Purple Eyes and Blush 2", "Freckles", "Beard Stubble", "Beard and Mustache Stubble"],
mouthCols3DS: ["Orange", "Red", "Pink", "Peach", "Black"],
glassesCols3DS: ["Black", "Brown", "Red", "Blue", "Yellow", "Grey"],
faces: {
indexLookup: true,
values: [
0x00, 0x01, 0x08,
0x02, 0x03, 0x09,
0x04, 0x05, 0x0a,
0x06, 0x07, 0x0b
]
},
hairs: {
paginated: true,
indexLookup: true,
values: [
[0x21, 0x2f, 0x28, 0x25, 0x20, 0x6b, 0x30, 0x33, 0x37, 0x46, 0x2c, 0x42],
[0x34, 0x32, 0x26, 0x31, 0x2b, 0x1f, 0x38, 0x44, 0x3e, 0x73, 0x4c, 0x77],
[0x40, 0x51, 0x74, 0x79, 0x16, 0x3a, 0x3c, 0x57, 0x7d, 0x75, 0x49, 0x4b],
[0x2a, 0x59, 0x39, 0x36, 0x50, 0x22, 0x17, 0x56, 0x58, 0x76, 0x27, 0x24],
[0x2d, 0x43, 0x3b, 0x41, 0x29, 0x1e, 0x0c, 0x10, 0x0a, 0x52, 0x80, 0x81],
[0x0e, 0x5f, 0x69, 0x64, 0x06, 0x14, 0x5d, 0x66, 0x1b, 0x04, 0x11, 0x6e],
[0x7b, 0x08, 0x6a, 0x48, 0x03, 0x15, 0x00, 0x62, 0x3f, 0x5a, 0x0b, 0x78],
[0x05, 0x4a, 0x6c, 0x5e, 0x7c, 0x19, 0x63, 0x45, 0x23, 0x0d, 0x7a, 0x71],
[0x35, 0x18, 0x55, 0x53, 0x47, 0x83, 0x60, 0x65, 0x1d, 0x07, 0x0f, 0x70],
[0x4f, 0x01, 0x6d, 0x7f, 0x5b, 0x1a, 0x3d, 0x67, 0x02, 0x4d, 0x12, 0x5c],
[0x54, 0x09, 0x13, 0x82, 0x61, 0x68, 0x2e, 0x4e, 0x1c, 0x72, 0x7e, 0x6f]
]
},
eyebrows: {
indexLookup: true,
paginated: true,
values: [
[0x06, 0x00, 0x0c, 0x01, 0x09, 0x13, 0x07, 0x15, 0x08, 0x11, 0x05, 0x04],
[0x0b, 0x0a, 0x02, 0x03, 0x0e, 0x14, 0x0f, 0x0d, 0x16, 0x12, 0x10, 0x17]
]
},
eyes: {
indexLookup: true,
paginated: true,
values: [
[0x02, 0x04, 0x00, 0x08, 0x27, 0x11, 0x01, 0x1a, 0x10, 0x0f, 0x1b, 0x14],
[0x21, 0x0b, 0x13, 0x20, 0x09, 0x0c, 0x17, 0x22, 0x15, 0x19, 0x28, 0x23],
[0x05, 0x29, 0x0d, 0x24, 0x25, 0x06, 0x18, 0x1e, 0x1f, 0x12, 0x1c, 0x2e],
[0x07, 0x2c, 0x26, 0x2a, 0x2d, 0x1d, 0x03, 0x2b, 0x16, 0x0a, 0x0e, 0x2f],
[0x30, 0x31, 0x32, 0x35, 0x3b, 0x38, 0x36, 0x3a, 0x39, 0x37, 0x33, 0x34]
]
},
noses: {
indexLookup: true,
paginated: true,
values: [
[0x01, 0x0a, 0x02, 0x03, 0x06, 0x00, 0x05, 0x04, 0x08, 0x09, 0x07, 0x0B],
[0x0d, 0x0e, 0x0c, 0x11, 0x10, 0x0f]
]
},
mouths: {
indexLookup: true,
paginated: true,
values: [
[0x17, 0x01, 0x13, 0x15, 0x16, 0x05, 0x00, 0x08, 0x0a, 0x10, 0x06, 0x0d],
[0x07, 0x09, 0x02, 0x11, 0x03, 0x04, 0x0f, 0x0b, 0x14, 0x12, 0x0e, 0x0c],
[0x1b, 0x1e, 0x18, 0x19, 0x1d, 0x1c, 0x1a, 0x23, 0x1f, 0x22, 0x21, 0x20]
]
}
};
var convTables={
face3DSToWii:[0,1,2,2,3,1,4,5,4,6,7,6],
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.
makeup3DSToWii:[0,1,1,2,1,1,2,2,2,3,9,9],
nose3DSToWii:[
[0,1,2,3,4,5,6,7,8,9,10,11],
[0,3,4,6,9,2]
],
mouth3DSToWii:[
["111","121","131","112","122","132","113","123","133","114","124","134"],
["211","221","231","212","222","232","213","223","233","214","224","234"],
["121","214","134","123","121","112","124","133","221","224","121","232"]
],
hair3DSToWii:[
[
"111","221","121",
"231","211","121",
"212","131","233",
"132","112","222"
],
[
"232","223","321",
"123","311","134",
"114","124","234",
"114","134","234"
],
[
"214","523","433",
"214","531","512",
"523","433","134",
"414","523","134"
],
[
"331","333","324",
"332","333","334",
"312","322","322",
"113","122","313"
],
[
"113","322","133",
"333","323","314",
"411","621","521",
"424","424","424"
],
[
"511","411","411",
"422","522","523",
"534","523","434",
"422","533","424"
],
[
"511","531","534",
"623","521","524",
"534","523","523",
"424","513","523"
],
[
"411","523","512",
"513","432","432",
"621","431","514",
"421","432","514"
],
[
"623","614","633",
"633","633","624",
"434","633","634",
"624","624","634"
],
[
"634","413","412",
"413","413","412",
"611","622","632",
"611","622","632"
],
[
"423","632","423",
"612","612","613",
"631","631","613",
"631","631","613"
]
],
eyebrows3DSToWii:[
[
"111","121","131",
"112","122","132",
"113","123","133",
"114","124","134"
],
[
"211","221","231",
"212","222","232",
"213","223","233",
"214","224","234"
]
],
eyes3DSToWii:[
[
"111","121","131",
"112","122","132",
"113","123","133",
"114","124","134"
],
[
"211","221","231",
"212","222","232",
"213","223","233",
"214","224","234"
],
[
"311","321","331",
"312","322","332",
"313","323","333",
"314","324","334"
],
[
"411","421","431",
"412","422","432",
"413","423","433",
"414","424","434"
],
[
"322","322","312",
"224","224","431",
"224","224","111",
"121","411","431"
]
],
hairWiiTo3DS:[
[
[0,0],[0,2],[0,7],
[0,10],[3,10],[0,9],
[4,0],[1,3],[3,8],
[1,6],[1,7],[1,5]
],
[
[0,4],[0,1],[0,3],
[0,6],[0,11],[1,0],
[2,5],[1,1],[0,8],
[2,0],[2,6],[1,8]
],
[
[1,4],[1,2],[3,0],
[4,0],[3,6],[3,3],
[3,11],[4,4],[4,3],
[4,5],[3,2],[3,5]
],
[
[4,6],[7,9],[7,7],
[9,5],[5,9],[7,5],
[9,1],[10,2],[7,0],
[6,1],[5,8],[8,9]
],
[
[5,0],[6,4],[2,4],
[4,8],[5,4],[5,5],
[6,10],[6,8],[5,10],
[7,8],[6,5],[6,6]
],
[
[9,6],[4,7],[10,6],
[10,1],[9,10],[9,8],
[10,8],[8,1],[2,0],
[9,1],[8,9],[8,8]
]
],
faceWiiTo3DS:[
0,1,
3,4,
6,7,
9,10
],
featureWiiTo3DS:[
0,"1","6",
"9",5,2,
3,7,8,
"10",9,11
]
};
const kidNames={
"Male":[
"Aaron",
"Adam",
"Adrian",
"Aiden",
"Ayden",
"Alex",
"Alexander",
"Alfie",
"Andrew",
"Anthony",
"Archie",
"Austin",
"Ben",
"Benjamin",
"Bentley",
"Bill",
"Billy",
"Blake",
"Bradley",
"Brandon",
"Brayden",
"Brody",
"Bryson",
"Caleb",
"Callum",
"Cameron",
"Carlos",
"Charlie",
"Charles",
"Carson",
"Carter",
"Chase",
"Chris",
"Christian",
"Cody",
"Colton",
"Connor",
"Cooper",
"Damian",
"Daniel",
"David",
"Dexter",
"Dominic",
"Dylan",
"Easton",
"Edward",
"Eli",
"Elijah",
"Elliot",
"Ethan",
"Evan",
"Finlay",
"Frankie",
"Freddie",
"Gabriel",
"Gavin",
"George",
"Grayson",
"Harrison",
"Harvey",
"Henry",
"Hudson",
"Hugo",
"Hunter",
"Ian",
"Isaac",
"Isaiah",
"Jace",
"Jack",
"Jackson",
"Jaxon",
"Jacob",
"Jake",
"James",
"Jason",
"Jayden",
"Jenson",
"Jeremiah",
"John",
"Juan",
"Jonathan",
"Jordan",
"Jose",
"Joseph",
"Josiah",
"Joshua",
"Jude",
"Julian",
"Justin",
"Kai",
"Kayden",
"Kevin",
"Kian",
"Landon",
"Levi",
"Leo",
"Logan",
"Lucas",
"Luke",
"Luis",
"Lachlan",
"Mason",
"Matthew",
"Max",
"Michael",
"Miguel",
"Nathan",
"Nathaniel",
"Nicholas",
"Noah",
"Nolan",
"Olly",
"Oliver",
"Owen",
"Parker",
"Philip",
"Rhys",
"Reece",
"Rob",
"Robert",
"Ryan",
"Ryder",
"Samuel",
"Sebastian",
"Seth",
"Thomas",
"Tommy",
"Trent",
"Tristan",
"Tyler",
"William",
"Liam",
"Wyatt",
"Xavier",
"Zac",
"Zachary",
"Alex",
"Alexis",
"Angel",
"Bailey",
"Darcy",
"Darcey",
"Genesis",
"Kennedy",
"Mackenzie",
"Morgan",
"Peyton",
"Sam",
"Taylor"
],
"Female":[
"Aaliyah",
"Abigail",
"Addison",
"Madison",
"Maddison",
"Alexa",
"Alexandra",
"Alison",
"Allison",
"Alyssa",
"Amelia",
"Amy",
"Andrea",
"Anna",
"Annabelle",
"Aria",
"Ariana",
"Arianna",
"Ashley",
"Aubree",
"Aubrey",
"Audrey",
"Autumn",
"Ava",
"Avery",
"Bella",
"Bethany",
"Brianna",
"Brooklyn",
"Camila",
"Caroline",
"Charlotte",
"Chloe",
"Khloe",
"Claire",
"Ella",
"Ellie",
"Elenor",
"Elizabeth",
"Lizabeth",
"Liza",
"Emily",
"Emma",
"Eva",
"Evie",
"Evelyn",
"Faith",
"Gabriella",
"Gianna",
"Grace",
"Hailey",
"Hannah",
"Harper",
"Heidi",
"Hollie",
"Holly",
"Isabella",
"Isobel",
"Jasmine",
"Jessica",
"Jocelyn",
"Julia",
"Katherine",
"Kayla",
"Kaylee",
"Kimberly",
"Kylie",
"Lacey",
"Lauren",
"Layla",
"Leah",
"Lexie",
"Lilian",
"Lily",
"Lola",
"London",
"Lucy",
"Lydia",
"Madeline",
"Madelyn",
"Maisie",
"Makayla",
"Maya",
"Mya",
"Megan",
"Melanie",
"Mia",
"Molly",
"Naomi",
"Natalie",
"Nevaeh",
"Olivia",
"Paige",
"Poppy",
"Piper",
"Reagan",
"Rebecca",
"Riley",
"Rosie",
"Samantha",
"Sarah",
"Savannah",
"Scarlett",
"Serenity",
"Skye",
"Skylar",
"Sofia",
"Sophia",
"Sophie",
"Spring",
"Stella",
"Summer",
"Sydney",
"Trinity",
"Vanessa",
"Victoria",
"Violet",
"Winter",
"Zara",
"Zoe",
"Zoey",
"Alex",
"Alexis",
"Angel",
"Bailey",
"Darcy",
"Darcey",
"Genesis",
"Kennedy",
"Mackenzie",
"Morgan",
"Peyton",
"Sam",
"Taylor"
]
};
//Defaults
const defaultInstrs={
wii:{
male:{
"col": "On the info page (first tab), set the Favorite Color to Red (1 from the left, top row).",
"heightWeight": "On the build page (second tab), set the height to 50%, and the weight to 50%.",
"faceShape": "On the face page (third tab), set the shape to the one 1 from the top, in the left column.",
"skinCol": "On the face page (third tab), set the color to the one 1 from the left, on the top row.",
"makeup": "On the face page's makeup tab, set the makeup to \"None\" (the one 1 from the top, and 1 from the left).",
"hairStyle": "On the hair page (fourth tab), set the hair style to the one 1 from the left, 1 from the top, on page 1.",
"hairFlipped": "",
"hairColor": "On the hair page (fourth tab), set the hair color to the one 2 from the left, on the top row.",
"eyebrowStyle": "On the eyebrow page (fifth tab), set the eyebrow style to the one 1 from the left, 1 from the top, on page 1.",
"eyebrowColor": "On the eyebrow page (fifth tab), set the eyebrow color to the one 2 from the left, on the top row.",
"eyebrowY": "",
"eyebrowSize": "",
"eyebrowRot": "",
"eyebrowDist": "",
"eyeType": "On the eye page (sixth tab), set the eye type to the one 1 from the left, 1 from the top, on page 1.",
"eyeColor": "On the eye page (sixth tab), set the color to the one 1 from the left, on the top row.",
"eyeY": "",
"eyeSize": "",
"eyeRot": "",
"eyeDist": "",
"noseType": "On the nose page (seventh tab), set the nose to the one 1 from the top, and 1 from the left.",
"noseY": "",
"noseSize": "",
"mouthType": "On the mouth page (eighth tab), set the mouth type to the one 1 from the left, 1 from the top, on page 1.",
"mouthCol": "On the mouth page (eighth tab), set the color to the one 1 from the left.",
"mouthY": "",
"mouthSize": "",
"glasses": "On the glasses page (within the ninth tab), set the glasses to the one 1 from the top, and 1 from the left.",
"glassesCol": "On the glasses page (within the ninth tab), set the color to the one 1 from the left, on the top row.",
"glassesY": "",
"glassesSize": "",
"stache": "On the mustache page (within the ninth tab), set the mustache to the one on the top-left.",
"stacheY": "",
"stacheSize": "",
"mole": "",
"moleX": "",
"moleY": "",
"moleSize": "",
"beard": "On the beard page (within the ninth tab), set the beard to the one on the top-left.",
"beardCol": "On the mustache OR beard pages (within the ninth tab), set the color to the one 1 from the left, on the top row."
},
female:{
"col": "On the info page (first tab), set the Favorite Color to Red (1 from the left, top row).",
"heightWeight": "On the build page (second tab), set the height to 50%, and the weight to 50%.",
"faceShape": "On the face page (third tab), set the shape to the one 1 from the top, in the left column.",
"skinCol": "On the face page (third tab), set the color to the one 1 from the left, on the top row.",
"makeup": "On the face page's makeup tab, set the makeup to \"None\" (the one 1 from the top, and 1 from the left).",
"hairStyle": "On the hair page (fourth tab), set the hair style to the one 1 from the left, 1 from the top, on page 4.",
"hairFlipped": "",
"hairColor": "On the hair page (fourth tab), set the hair color to the one 2 from the left, on the top row.",
"eyebrowStyle": "On the eyebrow page (fifth tab), set the eyebrow style to the one 2 from the left, 1 from the top, on page 1.",
"eyebrowColor": "On the eyebrow page (fifth tab), set the eyebrow color to the one 2 from the left, on the top row.",
"eyebrowY": "",
"eyebrowSize": "",
"eyebrowRot": "",
"eyebrowDist": "",
"eyeType": "On the eye page (sixth tab), set the eye type to the one 2 from the left, 1 from the top, on page 1.",
"eyeColor": "On the eye page (sixth tab), set the color to the one 1 from the left, on the top row.",
"eyeY": "",
"eyeSize": "",
"eyeRot": "On the eye page (sixth tab), press the rotate clockwise button 1 times.",
"eyeDist": "",
"noseType": "On the nose page (seventh tab), set the nose to the one 0 from the top, and 1 from the left.",
"noseY": "",
"noseSize": "",
"mouthType": "On the mouth page (eighth tab), set the mouth type to the one 1 from the left, 1 from the top, on page 1.",
"mouthCol": "On the mouth page (eighth tab), set the color to the one 1 from the left.",
"mouthY": "",
"mouthSize": "",
"glasses": "On the glasses page (within the ninth tab), set the glasses to the one 1 from the top, and 1 from the left.",
"glassesCol": "On the glasses page (within the ninth tab), set the color to the one 1 from the left, on the top row.",
"glassesY": "",
"glassesSize": "",
"stache": "On the mustache page (within the ninth tab), set the mustache to the one on the top-left.",
"stacheY": "",
"stacheSize": "",
"mole": "",
"moleX": "",
"moleY": "",
"moleSize": "",
"beard": "On the beard page (within the ninth tab), set the beard to the one on the top-left.",
"beardCol": "On the mustache OR beard pages (within the ninth tab), set the color to the one 1 from the left, on the top row."
}
},
"3ds":{
"male":{
"faceShape": "On the face page (first tab), set the face shape to the one 1 from the top, and 1 from the left.",
"skinCol": "On the face page (first tab), set the color to the one 1 from the top.",
"makeup": "On the face page's makeup tab, set the makeup to \"None\" (the one 1 from the top, and 1 from the left).",
"feature": "On the face page's wrinkles tab, set the facial feature to \"None\" (the one 2 from the top, and 1 from the left).",
"hairStyle": "On the hair page (second tab), set the hair style to the one 1 from the top, and 1 from the left, on page 1.",
"hairFlipped": "",
"hairColor": "On the hair page (second tab), set the hair color to the one 2 from the top.",
"eyebrowStyle": "On the eyebrow page (third tab), set the eyebrow style to the one 1 from the left, 1 from the top, on page 1.",
"eyebrowColor": "On the eyebrow page (third tab), set the eyebrow color to the one 2 from the top.",
"eyebrowY": "",
"eyebrowSize": "",
"eyebrowRot": "",
"eyebrowDist": "",
"eyebrowSquash": "",
"eyeType": "On the eye page (fourth tab), set the eye type to the one 1 from the left, 1 from the top, on page 1.",
"eyeColor": "On the eye page (fourth tab), set the color to the one 1 from the top.",
"eyeY": "",
"eyeSize": "",
"eyeRot": "",
"eyeDist": "",
"eyeSquash": "",
"noseType": "On the nose page (fifth tab), set the nose to the one 1 from the top, and 1 from the left, on page 0.",
"noseY": "",
"noseSize": "",
"mouthType": "On the mouth page (sixth tab), set the mouth type to the one 1 from the left, 1 from the top, on page 1.",
"mouthCol": "On the mouth page (sixth tab), set the color to the one 1 from the top.",
"mouthY": "",
"mouthSize": "",
"mouthSquash": "",
"glasses": "On the glasses page (within the seventh tab), set the glasses to the one 1 from the top, and 1 from the left.",
"glassesCol": "On the glasses page (within the seventh tab), set the color to the one 1 from the top.",
"glassesY": "",
"glassesSize": "",
"stache": "On the mustache page (within the seventh tab), set the mustache to the one on the top-left.",
"stacheY": "",
"stacheSize": "",
"mole": "",
"moleX": "",
"moleY": "",
"moleSize": "",
"beard": "On the beard page (within the seventh tab), set the beard to the one on the top-left.",
"beardCol": "On the mustache OR beard pages (within the seventh tab), set the color to the one 1 from the top.",
"heightWeight": "On the build page (eighth tab), set the height to 50%, and the weight to 50%.",
"col": "On the info page (after pressing \"Next\"), set the Favorite Color to Red (1 from the left, top row)."
},
"female":{
"faceShape": "On the face page (first tab), set the face shape to the one 1 from the top, and 1 from the left.",
"skinCol": "On the face page (first tab), set the color to the one 1 from the top.",
"makeup": "On the face page's makeup tab, set the makeup to \"None\" (the one 1 from the top, and 1 from the left).",
"feature": "On the face page's wrinkles tab, set the facial feature to \"None\" (the one 2 from the top, and 1 from the left).",
"hairStyle": "On the hair page (second tab), set the hair style to the one 3 from the top, and 1 from the left, on page 5.",
"hairFlipped": "",
"hairColor": "On the hair page (second tab), set the hair color to the one 2 from the top.",
"eyebrowStyle": "On the eyebrow page (third tab), set the eyebrow style to the one 2 from the left, 1 from the top, on page 1.",
"eyebrowColor": "On the eyebrow page (third tab), set the eyebrow color to the one 2 from the top.",
"eyebrowY": "",
"eyebrowSize": "",
"eyebrowRot": "",
"eyebrowDist": "",
"eyebrowSquash": "",
"eyeType": "On the eye page (fourth tab), set the eye type to the one 2 from the left, 1 from the top, on page 1.",
"eyeColor": "On the eye page (fourth tab), set the color to the one 1 from the top.",
"eyeY": "",
"eyeSize": "",
"eyeRot": "",
"eyeDist": "",
"eyeSquash": "",
"noseType": "On the nose page (fifth tab), set the nose to the one 1 from the top, and 1 from the left, on page 0.",
"noseY": "",
"noseSize": "",
"mouthType": "On the mouth page (sixth tab), set the mouth type to the one 1 from the left, 1 from the top, on page 1.",
"mouthCol": "On the mouth page (sixth tab), set the color to the one 1 from the top.",
"mouthY": "",
"mouthSize": "",
"mouthSquash": "",
"glasses": "On the glasses page (within the seventh tab), set the glasses to the one 1 from the top, and 1 from the left.",
"glassesCol": "On the glasses page (within the seventh tab), set the color to the one 1 from the top.",
"glassesY": "",
"glassesSize": "",
"stache": "On the mustache page (within the seventh tab), set the mustache to the one on the top-left.",
"stacheY": "",
"stacheSize": "",
"mole": "",
"moleX": "",
"moleY": "",
"moleSize": "",
"beard": "On the beard page (within the seventh tab), set the beard to the one on the top-left.",
"beardCol": "On the mustache OR beard pages (within the seventh tab), set the color to the one 1 from the top.",
"heightWeight": "On the build page (eighth tab), set the height to 50%, and the weight to 50%.",
"col": "On the info page (after pressing \"Next\"), set the Favorite Color to Red (1 from the left, top row)."
}
}
};
const defaultMii={
"male":{
"general": {
"type":3,
"birthday": 17,
"birthMonth": 4,
"height": 0,
"weight": 0,
"gender": 1,
"favoriteColor": 7
},
"meta":{
"name": "Madison",
"creatorName": "",
"console":"3ds"
},
"perms": {
"sharing": false,
"copying": true,
"fromCheckMiiOut": false,
"mingle": true
},
"hair": {
"page":0,
"type":7,
"color": 7,
"flipped": false
},
"face": {
"type": 5,
"color": 0,
"feature": 0,
"makeup": 0
},
"eyes": {
"page":0,
"type": 9,
"col": 4,
"size": 1,
"squash": 3,
"rotation": 4,
"distanceApart": 3,
"yPosition": 11
},
"eyebrows": {
"page":0,
"type":5,
"color":7,
"size": 2,
"squash": 4,
"rotation": 4,
"distanceApart": 4,
"yPosition": 6
},
"nose": {
"page":1,
"type":0,
"size": 0,
"yPosition": 5
},
"mouth": {
"page":1,
"type":6,
"color": 0,
"size": 2,
"squash": 3,
"yPosition": 10
},
"beard": {
"mustache":{
"type": 0,
"size": 4,
"yPosition": 10
},
"col": 0,
"type": 0
},
"glasses": {
"type": 0,
"color":0,
"size": 4,
"yPosition": 10
},
"mole": {
"on": false,
"size": 4,
"xPosition": 2,
"yPosition": 20
},
"name": "",
"creatorName": ""
},
"female":{
"general": {
"type":3,
"birthday": 17,
"birthMonth": 4,
"height": 0,
"weight": 0,
"gender": 1,
"favoriteColor": 7
},
"meta":{
"name": "Madison",
"creatorName": "",
"console":"3ds"
},
"perms": {
"sharing": false,
"copying": true,
"fromCheckMiiOut": false,
"mingle": true
},
"hair": {
"page":0,
"type":7,
"color": 7,
"flipped": false
},
"face": {
"type": 5,
"color": 0,
"feature": 0,
"makeup": 0
},
"eyes": {
"page":0,
"type": 9,
"col": 4,
"size": 1,
"squash": 3,
"rotation": 4,
"distanceApart": 3,
"yPosition": 11
},
"eyebrows": {
"page":0,
"type":5,
"color":7,
"size": 2,
"squash": 4,
"rotation": 4,
"distanceApart": 4,
"yPosition": 6
},
"nose": {
"page":1,
"type":0,
"size": 0,
"yPosition": 5
},
"mouth": {
"page":1,
"type":6,
"color": 0,
"size": 2,
"squash": 3,
"yPosition": 10
},
"beard": {
"mustache":{
"type": 0,
"size": 4,
"yPosition": 10
},
"col": 0,
"type": 0
},
"glasses": {
"type": 0,
"color":0,
"size": 4,
"yPosition": 10
},
"mole": {
"on": false,
"size": 4,
"xPosition": 2,
"yPosition": 20
},
"name": "",
"creatorName": ""
}
};
// Mii binary helpers
const decoders = {
number: (value, field) => value + (field.offset || 0),
boolean: (value, field) => field.invert ? value === 0 : value === 1,
enum: (value, field) => field.values[value],
lookup: (value, field, tables) => {
const table = getNestedProperty(tables, field.lookupTable)
if (!table) return "ERROR: could not find requested lookup table";
if (table.indexLookup) {
if (table.paginated) {
// Handle paginated (2D array) lookup
for (let page = 0; page < table.values.length; page++) {
for (let index = 0; index < table.values[page].length; index++) {
if (table.values[page][index] === value) {
return [page, index];
}
}
}
return undefined;
} else {
// Handle non-paginated index lookup
return table.values.indexOf(value);
}
} else if (Array.isArray(table)) {
return table[value];
} else {
return table[value.toString()];
}
},
lookupPage: (value, field, tables, type) => {
const table = getNestedProperty(tables, field.lookupTable)
if (!table) return "ERROR: could not find requested lookup table";
if (table.indexLookup) {
if (table.paginated) {
// Handle paginated (2D array) lookup
for (let page = 0; page < table.values.length; page++) {
for (let index = 0; index < table.values[page].length; index++) {
if (table.values[page][index] === value) {
return [page, index][0];
}
}
}
return undefined;
} else {
// Handle non-paginated index lookup
return table.values.indexOf(value);
}
} else if (Array.isArray(table)) {
return table[value];
} else {
return table[value.toString()];
}
},
lookupType: (value, field, tables, type) => {
const table = getNestedProperty(tables, field.lookupTable)
if (!table) return "ERROR: could not find requested lookup table";
if (table.indexLookup) {
if (table.paginated) {
// Handle paginated (2D array) lookup
for (let page = 0; page < table.values.length; page++) {
for (let index = 0; index < table.values[page].length; index++) {
if (table.values[page][index] === value) {
return [page, index][1];
}
}
}
return undefined;
} else {
// Handle non-paginated index lookup
return table.values.indexOf(value);
}
} else if (Array.isArray(table)) {
return table[value];
} else {
return table[value.toString()];
}
},
color: (value, field, tables) => tables[field.colorArray]?.[value] || value
};
const encoders = {
number: (value, field) => value - (field.offset || 0),
boolean: (value, field) => field.invert ? (value ? 0 : 1) : (value ? 1 : 0),
enum: (value, field) => field.values.indexOf(value),
lookup: (decodedValue, field, tables) => {
const table = getNestedProperty(tables, field.lookupTable)
if (!table) return "ERROR: could not find requested lookup table";
if (table.indexLookup){
if (table.paginated) {
if (!Array.isArray(decodedValue) || decodedValue.length !== 2) {
return undefined;
}
const [page, index] = decodedValue;
if (page >= 0 && page < table.values.length && index >= 0 && index < table.values[page].length) {
return table.values[page][index];
}
return undefined;
} else {
return table.values[decodedValue];
}
} else if (Array.isArray(table)) {
const index = table.indexOf(decodedValue);
return index !== -1 ? index : undefined;
} else {
// Handle object lookup
for (const [key, val] of Object.entries(table)) {
if (val === decodedValue) return parseInt(key);
}
return undefined;
}
},
color: (value, field, T) => {
const arr = T[field.colorArray];
return arr?.indexOf(value) ?? value;
},
};
// Decoding system
function decodeString(data, field) {
let result = "";
const maxLength = field.maxLength || 10;
for (let i = 0; i < maxLength; i++) {
const charOffset = field.byteOffset + (i * 2);
if (charOffset + 1 < data.length) {
const char1 = data[charOffset];
const char2 = data[charOffset + 1];
if (char1 === 0 && char2 === 0) break;
result += String.fromCharCode(field.endianness == "little" ? char1 : char2);
}
}
return result.replace(/\x00/g, "");
}
function encodeString(str, field) {
const result = [];
const maxLength = field.maxLength || 10;
for (let i = 0; i < maxLength; i++) {
const code = i < str.length ? str.charCodeAt(i) : 0;
if (field.endianness == "little") {
result.push(code); // Low byte
result.push(0); // High byte
} else {
result.push(0); // High byte
result.push(code); // Low byte
}
}
return result;
}
function extractMultiBits(data, bitSpecs, isBigEndian = true) {
let result = 0;
let totalBitsProcessed = 0;
// Process bit specs in order (they should be ordered from most significant to least significant)
for (const spec of bitSpecs) {
const bits = extractBits(data, spec.byteOffset, spec.bitOffset, spec.bitLength, isBigEndian);
result = (result << spec.bitLength) | bits;
totalBitsProcessed += spec.bitLength;
}
return result;
}
function setMultiBits(buffer, bitSpecs, value) {
let remainingValue = value;
// Process specs in reverse order (from least significant to most significant)
for (let i = bitSpecs.length - 1; i >= 0; i--) {
const spec = bitSpecs[i];
const mask = (1 << spec.bitLength) - 1;
const bits