Skip to content

Conversation

@jonbarrow
Copy link
Member

@jonbarrow jonbarrow commented Feb 7, 2025

Resolves #XXX

Changes:

Adds a MiiGen2 class which can decode and encode Mii data used by the Wii U, 3DS and Miitomo. This is meant to replace https://github.com/PretendoNetwork/mii-js eventually

Intended to be a more-or-less direct translation of the table found at https://www.3dbrew.org/wiki/Mii#Mii_format (which is why the bit handling is done with field0xXX sections), but in a way that conforms to the requirements of this library

Marking as draft because more work needs to be done

Required tasks:

Not required, but would really like done before merging, tasks:

  • Get rid of the Util class
  • Get rid of the BitReader and BitWriter classes and make something more in line with the other stream classes/handling in this library

Not required tasks for this PR, but should probably be done at some point:

  • Full gen1 (Wii/DSi) Mii handling
  • Full gen3 (Switch and above) Mii handling

Some example data/usage for others who want to contribute to this:

const buffer = Buffer.from('AwAAQK/S6FEgm4/62w3zHAOzuI0n2QAA0mJKAG8AbgAAAAAAAAAAAAAAAAAAAF0yAAAPAQJoRBgmNEYUgRIXaI0ADSkDUkhQSgBvAG4AAAAAAAAAAAAAAAAAAAAAAD1z', 'base64'); // My personal Mii
const mii = MiiGen2.fromBuffer(buffer);

// These should be identical
console.log(buffer.toString('hex'));
console.log(mii.bytes().toString('hex'));

@jonbarrow jonbarrow changed the base branch from master to dev February 7, 2025 07:00
Copy link
Member

@DaniElectra DaniElectra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MiiGen2 name feels a little weird tbh. What about something like MiiV2?

@jonbarrow
Copy link
Member Author

The MiiGen2 name feels a little weird tbh. What about something like MiiV2?

Initially I did use MiiV2 but thought it might be confusing since there's a NEX type of the same name

I ended up stealing the "gen" naming scheme from https://github.com/HEYimHeroic/mii2studio

I'm open to changing this though since I agree "gen2" sounds awkward

@jonbarrow
Copy link
Member Author

I'm wondering if we should change how creationDateSeconds is handled? Right now it does the 2x of the value internally, and then always assumes it's 2x (since it divides by 2 in a few places). I fear this may be confusing, since it's not SUPER clear that we've already done the 2x

Maybe we should just store the original value instead? Or change to using a Date object for consistency/clarity?

@HEYimHeroic
Copy link

I ended up stealing the "gen" naming scheme from https://github.com/HEYimHeroic/mii2studio

to be clear, i didn't actually make the filenames for those files - i always thought it was a poor choice. i would just go with the official internal name, ver3. i think Nintendo considers the DS format to be version/generation "2", and Wii to be 1, and for all other projects that's what i've been going by. Wii format, DS format, and the ver3 format (since it's used in so many different applications, a console name doesn't really fit). and then just "MiiStudio" or "charinfo" or "SwitchNAND" etc. for all the other format names... because there's like 5 different ones on the Switch alone, they don't really fit into a console name or a generation-type name

this format type being ver3 instead of 2 is also likely connected to the fact that all ver3 Mii data files should start with 0x03. unless the Mii was made with the Wii U Mii Maker's camera look-alike feature (yet another genius calculated decision by Nintendo)

@ariankordi
Copy link

ariankordi commented Feb 8, 2025

Here's my thoughts. Truncating this huge comment.

Accuracy suggestions
  • "Gen2" is a really confusing and bad name that only some Kaitai structs from long ago called it and nothing else ever has.

    • The struct is called nn::mii::Ver3StoreData on Switch, FFLStoreData on Wii U and then a few more names beyond that buut even that or 3DS/Wii U StoreData? would've been clearer in my opinion.
    • (The other names in question being: AFLStoreData, and for some reason the CTR SDK uses nnmiiStoreData as the "public" name. Yeah, let's go with Ver3StoreData for simplicity.)
  • These field names are all inaccurate. We really shouldn't be playing a guessing name with Mii fields anymore.

    • They are also not in groups like they are in mii-gen2.ts. I mean, CharInfo is structured like this but the packed structures do not do that as DWARF for RFL and CFL show.
    • DWARF information for CFL, including the one-to-one original structs, have been available for some time now (DM me for details), and nn::mii on Switch/libcocos2dcpp.so contains getter methods for nn::mii::detail::Ver3StoreDataRaw (and Kinnay has used getters from this lib before). MiiPort's mii_ext.h uses this to define structs for Ver3StoreData as well as all other Switch types.
    • (For the record RFL also has DWARF information that the RFL decompilation uses.)
      • ... Yes, they actually call it "sex" here but CFLiRFLMiiDataCore is a copy of this and they renamed it to "gender" there. Not going to argue my life away about this but, "gender" is the only word Nintendo has used to describe this field ever since, so it seems more appropriate...
    • Remember that most code that interfaces with Miis have forever guessed the field names. There's only one source of truth and it's the originals. Why not end the confusion, guessing, and mix-ups by just using the originals.
    • One example of a "perk" for getting these right is that, well, have you ever noticed the Mii Studio data structure almost seems like it's in alphabetical order? It follows the order of Switch nn::mii::CharInfo/nn::mii::CoreData field names, for instance using the "beard" prefix rather than "facialHair" which is a term Nintendo has never used in these fields.
Here is the entire CFLiMiiDataPacket (Ver3StoreData, FFLStoreData...) struct I reconstructed from DWARF.
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;

typedef struct CFLiAuthorID
{
    union
    {
        u8 data[8];
        u16 value16[4];
    };
}
CFLiAuthorID;

typedef struct CFLCreateID
{
    union
    {
        u8 data[10];
        u16 value16[5];
    };
}
CFLCreateID;

typedef struct CFLiMiiDataPacket // CFLiPackedMiiDataCore + CFLiPackedMiiDataOfficial + CRC
{
    u32 miiVersion : 8;
    u32 copyable : 1;
    u32 ngWord : 1;
    u32 regionMove : 2;
    u32 fontRegion : 2;
    u32 reserved_0 : 2;
    u32 roomIndex : 4;
    u32 positionInRoom : 4;
    u32 authorType : 4;
    u32 birthPlatform : 3;
    u32 reserved_1 : 1;
    struct CFLiAuthorID authorID;
    struct CFLCreateID createID;
    u8 reserved_2[2];
    u16 gender : 1;
    u16 birthMonth : 4;
    u16 birthDay : 5;
    u16 favoriteColor : 4;
    u16 favorite : 1;
    u16 padding_0 : 1;
    u16 name[10];
    u8 height;
    u8 build;
    u16 localonly : 1;
    u16 faceType : 4;
    u16 faceColor : 3;
    u16 faceTex : 4;
    u16 faceMake : 4;
    u16 hairType : 8;
    u16 hairColor : 3;
    u16 hairFlip : 1;
    u16 padding_1 : 4;
    u16 eyeType : 6;
    u16 eyeColor : 3;
    u16 eyeScale : 4;
    u16 eyeAspect : 3;
    u16 eyeRotate : 5;
    u16 eyeX : 4;
    u16 eyeY : 5;
    u16 padding_2 : 2;
    u16 eyebrowType : 5;
    u16 eyebrowColor : 3;
    u16 eyebrowScale : 4;
    u16 eyebrowAspect : 3;
    u16 padding_3 : 1;
    u16 eyebrowRotate : 5;
    u16 eyebrowX : 4;
    u16 eyebrowY : 5;
    u16 padding_4 : 2;
    u16 noseType : 5;
    u16 noseScale : 4;
    u16 noseY : 5;
    u16 padding_5 : 2;
    u16 mouthType : 6;
    u16 mouthColor : 3;
    u16 mouthScale : 4;
    u16 mouthAspect : 3;
    u16 mouthY : 5;
    u16 mustacheType : 3;
    u16 padding_6 : 8;
    u16 beardType : 3;
    u16 beardColor : 3;
    u16 beardScale : 4;
    u16 beardY : 5;
    u16 padding_7 : 1;
    u16 glassType : 4;
    u16 glassColor : 3;
    u16 glassScale : 4;
    u16 glassY : 5;
    u16 moleType : 1;
    u16 moleScale : 4;
    u16 moleX : 5;
    u16 moleY : 5;
    // ^^ In CFLiPackedMiiDataOfficial, this is called "coredata"
    u16 padding_8 : 1;
    u16 creatorName[10];
    // ^^ In CFLiPackedMiiDataPacket, this is called "rawdata"
    u16 padding_9;
    u16 crc;
}
CFLiMiiDataPacket;
And here are getters for Ver3StoreDataRaw from nn::mii.
enum nn::mii::detail::Ver3MiiVersion nn::mii::detail::Ver3StoreDataRaw::GetMiiVersion(void) const;
enum nn::mii::detail::Ver3Copyable nn::mii::detail::Ver3StoreDataRaw::GetCopyable(void) const;
enum nn::mii::detail::Ver3NgWord nn::mii::detail::Ver3StoreDataRaw::GetNgWord(void) const;
enum nn::mii::RegionMove nn::mii::detail::Ver3StoreDataRaw::GetRegionMove(void) const;
enum nn::mii::FontRegion nn::mii::detail::Ver3StoreDataRaw::GetFontRegion(void) const;
enum nn::mii::detail::Ver3RoomIndex nn::mii::detail::Ver3StoreDataRaw::GetRoomIndex(void) const;
enum nn::mii::detail::Ver3PositionInRoom nn::mii::detail::Ver3StoreDataRaw::GetPositionInRoom(void) const;
enum nn::mii::detail::Ver3AuthorType nn::mii::detail::Ver3StoreDataRaw::GetAuthorType(void) const;
enum nn::mii::detail::Ver3BirthPlatform nn::mii::detail::Ver3StoreDataRaw::GetBirthPlatform(void) const;
enum nn::mii::Gender nn::mii::detail::Ver3StoreDataRaw::GetGender(void) const;
enum nn::mii::detail::Ver3BirthMonth nn::mii::detail::Ver3StoreDataRaw::GetBirthMonth(void) const;
enum nn::mii::detail::Ver3BirthDay nn::mii::detail::Ver3StoreDataRaw::GetBirthDay(void) const;
enum nn::mii::FavoriteColor nn::mii::detail::Ver3StoreDataRaw::GetFavoriteColor(void) const;
enum nn::mii::detail::Ver3Favorite nn::mii::detail::Ver3StoreDataRaw::GetFavorite(void) const;
enum nn::mii::detail::Ver3MiiHeight nn::mii::detail::Ver3StoreDataRaw::GetHeight(void) const;
enum nn::mii::detail::Ver3MiiBuild nn::mii::detail::Ver3StoreDataRaw::GetBuild(void) const;
enum nn::mii::detail::Ver3LocalOnly nn::mii::detail::Ver3StoreDataRaw::GetLocalonly(void) const;
enum nn::mii::FacelineType nn::mii::detail::Ver3StoreDataRaw::GetFaceType(void) const;
enum nn::mii::detail::Ver3FacelineColor nn::mii::detail::Ver3StoreDataRaw::GetFaceColor(void) const;
enum nn::mii::FacelineWrinkle nn::mii::detail::Ver3StoreDataRaw::GetFaceTex(void) const;
enum nn::mii::FacelineMake nn::mii::detail::Ver3StoreDataRaw::GetFaceMake(void) const;
enum nn::mii::HairType nn::mii::detail::Ver3StoreDataRaw::GetHairType(void) const;
enum nn::mii::detail::Ver3HairColor nn::mii::detail::Ver3StoreDataRaw::GetHairColor(void) const;
enum nn::mii::HairFlip nn::mii::detail::Ver3StoreDataRaw::GetHairFlip(void) const;
enum nn::mii::EyeType nn::mii::detail::Ver3StoreDataRaw::GetEyeType(void) const;
enum nn::mii::detail::Ver3EyeColor nn::mii::detail::Ver3StoreDataRaw::GetEyeColor(void) const;
enum nn::mii::EyeScale nn::mii::detail::Ver3StoreDataRaw::GetEyeScale(void) const;
enum nn::mii::EyeAspect nn::mii::detail::Ver3StoreDataRaw::GetEyeAspect(void) const;
enum nn::mii::EyeRotate nn::mii::detail::Ver3StoreDataRaw::GetEyeRotate(void) const;
enum nn::mii::EyeX nn::mii::detail::Ver3StoreDataRaw::GetEyeX(void) const;
enum nn::mii::EyeY nn::mii::detail::Ver3StoreDataRaw::GetEyeY(void) const;
enum nn::mii::EyebrowType nn::mii::detail::Ver3StoreDataRaw::GetEyebrowType(void) const;
enum nn::mii::detail::Ver3HairColor nn::mii::detail::Ver3StoreDataRaw::GetEyebrowColor(void) const;
enum nn::mii::EyebrowScale nn::mii::detail::Ver3StoreDataRaw::GetEyebrowScale(void) const;
enum nn::mii::EyebrowAspect nn::mii::detail::Ver3StoreDataRaw::GetEyebrowAspect(void) const;
enum nn::mii::EyebrowRotate nn::mii::detail::Ver3StoreDataRaw::GetEyebrowRotate(void) const;
enum nn::mii::EyebrowX nn::mii::detail::Ver3StoreDataRaw::GetEyebrowX(void) const;
enum nn::mii::EyebrowY nn::mii::detail::Ver3StoreDataRaw::GetEyebrowY(void) const;
enum nn::mii::NoseType nn::mii::detail::Ver3StoreDataRaw::GetNoseType(void) const;
enum nn::mii::NoseScale nn::mii::detail::Ver3StoreDataRaw::GetNoseScale(void) const;
enum nn::mii::NoseY nn::mii::detail::Ver3StoreDataRaw::GetNoseY(void) const;
enum nn::mii::MouthType nn::mii::detail::Ver3StoreDataRaw::GetMouthType(void) const;
enum nn::mii::detail::Ver3MouthColor nn::mii::detail::Ver3StoreDataRaw::GetMouthColor(void) const;
enum nn::mii::MouthScale nn::mii::detail::Ver3StoreDataRaw::GetMouthScale(void) const;
enum nn::mii::MouthAspect nn::mii::detail::Ver3StoreDataRaw::GetMouthAspect(void) const;
enum nn::mii::MouthY nn::mii::detail::Ver3StoreDataRaw::GetMouthY(void) const;
enum nn::mii::MustacheType nn::mii::detail::Ver3StoreDataRaw::GetMustacheType(void) const;
enum nn::mii::BeardType nn::mii::detail::Ver3StoreDataRaw::GetBeardType(void) const;
enum nn::mii::detail::Ver3HairColor nn::mii::detail::Ver3StoreDataRaw::GetBeardColor(void) const;
enum nn::mii::MustacheScale nn::mii::detail::Ver3StoreDataRaw::GetBeardScale(void) const;
enum nn::mii::MustacheY nn::mii::detail::Ver3StoreDataRaw::GetBeardY(void) const;
enum nn::mii::detail::Ver3GlassType nn::mii::detail::Ver3StoreDataRaw::GetGlassType(void) const;
enum nn::mii::detail::Ver3GlassColor nn::mii::detail::Ver3StoreDataRaw::GetGlassColor(void) const;
enum nn::mii::GlassScale nn::mii::detail::Ver3StoreDataRaw::GetGlassScale(void) const;
enum nn::mii::GlassY nn::mii::detail::Ver3StoreDataRaw::GetGlassY(void) const;
enum nn::mii::MoleType nn::mii::detail::Ver3StoreDataRaw::GetMoleType(void) const;
enum nn::mii::MoleScale nn::mii::detail::Ver3StoreDataRaw::GetMoleScale(void) const;
enum nn::mii::MoleX nn::mii::detail::Ver3StoreDataRaw::GetMoleX(void) const;
enum nn::mii::MoleY nn::mii::detail::Ver3StoreDataRaw::GetMoleY(void) const;
void nn::mii::detail::Ver3StoreDataRaw::GetAuthorId(struct nn::mii::detail::Ver3AuthorId *) const;
void nn::mii::detail::Ver3StoreDataRaw::GetCreateId(struct nn::mii::detail::Ver3CreateId *) const;
void nn::mii::detail::Ver3StoreDataRaw::GetName(struct nn::mii::Nickname *) const;
void nn::mii::detail::Ver3StoreDataRaw::GetCreatorName(struct nn::mii::Nickname *) const;
int nn::mii::detail::Ver3StoreDataRaw::GetReserved0(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetReserved1(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetReserved2(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding0(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding1(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding2(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding3(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding4(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding5(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding6(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding7(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding8(void) const;
int nn::mii::detail::Ver3StoreDataRaw::GetPadding9(void) const;
^^ These also reveal that most of our "unknown" fields are just padding for alignment, amazing right?

...Except for this mysterious "authorType" that's also present in CFLiCharInfo and in FFL, but nn::mii requires it to be 0 to verify properly so it is truly unused.
FUN FACT!!!!!: This struct contains so many unused bits that it can be used to store Switch colors and still have a few bytes of space remaining whiiichh I have prototyped in this jsfiddle here. Unfortunately I never really finished it (as you can tell from the SLOP), but it should stiiilll work...

And by the way, this is the kind of thing I imagine us being able to do together, with our combined skills. Just picture if (a certain Miiverse clone...) allowed custom hair colors and you can still use the same data on Wii U/3DS, verifying correctly on both places.
(just note that my current code assumes roomIndex/positionInRoom are free to use, but not the last bit because values of both are limited to 9, oooops!)

Struct reading suggestion
  • I never liked the way this code reads/writes bits in a stream manually and I don't think I'm alone. Have you considered defining the fields in order like a real C struct, once, potentially with min/max fields too, and avoid duplicating offsets and logic?
    • I recently forked a library that lets you do just this. The original uses Node buffer but my fork uses browser APIs and fixes the ubitLE field which didn't work as expected with the way most compilers handle bitfields.
    • This sample of mine uses that fork and this code works on browsers all the way back to Safari 5.1.
    • That does serialization and deserialization but not validation. I still think it's really wasteful to write ALLLLLLL of those lines manually with hand written messages for each, really should be made into a separate table at worst and integrated into the definition at best.
      • (Validation should not be needed to read the data either, e.g. the Mii databases use CFLiPackedMiiDataOfficial/FFLiMiiDataOfficial that straight up don't have checksums to verify. And sometimes there aaree real uses for reading invalid data...)
    • Now I'm not saying you have to use that library but it's an example of how much nicer it could be.
(Spitballing another concept for doing structs in JS:)
  • ... though I will say in general that parsing structs, especially with bitfields, outside of C/C++ has never been fun. It's nice that compilers will just figure this out for you.
    • I've thought about poteentiallly..??? making something that generates deserializers/serializers based on C struct definitions, using DWARF information since it comes straight from a compiler.
    • So if the input function refers to the fields by name, it'll go through and make an output function that would figure out where those named fields are and manually refer to their byte offsets and do bitshifts on them.
    • To illustrate this, look at what a C function looks like compared to after it comes out of the compiler and into Ghidra. This is what I'm talking about.
  • I think it'd be best for this library to consume DWARF information, here's how I came to that conclusion.
    • The process of taking C structs with bitfields and making getter/setter methods from them, well, this is something ClangSharpPInvokeGenerator actually already did for me in FFLSharp and the result is good.
    • It's good and it required pretty much no modifications to the struct, problem is it had to import all of libclang to do that.
    • DWARF information IS optimized to store information about.. structs, among other constructs of course. So letting the compiler parse the original code and using the emitted debug information is the perfect way to get those offsets.

Noooowww.... iiin reality... this would really only help if you wanted to hyper-minimize JS code footprint by only including a few of these (de)serializer methods instead of a library for dealing with structs, and also, REALLY didn't want to rewrite the struct by hand so hard that you only want to import the original C version. I realized only after stumbling upon the excellent struct-fu library that this is not at all necessary though.
Just sharing the idea since this is probably? the right group for it.

CreateID/Mii ID handling
  • I don't blame you for this but the CreateID flags, the four bits at the beginning, haven't been understood well for a while.

    • Flags 0 and 2, yes, those are normal and temporary.
      • (And yes for a CreateID to be valid, temporary has to be cleared: FFLiIsValidMiiID(), CFLi_IsValidMiiID(), nn::mii::detail::Ver3CreateId::IsValid())
    • But 1 and 3 don't mean anything individually.
      • 00: Wii
      • 01: 3DS
      • 10: DS
      • 11: Wii U
      • For PROOOOOOOOOOOOOF, refer to FFL/AFL: FFLiIsWiiUMiiID, FFLiIsCTRMiiID, FFLiIsWiiMiiID, FFLiIsNTRMiiID
    • The entire CreateID is 10 bytes, and the last 6 (what everyone calls the "MAC address") is called the Create ID base (FFLiCreateIDBase), and on Wii U this is derived from nn::act::GetDeviceHash(). I don't think this is ever explicitly read either?
  • You maaayyy or may not choose to parse the date or skip it outright, after all I've never seen it be parsed anywhere. Switch produces an invalid date since it just makes CreateIDs at random using nn::os::GenerateRandomBytes(), but, I guess you're never seeing Ver3StoreData "organically" come from a Switch since it only writes it to amiibo.

    • If you didn't already realize it, those dates wrap around on: Jan. 5, 2027, 18:48:32
    • ... or I guess 2028 if you want to count the year of 2010 after it gets wrapped
Switch fields and colors
  • Just so you know, the fields have different names on the Switch formats compared to the CFL struct (they're called the same names as that old struct, even IN nn::mii getters...)
Ver3StoreData Name CharInfoRaw Name
faceColor facelineColor
faceType facelineType
faceTex facelineWrinkle
faceMake facelineMake
beardScale mustacheScale
beardY mustacheY
name nickname

Since this is such a long message I'll conclude by saying I am still eager and willing to work with you even if my tone may sound harsh. Thanks.

@jonbarrow
Copy link
Member Author

jonbarrow commented Feb 8, 2025

  • Basically anything to do with the Switch and its data is irrelevant here. I tried to structure things in a way that would make some sense if other Mii data types were added (in terms of the file/folder structure), but the class itself is SPECIFICALLY only designed to handle Mii data that is sent to NNAS. Nothing more, nothing less. The scope doesn’t even extend to other Mii data formats used by the Wii U/3DS, such as the Mii data Super Mario Maker sends to the NEX server. Only NNAS at the moment. The only thing really relevant to the Switch at this time would be creating the Mii Studio URLs, but that's a separate task than the actual handling of the Switch's Mii data format(s)
  • As stated the intention is to be a more-or-less direct translation of the table found on 3dbrew. Trying to make this conform to the actual C struct(s) is pretty out of scope, and not what this is intended to be. It's intention is to be a simple parser/encoder for a relatively simply data format, and then give access to the decoded fields in a reasonable way. It's not supposed to be 1:1 accurate to any decomp or official implementation
  • The grouping of the fields once decoded was done just for organizational convenience since there are some flags/values mixed in with (such as the "disable sharing" flag being placed between the width/height and face data, and the beard/mustache data being mixed together). Again, this is not intended to match the actual C structs or anything like that. It's its own implementation doing it’s own thing, with it’s own design goals
  • Most of the field names do already use the original names, even by coincidence (since I opted to use things like “type” other “style” just out of personal preference). So I'm not sure why you said multiple times that it's "inaccurate" or "guesses"? The names describe what the data is fairly accurately, even if they aren't using the exact character-for-character original names (there’s a few cases where the names are kinda ehh I do agree, like the DSi flag field, but those cases are by far the minority). If by field names you mean where I named things like field0x0C, I think it's pretty obvious that this is referring to data at byte offset 0x0C in the Mii data. This is modeled after how Ghidra names struct fields by default. Sure you could change some of these to something more descriptive, like field0x34 could be renamed to something like eyeData or whatever, but since not every offset contains a single "type" of data (see field0x30 which has both the sharing flag and face data), I opted to keep the field0xXX naming convention for consistency since the actual reading of the data FROM the section describes the data
  • Of the field names which do differ, either the difference is very small ("rotate" vs "rotation") so that really doesn't matter tbh or the name chosen for here makes more sense imo ("MouthAspect" is a terrible name for the field which, visually in the editor, controls the height of the mouth, "beard color" is a bad name for the field that controls the color of all the facial hair not JUST the beard, etc.). Again, this is not meant to be a direct translation of the C structs. This is a translation of the 3dbrew table with names/groups that would make logical sense to someone consuming it (again, if the fields were named like "EyeAspect" as they are originally, that does not make any sense at a glance. "Eye height", however, does as it's the height of the eyes)
  • The way the streams are currently implemented is already marked as something to change, the existing BitReader and BitWriter classes were always going to be temporary and so I marked them as such. The idea was always to add BitStream class of sorts, much like how we already have FileStream here and ExtendedBitStream in mii-js already
  • Reading/writing data in a stream is done for consistency with the implementations of the other file formats both in this repo and in other repos such as https://github.com/PretendoNetwork/pictobox, such that everything has a consistent API. By using streams like this with a consistent API it means we can pass in streams of varying types and the classes don't have to care much about where the data comes from. This is arguably more important in the other classes than it is here, since the ability to read individual bits is required here which the other streams don't support, but I value consistent implementations very highly. The stream implementation will remain
  • I think having "custom" Mii's on Miiverse is definitely a fun idea, I like that a lot. I don't think the right way to do it is to abuse the Mii data format, though so it’s not in scope for here, but I think the idea is fun nonetheless. Imo it would be better to just explore storing that sort of data separate from the main Mii data and maintaining 2 renders of the Mii (one “normal” and one “custom”) with the ability to select which is used. There's no real reason to abuse the Mii format and create "invalid" Mii data just for this, not to mention it’s still limiting
  • Thank you for the other bits in the Mii ID, that's helpful
  • (what everyone calls the "MAC address") this is because, iirc, this WAS tied to your console MAC address at one point. wiibrew is down right now, as is wiiubrew, so I can't verify that rn, but it was at somepoint iirc

@HEYimHeroic
Copy link

iirc, the MAC address field in Wii Mii data was actually more of a hash or checksum of sorts based on the MAC. after all, MACs are 6 bytes long, and on the Wii, this field was only 4. so the first four bytes of the MAC were combined into 2 and the latter two bytes were copied directly. and on ver3, i'm fairly certain it's just the MAC address in full now that this format has afforded it more space to keep the whole thing.

at least... on Wii U and 3DS. as of typing this message, i realize i don't actually know what other platforms do with it. the Nintendo Switch likely just randomizes it (it IS part of the CreateID after all) but does Miitomo pull the MAC address of your phone for this? though this is probably straying away from the original topic - ultimately "MAC address" is still a perfectly fit name for the field in my opinion, on ver3

@DaniElectra
Copy link
Member

I think we should follow the true names as closely as possible (within reason) as part of PretendoNetwork/org-issues#2 , as it would allow easier debugging if something goes wrong since the structure resembles the original

@jonbarrow
Copy link
Member Author

jonbarrow commented Feb 8, 2025

I think we should follow the true names as closely as possible (within reason) as part of PretendoNetwork/org-issues#2 , as it would allow easier debugging if something goes wrong since the structure resembles the original

We already do follow the original names almost everywhere, by coincidence. If you check the originals and the ones here almost all of them line up, with the only differences being very minor ("rotate" vs "rotation") or imo more clear in this implementation ("aspect" vs "height")

Of the ones that do differ and do so substantially, I already agreed that they could be better

@jonbarrow
Copy link
Member Author

jonbarrow commented Sep 23, 2025

I started this up again since Mii rendering is a hot topic again. With Mii Creator gaining a lot of attention recently and our website devs working on the Mii renderer, I figured now was as good a time as any to try and finish this (plus it's my birthday and this is something light to work on while I celebrate lol)

Some key changes since last time:

  1. I completely removed the old implementation. Now everything is structured using the official type/class names, along with some aliases for different contexts. The aliases have no functional differences, but using CFLStoreData instead of FFLStoreData when an application only expects 3DS Miis, for example, just felt like it looked nicer
    • Doing it this way, in separate classes, also makes it easier for us to support other file formats which may contain one of these classes internally
  2. Using the decomps from @ariankordi I went ahead and changed all the public fields to using the official names. I hate some of them (such as beardScale, which has no effect on the beard despite the name) but I added some TSDoc comments to try and help with that (plus people can access the decoded field if they really want some arguably better names)
  3. I kept the public stream API for the reasons I already mentioned (API compatibility and consistency), but decided to end up going with a structure based approach as suggested. The stream API is most important for the public API, so I'm willing to be a bit more flexible with internals in this case. Plus I found the API provided by binary-parser to be quite nice
  4. I checked out many existing binary reading libraries and found none that seemed to work correctly or had the features desired while having a clean API to work with. binary-parser seemed to be the closest fit, but after trying it out for a while I realized it was actually struggling really hard with Mii data bit fields. I tried a few other libraries and got similarly bad results. bit-buffer used by MiiJS is known to work perfectly though, so I just decided to say "fuck-it" and built a custom parser based off the clean binary-parser API, but using bit-buffer under the hood. It's compatible with the features from binary-parser we need, plus it has additional features like:
    • Boolean bit support. Not super major, but nice QoL
    • Validation functions/options. Currently no fields are validated, but support for validation is there so it can be added later
    • Support for encoding. This was not present at all in binary-parser, however a PR (Update @Ericbla encoder fork  keichi/binary-parser#243) is there. Using the fork seemed to work pretty well, but the library as a whole still had issues with Mii data bit fields
    • Zod-like "safe" parsing/encoding functions
    • Zod-like type inference . The result of parsing is automatically typed based on the given structure, and there is an infer method to create types
  5. Added a MiiStudioEncoder, which is basically just a straight copy-paste port of MiiJS with minor changes, to support the features used by our account server and website. The API is also designed with possibly supporting other Mii formats in the future

All in all I think this looks and feels a lot better than it was before. It's still not perfect, ideally this would be made browser compatible as well since I'd like for this to replace MiiJS entirely, and maybe some QoL features like propagating up the fields of child classes to parent ones (so data can be accessed like fflStoreData.miiVersion rather than fflStoreData.FFLiMiiDataOfficial.FFLiMiiDataCore.miiVersion), but I think this is a great start so far

@ariankordi
Copy link

ariankordi commented Sep 23, 2025

Excellent work and commitment here.

I checked out many existing binary reading libraries ... found none that seemed to work

Definitely feel your pain here. I had a personal list of struct libraries in JS I found a while ago, if you haven't seen all of these and want to keep trying: natevw/struct-fu#21 (comment)

I had to fork struct-fu to get little-endian bitfields to work right. See a sample here. It's pretty small when minified and I got it working in the browser, but I'd understand if it still has flaws or other reasons other reasons to not use the fork (like my JSDoc job).

(so data can be accessed like fflStoreData.miiVersion rather than fflStoreData.FFLiMiiDataOfficial.FFLiMiiDataCore.miiVersion)

Nintendo would name it like storeData.official.core.miiVersion but doesn't solve the problem.
I'll also say, it's a hard choice whether or not to include core/official/store or put it all in StoreData. You probably wouldn't get use out of anything other than StoreData, which is why the Switch nn::mii library just has "Ver3StoreData".

Official is used in the DB and core is for that unused "hidden" HDB, but also note that in the Wii U DB files they are big-endian making them unreadable by tools without handling it. But, I guess you could fill the rest of the 96 bytes with zeroes and treat core/official like StoreData if you need to...? I also know I aggressively preached accuracy though, so mixed bag there. Just food for thought.

this is not fully working, but it gets some basics flowing
@jonbarrow
Copy link
Member Author

Okay, its not ready yet but I did get the class to parse in a browser with these latest changes and this test file

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script type="importmap">
  {
    "imports": {
      "bit-buffer": "https://cdn.skypack.dev/bit-buffer",
      "buffer": "https://cdn.skypack.dev/buffer"
    }
  }
  </script>
</head>
<body>
  <script type="module">
    import FFLStoreData from './dist/mii/ffl-store-data.js';
    const storeData = FFLStoreData.fromString('AwAAQK/S6FEgm4/62w3zHAOzuI0n2QAA0mJKAG8AbgAAAAAAAAAAAAAAAAAAAF0yAAAPAQJoRBgmNEYUgRIXaI0ADSkDUkhQSgBvAG4AAAAAAAAAAAAAAAAAAAAAAD1z');

	console.log(storeData);
  </script>
</body>
</html>

@jonbarrow
Copy link
Member Author

@ariankordi By the way, I wanted to discuss with you at some point how possible it might be do Mii rendering on the server, without the need for a Rio game in the background. I'm not sure if that would be a good fit for this repo (unless maybe like a render method is added to the classes, but I think that has a couple issues of its own), but having some sort of server-side renderer that consumes these classes (like how the MiiStudioEncoder does) would be really nice for us to have in our case

Our biggest use case for Mii rendering is on the server, where we have to render a Mii during account creation and during Mii updates. I know that Mii Creator is getting a lot of attention right now, and it looks nice, but that's all browser based. They've vaguely mentioned some developer API, but that still has a number of issues:

  1. It's not even clear if that API would do rendering or if it would just be an API for account data. Mii Creator seems to have a heavy focus on app integrations, which is what I assume this API is for, not for arbitrary renders
  2. Even if it does do rendering, I'm not sure I want to rely on it for the same reasons I didn't want to rely on your rendering API:
    1. Being at the mercy of a 3rd party server we don't control is a huge part of the issue with our current setup, and I'd venture to say that a community-run server is far less reliable that Nintendo's
    2. We get an insane amount of traffic, which would almost certainly hammer their API pretty hard. Not only would I feel bad about doing that, the Mii Creator devs have stated that they do not make any money from the app, plus given that the app is almost entirely client-side, I suspect the servers they are running are not very powerful and might not be able to cope with our traffic. I don't want us to be the reason they have down time, or implement harsh rate limits, etc.
  3. Like mentioned in the 1st point, Mii Creator seems to have a heavy focus on app integrations, and directly integrating with them is not something I'm really interested in doing at this time

Ideally what we would end up using is something we run ourselves, to prevent issues with downtime and hammering 3rd parties, and allow for future customizations if we choose to add them

Is something like your FFL.js port capable of doing something like this? I'm unsure how browser-locked it is. I know sometimes you can get around the browser with other libraries (for example in my side project https://github.com/jonbarrow/trainercards.studio, I draw everything to a standard canvas element, and I plan to make a "render API" by just using the same drawing logic used by the browser and using https://www.npmjs.com/package/canvas on the server), but I'm unsure if FFL.js is built in a way that allows for simple tricks like that

@ariankordi
Copy link

Ah yes, rendering. Definitely out of scope for this repo and I hope not to derail the comments here too hard.

First, I am really biased against Mii Creator since the author has been a poor communicator/collaborator with me. They've had this push to rewrite FFL.js with another library of theirs, due to "wanting every line of code to be theirs", but I've found their new library to have a bunch of copy-pasted and uncredited code. I am trying to mend things with them, but that's how I feel.

  1. I believe they have a server-side API that allows access to a user's Mii library, and a client-side API that lets you access that and clothing/headwear models. Neither are applicable, and they don't do any server-side rendering.
  2. I wouldn't use anything from them if they aren't open source. There is a repo, but last push was in February and kat21 told me he doesn't want to update it.

(I know I've been pushy when it comes to licenses before, which is my own fault. If asked, I will happily consider a better license for FFL.js.)

  1. Personally I don't like the direction they're going in either. The server-side storage, Discord SSO, and lack of open source feels like "Always Online DRM".

Moving on, it should be "possible" to use FFL.js + Three.js headlessly but haven't tried myself. I noticed that this MiiJS library (totally not like the other mii-js...) has actually tried this in Node.
They modified FFL.js to support WebGL 1.0, which I didn't integrate at first because Three.js didn't have a good way to let you know if WebGL 1.0 was being used, and they dropped support for it in later versions.
I would try 2.0 if you can, but if not then I'll merge WebGL 1.0 support.

Next topic is the body model. Traditionally, the "Face Library" on consoles only provides the head model. FFL.js has a method to get a head icon, but the body is needed to match official renders.
I always intended to make a "body model example" for ffl.js but struggled for months to get body scaling 1:1 accurate. I recently made a jsfiddle that accomplishes this, but it probably isn't trivial to make this work in an icon if you're not familiar with Three.js.
Also, the model itself originally comes from a bfres but the conversion process isn't trivial. The status quo has been to take it from TheModelsResource (since Switch Toolbox can't open it?), then use Blender to make transforms, add anims, and export to glb. I would really love a script that takes the bfres and emits the glb directly, so that nobody has to rely on anyone (or God forbid use Blender) for this.

Last comment of mine is that my motivation has been low to work on a lot of this, so the fact you're considering this route is nice to hear. I'd be happy to work on that cleanup in the FFL.js readme, converting to ESM, making those examples, or merging changes if you make any.
I've additionally had plans to replace FFL in general since: it is walking tech debt, and it's not as complex to reimplement as it seems. Particularly when it comes to the FFL-Testing server, I've wanted to replace it for eons as you know. I've even looked into trying to do 2D rendering accurately as it'd be lightweight on servers. But being too ambitious is something that also makes me even less motivated to work on these, so it is what it is.

@jonbarrow
Copy link
Member Author

jonbarrow commented Sep 26, 2025

First, I am really biased against Mii Creator since the author has been a poor communicator/collaborator with me. They've had this push to rewrite FFL.js with another library of theirs, due to "wanting every line of code to be theirs", but I've found their new library to have a bunch of copy-pasted and uncredited code. I am trying to mend things with them, but that's how I feel.

I appreciate the honesty here. I did see Oman sent me a video of things relating to this, which I admittedly have not finished watching (my attention span sucks these days) so I totally get that. I also felt a certain kind of way recently when it was pointed out by Jemma that they have a "import PNID" button which, upon checking Cloudflare, has generated quite a bit of traffic towards us with no heads up that I was made aware of, so maybe I'm a bit biased as well (I know your site does too, but it has far less traffic and you've at least communicated with us before)

I believe they have a server-side API that allows access to a user's Mii library, and a client-side API that lets you access that and clothing/headwear models. Neither are applicable, and they don't do any server-side rendering.

Gotcha, that seems to confirm what I also thought

I wouldn't use anything from them if they aren't open source. There is a repo, but last push was in February and kat21 told me he doesn't want to update it.
...
Personally I don't like the direction they're going in either. The server-side storage, Discord SSO, and lack of open source feels like "Always Online DRM".

I also agree here. That does seem a little odd. I'm not going to make any accusations of course with regard to the closed source nature of things, but it does seem odd to me some of the decisions they made here and it makes it conflict with projects like ours

(I know I've been pushy when it comes to licenses before, which is my own fault. If asked, I will happily consider a better license for FFL.js.)

It's already under AGPLv3 which is what we default to, so no complaints from us there 👍

Moving on, it should be "possible" to use FFL.js + Three.js headlessly but haven't tried myself. I noticed that this MiiJS library (totally not like the other mii-js...) has actually tried this in Node.
They modified FFL.js to support WebGL 1.0, which I didn't integrate at first because Three.js didn't have a good way to let you know if WebGL 1.0 was being used, and they dropped support for it in later versions.
I would try 2.0 if you can, but if not then I'll merge WebGL 1.0 support.

I agree with not using 1.0 if possible. Honestly I do wonder if it's worth relying on ThreeJS at all still, and instead just building a custom rendering pipeline. Packages like webgpu and @kmamal/gpu expose Dawn to NodeJS, which might be better suited here for something cross-env? Iirc ThreeJS also supports rendering with WebGPU instead of WebGL, so if worse comes to worse maybe a bridge could just be made here if ThreeJS is a must-have?

Next topic is the body model. Traditionally, the "Face Library" on consoles only provides the head model. FFL.js has a method to get a head icon, but the body is needed to match official renders.

That definitely sucks, ouch

I always intended to make a "body model example" for ffl.js but struggled for months to get body scaling 1:1 accurate

We currently use Switch-style renders with shitty settings, so 1:1 is not a MASSIVE concern for me now :P so long as it looks good, it's whatever at this point I just want to make sure we have something

Also, the model itself originally comes from a bfres but the conversion process isn't trivial. The status quo has been to take it from TheModelsResource (since Switch Toolbox can't open it?), then use Blender to make transforms, add anims, and export to glb. I would really love a script that takes the bfres and emits the glb directly, so that nobody has to rely on anyone (or God forbid use Blender) for this.

Where is the BFRES on the console? That's in scope for this repo, I'd be willing to put in some leg work here. I'm no 3D model expert though so the process of linking everything together and exporting to different formats may be beyond me, but adding a BFRES parser here is within scope. It also helps that I already worked on a BFRES parser years ago, back when I was making a Super Mario Odyssey level/model viewer

EDIT: I found the BFRES, I'll start working on adding the needed file format support here soon

Last comment of mine is that my motivation has been low to work on a lot of this, so the fact you're considering this route is nice to hear. I'd be happy to work on that cleanup in the FFL.js readme, converting to ESM, making those examples, or merging changes if you make any

Of course 👍 I did look at some internals of FFL.js and saw some areas I think could use some work, but nothing major rn (mostly just clean up like you said). We've been looking to make a Mii renderer for almost 10 years now, but keep putting it off, it's about time we finally did it lol. Your work as been an incredible help with that

I've additionally had plans to replace FFL in general since: it is walking tech debt, and it's not as complex to reimplement as it seems. Particularly when it comes to the FFL-Testing server, I've wanted to replace it for eons as you know. I've even looked into trying to do 2D rendering accurately as it'd be lightweight on servers. But being too ambitious is something that also makes me even less motivated to work on these, so it is what it is.

When you say "replace FFL" do you mean the system library, or FFL.js? I only ask because I saw this project recently which looks really cool, even though we likely would never use it (while it looks really cool, when we do things for the Wii U we have to take into account non-modded users because of SSSL, so patches like these typically aren't compatible with us)

Definitely out of scope for this repo and I hope not to derail the comments here too hard.

Fair enough, and agreed. We don't have to discuss this here moving forward. We do have a repo already for a Mii renderer, back when we were going to try and port Miitomo's renderer. Until shutter said it looked to be a straight FFL port, anyway, so we never did anything with it. The repo is private, and empty, but I might open it and move discussions about rendering there

@ariankordi
Copy link

ariankordi commented Sep 28, 2025

I talked to Austin (author of Mii Creator) on Thursday and had a great conversation. Without getting into details, we are on better terms now and I don't think he's a bad person - none of us want any drama.

they have a "import PNID" button which, upon checking Cloudflare, has generated quite a bit of traffic towards us with no heads up...

Ah! He's using my API for NNID/PNIDs. I at least made it so PNIDs should cache for a week. But, you can definitely ask him or me (if you can't reach him) to reimplement it himself and add a user agent for rate limiting, or... whatever, depends how you want this managed.

(in regards to Three.js, WebGPU)

I've tried the THREE.WebGPURenderer once, but it didn't work (can't remember why) and it requires rewriting shaders from GLSL to TSL anyway :(

A dependency-less Mii icon renderer using raw WebGPU would be nice to have, but sounds like too much effort to be worth it right now. Three.js is obviously the path of least resistance.
In C/C++, there's libraries like bgfx and sokol_gfx that support a bunch of graphics APIs - the latter having WebGPU support, a shader transpiler, and wasm examples that are like, ~50-300 KB.

So I've thought a bunch of times about a simple Mii icon library in C++ using those that could act as a server and be embeddable, think of using it for something like this.
It would only have a few functions, so not like FFL where there's several functions managing the lifecycle of a CharModel and many structs to parse - That and JSDoc take up at least 50% of ffl.js. This is one of the reasons I'm not completely fond of it, doing interop with FFL kinda sucks.

Anyway, if FFL.js + Three.js works for now, that can be a starting point. I just now updated it for WebGL 1.0 support, and simplified the example I showed you by Kestron to give you a cleaner example of making a head-only icon in Node.

You should be able to: clone that repo, add createFFLMiiIcon to the exports of index.js, and paste the JSDoc example linked above into a new file. If that works, then extract his code into your own project and see where that goes. I'm curious if it'd work in an HTTP server, as OpenGL may? have thread safety issues- But we'll see.

Where is the BFRES?

Looks like you found it, but I'll link you to the issue I opened with KillzXGaming about it not opening in Switch Toolbox.
There's MiiBodyMiddle.bfres containing male/female bodies, and MiiAnim.bfres containing the still "Pose00" to apply.
Notably, you may find copies of these in the Wii U Menu but the shapes slightly differ, the body/pants need to be separate meshes to apply the material correctly.


When you say "replace FFL"...

I mean to make an alternative to the FFL PC port Abood made, in favor of a Mii parsing/rendering library that's more modular, flexible, and universal. I'll go into the why and how of this later on, you may get an idea by looking at this repo.
BUT- the only reasons I brought this up had to do with my motivation (really my fault for being too ambitious), and then to say that I know where we are now (FFL.js quirks mentioned earlier) isn't fully ideal.
Worth noting that Three.js is modular enough that the renderer just needs to create a reusable "mesh object" class. You can actually use the same FFLShaderMaterial class with a head model from glTF and that'd produce the same result.

I saw this project recently which looks really cool

Oh, thanks. That thing is long from production ready, as it causes the Wii U to eventually hang when loading a game. I'm currently in the "hesitate to ask Maschell about it until I eventually forget that I ever wanted to work on it" phase of development.

We don't have to discuss this here moving forward

I keep feeling guilty about notifying everyone in this thread, so just mention me in the place where we can continue.

@jonbarrow
Copy link
Member Author

jonbarrow commented Sep 30, 2025

I talked to Austin (author of Mii Creator) on Thursday and had a great conversation. Without getting into details, we are on better terms now and I don't think he's a bad person - none of us want any drama.

Glad to hear 👍

Ah! He's using my API for NNID/PNIDs. I at least made it so PNIDs should cache for a week. But, you can definitely ask him or me (if you can't reach him) to reimplement it himself and add a user agent for rate limiting, or... whatever, depends how you want this managed.

No need for that, the traffic hasn't caused any issues, it's just something that rubbed us the wrong way initially due to the perceived lack of communication

I've tried the THREE.WebGPURenderer once, but it didn't work (can't remember why) and it requires rewriting shaders from GLSL to TSL anyway :(

A dependency-less Mii icon renderer using raw WebGPU would be nice to have, but sounds like too much effort to be worth it right now. Three.js is obviously the path of least resistance. In C/C++, there's libraries like bgfx and sokol_gfx that support a bunch of graphics APIs - the latter having WebGPU support, a shader transpiler, and wasm examples that are like, ~50-300 KB.

So I've thought a bunch of times about a simple Mii icon library in C++ using those that could act as a server and be embeddable, think of using it for something like this. It would only have a few functions, so not like FFL where there's several functions managing the lifecycle of a CharModel and many structs to parse - That and JSDoc take up at least 50% of ffl.js. This is one of the reasons I'm not completely fond of it, doing interop with FFL kinda sucks.

Anyway, if FFL.js + Three.js works for now, that can be a starting point. I just now updated it for WebGL 1.0 support, and simplified the example I showed you by Kestron to give you a cleaner example of making a head-only icon in Node.

You should be able to: clone that repo, add createFFLMiiIcon to the exports of index.js, and paste the JSDoc example linked above into a new file. If that works, then extract his code into your own project and see where that goes. I'm curious if it'd work in an HTTP server, as OpenGL may? have thread safety issues- But we'll see.

I don't think it has to be "dependency-less", just trying to shoot for something that doesn't require using a ThreeJS version that is going to grow more and more out of date as time goes on. Something simple and embeddable like you showed off here would honestly be ideal, though that can be talked about in the other repo

Looks like you found it, but I'll link you to the issue I opened with KillzXGaming about it not opening in Switch Toolbox.
There's MiiBodyMiddle.bfres containing male/female bodies, and MiiAnim.bfres containing the still "Pose00" to apply.
Notably, you may find copies of these in the Wii U Menu but the shapes slightly differ, the body/pants need to be separate meshes to apply the material correctly.

I did find it yes. I've opened up relevant PRs for this here:

Ideally in the end these should be able to be used together to make a script like:

import fs from 'node:fs';
import { CMP } from '@pretendonetwork/nintendo-files/compression';
import { ME01, BFRES } from '@pretendonetwork/nintendo-files';

const decompressed = CMP.fromFile('./main.sgarc.cmp');
const archive = ME01.fromBuffer(decompressed);
const bfresFile = archive.files.find(({ name }) => name === 'g3d/MiiBody/MiiBodyHigh/MiiBodyHigh.bfres');
const bfres = BFRES.fromBuffer(bfres!.data);
const glb = BFRES.exportModelAsGLB('MaleBody');

fs.writeFileSync('MaleBody.glb', glb);

I keep feeling guilty about notifying everyone in this thread, so just mention me in the place where we can continue.

I invited you to https://github.com/PretendoNetwork/mii-renderer earlier today. It's still private since there's nothing there right now (this was originally going to be for our Miitomo-base renderer that we gave up on), but I did open 2 issues about the topic of rendering

@jonbarrow
Copy link
Member Author

Since rendering is going to be done in another repo though, I think I can finally take this out of draft?

@jonbarrow jonbarrow marked this pull request as ready for review September 30, 2025 17:22
@jonbarrow
Copy link
Member Author

Going to also ping @binaryoverload and @mrjvs since the changes made in PretendoNetwork/mii-js#8 seem to be having issues here, as well as @gitlimes to make sure this is still compatible with the website

@mrjvs
Copy link

mrjvs commented Sep 30, 2025

What do you want me to look at?

@jonbarrow
Copy link
Member Author

What do you want me to look at?

Mostly just the tsup parts and making sure it's browser compatible. I went over the issues I was having in this comment

@mrjvs
Copy link

mrjvs commented Sep 30, 2025

Reading your comment, I see a couple of main points:

  • tsup generates one big index.js file.
  • the output cant be directly imported into browser with a script tag.
  • DTS generation issues when using tsup dts generation

As for tsup generating one big index.js file. Depending on what your usecases are, that may be what you want.
Browsers don't have require(), so the preferred output for libraries are to be selfcontained into one javascript file.
For nodejs projects you probably want that.

What you can do is make 2 compile targets with tsup. One for node, one for web.
You can disable all the bundling features for node one (bundle: false), and keep them all turned on for the web one.

That, along with a "browser" exports type in the package.json (or publish it on a /web path), you can have the best for both platforms.


Output can't currently be imported with a script tag for the browser because:

  1. You're compiling for the node platform
  2. You're using Buffer

The reason this worked for Nuxt in PR PretendoNetwork/mii-js#8 , is because nuxt has a crap ton of compilation steps under the hood that does a best-effort attempt at bundling NodeJS NPM packages for web.

Ideally, you'd remove the need for Buffer all together, for example:

  1. You can replace buffer with ArrayBuffer/UInt8Array, which exists on web
  2. You write an abstraction layer for buffer that uses either an ArrayBuffer/UInt8Array or a Buffer depending on which platform (using treeshaking)
  3. Use a polyfill for buffer

If you can't do those, or don't want to put in the effort. You could also half-implement browser support like mii-js did in my PR, it won't work in browsers but bundlers like nuxt and vite will have an easier time with it.


About DTS generation being buggy, I'll be honest I have no idea how or why. but using this tsconfig fixed the problems:

{
	"compilerOptions": {
		"strict": true,
		"strictPropertyInitialization": false,
		"skipLibCheck": true,
		"moduleResolution": "bundler",
		"baseUrl": "src",
		"outDir": "dist",
		"target": "es2022",
		"paths": {
			"@/*": ["./*"]
		}
	},
	"include": ["src"]
}

I just updated everything so it makes the most sense to me and now dts compiling works (you do have to remove --emitDeclaration again from tsc.

@mrjvs
Copy link

mrjvs commented Sep 30, 2025

About DTS generation being buggy, I'll be honest I have no idea how or why. but using this tsconfig fixed the problems

scratch that, I cant even use the fix now. why 🙃

@jonbarrow
Copy link
Member Author

The issue of the big index.js has been fixed already, that's my fault for not mentioning that before my apologies. I thought I had left another comment about it

As for the browser stuff, I honestly had no idea that Nuxt did that. If that's the case then I don't think I'll put too much more effort into making it browser compatible right now. My main use case was our website, which is getting rewritten in Nuxt. I'm open to someone making this truly browser compatible using the methods you mentioned, but so long as it works in Nuxt, I'm happy for now. Thank you for looking into that!

when lastField is in the instance of the parser, there can be a race condition where a call to both parse and encode, or multiple calls to either, at the same time can set the wrong value in the instance
jonbarrow added a commit to PretendoNetwork/binary-parser that referenced this pull request Oct 8, 2025
this commit takes the BinaryParser class from PretendoNetwork/nintendo-file-formats#2 and pulls it into its own library
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.

6 participants