Skip to content

feat: save files extractor#26

Draft
trafficlunar wants to merge 5 commits into
next.jsfrom
feat/save-files
Draft

feat: save files extractor#26
trafficlunar wants to merge 5 commits into
next.jsfrom
feat/save-files

Conversation

@trafficlunar

@trafficlunar trafficlunar commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Semi-fixes #15

TODO:

  • Makeup?
  • Icons are mismatched, hair seems to be the worst offender
  • Colors are mismatched Only partially now
  • Base the parts geometry (height, distance, etc.) off of default Male Mii
  • Support for .ltd files (collides with makeup) https://gamebanana.com/tools/22305
  • Store raw data of Mii in database for later (like exporting)
  • Security, prevent people from sending malicious files
image

@ariankordi

Copy link
Copy Markdown
Contributor

Do you think it'd be better to reference the icons from the external repo than to drop them all here, or is there a risk that repo will change?

@trafficlunar

trafficlunar commented Apr 10, 2026

Copy link
Copy Markdown
Owner Author

I had to change some icons like the eyes since they have a colorable layer, and without it the eye icons looked weird so I merged them. Placing all the icons here does make the repository a bit untidy so I guess I could do a hybrid with submodules and the edited icons. The external changes is a valid concern but I guess it wouldn't matter if I use submodules

@ariankordi

Copy link
Copy Markdown
Contributor

I know what you're talking about. That wouldn't be a problem if they were SVGs, someone should eventually trace them :/

@trafficlunar

Copy link
Copy Markdown
Owner Author

@ariankordi can you verify I've mapped these correctly?

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);
}

I'm most confused about the hair, unsure if I mapped it wrong or the icons are just really confusing

@ariankordi

Copy link
Copy Markdown
Contributor

I didn't look into the new fields too much, @McSpazzy ?

@Crafty-The-Fox

Copy link
Copy Markdown

What do you mean by "Collides with makeup"? The LTD format supports makeup and personality as far as im aware

@trafficlunar

Copy link
Copy Markdown
Owner Author

What do you mean by "Collides with makeup"? The LTD format supports makeup and personality as far as im aware

This pull request was meant to originally support save files, but I decided to pivot to just .ltd files instead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add ability to upload and render facepaint texture/upload mii character data

3 participants