UNPKG

miijs

Version:

Work with Mii characters in every possible way needed for your project.

1,574 lines (1,546 loc) 110 kB
//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