feat: mii rendering

This commit is contained in:
trafficlunar 2025-04-04 20:42:03 +01:00
parent 3a2e4c9b63
commit fb4d790b3d
3 changed files with 67 additions and 7 deletions

View file

@ -11,6 +11,7 @@
},
"dependencies": {
"@auth/prisma-adapter": "2.7.2",
"@pretendonetwork/mii-js": "github:PretendoNetwork/mii-js#publish-and-eslint",
"@prisma/client": "^6.5.0",
"@trafficlunar/asmcrypto.js": "^1.0.2",
"@yudiel/react-qr-scanner": "2.2.2-beta.2",
@ -18,6 +19,7 @@
"jsqr": "^1.4.0",
"next": "15.2.4",
"next-auth": "5.0.0-beta.25",
"qrcode-generator": "^1.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.8",

View file

@ -11,6 +11,9 @@ importers:
'@auth/prisma-adapter':
specifier: 2.7.2
version: 2.7.2(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2))
'@pretendonetwork/mii-js':
specifier: github:PretendoNetwork/mii-js#publish-and-eslint
version: https://codeload.github.com/PretendoNetwork/mii-js/tar.gz/8b857460b61dde9729eff848d99fd3ef9128f2a5
'@prisma/client':
specifier: ^6.5.0
version: 6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2)
@ -32,6 +35,9 @@ importers:
next-auth:
specifier: 5.0.0-beta.25
version: 5.0.0-beta.25(next@15.2.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
qrcode-generator:
specifier: ^1.4.4
version: 1.4.4
react:
specifier: ^19.0.0
version: 19.1.0
@ -626,6 +632,10 @@ packages:
'@panva/hkdf@1.2.1':
resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
'@pretendonetwork/mii-js@https://codeload.github.com/PretendoNetwork/mii-js/tar.gz/8b857460b61dde9729eff848d99fd3ef9128f2a5':
resolution: {tarball: https://codeload.github.com/PretendoNetwork/mii-js/tar.gz/8b857460b61dde9729eff848d99fd3ef9128f2a5}
version: 1.0.4
'@prisma/client@6.5.0':
resolution: {integrity: sha512-M6w1Ql/BeiGoZmhMdAZUXHu5sz5HubyVcKukbLs3l0ELcQb8hTUJxtGEChhv4SVJ0QJlwtLnwOLgIRQhpsm9dw==}
engines: {node: '>=18.18'}
@ -1001,6 +1011,9 @@ packages:
barcode-detector@3.0.1:
resolution: {integrity: sha512-3fCzG/Py4SVgZJhubD1mt7rVprtHEVWrxQN4FUOG0oulPE4193evbgyptxcOYsfTNEtMlWc+Ec9tdxhjlR4/Ww==}
bit-buffer@0.2.5:
resolution: {integrity: sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@ -1934,6 +1947,9 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
qrcode-generator@1.4.4:
resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -2722,6 +2738,10 @@ snapshots:
'@panva/hkdf@1.2.1': {}
'@pretendonetwork/mii-js@https://codeload.github.com/PretendoNetwork/mii-js/tar.gz/8b857460b61dde9729eff848d99fd3ef9128f2a5':
dependencies:
bit-buffer: 0.2.5
'@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2)':
optionalDependencies:
prisma: 6.5.0(typescript@5.8.2)
@ -3109,6 +3129,8 @@ snapshots:
transitivePeerDependencies:
- '@types/emscripten'
bit-buffer@0.2.5: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
@ -4201,6 +4223,8 @@ snapshots:
punycode@2.3.1: {}
qrcode-generator@1.4.4: {}
queue-microtask@1.2.3: {}
react-dom@19.1.0(react@19.1.0):

View file

@ -4,12 +4,14 @@ import { useEffect, useState } from "react";
import { useDropzone } from "react-dropzone";
import { Icon } from "@iconify/react";
import { AES_CCM } from "@trafficlunar/asmcrypto.js";
import Mii from "@pretendonetwork/mii-js";
import qrcode from "qrcode-generator";
import TagSelector from "./submit/tag-selector";
import QrUpload from "./submit/qr-upload";
import QrScanner from "./submit/qr-scanner";
import { AES_CCM } from "@trafficlunar/asmcrypto.js";
const key = new Uint8Array([0x59, 0xfc, 0x81, 0x7e, 0x64, 0x46, 0xea, 0x61, 0x90, 0x34, 0x7b, 0x20, 0xe9, 0xbd, 0xce, 0x52]);
export default function SubmitForm() {
@ -22,10 +24,14 @@ export default function SubmitForm() {
const [isQrScannerOpen, setIsQrScannerOpen] = useState(false);
const [qrBytes, setQrBytes] = useState<Uint8Array>(new Uint8Array());
const [studioUrl, setStudioUrl] = useState<string | undefined>();
const [generatedQrCodeUrl, setGeneratedQrCodeUrl] = useState<string | undefined>();
useEffect(() => {
if (qrBytes.length == 0) return;
const decrypt = async () => {
const decode = async () => {
// Decrypt the QR code
const nonce = qrBytes.subarray(0, 8);
const content = qrBytes.subarray(8, 0x70);
@ -33,17 +39,45 @@ export default function SubmitForm() {
nonceWithZeros.set(nonce, 0);
const decrypted = AES_CCM.decrypt(content, key, nonceWithZeros, undefined, 16);
const result = new Uint8Array([...decrypted.subarray(0, 12), ...qrBytes.subarray(0, 8), ...decrypted.subarray(12, decrypted.length - 4)]);
const result = new Uint8Array(96);
result.set(decrypted.subarray(0, 12), 0);
result.set(nonce, 12);
result.set(decrypted.subarray(12), 20);
console.log(result);
// Convert to Mii class
const buffer = Buffer.from(result);
const mii = new Mii(buffer);
setStudioUrl(mii.studioUrl({ width: 128 }));
// Generate a new QR code for aesthetic reasons
const byteString = String.fromCharCode(...qrBytes);
const generatedCode = qrcode(0, "L");
generatedCode.addData(byteString, "Byte");
generatedCode.make();
setGeneratedQrCodeUrl(generatedCode.createDataURL());
};
decrypt();
decode();
}, [qrBytes]);
return (
<form onSubmit={(e) => e.preventDefault()} className="grid grid-cols-2">
<div className="p-4">
<div className="p-4 flex flex-col gap-2">
<div className="flex justify-center gap-2">
<img
src={studioUrl}
alt="Nintendo Studio URL"
className="aspect-square size-32 bg-orange-100 rounded-xl border-2 border-amber-500 text-[0px]"
/>
<img
src={generatedQrCodeUrl}
alt="Generated QR Code"
className="aspect-square size-32 bg-orange-100 rounded-xl border-2 border-amber-500 text-[0px]"
/>
</div>
<div className="p-2 border-2 bg-orange-100 border-amber-500 rounded-2xl shadow-lg h-48">
<div
{...getRootProps({