import { profanity } from "@2toad/profanity"; import { AES_CCM } from "@trafficlunar/asmcrypto.js"; import { MII_DECRYPTION_KEY } from "./constants"; import Mii from "./mii.js/mii"; import TomodachiLifeMii from "./tomodachi-life-mii"; export function convertQrCode(bytes: Uint8Array): { mii: Mii; tomodachiLifeMii: TomodachiLifeMii } { // Decrypt the Mii part of the QR code // (Credits to kazuki-4ys) const nonce = bytes.subarray(0, 8); const content = bytes.subarray(8, 0x70); const nonceWithZeros = new Uint8Array(12); nonceWithZeros.set(nonce, 0); let decrypted: Uint8Array = new Uint8Array(); try { decrypted = AES_CCM.decrypt(content, MII_DECRYPTION_KEY, nonceWithZeros, undefined, 16); } catch { throw new Error("Failed to decrypt QR code. It may be invalid or corrupted"); } const result = new Uint8Array(96); result.set(decrypted.subarray(0, 12), 0); result.set(nonce, 12); result.set(decrypted.subarray(12), 20); // Check if QR code is valid (after decryption) if (result.length !== 0x60 || (result[0x16] !== 0 && result[0x17] !== 0)) throw new Error("QR code is not a valid Mii QR code"); // Convert to Mii classes try { const buffer = Buffer.from(result); const mii = new Mii(buffer); const tomodachiLifeMii = TomodachiLifeMii.fromBytes(bytes); if (tomodachiLifeMii.hairDyeEnabled) { mii.hairColor = tomodachiLifeMii.studioHairColor; mii.eyebrowColor = tomodachiLifeMii.studioHairColor; mii.facialHairColor = tomodachiLifeMii.studioHairColor; } // Censor potential inappropriate words tomodachiLifeMii.firstName = profanity.censor(tomodachiLifeMii.firstName); tomodachiLifeMii.lastName = profanity.censor(tomodachiLifeMii.lastName); tomodachiLifeMii.islandName = profanity.censor(tomodachiLifeMii.islandName); return { mii, tomodachiLifeMii }; } catch (error) { console.error(error); throw new Error("Mii data is not valid"); } }