mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-06-27 22:24:14 +00:00
feat: .ltd files
no automatic instructions
This commit is contained in:
parent
a000447b0a
commit
817bda4993
36 changed files with 1733 additions and 1037 deletions
|
|
@ -4,6 +4,9 @@
|
|||
"dependencies": {
|
||||
"@2toad/profanity": "^3.3.0",
|
||||
"bit-buffer": "^0.3.0",
|
||||
"charinfo-ex": "^0.0.5",
|
||||
"fzstd": "^0.1.1",
|
||||
"sharp": "^0.34.5",
|
||||
"sjcl-with-all": "1.0.8",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
|
|
|
|||
133
shared/src/deswizzle.ts
Normal file
133
shared/src/deswizzle.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// TypeScript implementation of https://github.com/Aclios/pyswizzle/blob/main/src/pyswizzle/pyswizzle.py
|
||||
type Grid = Uint8Array[][];
|
||||
|
||||
export class BytesDeswizzle {
|
||||
private data: Uint8Array;
|
||||
private deswizzleDataList: [number, 0 | 1][];
|
||||
private readSize: number;
|
||||
private readPerTileCount: number;
|
||||
private tileCount: number;
|
||||
private tilePerWidth: number;
|
||||
private dataReadIdx: number;
|
||||
|
||||
constructor(data: Uint8Array, imSize: [number, number], blockSize: [number, number], bytesPerBlock: number, swizzleMode: number) {
|
||||
this.data = data;
|
||||
const datasize = data.length;
|
||||
const [imWidth, imHeight] = imSize;
|
||||
const [blockWidth, blockHeight] = blockSize;
|
||||
|
||||
const expectedDataSize = Math.floor((imWidth * imHeight) / (blockWidth * blockHeight)) * bytesPerBlock;
|
||||
|
||||
if (expectedDataSize !== datasize)
|
||||
throw new Error(
|
||||
`Error: Invalid data size.\nExpected datasize (according to image and format specifications): ${expectedDataSize}\nActual datasize: ${datasize}`,
|
||||
);
|
||||
|
||||
const tileDatasize = 512 * 2 ** swizzleMode;
|
||||
const tileWidth = Math.floor(64 / bytesPerBlock) * blockWidth;
|
||||
const tileHeight = 8 * blockHeight * 2 ** swizzleMode;
|
||||
this.deswizzleDataList = [
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[4, 0],
|
||||
[2, 1],
|
||||
[2 ** swizzleMode, 0],
|
||||
];
|
||||
this.readSize = 16;
|
||||
this.readPerTileCount = 32 * 2 ** swizzleMode;
|
||||
|
||||
if (datasize % tileDatasize !== 0)
|
||||
throw new Error(
|
||||
`Error: Invalid data size. The data size should be a multiple of ${tileDatasize}, while the given datasize is ${datasize}. Height and/or width padding may be required in the original image.`,
|
||||
);
|
||||
|
||||
this.tileCount = Math.floor(datasize / tileDatasize);
|
||||
|
||||
if (imWidth % tileWidth !== 0)
|
||||
throw new Error(`Error: with the current parameters, image width should be a multiple of ${tileWidth}, but the given width is ${imWidth}`);
|
||||
if (imHeight % tileHeight !== 0)
|
||||
throw new Error(`Error: with the current parameters, image height should be a multiple of ${tileHeight}, but the given height is ${imHeight}`);
|
||||
|
||||
this.tilePerWidth = Math.floor(imWidth / tileWidth);
|
||||
this.dataReadIdx = 0;
|
||||
}
|
||||
|
||||
private getTileData(): Grid[] {
|
||||
const arrayList: Grid[] = [];
|
||||
for (let i = 0; i < this.readPerTileCount; i++) {
|
||||
arrayList.push([[this.data.slice(this.dataReadIdx, this.dataReadIdx + this.readSize)]]);
|
||||
this.dataReadIdx += this.readSize;
|
||||
}
|
||||
return arrayList;
|
||||
}
|
||||
|
||||
private concatArrays(arrayList: Grid[], sectionNumber: number, axis: 0 | 1): Grid[] {
|
||||
const newArrayList: Grid[] = [];
|
||||
let idx = 0;
|
||||
|
||||
for (let i = 0; i < Math.floor(arrayList.length / sectionNumber); i++) {
|
||||
const slice = arrayList.slice(idx, idx + sectionNumber);
|
||||
let newGrid: Grid;
|
||||
|
||||
if (axis === 0) {
|
||||
// np.concatenate(..., axis=0)
|
||||
newGrid = [];
|
||||
for (const grid of slice) {
|
||||
newGrid.push(...grid);
|
||||
}
|
||||
} else {
|
||||
// np.concatenate(..., axis=1)
|
||||
newGrid = [];
|
||||
for (let r = 0; r < slice[0].length; r++) {
|
||||
const newRow: Uint8Array[] = [];
|
||||
for (const grid of slice) {
|
||||
newRow.push(...grid[r]);
|
||||
}
|
||||
newGrid.push(newRow);
|
||||
}
|
||||
}
|
||||
|
||||
newArrayList.push(newGrid);
|
||||
idx += sectionNumber;
|
||||
}
|
||||
|
||||
return newArrayList;
|
||||
}
|
||||
|
||||
private deswizzleTile(): Grid {
|
||||
let arrayList = this.getTileData();
|
||||
for (const deswizzleData of this.deswizzleDataList) {
|
||||
arrayList = this.concatArrays(arrayList, deswizzleData[0], deswizzleData[1]);
|
||||
}
|
||||
return arrayList[0];
|
||||
}
|
||||
|
||||
public deswizzle(): Uint8Array {
|
||||
const tileList: Grid[] = [];
|
||||
for (let i = 0; i < this.tileCount; i++) {
|
||||
tileList.push(this.deswizzleTile());
|
||||
}
|
||||
|
||||
const tileListWidthConcat = this.concatArrays(tileList, this.tilePerWidth, 1);
|
||||
const deswizzledGrid = this.concatArrays(tileListWidthConcat, tileListWidthConcat.length, 0)[0];
|
||||
|
||||
// tobytes()
|
||||
const deswizzledData = new Uint8Array(this.data.length);
|
||||
let offset = 0;
|
||||
|
||||
for (const row of deswizzledGrid) {
|
||||
for (const chunk of row) {
|
||||
deswizzledData.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (deswizzledData.length !== this.data.length) {
|
||||
throw new Error(
|
||||
`An unknown error occurred while deswizzling bytes: output data length is (somehow) different than input data length. Input data: ${this.data.length}, Output data: ${deswizzledData.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
return deswizzledData;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,5 +2,6 @@ export * from "./constants";
|
|||
export * from "./qr-codes";
|
||||
export * from "./switch";
|
||||
export * from "./three-ds-tomodachi-life-mii";
|
||||
export * from "./switch-tomodachi-life-mii";
|
||||
export * from "./utils";
|
||||
export type { SwitchMiiInstructions, MiiGender, MiiMakeup, MiiPlatform, ReportReason } from "./types";
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ export const searchSchema = z.object({
|
|||
platform: z.enum(["THREE_DS", "SWITCH"], { error: "Platform must be either 'THREE_DS', or 'SWITCH'" }).optional(),
|
||||
gender: z.enum(["MALE", "FEMALE", "NONBINARY"], { error: "Gender must be either 'MALE', 'FEMALE', or 'NONBINARY' if on Switch platform" }).optional(),
|
||||
makeup: z.enum(["FULL", "PARTIAL", "NONE"], { error: "Makeup must be either 'FULL', 'PARTIAL', or 'NONE'" }).optional(),
|
||||
isFromSaveFile: z.coerce.boolean({ error: "'isFromSaveFile' must be either true or false" }).optional(),
|
||||
allowCopying: z.coerce.boolean({ error: "Allow Copying must be either true or false" }).optional(),
|
||||
quarantined: z.coerce.boolean({ error: "Quarantined must be either true or false" }).optional(),
|
||||
// Pages
|
||||
|
|
|
|||
277
shared/src/switch-tomodachi-life-mii.ts
Normal file
277
shared/src/switch-tomodachi-life-mii.ts
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
import { CharInfoEx } from "charinfo-ex";
|
||||
import * as fzstd from "fzstd";
|
||||
import { BytesDeswizzle } from "./deswizzle";
|
||||
|
||||
import { minifyInstructions } from "./switch";
|
||||
import { type MiiGender, type SwitchMiiInstructions } from "./types";
|
||||
|
||||
export class SwitchTomodachiLifeMii {
|
||||
buffer: ArrayBuffer;
|
||||
data: CharInfoEx;
|
||||
|
||||
datingPreferences: MiiGender[];
|
||||
birthday: { month: number; day: number; age: number; dontAge: boolean };
|
||||
voice: { speed: number; pitch: number; depth: number; delivery: number; tone: number };
|
||||
personality: { movement: number; speech: number; energy: number; thinking: number; overall: number };
|
||||
|
||||
constructor(buffer: ArrayBuffer, data: CharInfoEx) {
|
||||
this.buffer = buffer;
|
||||
this.data = data;
|
||||
|
||||
const view = new DataView(buffer);
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const parse = (index: number): number => view.getUint8(161 + index * 4);
|
||||
|
||||
const age = view.getUint32(0x00e1, true);
|
||||
const year = view.getUint32(0x00d9, true);
|
||||
const dontAge = age !== 0xffffffff;
|
||||
|
||||
this.datingPreferences = (["MALE", "FEMALE", "NONBINARY"] as const).filter((_, i) => bytes[0x01a9 + i] === 1);
|
||||
this.birthday = {
|
||||
month: parse(17),
|
||||
day: parse(15),
|
||||
age: dontAge ? age : new Date().getFullYear() - year,
|
||||
dontAge,
|
||||
};
|
||||
this.voice = {
|
||||
speed: parse(6),
|
||||
pitch: parse(8),
|
||||
depth: parse(5),
|
||||
delivery: Math.max(0, view.getInt8(0xc5)), // why is this an integer??
|
||||
tone: parse(7) + 1,
|
||||
// TODO: add voice preset to instructions type?
|
||||
};
|
||||
this.personality = {
|
||||
movement: parse(4) - 1,
|
||||
speech: parse(2) - 1,
|
||||
energy: parse(1) - 1,
|
||||
thinking: parse(0) - 1,
|
||||
overall: parse(3) - 1,
|
||||
};
|
||||
|
||||
// Validate
|
||||
if (bytes[0x01a9] > 1 || bytes[0x01aa] > 1 || bytes[0x01ab] > 1) throw new Error("Invalid dating preference bytes");
|
||||
if (this.birthday.month < 1 || this.birthday.month > 12) throw new Error("Invalid birthday month");
|
||||
if (this.birthday.day < 1 || this.birthday.day > 31) throw new Error("Invalid birthday day");
|
||||
if (
|
||||
this.personality.movement < 0 ||
|
||||
this.personality.movement > 4 ||
|
||||
this.personality.speech < 0 ||
|
||||
this.personality.speech > 4 ||
|
||||
this.personality.energy < 0 ||
|
||||
this.personality.energy > 4 ||
|
||||
this.personality.thinking < 0 ||
|
||||
this.personality.thinking > 4 ||
|
||||
this.personality.overall < 0 ||
|
||||
this.personality.overall > 4
|
||||
)
|
||||
throw new Error("Invalid personality values");
|
||||
}
|
||||
|
||||
// There's a UGC Texture image but we're ignoring it
|
||||
public async extractFacePaintImage(): Promise<Buffer | null> {
|
||||
try {
|
||||
if (typeof window !== "undefined") {
|
||||
throw new Error("sharp cannot run in the browser");
|
||||
}
|
||||
|
||||
const { default: sharp } = await import("sharp");
|
||||
|
||||
const buf = Buffer.from(this.buffer);
|
||||
|
||||
const canvasMarker = Buffer.from([0xa3, 0xa3, 0xa3, 0xa3]);
|
||||
const ugcMarker = Buffer.from([0xa4, 0xa4, 0xa4, 0xa4]);
|
||||
|
||||
const canvasStart = buf.indexOf(canvasMarker);
|
||||
if (canvasStart === -1) return null;
|
||||
|
||||
const ugcStart = buf.indexOf(ugcMarker);
|
||||
const canvasData = buf.subarray(canvasStart + 4, ugcStart === -1 ? undefined : ugcStart);
|
||||
|
||||
const decompressed = Buffer.from(fzstd.decompress(canvasData));
|
||||
const deswizzled = new BytesDeswizzle(decompressed, [256, 256], [1, 1], 4, 4).deswizzle();
|
||||
|
||||
return await sharp(deswizzled, {
|
||||
raw: { width: 256, height: 256, channels: 4 },
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
} catch (err) {
|
||||
console.error("extractFacePaintImage failed:", err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public toInstructions() {
|
||||
const instructions: Partial<SwitchMiiInstructions> = {
|
||||
head: {
|
||||
type: this.data.facelineType,
|
||||
skinColor: this.data.facelineColor,
|
||||
},
|
||||
hair: {
|
||||
set: this.data.hairType,
|
||||
bangs: this.data.hairTypeFront,
|
||||
back: this.data.hairTypeBack,
|
||||
color: this.data.hairColor0,
|
||||
subColor: this.data.hairColor1,
|
||||
subColor2: this.data.hairColor0, // TODO: check
|
||||
style: this.data.hairStyle,
|
||||
isFlipped: (this.data.faceFlags & (1 << 2)) !== 0, // bangsSide
|
||||
},
|
||||
eyebrows: {
|
||||
type: this.data.eyebrowType,
|
||||
color: this.data.eyebrowColor,
|
||||
height: this.data.eyebrowY - 10,
|
||||
distance: this.data.eyebrowX - 4,
|
||||
rotation: this.data.eyebrowRotate - 6,
|
||||
size: this.data.eyebrowScale - 4,
|
||||
stretch: this.data.eyebrowAspect - 3,
|
||||
},
|
||||
eyes: {
|
||||
main: {
|
||||
type: this.data.eyeType,
|
||||
color: this.data.eyeColor,
|
||||
height: this.data.eyeY - 12,
|
||||
distance: this.data.eyeX - 2,
|
||||
rotation: this.data.eyeRotate - 4,
|
||||
size: this.data.eyeScale - 4,
|
||||
stretch: this.data.eyeAspect - 3,
|
||||
},
|
||||
eyelashesTop: {
|
||||
type: this.data.eyelashUpperType,
|
||||
height: this.data.eyelashUpperY,
|
||||
distance: this.data.eyelashUpperX,
|
||||
rotation: this.data.eyelashUpperRotate,
|
||||
size: this.data.eyelashUpperScale,
|
||||
stretch: this.data.eyelashUpperAspect,
|
||||
},
|
||||
eyelashesBottom: {
|
||||
type: this.data.eyelashLowerType,
|
||||
height: this.data.eyelashLowerY,
|
||||
distance: this.data.eyelashLowerX,
|
||||
rotation: this.data.eyelashLowerRotate,
|
||||
size: this.data.eyelashLowerScale,
|
||||
stretch: this.data.eyelashLowerAspect,
|
||||
},
|
||||
eyelidTop: {
|
||||
type: this.data.eyelidUpperType,
|
||||
height: this.data.eyelidUpperY,
|
||||
distance: this.data.eyelidUpperX,
|
||||
rotation: this.data.eyelidUpperRotate,
|
||||
size: this.data.eyelidUpperScale,
|
||||
stretch: this.data.eyelidUpperAspect,
|
||||
},
|
||||
eyelidBottom: {
|
||||
type: this.data.eyelidLowerType,
|
||||
height: this.data.eyelidLowerY,
|
||||
distance: this.data.eyelidLowerX,
|
||||
rotation: this.data.eyelidLowerRotate,
|
||||
size: this.data.eyelidLowerScale,
|
||||
stretch: this.data.eyelidLowerAspect,
|
||||
},
|
||||
eyeliner: {
|
||||
type: (this.data.faceFlags & (1 << 4)) !== 0, // eyeShadowEnabled
|
||||
color: this.data.eyeShadowColor,
|
||||
},
|
||||
pupil: {
|
||||
type: this.data.eyeHighlightType,
|
||||
height: this.data.eyeHighlightY,
|
||||
distance: this.data.eyeHighlightX,
|
||||
rotation: this.data.eyeHighlightRotate,
|
||||
size: this.data.eyeHighlightScale,
|
||||
stretch: this.data.eyeHighlightAspect,
|
||||
},
|
||||
},
|
||||
nose: {
|
||||
type: this.data.noseType,
|
||||
height: this.data.noseY - 9,
|
||||
size: this.data.noseScale - 4,
|
||||
},
|
||||
lips: {
|
||||
type: this.data.mouthType,
|
||||
color: this.data.mouthColor,
|
||||
height: this.data.mouthY - 13,
|
||||
rotation: this.data.mouthRotate,
|
||||
size: this.data.mouthScale - 4,
|
||||
stretch: this.data.mouthAspect - 3,
|
||||
hasLipstick: (this.data.faceFlags & (1 << 5)) !== 0, // mouthInvert
|
||||
},
|
||||
ears: {
|
||||
type: this.data.earType,
|
||||
height: this.data.earY - 4,
|
||||
size: this.data.earScale - 2,
|
||||
},
|
||||
glasses: {
|
||||
type: this.data.glassType1,
|
||||
type2: this.data.glassType2,
|
||||
ringColor: this.data.glassColor1,
|
||||
shadesColor: this.data.glassColor2,
|
||||
height: this.data.glassY - 11,
|
||||
size: this.data.glassScale - 4,
|
||||
stretch: this.data.glassAspect - 3,
|
||||
},
|
||||
other: {
|
||||
wrinkles1: {
|
||||
type: this.data.wrinkleLowerType,
|
||||
height: this.data.wrinkleLowerY - 15,
|
||||
distance: this.data.wrinkleLowerX - 2,
|
||||
size: this.data.wrinkleLowerScale - 6,
|
||||
stretch: this.data.wrinkleLowerAspect - 3,
|
||||
},
|
||||
wrinkles2: {
|
||||
type: this.data.wrinkleUpperType,
|
||||
height: this.data.wrinkleUpperY - 23,
|
||||
distance: this.data.wrinkleUpperX - 7,
|
||||
size: this.data.wrinkleUpperScale - 6,
|
||||
stretch: this.data.wrinkleUpperAspect - 3,
|
||||
},
|
||||
beard: {
|
||||
type: this.data.beardType,
|
||||
color: this.data.beardColor,
|
||||
},
|
||||
moustache: {
|
||||
type: this.data.mustacheType,
|
||||
color: this.data.mustacheColor,
|
||||
height: this.data.mustacheY - 10,
|
||||
isFlipped: (this.data.faceFlags & (1 << 6)) !== 0, // mustacheInverted
|
||||
size: this.data.mustacheScale - 4,
|
||||
stretch: this.data.mustacheAspect - 3,
|
||||
},
|
||||
goatee: {
|
||||
type: this.data.beardShortType,
|
||||
color: this.data.beardShortColor,
|
||||
},
|
||||
mole: {
|
||||
type: this.data.moleX != 0,
|
||||
height: this.data.moleY - 20,
|
||||
distance: this.data.moleX - 2,
|
||||
size: this.data.moleScale - 4,
|
||||
},
|
||||
eyeShadow: {
|
||||
type: this.data.makeup0,
|
||||
color: this.data.makeup0Color,
|
||||
height: this.data.makeup0Y - 12,
|
||||
distance: this.data.makeup0X - 1,
|
||||
size: this.data.makeup0Scale - 6,
|
||||
stretch: this.data.makeup0Aspect - 3,
|
||||
},
|
||||
blush: {
|
||||
type: this.data.makeup1,
|
||||
color: this.data.makeup1Color,
|
||||
height: this.data.makeup1Y - 19,
|
||||
distance: this.data.makeup1X - 6,
|
||||
size: this.data.makeup1Scale - 5,
|
||||
stretch: this.data.makeup1Aspect - 3,
|
||||
},
|
||||
},
|
||||
height: this.data.height,
|
||||
weight: this.data.build,
|
||||
datingPreferences: this.datingPreferences,
|
||||
birthday: this.birthday,
|
||||
voice: this.voice,
|
||||
personality: this.personality,
|
||||
};
|
||||
|
||||
return minifyInstructions(instructions);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,37 +28,40 @@ export function minifyInstructions(instructions: Partial<SwitchMiiInstructions>)
|
|||
}
|
||||
|
||||
export const defaultInstructions: SwitchMiiInstructions = {
|
||||
head: { skinColor: null },
|
||||
head: { type: null, skinColor: null },
|
||||
hair: {
|
||||
set: null,
|
||||
bangs: null,
|
||||
back: null,
|
||||
color: null,
|
||||
subColor: null,
|
||||
subColor2: null,
|
||||
style: null,
|
||||
isFlipped: false,
|
||||
},
|
||||
eyebrows: { color: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyebrows: { type: null, color: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyes: {
|
||||
main: { color: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyelashesTop: { height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyelashesBottom: { height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyelidTop: { height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyelidBottom: { height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyeliner: { color: null },
|
||||
pupil: { height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
main: { type: null, color: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyelashesTop: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyelashesBottom: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyelidTop: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyelidBottom: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
eyeliner: { type: false, color: null },
|
||||
pupil: { type: null, height: null, distance: null, rotation: null, size: null, stretch: null },
|
||||
},
|
||||
nose: { height: null, size: null },
|
||||
lips: { color: null, height: null, rotation: null, size: null, stretch: null, hasLipstick: false },
|
||||
ears: { height: null, size: null },
|
||||
glasses: { ringColor: null, shadesColor: null, height: null, size: null, stretch: null },
|
||||
nose: { type: null, height: null, size: null },
|
||||
lips: { type: null, color: null, height: null, rotation: null, size: null, stretch: null, hasLipstick: false },
|
||||
ears: { type: null, height: null, size: null },
|
||||
glasses: { type: null, type2: null, ringColor: null, shadesColor: null, height: null, size: null, stretch: null },
|
||||
other: {
|
||||
wrinkles1: { height: null, distance: null, size: null, stretch: null },
|
||||
wrinkles2: { height: null, distance: null, size: null, stretch: null },
|
||||
beard: { color: null },
|
||||
moustache: { color: null, height: null, isFlipped: false, size: null, stretch: null },
|
||||
goatee: { color: null },
|
||||
mole: { color: null, height: null, distance: null, size: null },
|
||||
eyeShadow: { color: null, height: null, distance: null, size: null, stretch: null },
|
||||
blush: { color: null, height: null, distance: null, size: null, stretch: null },
|
||||
wrinkles1: { type: null, height: null, distance: null, size: null, stretch: null },
|
||||
wrinkles2: { type: null, height: null, distance: null, size: null, stretch: null },
|
||||
beard: { type: null, color: null },
|
||||
moustache: { type: null, color: null, height: null, isFlipped: false, size: null, stretch: null },
|
||||
goatee: { type: null, color: null },
|
||||
mole: { type: false, height: null, distance: null, size: null },
|
||||
eyeShadow: { type: null, color: null, height: null, distance: null, size: null, stretch: null },
|
||||
blush: { type: null, color: null, height: null, distance: null, size: null, stretch: null },
|
||||
},
|
||||
height: null,
|
||||
weight: null,
|
||||
|
|
|
|||
50
shared/src/types.d.ts
vendored
50
shared/src/types.d.ts
vendored
|
|
@ -5,9 +5,15 @@ type ReportReason = "INAPPROPRIATE" | "SPAM" | "BAD_QUALITY" | "OTHER";
|
|||
|
||||
export interface SwitchMiiInstructions {
|
||||
head: {
|
||||
type: number | null;
|
||||
|
||||
skinColor: number | null; // Additional 14 are not in color menu, default is 2
|
||||
};
|
||||
hair: {
|
||||
set: number | null;
|
||||
bangs: number | null;
|
||||
back: number | null;
|
||||
|
||||
color: number | null;
|
||||
subColor: number | null; // Default is none
|
||||
subColor2: number | null; // Only used when bangs/back is selected
|
||||
|
|
@ -15,6 +21,8 @@ export interface SwitchMiiInstructions {
|
|||
isFlipped: boolean; // Only for sets and fringe
|
||||
};
|
||||
eyebrows: {
|
||||
type: number | null;
|
||||
|
||||
color: number | null;
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
|
|
@ -24,6 +32,8 @@ export interface SwitchMiiInstructions {
|
|||
};
|
||||
eyes: {
|
||||
main: {
|
||||
type: number | null;
|
||||
|
||||
color: number | null;
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
|
|
@ -32,6 +42,8 @@ export interface SwitchMiiInstructions {
|
|||
stretch: number | null;
|
||||
};
|
||||
eyelashesTop: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
rotation: number | null;
|
||||
|
|
@ -39,6 +51,8 @@ export interface SwitchMiiInstructions {
|
|||
stretch: number | null;
|
||||
};
|
||||
eyelashesBottom: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
rotation: number | null;
|
||||
|
|
@ -46,6 +60,8 @@ export interface SwitchMiiInstructions {
|
|||
stretch: number | null;
|
||||
};
|
||||
eyelidTop: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
rotation: number | null;
|
||||
|
|
@ -53,6 +69,8 @@ export interface SwitchMiiInstructions {
|
|||
stretch: number | null;
|
||||
};
|
||||
eyelidBottom: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
rotation: number | null;
|
||||
|
|
@ -60,9 +78,12 @@ export interface SwitchMiiInstructions {
|
|||
stretch: number | null;
|
||||
};
|
||||
eyeliner: {
|
||||
type: boolean;
|
||||
color: number | null;
|
||||
};
|
||||
pupil: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
rotation: number | null;
|
||||
|
|
@ -71,10 +92,14 @@ export interface SwitchMiiInstructions {
|
|||
};
|
||||
};
|
||||
nose: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null;
|
||||
size: number | null;
|
||||
};
|
||||
lips: {
|
||||
type: number | null;
|
||||
|
||||
color: number | null;
|
||||
height: number | null;
|
||||
rotation: number | null;
|
||||
|
|
@ -83,10 +108,15 @@ export interface SwitchMiiInstructions {
|
|||
hasLipstick: boolean;
|
||||
};
|
||||
ears: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null; // Does not work for default
|
||||
size: number | null; // Does not work for default
|
||||
};
|
||||
glasses: {
|
||||
type: number | null;
|
||||
type2: number | null;
|
||||
|
||||
ringColor: number | null;
|
||||
shadesColor: number | null; // Only works after gap
|
||||
height: number | null;
|
||||
|
|
@ -96,37 +126,50 @@ export interface SwitchMiiInstructions {
|
|||
other: {
|
||||
// names were assumed
|
||||
wrinkles1: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
size: number | null;
|
||||
stretch: number | null;
|
||||
};
|
||||
wrinkles2: {
|
||||
type: number | null;
|
||||
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
size: number | null;
|
||||
stretch: number | null;
|
||||
};
|
||||
beard: {
|
||||
type: number | null;
|
||||
|
||||
color: number | null;
|
||||
};
|
||||
moustache: {
|
||||
color: number | null; // is this same as hair?
|
||||
type: number | null;
|
||||
|
||||
color: number | null;
|
||||
height: number | null;
|
||||
isFlipped: boolean;
|
||||
size: number | null;
|
||||
stretch: number | null;
|
||||
};
|
||||
goatee: {
|
||||
type: number | null;
|
||||
|
||||
color: number | null;
|
||||
};
|
||||
mole: {
|
||||
color: number | null; // is this same as hair?
|
||||
type: boolean;
|
||||
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
size: number | null;
|
||||
};
|
||||
eyeShadow: {
|
||||
type: number | null;
|
||||
|
||||
color: number | null;
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
|
|
@ -134,6 +177,8 @@ export interface SwitchMiiInstructions {
|
|||
stretch: number | null;
|
||||
};
|
||||
blush: {
|
||||
type: number | null;
|
||||
|
||||
color: number | null;
|
||||
height: number | null;
|
||||
distance: number | null;
|
||||
|
|
@ -141,7 +186,6 @@ export interface SwitchMiiInstructions {
|
|||
stretch: number | null;
|
||||
};
|
||||
};
|
||||
// makeup, use video?
|
||||
height: number | null;
|
||||
weight: number | null;
|
||||
datingPreferences: MiiGender[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue