mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-28 11:13:16 +00:00
chore: update packages
also accidentally prettified some code along the way
This commit is contained in:
parent
4656b969d6
commit
e05533b19a
6 changed files with 1150 additions and 850 deletions
32
package.json
32
package.json
|
|
@ -2,7 +2,7 @@
|
|||
"name": "tomodachi-share",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
"packageManager": "pnpm@10.28.2",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
|
|
@ -16,45 +16,45 @@
|
|||
"@auth/prisma-adapter": "2.11.1",
|
||||
"@bprogress/next": "^3.2.12",
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@prisma/client": "^6.19.1",
|
||||
"bit-buffer": "^0.2.5",
|
||||
"@prisma/client": "^6.19.2",
|
||||
"bit-buffer": "^0.3.0",
|
||||
"canvas-confetti": "^1.9.4",
|
||||
"dayjs": "^1.11.19",
|
||||
"downshift": "^9.0.13",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"file-type": "^21.1.1",
|
||||
"ioredis": "^5.8.2",
|
||||
"file-type": "^21.3.0",
|
||||
"ioredis": "^5.9.2",
|
||||
"jsqr": "^1.4.0",
|
||||
"next": "16.0.10",
|
||||
"next": "16.1.6",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"qrcode-generator": "^2.0.4",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-webcam": "^7.2.0",
|
||||
"satori": "^0.18.3",
|
||||
"satori": "^0.19.1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"sharp": "^0.34.5",
|
||||
"sjcl-with-all": "1.0.8",
|
||||
"swr": "^2.3.7",
|
||||
"zod": "^4.1.13"
|
||||
"swr": "^2.3.8",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@iconify/react": "^6.0.2",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@types/canvas-confetti": "^1.9.0",
|
||||
"@types/node": "^25.0.2",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/node": "^25.1.0",
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/seedrandom": "^3.0.8",
|
||||
"@types/sjcl": "^1.0.34",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "16.0.10",
|
||||
"prisma": "^6.19.1",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"prisma": "^6.19.2",
|
||||
"schema-dts": "^1.1.5",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.15"
|
||||
"vitest": "^4.0.18"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1377
pnpm-lock.yaml
1377
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -25,7 +25,11 @@ const submitSchema = z.object({
|
|||
name: nameSchema,
|
||||
tags: tagsSchema,
|
||||
description: z.string().trim().max(256).optional(),
|
||||
qrBytesRaw: z.array(z.number(), { error: "A QR code is required" }).length(372, { error: "QR code size is not a valid Tomodachi Life QR code" }),
|
||||
qrBytesRaw: z
|
||||
.array(z.number(), { error: "A QR code is required" })
|
||||
.length(372, {
|
||||
error: "QR code size is not a valid Tomodachi Life QR code",
|
||||
}),
|
||||
image1: z.union([z.instanceof(File), z.any()]).optional(),
|
||||
image2: z.union([z.instanceof(File), z.any()]).optional(),
|
||||
image3: z.union([z.instanceof(File), z.any()]).optional(),
|
||||
|
|
@ -33,15 +37,19 @@ const submitSchema = z.object({
|
|||
|
||||
export async function POST(request: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
if (!session)
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const rateLimit = new RateLimit(request, 2);
|
||||
const check = await rateLimit.handle();
|
||||
if (check) return check;
|
||||
|
||||
const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`);
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`,
|
||||
);
|
||||
const { value } = await response.json();
|
||||
if (!value) return rateLimit.sendResponse({ error: "Submissions are disabled" }, 409);
|
||||
if (!value)
|
||||
return rateLimit.sendResponse({ error: "Submissions are disabled" }, 409);
|
||||
|
||||
// Parse data
|
||||
const formData = await request.formData();
|
||||
|
|
@ -52,7 +60,10 @@ export async function POST(request: NextRequest) {
|
|||
rawTags = JSON.parse(formData.get("tags") as string);
|
||||
rawQrBytesRaw = JSON.parse(formData.get("qrBytesRaw") as string);
|
||||
} catch {
|
||||
return rateLimit.sendResponse({ error: "Invalid JSON in tags or QR bytes" }, 400);
|
||||
return rateLimit.sendResponse(
|
||||
{ error: "Invalid JSON in tags or QR bytes" },
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = submitSchema.safeParse({
|
||||
|
|
@ -65,13 +76,26 @@ export async function POST(request: NextRequest) {
|
|||
image3: formData.get("image3"),
|
||||
});
|
||||
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const { name: uncensoredName, tags: uncensoredTags, description: uncensoredDescription, qrBytesRaw, image1, image2, image3 } = parsed.data;
|
||||
if (!parsed.success)
|
||||
return rateLimit.sendResponse(
|
||||
{ error: parsed.error.issues[0].message },
|
||||
400,
|
||||
);
|
||||
const {
|
||||
name: uncensoredName,
|
||||
tags: uncensoredTags,
|
||||
description: uncensoredDescription,
|
||||
qrBytesRaw,
|
||||
image1,
|
||||
image2,
|
||||
image3,
|
||||
} = parsed.data;
|
||||
|
||||
// Censor potential inappropriate words
|
||||
const name = profanity.censor(uncensoredName);
|
||||
const tags = uncensoredTags.map((t) => profanity.censor(t));
|
||||
const description = uncensoredDescription && profanity.censor(uncensoredDescription);
|
||||
const description =
|
||||
uncensoredDescription && profanity.censor(uncensoredDescription);
|
||||
|
||||
// Validate image files
|
||||
const images: File[] = [];
|
||||
|
|
@ -83,7 +107,10 @@ export async function POST(request: NextRequest) {
|
|||
if (imageValidation.valid) {
|
||||
images.push(img);
|
||||
} else {
|
||||
return rateLimit.sendResponse({ error: imageValidation.error }, imageValidation.status ?? 400);
|
||||
return rateLimit.sendResponse(
|
||||
{ error: imageValidation.error },
|
||||
imageValidation.status ?? 400,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +141,10 @@ export async function POST(request: NextRequest) {
|
|||
});
|
||||
|
||||
// Ensure directories exist
|
||||
const miiUploadsDirectory = path.join(uploadsDirectory, miiRecord.id.toString());
|
||||
const miiUploadsDirectory = path.join(
|
||||
uploadsDirectory,
|
||||
miiRecord.id.toString(),
|
||||
);
|
||||
await fs.mkdir(miiUploadsDirectory, { recursive: true });
|
||||
|
||||
// Download the image of the Mii
|
||||
|
|
@ -134,12 +164,17 @@ export async function POST(request: NextRequest) {
|
|||
await prisma.mii.delete({ where: { id: miiRecord.id } });
|
||||
|
||||
console.error("Failed to download Mii image:", error);
|
||||
return rateLimit.sendResponse({ error: "Failed to download Mii image" }, 500);
|
||||
return rateLimit.sendResponse(
|
||||
{ error: "Failed to download Mii image" },
|
||||
500,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Compress and store
|
||||
const studioWebpBuffer = await sharp(studioBuffer).webp({ quality: 85 }).toBuffer();
|
||||
const studioWebpBuffer = await sharp(studioBuffer)
|
||||
.webp({ quality: 85 })
|
||||
.toBuffer();
|
||||
const studioFileLocation = path.join(miiUploadsDirectory, "mii.webp");
|
||||
|
||||
await fs.writeFile(studioFileLocation, studioWebpBuffer);
|
||||
|
|
@ -156,7 +191,9 @@ export async function POST(request: NextRequest) {
|
|||
const codeBuffer = Buffer.from(codeBase64, "base64");
|
||||
|
||||
// Compress and store
|
||||
const codeWebpBuffer = await sharp(codeBuffer).webp({ quality: 85 }).toBuffer();
|
||||
const codeWebpBuffer = await sharp(codeBuffer)
|
||||
.webp({ quality: 85 })
|
||||
.toBuffer();
|
||||
const codeFileLocation = path.join(miiUploadsDirectory, "qr-code.webp");
|
||||
|
||||
await fs.writeFile(codeFileLocation, codeWebpBuffer);
|
||||
|
|
@ -166,7 +203,10 @@ export async function POST(request: NextRequest) {
|
|||
await prisma.mii.delete({ where: { id: miiRecord.id } });
|
||||
|
||||
console.error("Error processing Mii files:", error);
|
||||
return rateLimit.sendResponse({ error: "Failed to process and store Mii files" }, 500);
|
||||
return rateLimit.sendResponse(
|
||||
{ error: "Failed to process and store Mii files" },
|
||||
500,
|
||||
);
|
||||
}
|
||||
|
||||
// Compress and store user images
|
||||
|
|
@ -175,10 +215,13 @@ export async function POST(request: NextRequest) {
|
|||
images.map(async (image, index) => {
|
||||
const buffer = Buffer.from(await image.arrayBuffer());
|
||||
const webpBuffer = await sharp(buffer).webp({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(miiUploadsDirectory, `image${index}.webp`);
|
||||
const fileLocation = path.join(
|
||||
miiUploadsDirectory,
|
||||
`image${index}.webp`,
|
||||
);
|
||||
|
||||
await fs.writeFile(fileLocation, webpBuffer);
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
// Update database to tell it how many images exist
|
||||
|
|
@ -192,7 +235,10 @@ export async function POST(request: NextRequest) {
|
|||
});
|
||||
} catch (error) {
|
||||
console.error("Error storing user images:", error);
|
||||
return rateLimit.sendResponse({ error: "Failed to store user images" }, 500);
|
||||
return rateLimit.sendResponse(
|
||||
{ error: "Failed to store user images" },
|
||||
500,
|
||||
);
|
||||
}
|
||||
|
||||
return rateLimit.sendResponse({ success: true, id: miiRecord.id });
|
||||
|
|
|
|||
|
|
@ -31,12 +31,14 @@ export default function SubmitForm() {
|
|||
if (files.length >= 3) return;
|
||||
setFiles((prev) => [...prev, ...acceptedFiles]);
|
||||
},
|
||||
[files.length]
|
||||
[files.length],
|
||||
);
|
||||
|
||||
const [isQrScannerOpen, setIsQrScannerOpen] = useState(false);
|
||||
const [studioUrl, setStudioUrl] = useState<string | undefined>();
|
||||
const [generatedQrCodeUrl, setGeneratedQrCodeUrl] = useState<string | undefined>();
|
||||
const [generatedQrCodeUrl, setGeneratedQrCodeUrl] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
|
|
@ -76,7 +78,7 @@ export default function SubmitForm() {
|
|||
const { id, error } = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setError(error);
|
||||
setError(String(error)); // app can crash if error message is not a string
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -127,16 +129,29 @@ export default function SubmitForm() {
|
|||
<form className="flex justify-center gap-4 w-full max-lg:flex-col max-lg:items-center">
|
||||
<div className="flex justify-center">
|
||||
<div className="w-75 h-min flex flex-col bg-zinc-50 rounded-3xl border-2 border-zinc-300 shadow-lg p-3">
|
||||
<Carousel images={[studioUrl ?? "/loading.svg", generatedQrCodeUrl ?? "/loading.svg", ...files.map((file) => URL.createObjectURL(file))]} />
|
||||
<Carousel
|
||||
images={[
|
||||
studioUrl ?? "/loading.svg",
|
||||
generatedQrCodeUrl ?? "/loading.svg",
|
||||
...files.map((file) => URL.createObjectURL(file)),
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="p-4 flex flex-col gap-1 h-full">
|
||||
<h1 className="font-bold text-2xl line-clamp-1" title={name}>
|
||||
{name || "Mii name"}
|
||||
</h1>
|
||||
<div id="tags" className="flex flex-wrap gap-1">
|
||||
{tags.length == 0 && <span className="px-2 py-1 bg-orange-300 rounded-full text-xs">tag</span>}
|
||||
{tags.length == 0 && (
|
||||
<span className="px-2 py-1 bg-orange-300 rounded-full text-xs">
|
||||
tag
|
||||
</span>
|
||||
)}
|
||||
{tags.map((tag) => (
|
||||
<span key={tag} className="px-2 py-1 bg-orange-300 rounded-full text-xs">
|
||||
<span
|
||||
key={tag}
|
||||
className="px-2 py-1 bg-orange-300 rounded-full text-xs"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
|
|
@ -152,7 +167,9 @@ export default function SubmitForm() {
|
|||
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4 flex flex-col gap-2 max-w-2xl w-full">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold">Submit your Mii</h2>
|
||||
<p className="text-sm text-zinc-500">Share your creation for others to see.</p>
|
||||
<p className="text-sm text-zinc-500">
|
||||
Share your creation for others to see.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
|
|
@ -210,15 +227,26 @@ export default function SubmitForm() {
|
|||
<QrUpload setQrBytesRaw={setQrBytesRaw} />
|
||||
<span>or</span>
|
||||
|
||||
<button type="button" aria-label="Use your camera" onClick={() => setIsQrScannerOpen(true)} className="pill button gap-2">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Use your camera"
|
||||
onClick={() => setIsQrScannerOpen(true)}
|
||||
className="pill button gap-2"
|
||||
>
|
||||
<Icon icon="mdi:camera" fontSize={20} />
|
||||
Use your camera
|
||||
</button>
|
||||
|
||||
<QrScanner isOpen={isQrScannerOpen} setIsOpen={setIsQrScannerOpen} setQrBytesRaw={setQrBytesRaw} />
|
||||
<QrScanner
|
||||
isOpen={isQrScannerOpen}
|
||||
setIsOpen={setIsQrScannerOpen}
|
||||
setQrBytesRaw={setQrBytesRaw}
|
||||
/>
|
||||
<SubmitTutorialButton />
|
||||
|
||||
<span className="text-xs text-zinc-400">For emulators, aes_keys.txt is required.</span>
|
||||
<span className="text-xs text-zinc-400">
|
||||
For emulators, aes_keys.txt is required.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Separator */}
|
||||
|
|
@ -237,14 +265,18 @@ export default function SubmitForm() {
|
|||
</p>
|
||||
</Dropzone>
|
||||
|
||||
<span className="text-xs text-zinc-400 mt-2">Animated images currently not supported.</span>
|
||||
<span className="text-xs text-zinc-400 mt-2">
|
||||
Animated images currently not supported.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ImageList files={files} setFiles={setFiles} />
|
||||
|
||||
<hr className="border-zinc-300 my-2" />
|
||||
<div className="flex justify-between items-center">
|
||||
{error && <span className="text-red-400 font-bold">Error: {error}</span>}
|
||||
{error && (
|
||||
<span className="text-red-400 font-bold">Error: {error}</span>
|
||||
)}
|
||||
|
||||
<SubmitButton onClick={handleSubmit} className="ml-auto" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Stolen from https://github.com/PretendoNetwork/mii-js/
|
||||
// Based on https://github.com/PretendoNetwork/mii-js/
|
||||
// Updated to bit-buffer v0.3.0
|
||||
|
||||
import { BitStream } from "bit-buffer";
|
||||
|
||||
|
|
@ -11,78 +12,34 @@ export default class ExtendedBitStream extends BitStream {
|
|||
this.bigEndian = !this.bigEndian;
|
||||
}
|
||||
|
||||
// the type definition for BitStream does not include the _index property
|
||||
// since it's supposed to be private, but it's needed 4 times here sooo
|
||||
|
||||
public alignByte(): void {
|
||||
// @ts-expect-error _index is private
|
||||
const nextClosestByteIndex = 8 * Math.ceil(this._index / 8);
|
||||
// @ts-expect-error _index is private
|
||||
const bitDistance = nextClosestByteIndex - this._index;
|
||||
|
||||
const nextClosestByteIndex = 8 * Math.ceil(this.index / 8);
|
||||
const bitDistance = nextClosestByteIndex - this.index;
|
||||
this.skipBits(bitDistance);
|
||||
}
|
||||
|
||||
public bitSeek(bitPos: number): void {
|
||||
// @ts-expect-error _index is private
|
||||
this._index = bitPos;
|
||||
}
|
||||
|
||||
public skipBits(bitCount: number): void {
|
||||
// @ts-expect-error _index is private
|
||||
this._index += bitCount;
|
||||
}
|
||||
|
||||
public skipBytes(bytes: number): void {
|
||||
const bits = bytes * 8;
|
||||
this.skipBits(bits);
|
||||
this.index += bitCount;
|
||||
}
|
||||
|
||||
public skipBit(): void {
|
||||
this.skipBits(1);
|
||||
}
|
||||
|
||||
public skipInt8(): void {
|
||||
this.skipBytes(1);
|
||||
}
|
||||
|
||||
public skipInt16(): void {
|
||||
// Skipping a uint16 is the same as skipping 2 uint8's
|
||||
this.skipBytes(2);
|
||||
this.skipBits(16);
|
||||
}
|
||||
|
||||
public readBit(): number {
|
||||
return this.readBits(1);
|
||||
}
|
||||
|
||||
public readBytes(length: number): number[] {
|
||||
return Array(length)
|
||||
.fill(0)
|
||||
.map(() => this.readUint8());
|
||||
return this.readBits(1, false);
|
||||
}
|
||||
|
||||
public readBuffer(length: number): Buffer {
|
||||
return Buffer.from(this.readBytes(length));
|
||||
return Buffer.from(super.readBytes(length));
|
||||
}
|
||||
|
||||
public readUTF16String(length: number): string {
|
||||
return this.readBuffer(length).toString("utf16le").replace(/\0.*$/, "");
|
||||
}
|
||||
|
||||
public writeBit(bit: number): void {
|
||||
this.writeBits(bit, 1);
|
||||
}
|
||||
|
||||
public writeBuffer(buffer: Buffer): void {
|
||||
buffer.forEach((byte) => this.writeUint8(byte));
|
||||
}
|
||||
|
||||
public writeUTF16String(string: string): void {
|
||||
const stringBuffer = Buffer.from(string, "utf16le");
|
||||
const terminatedBuffer = Buffer.alloc(0x14);
|
||||
|
||||
stringBuffer.copy(terminatedBuffer);
|
||||
|
||||
this.writeBuffer(terminatedBuffer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,14 @@ const STUDIO_RENDER_CLOTHES_COLORS = [
|
|||
"black",
|
||||
];
|
||||
|
||||
const STUDIO_RENDER_LIGHT_DIRECTION_MODS = ["none", "zerox", "flipx", "camera", "offset", "set"];
|
||||
const STUDIO_RENDER_LIGHT_DIRECTION_MODS = [
|
||||
"none",
|
||||
"zerox",
|
||||
"flipx",
|
||||
"camera",
|
||||
"offset",
|
||||
"set",
|
||||
];
|
||||
|
||||
const STUDIO_RENDER_INSTANCE_ROTATION_MODES = ["model", "camera", "both"];
|
||||
|
||||
|
|
@ -74,6 +81,7 @@ const STUDIO_BG_COLOR_REGEX = /^[0-9A-F]{8}$/; // Mii Studio does not allow lowe
|
|||
|
||||
export default class Mii {
|
||||
public bitStream: ExtendedBitStream;
|
||||
public buffer: Buffer;
|
||||
|
||||
// Mii data
|
||||
// can be sure that these are all initialized in decode()
|
||||
|
|
@ -150,92 +158,292 @@ export default class Mii {
|
|||
public checksum!: number;
|
||||
|
||||
constructor(buffer: Buffer) {
|
||||
this.buffer = buffer;
|
||||
this.bitStream = new ExtendedBitStream(buffer);
|
||||
this.decode();
|
||||
}
|
||||
|
||||
public validate(): void {
|
||||
// Size check
|
||||
assert.equal(this.bitStream.length / 8, 0x60, `Invalid Mii data size. Got ${this.bitStream.length / 8}, expected 96`);
|
||||
assert.equal(
|
||||
this.bitStream.length / 8,
|
||||
0x60,
|
||||
`Invalid Mii data size. Got ${this.bitStream.length / 8}, expected 96`,
|
||||
);
|
||||
|
||||
// Value range and type checks
|
||||
assert.ok(this.version === 0 || this.version === 3, `Invalid Mii version. Got ${this.version}, expected 0 or 3`);
|
||||
assert.equal(typeof this.allowCopying, "boolean", `Invalid Mii allow copying. Got ${this.allowCopying}, expected true or false`);
|
||||
assert.equal(typeof this.profanityFlag, "boolean", `Invalid Mii profanity flag. Got ${this.profanityFlag}, expected true or false`);
|
||||
assert.ok(Util.inRange(this.regionLock, Util.range(4)), `Invalid Mii region lock. Got ${this.regionLock}, expected 0-3`);
|
||||
assert.ok(Util.inRange(this.characterSet, Util.range(4)), `Invalid Mii region lock. Got ${this.characterSet}, expected 0-3`);
|
||||
assert.ok(Util.inRange(this.pageIndex, Util.range(10)), `Invalid Mii page index. Got ${this.pageIndex}, expected 0-9`);
|
||||
assert.ok(Util.inRange(this.slotIndex, Util.range(10)), `Invalid Mii slot index. Got ${this.slotIndex}, expected 0-9`);
|
||||
assert.equal(this.unknown1, 0, `Invalid Mii unknown1. Got ${this.unknown1}, expected 0`);
|
||||
assert.ok(Util.inRange(this.deviceOrigin, Util.range(1, 5)), `Invalid Mii device origin. Got ${this.deviceOrigin}, expected 1-4`);
|
||||
assert.equal(this.systemId.length, 8, `Invalid Mii system ID size. Got ${this.systemId.length}, system IDs must be 8 bytes long`);
|
||||
assert.equal(typeof this.normalMii, "boolean", `Invalid normal Mii flag. Got ${this.normalMii}, expected true or false`);
|
||||
assert.equal(typeof this.dsMii, "boolean", `Invalid DS Mii flag. Got ${this.dsMii}, expected true or false`);
|
||||
assert.equal(typeof this.nonUserMii, "boolean", `Invalid non-user Mii flag. Got ${this.nonUserMii}, expected true or false`);
|
||||
assert.equal(typeof this.isValid, "boolean", `Invalid Mii valid flag. Got ${this.isValid}, expected true or false`);
|
||||
assert.ok(this.creationTime < 268435456, `Invalid Mii creation time. Got ${this.creationTime}, max value for 28 bit integer is 268,435,456`);
|
||||
assert.ok(
|
||||
this.version === 0 || this.version === 3,
|
||||
`Invalid Mii version. Got ${this.version}, expected 0 or 3`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.allowCopying,
|
||||
"boolean",
|
||||
`Invalid Mii allow copying. Got ${this.allowCopying}, expected true or false`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.profanityFlag,
|
||||
"boolean",
|
||||
`Invalid Mii profanity flag. Got ${this.profanityFlag}, expected true or false`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.regionLock, Util.range(4)),
|
||||
`Invalid Mii region lock. Got ${this.regionLock}, expected 0-3`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.characterSet, Util.range(4)),
|
||||
`Invalid Mii region lock. Got ${this.characterSet}, expected 0-3`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.pageIndex, Util.range(10)),
|
||||
`Invalid Mii page index. Got ${this.pageIndex}, expected 0-9`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.slotIndex, Util.range(10)),
|
||||
`Invalid Mii slot index. Got ${this.slotIndex}, expected 0-9`,
|
||||
);
|
||||
assert.equal(
|
||||
this.unknown1,
|
||||
0,
|
||||
`Invalid Mii unknown1. Got ${this.unknown1}, expected 0`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.deviceOrigin, Util.range(1, 5)),
|
||||
`Invalid Mii device origin. Got ${this.deviceOrigin}, expected 1-4`,
|
||||
);
|
||||
assert.equal(
|
||||
this.systemId.length,
|
||||
8,
|
||||
`Invalid Mii system ID size. Got ${this.systemId.length}, system IDs must be 8 bytes long`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.normalMii,
|
||||
"boolean",
|
||||
`Invalid normal Mii flag. Got ${this.normalMii}, expected true or false`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.dsMii,
|
||||
"boolean",
|
||||
`Invalid DS Mii flag. Got ${this.dsMii}, expected true or false`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.nonUserMii,
|
||||
"boolean",
|
||||
`Invalid non-user Mii flag. Got ${this.nonUserMii}, expected true or false`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.isValid,
|
||||
"boolean",
|
||||
`Invalid Mii valid flag. Got ${this.isValid}, expected true or false`,
|
||||
);
|
||||
assert.ok(
|
||||
this.creationTime < 268435456,
|
||||
`Invalid Mii creation time. Got ${this.creationTime}, max value for 28 bit integer is 268,435,456`,
|
||||
);
|
||||
assert.equal(
|
||||
this.consoleMAC.length,
|
||||
6,
|
||||
`Invalid Mii console MAC address size. Got ${this.consoleMAC.length}, console MAC addresses must be 6 bytes long`
|
||||
`Invalid Mii console MAC address size. Got ${this.consoleMAC.length}, console MAC addresses must be 6 bytes long`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.gender, Util.range(2)),
|
||||
`Invalid Mii gender. Got ${this.gender}, expected 0 or 1`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.birthMonth, Util.range(13)),
|
||||
`Invalid Mii birth month. Got ${this.birthMonth}, expected 0-12`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.birthDay, Util.range(32)),
|
||||
`Invalid Mii birth day. Got ${this.birthDay}, expected 0-31`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.favoriteColor, Util.range(12)),
|
||||
`Invalid Mii favorite color. Got ${this.favoriteColor}, expected 0-11`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.favorite,
|
||||
"boolean",
|
||||
`Invalid favorite Mii flag. Got ${this.favorite}, expected true or false`,
|
||||
);
|
||||
assert.ok(
|
||||
Buffer.from(this.miiName, "utf16le").length <= 0x14,
|
||||
`Invalid Mii name. Got ${this.miiName}, name may only be up to 10 characters`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.height, Util.range(128)),
|
||||
`Invalid Mii height. Got ${this.height}, expected 0-127`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.build, Util.range(128)),
|
||||
`Invalid Mii build. Got ${this.build}, expected 0-127`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.disableSharing,
|
||||
"boolean",
|
||||
`Invalid disable sharing Mii flag. Got ${this.disableSharing}, expected true or false`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.faceType, Util.range(12)),
|
||||
`Invalid Mii face type. Got ${this.faceType}, expected 0-11`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.skinColor, Util.range(7)),
|
||||
`Invalid Mii skin color. Got ${this.skinColor}, expected 0-6`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.wrinklesType, Util.range(12)),
|
||||
`Invalid Mii wrinkles type. Got ${this.wrinklesType}, expected 0-11`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.makeupType, Util.range(12)),
|
||||
`Invalid Mii makeup type. Got ${this.makeupType}, expected 0-11`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.hairType, Util.range(132)),
|
||||
`Invalid Mii hair type. Got ${this.hairType}, expected 0-131`,
|
||||
);
|
||||
assert.ok(Util.inRange(this.gender, Util.range(2)), `Invalid Mii gender. Got ${this.gender}, expected 0 or 1`);
|
||||
assert.ok(Util.inRange(this.birthMonth, Util.range(13)), `Invalid Mii birth month. Got ${this.birthMonth}, expected 0-12`);
|
||||
assert.ok(Util.inRange(this.birthDay, Util.range(32)), `Invalid Mii birth day. Got ${this.birthDay}, expected 0-31`);
|
||||
assert.ok(Util.inRange(this.favoriteColor, Util.range(12)), `Invalid Mii favorite color. Got ${this.favoriteColor}, expected 0-11`);
|
||||
assert.equal(typeof this.favorite, "boolean", `Invalid favorite Mii flag. Got ${this.favorite}, expected true or false`);
|
||||
assert.ok(Buffer.from(this.miiName, "utf16le").length <= 0x14, `Invalid Mii name. Got ${this.miiName}, name may only be up to 10 characters`);
|
||||
assert.ok(Util.inRange(this.height, Util.range(128)), `Invalid Mii height. Got ${this.height}, expected 0-127`);
|
||||
assert.ok(Util.inRange(this.build, Util.range(128)), `Invalid Mii build. Got ${this.build}, expected 0-127`);
|
||||
assert.equal(typeof this.disableSharing, "boolean", `Invalid disable sharing Mii flag. Got ${this.disableSharing}, expected true or false`);
|
||||
assert.ok(Util.inRange(this.faceType, Util.range(12)), `Invalid Mii face type. Got ${this.faceType}, expected 0-11`);
|
||||
assert.ok(Util.inRange(this.skinColor, Util.range(7)), `Invalid Mii skin color. Got ${this.skinColor}, expected 0-6`);
|
||||
assert.ok(Util.inRange(this.wrinklesType, Util.range(12)), `Invalid Mii wrinkles type. Got ${this.wrinklesType}, expected 0-11`);
|
||||
assert.ok(Util.inRange(this.makeupType, Util.range(12)), `Invalid Mii makeup type. Got ${this.makeupType}, expected 0-11`);
|
||||
assert.ok(Util.inRange(this.hairType, Util.range(132)), `Invalid Mii hair type. Got ${this.hairType}, expected 0-131`);
|
||||
// assert.ok(Util.inRange(this.hairColor, Util.range(8)), `Invalid Mii hair color. Got ${this.hairColor}, expected 0-7`);
|
||||
assert.equal(typeof this.flipHair, "boolean", `Invalid flip hair flag. Got ${this.flipHair}, expected true or false`);
|
||||
assert.ok(Util.inRange(this.eyeType, Util.range(60)), `Invalid Mii eye type. Got ${this.eyeType}, expected 0-59`);
|
||||
assert.ok(Util.inRange(this.eyeColor, Util.range(6)), `Invalid Mii eye color. Got ${this.eyeColor}, expected 0-5`);
|
||||
assert.ok(Util.inRange(this.eyeScale, Util.range(8)), `Invalid Mii eye scale. Got ${this.eyeScale}, expected 0-7`);
|
||||
assert.ok(Util.inRange(this.eyeVerticalStretch, Util.range(7)), `Invalid Mii eye vertical stretch. Got ${this.eyeVerticalStretch}, expected 0-6`);
|
||||
assert.ok(Util.inRange(this.eyeRotation, Util.range(8)), `Invalid Mii eye rotation. Got ${this.eyeRotation}, expected 0-7`);
|
||||
assert.ok(Util.inRange(this.eyeSpacing, Util.range(13)), `Invalid Mii eye spacing. Got ${this.eyeSpacing}, expected 0-12`);
|
||||
assert.ok(Util.inRange(this.eyeYPosition, Util.range(19)), `Invalid Mii eye Y position. Got ${this.eyeYPosition}, expected 0-18`);
|
||||
assert.ok(Util.inRange(this.eyebrowType, Util.range(25)), `Invalid Mii eyebrow type. Got ${this.eyebrowType}, expected 0-24`);
|
||||
assert.equal(
|
||||
typeof this.flipHair,
|
||||
"boolean",
|
||||
`Invalid flip hair flag. Got ${this.flipHair}, expected true or false`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyeType, Util.range(60)),
|
||||
`Invalid Mii eye type. Got ${this.eyeType}, expected 0-59`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyeColor, Util.range(6)),
|
||||
`Invalid Mii eye color. Got ${this.eyeColor}, expected 0-5`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyeScale, Util.range(8)),
|
||||
`Invalid Mii eye scale. Got ${this.eyeScale}, expected 0-7`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyeVerticalStretch, Util.range(7)),
|
||||
`Invalid Mii eye vertical stretch. Got ${this.eyeVerticalStretch}, expected 0-6`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyeRotation, Util.range(8)),
|
||||
`Invalid Mii eye rotation. Got ${this.eyeRotation}, expected 0-7`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyeSpacing, Util.range(13)),
|
||||
`Invalid Mii eye spacing. Got ${this.eyeSpacing}, expected 0-12`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyeYPosition, Util.range(19)),
|
||||
`Invalid Mii eye Y position. Got ${this.eyeYPosition}, expected 0-18`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyebrowType, Util.range(25)),
|
||||
`Invalid Mii eyebrow type. Got ${this.eyebrowType}, expected 0-24`,
|
||||
);
|
||||
// assert.ok(Util.inRange(this.eyebrowColor, Util.range(8)), `Invalid Mii eyebrow color. Got ${this.eyebrowColor}, expected 0-7`);
|
||||
assert.ok(Util.inRange(this.eyebrowScale, Util.range(9)), `Invalid Mii eyebrow scale. Got ${this.eyebrowScale}, expected 0-8`);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyebrowScale, Util.range(9)),
|
||||
`Invalid Mii eyebrow scale. Got ${this.eyebrowScale}, expected 0-8`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyebrowVerticalStretch, Util.range(7)),
|
||||
`Invalid Mii eyebrow vertical stretch. Got ${this.eyebrowVerticalStretch}, expected 0-6`
|
||||
`Invalid Mii eyebrow vertical stretch. Got ${this.eyebrowVerticalStretch}, expected 0-6`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyebrowRotation, Util.range(12)),
|
||||
`Invalid Mii eyebrow rotation. Got ${this.eyebrowRotation}, expected 0-11`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyebrowSpacing, Util.range(13)),
|
||||
`Invalid Mii eyebrow spacing. Got ${this.eyebrowSpacing}, expected 0-12`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.eyebrowYPosition, Util.range(3, 19)),
|
||||
`Invalid Mii eyebrow Y position. Got ${this.eyebrowYPosition}, expected 3-18`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.noseType, Util.range(18)),
|
||||
`Invalid Mii nose type. Got ${this.noseType}, expected 0-17`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.noseScale, Util.range(9)),
|
||||
`Invalid Mii nose scale. Got ${this.noseScale}, expected 0-8`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.noseYPosition, Util.range(19)),
|
||||
`Invalid Mii nose Y position. Got ${this.noseYPosition}, expected 0-18`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.mouthType, Util.range(36)),
|
||||
`Invalid Mii mouth type. Got ${this.mouthType}, expected 0-35`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.mouthColor, Util.range(5)),
|
||||
`Invalid Mii mouth color. Got ${this.mouthColor}, expected 0-4`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.mouthScale, Util.range(9)),
|
||||
`Invalid Mii mouth scale. Got ${this.mouthScale}, expected 0-8`,
|
||||
);
|
||||
assert.ok(Util.inRange(this.eyebrowRotation, Util.range(12)), `Invalid Mii eyebrow rotation. Got ${this.eyebrowRotation}, expected 0-11`);
|
||||
assert.ok(Util.inRange(this.eyebrowSpacing, Util.range(13)), `Invalid Mii eyebrow spacing. Got ${this.eyebrowSpacing}, expected 0-12`);
|
||||
assert.ok(Util.inRange(this.eyebrowYPosition, Util.range(3, 19)), `Invalid Mii eyebrow Y position. Got ${this.eyebrowYPosition}, expected 3-18`);
|
||||
assert.ok(Util.inRange(this.noseType, Util.range(18)), `Invalid Mii nose type. Got ${this.noseType}, expected 0-17`);
|
||||
assert.ok(Util.inRange(this.noseScale, Util.range(9)), `Invalid Mii nose scale. Got ${this.noseScale}, expected 0-8`);
|
||||
assert.ok(Util.inRange(this.noseYPosition, Util.range(19)), `Invalid Mii nose Y position. Got ${this.noseYPosition}, expected 0-18`);
|
||||
assert.ok(Util.inRange(this.mouthType, Util.range(36)), `Invalid Mii mouth type. Got ${this.mouthType}, expected 0-35`);
|
||||
assert.ok(Util.inRange(this.mouthColor, Util.range(5)), `Invalid Mii mouth color. Got ${this.mouthColor}, expected 0-4`);
|
||||
assert.ok(Util.inRange(this.mouthScale, Util.range(9)), `Invalid Mii mouth scale. Got ${this.mouthScale}, expected 0-8`);
|
||||
assert.ok(
|
||||
Util.inRange(this.mouthHorizontalStretch, Util.range(7)),
|
||||
`Invalid Mii mouth stretch. Got ${this.mouthHorizontalStretch}, expected 0-6`
|
||||
`Invalid Mii mouth stretch. Got ${this.mouthHorizontalStretch}, expected 0-6`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.mouthYPosition, Util.range(19)),
|
||||
`Invalid Mii mouth Y position. Got ${this.mouthYPosition}, expected 0-18`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.mustacheType, Util.range(6)),
|
||||
`Invalid Mii mustache type. Got ${this.mustacheType}, expected 0-5`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.beardType, Util.range(6)),
|
||||
`Invalid Mii beard type. Got ${this.beardType}, expected 0-5`,
|
||||
);
|
||||
assert.ok(Util.inRange(this.mouthYPosition, Util.range(19)), `Invalid Mii mouth Y position. Got ${this.mouthYPosition}, expected 0-18`);
|
||||
assert.ok(Util.inRange(this.mustacheType, Util.range(6)), `Invalid Mii mustache type. Got ${this.mustacheType}, expected 0-5`);
|
||||
assert.ok(Util.inRange(this.beardType, Util.range(6)), `Invalid Mii beard type. Got ${this.beardType}, expected 0-5`);
|
||||
// assert.ok(Util.inRange(this.facialHairColor, Util.range(8)), `Invalid Mii beard type. Got ${this.facialHairColor}, expected 0-7`);
|
||||
assert.ok(Util.inRange(this.mustacheScale, Util.range(9)), `Invalid Mii mustache scale. Got ${this.mustacheScale}, expected 0-8`);
|
||||
assert.ok(Util.inRange(this.mustacheYPosition, Util.range(17)), `Invalid Mii mustache Y position. Got ${this.mustacheYPosition}, expected 0-16`);
|
||||
assert.ok(Util.inRange(this.glassesType, Util.range(9)), `Invalid Mii glassess type. Got ${this.glassesType}, expected 0-8`);
|
||||
assert.ok(Util.inRange(this.glassesColor, Util.range(6)), `Invalid Mii glassess type. Got ${this.glassesColor}, expected 0-5`);
|
||||
assert.ok(Util.inRange(this.glassesScale, Util.range(8)), `Invalid Mii glassess type. Got ${this.glassesScale}, expected 0-7`);
|
||||
assert.ok(Util.inRange(this.glassesYPosition, Util.range(21)), `Invalid Mii glassess Y position. Got ${this.glassesYPosition}, expected 0-20`);
|
||||
assert.equal(typeof this.moleEnabled, "boolean", `Invalid mole enabled flag. Got ${this.moleEnabled}, expected true or false`);
|
||||
assert.ok(Util.inRange(this.moleScale, Util.range(9)), `Invalid Mii mole scale. Got ${this.moleScale}, expected 0-8`);
|
||||
assert.ok(Util.inRange(this.moleXPosition, Util.range(17)), `Invalid Mii mole X position. Got ${this.moleXPosition}, expected 0-16`);
|
||||
assert.ok(Util.inRange(this.moleYPosition, Util.range(31)), `Invalid Mii mole Y position. Got ${this.moleYPosition}, expected 0-30`);
|
||||
assert.ok(
|
||||
Util.inRange(this.mustacheScale, Util.range(9)),
|
||||
`Invalid Mii mustache scale. Got ${this.mustacheScale}, expected 0-8`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.mustacheYPosition, Util.range(17)),
|
||||
`Invalid Mii mustache Y position. Got ${this.mustacheYPosition}, expected 0-16`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.glassesType, Util.range(9)),
|
||||
`Invalid Mii glassess type. Got ${this.glassesType}, expected 0-8`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.glassesColor, Util.range(6)),
|
||||
`Invalid Mii glassess type. Got ${this.glassesColor}, expected 0-5`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.glassesScale, Util.range(8)),
|
||||
`Invalid Mii glassess type. Got ${this.glassesScale}, expected 0-7`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.glassesYPosition, Util.range(21)),
|
||||
`Invalid Mii glassess Y position. Got ${this.glassesYPosition}, expected 0-20`,
|
||||
);
|
||||
assert.equal(
|
||||
typeof this.moleEnabled,
|
||||
"boolean",
|
||||
`Invalid mole enabled flag. Got ${this.moleEnabled}, expected true or false`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.moleScale, Util.range(9)),
|
||||
`Invalid Mii mole scale. Got ${this.moleScale}, expected 0-8`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.moleXPosition, Util.range(17)),
|
||||
`Invalid Mii mole X position. Got ${this.moleXPosition}, expected 0-16`,
|
||||
);
|
||||
assert.ok(
|
||||
Util.inRange(this.moleYPosition, Util.range(31)),
|
||||
`Invalid Mii mole Y position. Got ${this.moleYPosition}, expected 0-30`,
|
||||
);
|
||||
|
||||
// Sanity checks
|
||||
/*
|
||||
|
|
@ -251,7 +459,10 @@ export default class Mii {
|
|||
}
|
||||
*/
|
||||
|
||||
if (this.nonUserMii && (this.creationTime !== 0 || this.isValid || this.dsMii || this.normalMii)) {
|
||||
if (
|
||||
this.nonUserMii &&
|
||||
(this.creationTime !== 0 || this.isValid || this.dsMii || this.normalMii)
|
||||
) {
|
||||
assert.fail("Non-user Mii's must have all other Mii ID bits set to 0");
|
||||
}
|
||||
|
||||
|
|
@ -357,10 +568,12 @@ export default class Mii {
|
|||
}
|
||||
|
||||
public calculateCRC(): number {
|
||||
const view = this.bitStream.view;
|
||||
|
||||
// @ts-expect-error _view is private
|
||||
const data = view._view.subarray(0, 0x5e);
|
||||
// #view is inaccessible
|
||||
const data = new Uint8Array(
|
||||
this.buffer.buffer,
|
||||
this.buffer.byteOffset,
|
||||
this.buffer.length,
|
||||
).subarray(0, 0x5e);
|
||||
|
||||
let crc = 0x0000;
|
||||
|
||||
|
|
@ -506,7 +719,7 @@ export default class Mii {
|
|||
instanceCount?: number;
|
||||
instanceRotationMode?: string;
|
||||
data?: string;
|
||||
} = STUDIO_RENDER_DEFAULTS
|
||||
} = STUDIO_RENDER_DEFAULTS,
|
||||
): string {
|
||||
const params = {
|
||||
...STUDIO_RENDER_DEFAULTS,
|
||||
|
|
@ -514,11 +727,23 @@ export default class Mii {
|
|||
data: this.encodeStudio().toString("hex"),
|
||||
};
|
||||
|
||||
params.type = STUDIO_RENDER_TYPES.includes(params.type as string) ? params.type : STUDIO_RENDER_DEFAULTS.type;
|
||||
params.expression = STUDIO_RENDER_EXPRESSIONS.includes(params.expression as string) ? params.expression : STUDIO_RENDER_DEFAULTS.expression;
|
||||
params.type = STUDIO_RENDER_TYPES.includes(params.type as string)
|
||||
? params.type
|
||||
: STUDIO_RENDER_DEFAULTS.type;
|
||||
params.expression = STUDIO_RENDER_EXPRESSIONS.includes(
|
||||
params.expression as string,
|
||||
)
|
||||
? params.expression
|
||||
: STUDIO_RENDER_DEFAULTS.expression;
|
||||
params.width = Util.clamp(params.width, 512);
|
||||
params.bgColor = STUDIO_BG_COLOR_REGEX.test(params.bgColor as string) ? params.bgColor : STUDIO_RENDER_DEFAULTS.bgColor;
|
||||
params.clothesColor = STUDIO_RENDER_CLOTHES_COLORS.includes(params.clothesColor) ? params.clothesColor : STUDIO_RENDER_DEFAULTS.clothesColor;
|
||||
params.bgColor = STUDIO_BG_COLOR_REGEX.test(params.bgColor as string)
|
||||
? params.bgColor
|
||||
: STUDIO_RENDER_DEFAULTS.bgColor;
|
||||
params.clothesColor = STUDIO_RENDER_CLOTHES_COLORS.includes(
|
||||
params.clothesColor,
|
||||
)
|
||||
? params.clothesColor
|
||||
: STUDIO_RENDER_DEFAULTS.clothesColor;
|
||||
params.cameraXRotate = Util.clamp(params.cameraXRotate, 359);
|
||||
params.cameraYRotate = Util.clamp(params.cameraYRotate, 359);
|
||||
params.cameraZRotate = Util.clamp(params.cameraZRotate, 359);
|
||||
|
|
@ -528,16 +753,25 @@ export default class Mii {
|
|||
params.lightXDirection = Util.clamp(params.lightXDirection, 359);
|
||||
params.lightYDirection = Util.clamp(params.lightYDirection, 359);
|
||||
params.lightZDirection = Util.clamp(params.lightZDirection, 359);
|
||||
params.lightDirectionMode = STUDIO_RENDER_LIGHT_DIRECTION_MODS.includes(params.lightDirectionMode)
|
||||
params.lightDirectionMode = STUDIO_RENDER_LIGHT_DIRECTION_MODS.includes(
|
||||
params.lightDirectionMode,
|
||||
)
|
||||
? params.lightDirectionMode
|
||||
: STUDIO_RENDER_DEFAULTS.lightDirectionMode;
|
||||
params.instanceCount = Util.clamp(params.instanceCount, 1, 16);
|
||||
params.instanceRotationMode = STUDIO_RENDER_INSTANCE_ROTATION_MODES.includes(params.instanceRotationMode)
|
||||
? params.instanceRotationMode
|
||||
: STUDIO_RENDER_DEFAULTS.instanceRotationMode;
|
||||
params.instanceRotationMode =
|
||||
STUDIO_RENDER_INSTANCE_ROTATION_MODES.includes(
|
||||
params.instanceRotationMode,
|
||||
)
|
||||
? params.instanceRotationMode
|
||||
: STUDIO_RENDER_DEFAULTS.instanceRotationMode;
|
||||
|
||||
// converts non-string params to strings
|
||||
const query = new URLSearchParams(Object.fromEntries(Object.entries(params).map(([key, value]) => [key, value.toString()])));
|
||||
const query = new URLSearchParams(
|
||||
Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => [key, value.toString()]),
|
||||
),
|
||||
);
|
||||
|
||||
if (params.lightDirectionMode === "none") {
|
||||
query.delete("lightDirectionMode");
|
||||
|
|
|
|||
Loading…
Reference in a new issue