Jump to content

RFLCharData: Difference between revisions

From Mii Technical Wiki
Created page with "<syntaxhighlight lang="rust"> #[bitsize(4)] #[repr(u8)] #[derive(FromBits, Debug, PartialEq)] pub enum FavoriteColor { Red = 0, Orange = 1, Yellow = 2, YellowGreen = 3, Green = 4, Blue = 5, SkyBlue = 6, Pink = 7, Purple = 8, Brown = 9, White = 10, Black = 11, #[fallback] Invalid(u4), } impl FavoriteColor { fn as_u8(&self) -> u8 { match self { FavoriteColor::Red => 0, FavoriteColo..."
 
No edit summary
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
<syntaxhighlight lang="rust">
RFLCharData is the [[Mii Character Data|Mii data format]] used on [[Wii]]. This format has big endianness, and it's little endian counterpart ''NFLCharData'' is used on [[DS]].
#[bitsize(4)]
#[repr(u8)]
#[derive(FromBits, Debug, PartialEq)]
pub enum FavoriteColor {
    Red = 0,
    Orange = 1,
    Yellow = 2,
    YellowGreen = 3,
    Green = 4,
    Blue = 5,
    SkyBlue = 6,
    Pink = 7,
    Purple = 8,
    Brown = 9,
    White = 10,
    Black = 11,
    #[fallback]
    Invalid(u4),
}


impl FavoriteColor {
== Data format ==
     fn as_u8(&self) -> u8 {
This sample is in ''Kaitai Struct'' language.<syntaxhighlight lang="yaml">
        match self {
meta:
            FavoriteColor::Red => 0,
  id: r_f_l_store_data
            FavoriteColor::Orange => 1,
  application: |
            FavoriteColor::Yellow => 2,
     Mii data format used on Wii (big-endian) and DS (little-endian).
            FavoriteColor::YellowGreen => 3,
    Also included in CFL and FFL.
            FavoriteColor::Green => 4,
    Fields marked as "padding" are always zero.
            FavoriteColor::Blue => 5,
  file-extension:
            FavoriteColor::SkyBlue => 6,
    - rcd
            FavoriteColor::Pink => 7,
    - rsd
            FavoriteColor::Purple => 8,
    # Unofficial:
            FavoriteColor::Brown => 9,
    - mii
            FavoriteColor::White => 10,
    - miigx
            FavoriteColor::Black => 11,
    - mae
            FavoriteColor::Invalid(n) => n.as_u8(),
  xref:
        }
    struct_names: |
    }
      RFLCharData/RFLiCharData
}
      CFLiRFLMiiDataCore, FFLiMiiDataCoreRFL, FFLiMiiDataOfficialRFL
      RFLStoreData
  endian: be
  bit-endian: be


#[bitsize(1)]
seq:
#[derive(FromBits, Debug, PartialEq)]
  - id: padding0
pub enum Gender {
    type: b1
     Male,
  - id: gender
     Female,
    type: b1
}
    doc: Originally named sex
    enum: gender
  - id: birth_month
    type: b4
    doc: Originally named birth_month despite other fields using camelCase.
  - id: birth_day
    type: b5
    doc: |
      Originally named birth_day despite other fields using camelCase.
      0 = not set. Counts from 1-31.
      Consoles only let you set both fields, not just one.
      Note that the maximum day per month is validated in
      CFL (CFLi_GetMonthOfDay::monthOfDay) and
      FFL (MONTH_OF_DAY, https://github.com/aboood40091/ffl/blob/master/src/FFLiDateTime.cpp#L47)
  - id: favorite_color
    type: b4
    enum: favorite_color
  - id: favorite
    type: b1
  - id: name
    type: str
    encoding: utf-16be
    size: 20
    doc: "Not null-terminated, contains 10 characters"
  - id: height
    type: u1
  - id: build
    type: u1
  # Byte 0x18 (RFLCreateID is 8 bytes)
  - id: create_id
    type: create_id
  - id: face_type
    type: b3
  - id: face_color
    type: b3
  - id: face_tex
    type: b4
  - id: padding2
    type: b3
  - id: localonly
    type: b1
  - id: type
    type: b2
    enum: type
    doc: |
      Set to 1 if downloaded from
      Check Mii Out Channel. Enum is
      deleted "RFLDataType" from RVLFaceLibraryAlpha3_0926
  - id: hair_type
    type: b7
  - id: hair_color
    type: b3
  - id: hair_flip
    type: b1
  - id: padding3
    type: b5
  - id: eyebrow_type
    type: b5
  - id: eyebrow_rotate
    type: b5
  - id: padding4
    type: b6
  - id: eyebrow_color
    type: b3
  - id: eyebrow_scale
    type: b4
  - id: eyebrow_y
    type: b5
  - id: eyebrow_x
    type: b4
  - id: eye_type
    type: b6
  - id: eye_rotate
    type: b5
  - id: eye_y
    type: b5
  - id: eye_color
    type: b3
  - id: eye_scale
    type: b4
  - id: eye_x
    type: b4
  - id: padding5
    type: b5
  - id: nose_type
    type: b4
  - id: nose_scale
    type: b4
  - id: nose_y
    type: b5
  - id: padding6
    type: b3
  - id: mouth_type
    type: b5
  - id: mouth_color
    type: b2
  - id: mouth_scale
    type: b4
  - id: mouth_y
    type: b5
  - id: glass_type
    type: b4
  - id: glass_color
    type: b3
  - id: glass_scale
    type: b4
  - id: glass_y
    type: b5
  - id: mustache_type
    type: b2
  - id: beard_type
    type: b2
  - id: beard_color
    type: b3
  - id: beard_scale
    type: b4
  - id: beard_y
    type: b5
  - id: mole_type
    type: b1
  - id: mole_scale
    type: b4
  - id: mole_y
    type: b5
  - id: mole_x
    type: b5
  - id: padding8
    type: b1
  - id: creator_name
    type: str
    encoding: utf-16be
     size: 20
    doc: "Not null-terminated, contains 10 characters"
     if: not _io.eof
# Not in RFLCharData, added in RFLStoreData
#  - id: crc
#    doc: Additional in CFLiPackedMiiDataPacket.
#    type: u2
#    if: not _io.eof


#[bitfield(16)]
# Mirror fields based on naming
pub struct PersonalInfoField {
# used in other structures.
     pub padding: u1,
instances:
     /// Originally named sex
  # nn::mii naming:
     pub gender: Gender,
  faceline_color:
     pub birth_month: u4,
     value: face_color
  faceline_type:
     value: face_type
  faceline_wrinkle:
     value: face_tex
  mustache_scale:
    value: beard_scale
  mustache_y:
    value: beard_y
  nickname:
     value: name


     /// 0 = unset, Counts from 1-31
# Expand CreateID type.
    /// Birth month and day must be set together if set.
types:
    pub birth_day: u5,
  create_id:
    pub favorite_color: FavoriteColor,
     seq:
    pub favorite: bool,
      - id: flags
}
        type: create_id_flags
      - id: create_date_offset
        type: b28be
      - id: addr_low
        doc: |
          On Wii and DS, this contains the last 3 bytes
          of the MAC address, with the first byte being a
          checksum over the first 3 bytes, calculated (in JS) like:
          `macAddressUint8Array.slice(0, 3).reduce((sum, b) => sum + b, 0) & 0x7F`
          (Calculated in createLowAddr_ in RFL_Database.c)
          NOTE: Completely unknown how it is calculated on DS.


#[bitfield(16)]
          In order for the Mii to be considered as created from the
pub struct FaceField {
          same console on Wii (RFLiIsMyHomeID), the CreateID has to
     pub face_type: u3,
          be non-null, not from DS, and the MAC/sum has to match.
    pub face_color: u3,
        type: u1
    pub face_tex: u4,
        repeat: expr
    pub padding2: u3,
        repeat-expr: 4
    pub localonly: u1,
    instances:
      create_date_timestamp:
        # Time is divided by two when writing.
        # Not sure if this is ever read by consoles.
        value: |
          (create_date_offset * 4)
            + 1136073600
        #     ^^^^^^^^^^ Timestamp of first day of 2006.
        # Dates wrap around on: Jan. 5, 2023, 18:48:32
        # TODO IF THEY ARE BY 4: Jan. 10, 2040, 13:37:04
        doc: |
          Creation date as a Unix timestamp.
          TODO Unknown if this is accurate
      data:
        pos: 24          # << Offset of createID field.
        type: u1
        repeat: expr
        repeat-expr: 8


     /// Set to 1 if downloaded from "Check Mii Out Channel".
  create_id_flags:
    pub type_: u2,
     seq:
}
      - id: normal
        doc: Cleared = Special, Set = Normal
        type: b1be
      - id: field_1
        doc: Cleared on Wii and 3DS, set on DS and Wii U.
        type: b1be
      - id: temporary
        doc: |
          Given to random Miis and seen in some games' CPU Miis.
          Its meaning is derived from how random Miis are obtained
          from a database (RFLMiddleDBType_Random, FFLiDatabaseRandom)
          that returns a random Mii on each index. So, every time you
          fetch from the database, it creates a new random Mii, so the index
          cannot be relied upon, and this also effectively prevents those
          random Miis from being saved.


#[bitfield(16)]
          The functions FFLiIsValidMiiID(), CFLi_IsValidMiiID(),
pub struct HairField {
          nn::mii::detail::Ver3CreateId::IsValid()
    pub hair_type: u7,
          all check against this bit - e.g. makes data INVALID.
     pub hair_color: u3,
        type: b1be
    pub hair_flip: u1,
      - id: field_3
    pub padding3: u5,
        doc: Cleared on Wii and DS, set on 3DS and Wii U.
}
        type: b1be
     instances:
      platform:
        doc: Second and fourth bit of flags.
        value: (field_1.to_i << 1) | field_3.to_i
        enum: create_id_platform


#[bitfield(32)]
     enums:
pub struct EyebrowField {
      create_id_platform:
     pub eyebrow_type: u5,
        0: wii  # 00 - Bit 2 clear, 4 clear
    pub eyebrow_rotate: u5,
        1: ctr  # 01 - Bit 2 clear, 4 set
    pub padding4: u6,
        2: ntr  # 10 - Bit 2 set,   4 clear
    pub eyebrow_color: u3,
        3: wiiu # 11 - Bit 2 set,   4 set
    pub eyebrow_scale: u4,
        #  ^^^^ Also Miitomo, Switch (nn::mii::detail::ModifyVer3CreateIdWiiUAndNormal)
    pub eyebrow_y: u5,
enums:
    pub eyebrow_x: u4,
  type:          # RFLDataType (unused?) - ONLY defined in RFL_Types.h from RVLFaceLibraryAlpha3_0926
}
     0: normal
 
     1: presented  # Set when downloaded from Check Mii Out Channel.
#[bitfield(32)]
     2: underground
pub struct EyeField {
     3: special
     pub eye_type: u6,
  gender:        # RFLSex/CFLGender/FFLGender/nn::mii::Gender
     pub eye_rotate: u5,
     0: male
     pub eye_y: u5,
     1: female
     pub eye_color: u3,
     2: all
    pub eye_scale: u4,
  favorite_color: # RFLFavoriteColor/CFLFavoriteColor/FFLFavoriteColor/nn::mii::FavoriteColor
    pub eye_x: u4,
     0: red
    pub padding5: u5,
     1: orange
}
     2: yellow
 
     3: yellowgreen
#[bitfield(16)]
     4: green
pub struct NoseField {
     5: blue
     pub nose_type: u4,
     6: skyblue
     pub nose_scale: u4,
     7: pink
     pub nose_y: u5,
     8: purple
    pub padding6: u3,
     9: brown
}
     10: white
 
     11: black
#[bitfield(16)]
  # month? day? month_of_day?
pub struct MouthField {
</syntaxhighlight>
     pub mouth_type: u5,
     pub mouth_color: u2,
     pub mouth_scale: u4,
     pub mouth_y: u5,
}
 
#[bitfield(16)]
pub struct GlassField {
     pub glass_type: u4,
     pub glass_color: u3,
     pub glass_scale: u4,
     pub glass_y: u5,
}
 
#[bitfield(16)]
pub struct FaceHairField {
     pub mustache_type: u2,
     pub beard_type: u2,
     pub beard_color: u3,
     pub beard_scale: u4,
    pub beard_y: u5,
}


#[bitfield(16)]
== In codebases ==
pub struct MoleField {
    pub mole_type: u1,
    pub mole_scale: u4,
    pub mole_y: u5,
    pub mole_x: u5,
    pub padding8: u1,
}


/// `flags` contain information about where the Char was created,
* [https://github.com/SMGCommunity/Petari/blob/629da989f8e8f166f330a1e9d32ae70f78e302df/libs/RVLFaceLib/include/RFLi_Types.h#L260 Petari]
/// and some other miscellaneous state. See [RvlCreateIdFlags::platform].
{{Format-Navbox}}
///
/// `create_date_offset` contains an offset timestamp of creation date.
/// See [Self::create_date_timestamp]'s implementation for more information.
///
/// `addr_low` is built from the Mac address of the Rvl/Ntr console.
/// It contains a checksum of the first three bytes, and the last three bytes.
///
/// ```rust
/// fn build_addr_low(mac: &[u8; 6]) -> [u8; 4] {
///    let checksum = mac.iter().take(3).fold(0u8, |sum, &b| sum.wrapping_add(b)) & 0x7F;
///     [checksum, mac[3], mac[4], mac[5]]
/// }
/// ```
///
/// It is unknown how the checksum byte is calculated on Ntr targets.
///
/// In order for the Char to be considered as created from the
/// same console on Rvl (RFLiIsMyHomeID), the CreateId has to
/// be non-null, not from Ntr, and the `addr_low` has to match.
///
#[bitfield(64)]
pub struct CreateId {
    pub flags: CreateIdFlags,
 
    pub create_date_offset: u28,
 
    pub addr_low: [u8; 4],
}
 
impl CreateId {
    /// Outputs the creation date timestamp from the offset encoded.
    /// It is not known if this implementation is accurate.
    pub fn create_date_timestamp(&self) -> u32 {
        const JAN_1_2006: u32 = 1136073600;
        let offset = self.create_date_offset().as_u32();
 
        (offset * 4) + JAN_1_2006
    }
}
 
/// `Etc` can be either targets: Cafe, Nx, Miitomo
#[bitsize(2)]
#[derive(FromBits, Debug, PartialEq)]
pub enum CreateIdPlatform {
    Rvl = 0b00,
    Ctr = 0b01,
    Ntr = 0b10,
    Etc = 0b11,
}
 
/// These flags can be used to derive the creation platform,
/// see [Self::platform].
///
#[bitfield(4)]
pub struct CreateIdFlags {
    /// Cleared = Special, Set = Normal
    pub normal: bool,
 
    /// Cleared on Wii and 3DS, set on DS and Wii U.
    pub field_1: bool,
 
    /// Given to random Miis and seen in some games' CPU Miis.
    pub temporary: bool,
 
    /// Cleared on Wii and DS, set on 3DS and Wii U.
    pub field_3: bool,
}
 
impl CreateIdFlags {
    /// Outputs the creation platform for the Char.
    /// Uses `field_1` and `field_3`.
    pub fn platform(&self) -> CreateIdPlatform {
        let bits = u2::from_u8(match (self.field_1(), self.field_3()) {
            (false, false) => 0b00_u8,
            (true, false) => 0b10,
            (false, true) => 0b01,
            (true, true) => 0b11,
        });
 
        CreateIdPlatform::from(bits)
    }
}
 
/// A packed character info format.
/// This structure has a lot of bitfields.
/// These fields have been given speculative names.
///
/// This format is commonly known as `.{r,n}cd`.
/// Rvl and Ntr only differ by endian-ness.
#[derive(Debug)]
#[brw(big)]
pub struct RvlCharData {
    pub personal_info: PersonalInfoField,
    pub name: FixedLengthWideString<10>,
    pub height: u8,
    pub build: u8,
    pub create_id: CreateId,
    pub face: FaceField,
    pub hair: HairField,
    pub eyebrow: EyebrowField,
    pub eye: EyeField,
    pub nose: NoseField,
    pub mouth: MouthField,
    pub glass: GlassField,
    pub face_hair: FaceHairField,
    pub mole: MoleField,
    pub creator_name: FixedLengthWideString<10>,
}
</syntaxhighlight>

Latest revision as of 12:02, 24 September 2025

RFLCharData is the Mii data format used on Wii. This format has big endianness, and it's little endian counterpart NFLCharData is used on DS.

Data format

[edit | edit source]

This sample is in Kaitai Struct language.

meta:
  id: r_f_l_store_data
  application: |
    Mii data format used on Wii (big-endian) and DS (little-endian).
    Also included in CFL and FFL.
    Fields marked as "padding" are always zero.
  file-extension:
    - rcd
    - rsd
    # Unofficial:
    - mii
    - miigx
    - mae
  xref:
    struct_names: |
      RFLCharData/RFLiCharData
      CFLiRFLMiiDataCore, FFLiMiiDataCoreRFL, FFLiMiiDataOfficialRFL
      RFLStoreData
  endian: be
  bit-endian: be

seq:
  - id: padding0
    type: b1
  - id: gender
    type: b1
    doc: Originally named sex
    enum: gender
  - id: birth_month
    type: b4
    doc: Originally named birth_month despite other fields using camelCase.
  - id: birth_day
    type: b5
    doc: |
      Originally named birth_day despite other fields using camelCase.
      0 = not set. Counts from 1-31.
      Consoles only let you set both fields, not just one.
      Note that the maximum day per month is validated in
      CFL (CFLi_GetMonthOfDay::monthOfDay) and
      FFL (MONTH_OF_DAY, https://github.com/aboood40091/ffl/blob/master/src/FFLiDateTime.cpp#L47)
  - id: favorite_color
    type: b4
    enum: favorite_color
  - id: favorite
    type: b1
  - id: name
    type: str
    encoding: utf-16be
    size: 20
    doc: "Not null-terminated, contains 10 characters"
  - id: height
    type: u1
  - id: build
    type: u1
  # Byte 0x18 (RFLCreateID is 8 bytes)
  - id: create_id
    type: create_id
  - id: face_type
    type: b3
  - id: face_color
    type: b3
  - id: face_tex
    type: b4
  - id: padding2
    type: b3
  - id: localonly
    type: b1
  - id: type
    type: b2
    enum: type
    doc: |
      Set to 1 if downloaded from
      Check Mii Out Channel. Enum is
      deleted "RFLDataType" from RVLFaceLibraryAlpha3_0926
  - id: hair_type
    type: b7
  - id: hair_color
    type: b3
  - id: hair_flip
    type: b1
  - id: padding3
    type: b5
  - id: eyebrow_type
    type: b5
  - id: eyebrow_rotate
    type: b5
  - id: padding4
    type: b6
  - id: eyebrow_color
    type: b3
  - id: eyebrow_scale
    type: b4
  - id: eyebrow_y
    type: b5
  - id: eyebrow_x
    type: b4
  - id: eye_type
    type: b6
  - id: eye_rotate
    type: b5
  - id: eye_y
    type: b5
  - id: eye_color
    type: b3
  - id: eye_scale
    type: b4
  - id: eye_x
    type: b4
  - id: padding5
    type: b5
  - id: nose_type
    type: b4
  - id: nose_scale
    type: b4
  - id: nose_y
    type: b5
  - id: padding6
    type: b3
  - id: mouth_type
    type: b5
  - id: mouth_color
    type: b2
  - id: mouth_scale
    type: b4
  - id: mouth_y
    type: b5
  - id: glass_type
    type: b4
  - id: glass_color
    type: b3
  - id: glass_scale
    type: b4
  - id: glass_y
    type: b5
  - id: mustache_type
    type: b2
  - id: beard_type
    type: b2
  - id: beard_color
    type: b3
  - id: beard_scale
    type: b4
  - id: beard_y
    type: b5
  - id: mole_type
    type: b1
  - id: mole_scale
    type: b4
  - id: mole_y
    type: b5
  - id: mole_x
    type: b5
  - id: padding8
    type: b1
  - id: creator_name
    type: str
    encoding: utf-16be
    size: 20
    doc: "Not null-terminated, contains 10 characters"
    if: not _io.eof
# Not in RFLCharData, added in RFLStoreData
#  - id: crc
#    doc: Additional in CFLiPackedMiiDataPacket.
#    type: u2
#    if: not _io.eof

# Mirror fields based on naming
# used in other structures.
instances:
  # nn::mii naming:
  faceline_color:
    value: face_color
  faceline_type:
    value: face_type
  faceline_wrinkle:
    value: face_tex
  mustache_scale:
    value: beard_scale
  mustache_y:
    value: beard_y
  nickname:
    value: name

# Expand CreateID type.
types:
  create_id:
    seq:
      - id: flags
        type: create_id_flags
      - id: create_date_offset
        type: b28be
      - id: addr_low
        doc: |
          On Wii and DS, this contains the last 3 bytes
          of the MAC address, with the first byte being a
          checksum over the first 3 bytes, calculated (in JS) like:
          `macAddressUint8Array.slice(0, 3).reduce((sum, b) => sum + b, 0) & 0x7F`
          (Calculated in createLowAddr_ in RFL_Database.c)
          NOTE: Completely unknown how it is calculated on DS.

          In order for the Mii to be considered as created from the
          same console on Wii (RFLiIsMyHomeID), the CreateID has to
          be non-null, not from DS, and the MAC/sum has to match.
        type: u1
        repeat: expr
        repeat-expr: 4
    instances:
      create_date_timestamp:
        # Time is divided by two when writing.
        # Not sure if this is ever read by consoles.
        value: |
          (create_date_offset * 4)
            + 1136073600
        #     ^^^^^^^^^^ Timestamp of first day of 2006.
        # Dates wrap around on: Jan. 5, 2023, 18:48:32
        # TODO IF THEY ARE BY 4: Jan. 10, 2040, 13:37:04
        doc: |
          Creation date as a Unix timestamp.
          TODO Unknown if this is accurate
      data:
        pos: 24          # << Offset of createID field.
        type: u1
        repeat: expr
        repeat-expr: 8

  create_id_flags:
    seq:
      - id: normal
        doc: Cleared = Special, Set = Normal
        type: b1be
      - id: field_1
        doc: Cleared on Wii and 3DS, set on DS and Wii U.
        type: b1be
      - id: temporary
        doc: |
          Given to random Miis and seen in some games' CPU Miis.
          Its meaning is derived from how random Miis are obtained
          from a database (RFLMiddleDBType_Random, FFLiDatabaseRandom)
          that returns a random Mii on each index. So, every time you
          fetch from the database, it creates a new random Mii, so the index
          cannot be relied upon, and this also effectively prevents those
          random Miis from being saved.

          The functions FFLiIsValidMiiID(), CFLi_IsValidMiiID(),
          nn::mii::detail::Ver3CreateId::IsValid()
          all check against this bit - e.g. makes data INVALID.
        type: b1be
      - id: field_3
        doc: Cleared on Wii and DS, set on 3DS and Wii U.
        type: b1be
    instances:
      platform:
        doc: Second and fourth bit of flags.
        value: (field_1.to_i << 1) | field_3.to_i
        enum: create_id_platform

    enums:
      create_id_platform:
        0: wii  # 00 - Bit 2 clear, 4 clear
        1: ctr  # 01 - Bit 2 clear, 4 set
        2: ntr  # 10 - Bit 2 set,   4 clear
        3: wiiu # 11 - Bit 2 set,   4 set
        #  ^^^^ Also Miitomo, Switch (nn::mii::detail::ModifyVer3CreateIdWiiUAndNormal)
enums:
  type:           # RFLDataType (unused?) - ONLY defined in RFL_Types.h from RVLFaceLibraryAlpha3_0926
    0: normal
    1: presented  # Set when downloaded from Check Mii Out Channel.
    2: underground
    3: special
  gender:         # RFLSex/CFLGender/FFLGender/nn::mii::Gender
    0: male
    1: female
    2: all
  favorite_color: # RFLFavoriteColor/CFLFavoriteColor/FFLFavoriteColor/nn::mii::FavoriteColor
    0: red
    1: orange
    2: yellow
    3: yellowgreen
    4: green
    5: blue
    6: skyblue
    7: pink
    8: purple
    9: brown
    10: white
    11: black
  # month? day? month_of_day?

In codebases

[edit | edit source]