Jump to content

RFLCharData: Difference between revisions

From Mii Technical Wiki
No edit summary
No edit summary
 
(7 intermediate revisions by the same user not shown)
Line 1: Line 1:
RFLCharData is the [[Mii Character Data|Mii data format]] used on [[Wii]]. This format has big endianness, and it's little endian counterpart is used on [[DS]].
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]].


== Data format ==
== Data format ==
Sorry, it's in Rust again.<syntaxhighlight lang="rust">
This sample is in ''Kaitai Struct'' language.<syntaxhighlight lang="yaml">
#[bitsize(4)]
meta:
#[repr(u8)]
  id: r_f_l_store_data
#[derive(FromBits, Debug, PartialEq)]
  application: |
pub enum FavoriteColor {
    Mii data format used on Wii (big-endian) and DS (little-endian).
     Red = 0,
    Also included in CFL and FFL.
     Orange = 1,
     Fields marked as "padding" are always zero.
     Yellow = 2,
  file-extension:
     YellowGreen = 3,
     - rcd
     Green = 4,
     - rsd
     Blue = 5,
     # Unofficial:
     SkyBlue = 6,
     - mii
    Pink = 7,
     - miigx
     Purple = 8,
     - mae
    Brown = 9,
  xref:
    White = 10,
     struct_names: |
    Black = 11,
      RFLCharData/RFLiCharData
    #[fallback]
      CFLiRFLMiiDataCore, FFLiMiiDataCoreRFL, FFLiMiiDataOfficialRFL
    Invalid(u4),
      RFLStoreData
}
  endian: be
  bit-endian: be


impl FavoriteColor {
seq:
     fn as_u8(&self) -> u8 {
  - id: padding0
        match self {
    type: b1
            FavoriteColor::Red => 0,
  - id: gender
            FavoriteColor::Orange => 1,
     type: b1
            FavoriteColor::Yellow => 2,
    doc: Originally named sex
            FavoriteColor::YellowGreen => 3,
    enum: gender
            FavoriteColor::Green => 4,
  - id: birth_month
            FavoriteColor::Blue => 5,
    type: b4
            FavoriteColor::SkyBlue => 6,
    doc: Originally named birth_month despite other fields using camelCase.
            FavoriteColor::Pink => 7,
  - id: birth_day
            FavoriteColor::Purple => 8,
    type: b5
            FavoriteColor::Brown => 9,
    doc: |
            FavoriteColor::White => 10,
      Originally named birth_day despite other fields using camelCase.
            FavoriteColor::Black => 11,
      0 = not set. Counts from 1-31.
            FavoriteColor::Invalid(n) => n.as_u8(),
      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


#[bitsize(1)]
# Mirror fields based on naming
#[derive(FromBits, Debug, PartialEq)]
# used in other structures.
pub enum Gender {
instances:
     Male,
  # nn::mii naming:
     Female,
  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


#[bitfield(16)]
# Expand CreateID type.
pub struct PersonalInfoField {
types:
     pub padding: u1,
  create_id:
    /// Originally named sex
     seq:
    pub gender: Gender,
      - id: flags
    pub birth_month: u4,
        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.


    /// 0 = unset, Counts from 1-31
          In order for the Mii to be considered as created from the
     /// Birth month and day must be set together if set.
          same console on Wii (RFLiIsMyHomeID), the CreateID has to
     pub birth_day: u5,
          be non-null, not from DS, and the MAC/sum has to match.
    pub favorite_color: FavoriteColor,
        type: u1
    pub favorite: bool,
        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


#[bitfield(16)]
  create_id_flags:
pub struct FaceField {
    seq:
    pub face_type: u3,
      - id: normal
    pub face_color: u3,
        doc: Cleared = Special, Set = Normal
    pub face_tex: u4,
        type: b1be
    pub padding2: u3,
      - id: field_1
    pub localonly: u1,
        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.


     /// Set to 1 if downloaded from "Check Mii Out Channel".
          The functions FFLiIsValidMiiID(), CFLi_IsValidMiiID(),
    pub type_: u2,
          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


#[bitfield(16)]
     enums:
pub struct HairField {
      create_id_platform:
     pub hair_type: u7,
        0: wii  # 00 - Bit 2 clear, 4 clear
    pub hair_color: u3,
        1: ctr  # 01 - Bit 2 clear, 4 set
    pub hair_flip: u1,
        2: ntr  # 10 - Bit 2 set,   4 clear
    pub padding3: u5,
        3: wiiu # 11 - Bit 2 set,   4 set
}
        #  ^^^^ Also Miitomo, Switch (nn::mii::detail::ModifyVer3CreateIdWiiUAndNormal)
 
enums:
#[bitfield(32)]
  type:           # RFLDataType (unused?) - ONLY defined in RFL_Types.h from RVLFaceLibraryAlpha3_0926
pub struct EyebrowField {
     0: normal
    pub eyebrow_type: u5,
     1: presented  # Set when downloaded from Check Mii Out Channel.
    pub eyebrow_rotate: u5,
     2: underground
    pub padding4: u6,
     3: special
    pub eyebrow_color: u3,
  gender:        # RFLSex/CFLGender/FFLGender/nn::mii::Gender
    pub eyebrow_scale: u4,
     0: male
    pub eyebrow_y: u5,
     1: female
    pub eyebrow_x: u4,
     2: all
}
  favorite_color: # RFLFavoriteColor/CFLFavoriteColor/FFLFavoriteColor/nn::mii::FavoriteColor
 
     0: red
#[bitfield(32)]
     1: orange
pub struct EyeField {
     2: yellow
    pub eye_type: u6,
     3: yellowgreen
    pub eye_rotate: u5,
     4: green
    pub eye_y: u5,
     5: blue
    pub eye_color: u3,
     6: skyblue
    pub eye_scale: u4,
     7: pink
    pub eye_x: u4,
     8: purple
    pub padding5: u5,
     9: brown
}
     10: white
 
     11: black
#[bitfield(16)]
  # month? day? month_of_day?
pub struct NoseField {
</syntaxhighlight>
     pub nose_type: u4,
     pub nose_scale: u4,
     pub nose_y: u5,
     pub padding6: u3,
}
 
#[bitfield(16)]
pub struct MouthField {
    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)]
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,
/// and some other miscellaneous state. See [RvlCreateIdFlags::platform].
///
/// `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.
== In codebases ==
    pub temporary: bool,


    /// Cleared on Wii and DS, set on 3DS and Wii U.
* [https://github.com/SMGCommunity/Petari/blob/629da989f8e8f166f330a1e9d32ae70f78e302df/libs/RVLFaceLib/include/RFLi_Types.h#L260 Petari]
    pub field_3: bool,
{{Format-Navbox}}
}
 
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]