Mii rendering and parsing library

Added CafeCharStoreData

Changed files
+533 -77
crates
vee_parse
vee_parse_macros
resources_here
testbed
+72 -1
Cargo.lock
··· 151 151 checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" 152 152 153 153 [[package]] 154 + name = "arbitrary-int" 155 + version = "1.3.0" 156 + source = "registry+https://github.com/rust-lang/crates.io-index" 157 + checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5" 158 + 159 + [[package]] 154 160 name = "arg_enum_proc_macro" 155 161 version = "0.3.4" 156 162 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 234 240 version = "0.13.1" 235 241 source = "registry+https://github.com/rust-lang/crates.io-index" 236 242 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 243 + 244 + [[package]] 245 + name = "bilge" 246 + version = "0.2.0" 247 + source = "registry+https://github.com/rust-lang/crates.io-index" 248 + checksum = "dc707ed8ebf81de5cd6c7f48f54b4c8621760926cdf35a57000747c512e67b57" 249 + dependencies = [ 250 + "arbitrary-int", 251 + "bilge-impl", 252 + ] 253 + 254 + [[package]] 255 + name = "bilge-impl" 256 + version = "0.2.0" 257 + source = "registry+https://github.com/rust-lang/crates.io-index" 258 + checksum = "feb11e002038ad243af39c2068c8a72bcf147acf05025dcdb916fcc000adb2d8" 259 + dependencies = [ 260 + "itertools 0.11.0", 261 + "proc-macro-error", 262 + "proc-macro2", 263 + "quote", 264 + "syn", 265 + ] 237 266 238 267 [[package]] 239 268 name = "binrw" ··· 1011 1040 1012 1041 [[package]] 1013 1042 name = "itertools" 1043 + version = "0.11.0" 1044 + source = "registry+https://github.com/rust-lang/crates.io-index" 1045 + checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 1046 + dependencies = [ 1047 + "either", 1048 + ] 1049 + 1050 + [[package]] 1051 + name = "itertools" 1014 1052 version = "0.12.1" 1015 1053 source = "registry+https://github.com/rust-lang/crates.io-index" 1016 1054 checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" ··· 1837 1875 ] 1838 1876 1839 1877 [[package]] 1878 + name = "proc-macro-error" 1879 + version = "1.0.4" 1880 + source = "registry+https://github.com/rust-lang/crates.io-index" 1881 + checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1882 + dependencies = [ 1883 + "proc-macro-error-attr", 1884 + "proc-macro2", 1885 + "quote", 1886 + "version_check", 1887 + ] 1888 + 1889 + [[package]] 1890 + name = "proc-macro-error-attr" 1891 + version = "1.0.4" 1892 + source = "registry+https://github.com/rust-lang/crates.io-index" 1893 + checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1894 + dependencies = [ 1895 + "proc-macro2", 1896 + "quote", 1897 + "version_check", 1898 + ] 1899 + 1900 + [[package]] 1840 1901 name = "proc-macro2" 1841 1902 version = "1.0.95" 1842 1903 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1953 2014 "built", 1954 2015 "cfg-if", 1955 2016 "interpolate_name", 1956 - "itertools", 2017 + "itertools 0.12.1", 1957 2018 "libc", 1958 2019 "libfuzzer-sys", 1959 2020 "log", ··· 2526 2587 name = "vee_parse" 2527 2588 version = "0.1.1" 2528 2589 dependencies = [ 2590 + "bilge", 2529 2591 "binrw", 2592 + "vee_parse_macros", 2593 + ] 2594 + 2595 + [[package]] 2596 + name = "vee_parse_macros" 2597 + version = "0.1.1" 2598 + dependencies = [ 2599 + "quote", 2600 + "syn", 2530 2601 ] 2531 2602 2532 2603 [[package]]
+1 -1
Cargo.toml
··· 4 4 # bevy viewer is on the backburner 5 5 # "crates/bevy_viewer", 6 6 "crates/lightweight_viewer", "crates/vee_models", 7 - "crates/vee_parse", "crates/vee_resources", 7 + "crates/vee_parse", "crates/vee_parse_macros", "crates/vee_resources", 8 8 "crates/vee_wgpu", 9 9 "crates/vfl", 10 10 "crates/vfl-cli",
+2
crates/vee_parse/Cargo.toml
··· 12 12 description = "Parsing Mii save structures. Part of `vfl`." 13 13 14 14 [dependencies] 15 + bilge = "0.2.0" 15 16 binrw = "0.15.0" 17 + vee_parse_macros = { path = "../vee_parse_macros" } 16 18 17 19 [lints] 18 20 workspace = true
+176
crates/vee_parse/src/cafe.rs
··· 1 + use crate::FixedLengthWideString; 2 + use std::{fmt::Display, fs::read, io::Cursor}; 3 + 4 + use bilge::prelude::*; 5 + use binrw::{BinRead, BinWrite, NullWideString, binrw}; 6 + use vee_parse_macros::bitfield; 7 + 8 + #[bitfield(32)] 9 + pub struct CharDataField { 10 + pub char_version: u8, 11 + pub copyable: u1, 12 + pub ng_word: u1, 13 + pub region_move: u2, 14 + pub font_region: u2, 15 + pub reserved_0: u2, 16 + pub room_index: u4, 17 + pub position_in_room: u4, 18 + pub author_type: u4, 19 + pub birth_platform: u3, 20 + pub reserved_1: u1, 21 + } 22 + 23 + #[bitfield(16)] 24 + pub struct PersonalInfoField { 25 + pub gender: u1, 26 + pub birth_month: u4, 27 + pub birth_day: u5, 28 + pub favorite_color: u4, 29 + pub favorite: u1, 30 + pub padding: u1, 31 + } 32 + 33 + #[bitfield(16)] 34 + pub struct FaceField { 35 + pub local_only: u1, 36 + pub face_type: u4, 37 + pub face_color: u3, 38 + pub face_texture: u4, 39 + pub face_makeup: u4, 40 + } 41 + 42 + #[bitfield(16)] 43 + pub struct HairField { 44 + pub hair_type: u8, 45 + pub hair_color: u3, 46 + pub hair_flip: u1, 47 + pub padding: u4, 48 + } 49 + 50 + #[bitfield(16)] 51 + pub struct EyeField { 52 + pub eye_type: u6, 53 + pub eye_color: u3, 54 + pub eye_scale: u4, 55 + pub eye_aspect: u3, 56 + } 57 + 58 + #[bitfield(16)] 59 + pub struct EyePositionField { 60 + pub eye_rotate: u5, 61 + pub eye_x: u4, 62 + pub eye_y: u5, 63 + pub padding: u2, 64 + } 65 + 66 + #[bitfield(16)] 67 + pub struct EyebrowField { 68 + pub eyebrow_type: u5, 69 + pub eyebrow_color: u3, 70 + pub eyebrow_scale: u4, 71 + pub eyebrow_aspect: u3, 72 + pub padding_3: u1, 73 + } 74 + 75 + #[bitfield(16)] 76 + pub struct EyebrowPositionField { 77 + pub eyebrow_rotate: u5, 78 + pub eyebrow_x: u4, 79 + pub eyebrow_y: u5, 80 + pub padding: u2, 81 + } 82 + 83 + #[bitfield(16)] 84 + pub struct NoseField { 85 + pub nose_type: u5, 86 + pub nose_scale: u4, 87 + pub nose_y: u5, 88 + pub padding: u2, 89 + } 90 + 91 + #[bitfield(16)] 92 + pub struct MouthField { 93 + pub mouth_type: u6, 94 + pub mouth_color: u3, 95 + pub mouth_scale: u4, 96 + pub mouth_aspect: u3, 97 + } 98 + 99 + #[bitfield(16)] 100 + pub struct MouthPositionField { 101 + pub mouth_y: u5, 102 + pub mustache_type: u3, 103 + pub padding: u8, 104 + } 105 + 106 + #[bitfield(16)] 107 + pub struct BeardField { 108 + pub beard_type: u3, 109 + pub beard_color: u3, 110 + pub beard_scale: u4, 111 + pub beard_y: u5, 112 + pub padding_7: u1, 113 + } 114 + 115 + #[bitfield(16)] 116 + pub struct GlassField { 117 + pub glass_type: u4, 118 + pub glass_color: u3, 119 + pub glass_scale: u4, 120 + pub glass_y: u5, 121 + } 122 + 123 + #[bitfield(16)] 124 + pub struct MoleField { 125 + pub mole_type: u1, 126 + pub mole_scale: u4, 127 + pub mole_x: u5, 128 + pub mole_y: u5, 129 + pub padding_8: u1, 130 + } 131 + 132 + #[binrw] 133 + #[derive(Debug)] 134 + pub struct CafeAuthorId { 135 + pub data: [u8; 8], 136 + } 137 + 138 + #[binrw] 139 + #[derive(Debug)] 140 + pub struct CafeCreateId { 141 + pub data: [u8; 10], 142 + } 143 + 144 + /// A packed character info format. 145 + /// This structure has a lot of bitfields. 146 + /// These fields have been given speculative names. 147 + /// 148 + /// This format is commonly known as `.ffsd`. 149 + #[binrw] 150 + #[brw(little)] 151 + #[derive(Debug)] 152 + pub struct CafeCharStoreData { 153 + pub char_data: CharDataField, 154 + pub author_id: CafeAuthorId, 155 + pub create_id: CafeCreateId, 156 + pub reserved: [u8; 2], 157 + pub personal_info: PersonalInfoField, 158 + pub name: FixedLengthWideString<10>, 159 + pub height: u8, 160 + pub build: u8, 161 + pub face: FaceField, 162 + pub hair: HairField, 163 + pub eye: EyeField, 164 + pub eye_position: EyePositionField, 165 + pub eyebrow: EyebrowField, 166 + pub eyebrow_position: EyebrowPositionField, 167 + pub nose: NoseField, 168 + pub mouth: MouthField, 169 + pub mouth_position: MouthPositionField, 170 + pub beard: BeardField, 171 + pub glass: GlassField, 172 + pub mole: MoleField, 173 + pub creator_name: FixedLengthWideString<10>, 174 + pub padding: u16, 175 + pub crc: u16, 176 + }
+53 -75
crates/vee_parse/src/lib.rs
··· 2 2 //! In the future, multiple formats will be supported and a trait will allow 3 3 //! for being generic over formats. 4 4 5 - pub use binrw::{binrw, BinRead, NullWideString}; 5 + pub mod cafe; 6 + pub mod nx; 6 7 7 - /// Wrapper for nn::mii color index. 8 - #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)] 8 + pub use cafe::CafeCharStoreData; 9 + pub use nx::NxCharInfo; 10 + 11 + pub use binrw::{BinRead, NullWideString, binrw}; 12 + 13 + /// A UTF-16 String with a fixed length and non-enforced null termination. 14 + /// The string is allowed to reach the maximum length without a null terminator, 15 + /// and any nulls are stripped. 16 + #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] 9 17 #[binrw] 10 18 #[repr(transparent)] 11 - pub struct NxColor(pub u8); 19 + pub struct FixedLengthWideString<const CHARS: usize>(pub [u16; CHARS]); 12 20 13 - /// Simple UuidV4. No logic provided as create info is not useful for anything... yet. 14 - #[derive(Debug, Copy, Clone)] 15 - #[binrw] 16 - #[brw(little)] 17 - pub struct UuidVer4 { 18 - idc: [u8; 16], 21 + impl<const N: usize> std::fmt::Debug for FixedLengthWideString<N> { 22 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 + write!(f, "FixedLengthWideString(\"{}\")", self) 24 + } 19 25 } 20 26 21 - /// Bindings to the `.charinfo` format. 22 - /// This format is not used in transmission, 23 - /// so is not packed like other formats (e.g. Cafe, Revolution) 24 - #[derive(Debug, Clone)] 25 - #[binrw] 26 - #[brw(little, assert(nickname.len() <= 22))] 27 - pub struct NxCharInfo { 28 - create_info: UuidVer4, 29 - #[brw(pad_size_to = 0x16)] 30 - pub nickname: NullWideString, 31 - pub font_region: u8, 32 - pub favorite_color: NxColor, 33 - pub gender: u8, 34 - pub height: u8, 35 - pub build: u8, 36 - pub is_special: u8, 37 - pub region_move: u8, 38 - pub faceline_type: u8, 39 - pub faceline_color: NxColor, 40 - pub faceline_wrinkle: u8, 41 - pub faceline_make: u8, 42 - pub hair_type: u8, 43 - pub hair_color: NxColor, 44 - pub hair_flip: u8, 45 - pub eye_type: u8, 46 - pub eye_color: NxColor, 47 - pub eye_scale: u8, 48 - pub eye_aspect: u8, 49 - pub eye_rotate: u8, 50 - pub eye_x: u8, 51 - pub eye_y: u8, 52 - pub eyebrow_type: u8, 53 - pub eyebrow_color: NxColor, 54 - pub eyebrow_scale: u8, 55 - pub eyebrow_aspect: u8, 56 - pub eyebrow_rotate: u8, 57 - pub eyebrow_x: u8, 58 - pub eyebrow_y: u8, 59 - pub nose_type: u8, 60 - pub nose_scale: u8, 61 - pub nose_y: u8, 62 - pub mouth_type: u8, 63 - pub mouth_color: NxColor, 64 - pub mouth_scale: u8, 65 - pub mouth_aspect: u8, 66 - pub mouth_y: u8, 67 - pub beard_color: NxColor, 68 - pub beard_type: u8, 69 - pub mustache_type: u8, 70 - pub mustache_scale: u8, 71 - pub mustache_y: u8, 72 - pub glass_type: u8, 73 - pub glass_color: NxColor, 74 - pub glass_scale: u8, 75 - pub glass_y: u8, 76 - pub mole_type: u8, 77 - pub mole_scale: u8, 78 - pub mole_x: u8, 79 - pub mole_y: u8, 80 - reserved: u8, /* always zero */ 27 + impl<const N: usize> std::fmt::Display for FixedLengthWideString<N> { 28 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 + write!(f, "{}", self.parse_utf16()) 30 + } 31 + } 32 + 33 + impl<const N: usize> FixedLengthWideString<N> { 34 + /// Will fail on big endian hardware. Wait for `str_from_utf16_endian` to drop. 35 + fn parse_utf16(self) -> String { 36 + String::from_utf16(&self.0[..]) 37 + .expect( 38 + "UTF-16 string parse error. Parsing little endian string on big endian hardware?", 39 + ) 40 + .replace("\0", "") 41 + } 81 42 } 82 43 83 44 #[cfg(test)] 84 45 mod tests { 85 - use crate::NxCharInfo; 46 + use crate::{CafeCharStoreData, NxCharInfo}; 86 47 use binrw::BinRead; 87 48 use std::{error::Error, fs::File}; 88 49 89 50 type R = Result<(), Box<dyn Error>>; 90 51 91 52 #[test] 92 - fn mii_deser() -> R { 93 - let mut mii = File::open(concat!( 94 - std::env::var("CARGO_WORKSPACE_DIR").unwrap(), 95 - "/resources_here/j0.charinfo" 53 + fn nx_deser() -> R { 54 + let mut mii = File::open(format!( 55 + "{}/resources_here/j0.charinfo", 56 + std::env::var("CARGO_WORKSPACE_DIR").unwrap() 96 57 ))?; 97 58 98 59 let mii = NxCharInfo::read(&mut mii)?; 99 60 61 + assert_eq!(mii.nickname.to_string(), "Jo Null".to_string()); 62 + 100 63 assert_eq!(mii.glass_color.0, 17); 101 64 assert_eq!(mii.reserved, 0); 65 + 66 + Ok(()) 67 + } 68 + 69 + #[test] 70 + fn cafe_deser() -> R { 71 + let mut mii = File::open(format!( 72 + "{}/resources_here/j0.ffsd", 73 + std::env::var("CARGO_WORKSPACE_DIR").unwrap() 74 + ))?; 75 + 76 + let mii = CafeCharStoreData::read(&mut mii)?; 77 + 78 + assert_eq!(mii.name.to_string(), "Jo Null".to_string()); 79 + assert_eq!(mii.personal_info.favorite_color().value(), 8); 102 80 103 81 Ok(()) 104 82 }
+79
crates/vee_parse/src/nx.rs
··· 1 + use binrw::NullWideString; 2 + use binrw::binrw; 3 + 4 + use crate::FixedLengthWideString; 5 + 6 + /// Wrapper for nn::mii color index. 7 + #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)] 8 + #[binrw] 9 + #[repr(transparent)] 10 + pub struct NxColor(pub u8); 11 + 12 + /// Simple UuidV4. No logic provided as create info is not useful for anything... yet. 13 + #[derive(Debug, Copy, Clone)] 14 + #[binrw] 15 + #[brw(little)] 16 + pub struct UuidVer4 { 17 + idc: [u8; 16], 18 + } 19 + 20 + /// Bindings to the `.charinfo` format. 21 + /// This format is not used in transmission, 22 + /// so is not packed like other formats (e.g. Cafe, Revolution.) 23 + #[derive(Debug, Clone)] 24 + #[binrw] 25 + #[brw(little)] 26 + pub struct NxCharInfo { 27 + pub create_info: UuidVer4, 28 + pub nickname: FixedLengthWideString<11>, 29 + pub font_region: u8, 30 + pub favorite_color: NxColor, 31 + pub gender: u8, 32 + pub height: u8, 33 + pub build: u8, 34 + pub is_special: u8, 35 + pub region_move: u8, 36 + pub faceline_type: u8, 37 + pub faceline_color: NxColor, 38 + pub faceline_wrinkle: u8, 39 + pub faceline_make: u8, 40 + pub hair_type: u8, 41 + pub hair_color: NxColor, 42 + pub hair_flip: u8, 43 + pub eye_type: u8, 44 + pub eye_color: NxColor, 45 + pub eye_scale: u8, 46 + pub eye_aspect: u8, 47 + pub eye_rotate: u8, 48 + pub eye_x: u8, 49 + pub eye_y: u8, 50 + pub eyebrow_type: u8, 51 + pub eyebrow_color: NxColor, 52 + pub eyebrow_scale: u8, 53 + pub eyebrow_aspect: u8, 54 + pub eyebrow_rotate: u8, 55 + pub eyebrow_x: u8, 56 + pub eyebrow_y: u8, 57 + pub nose_type: u8, 58 + pub nose_scale: u8, 59 + pub nose_y: u8, 60 + pub mouth_type: u8, 61 + pub mouth_color: NxColor, 62 + pub mouth_scale: u8, 63 + pub mouth_aspect: u8, 64 + pub mouth_y: u8, 65 + pub beard_color: NxColor, 66 + pub beard_type: u8, 67 + pub mustache_type: u8, 68 + pub mustache_scale: u8, 69 + pub mustache_y: u8, 70 + pub glass_type: u8, 71 + pub glass_color: NxColor, 72 + pub glass_scale: u8, 73 + pub glass_y: u8, 74 + pub mole_type: u8, 75 + pub mole_scale: u8, 76 + pub mole_x: u8, 77 + pub mole_y: u8, 78 + pub reserved: u8, /* always zero */ 79 + }
+19
crates/vee_parse_macros/Cargo.toml
··· 1 + [lib] 2 + proc-macro = true 3 + 4 + [package] 5 + name = "vee_parse_macros" 6 + version.workspace = true 7 + edition.workspace = true 8 + authors.workspace = true 9 + exclude.workspace = true 10 + license.workspace = true 11 + repository.workspace = true 12 + readme.workspace = true 13 + 14 + [dependencies] 15 + quote = "1" 16 + syn = "2" 17 + 18 + [lints] 19 + workspace = true
+59
crates/vee_parse_macros/src/lib.rs
··· 1 + use proc_macro::TokenStream; 2 + use quote::quote; 3 + use syn::{ItemStruct, LitInt, parse_macro_input}; 4 + 5 + /// For internal use. Annotates a bitfield so I don't have to do a bunch of boilerplate. 6 + /// Defaults to 32 if you don't pass it an integer n ∈ `[8, 16, 32, 64]` 7 + #[proc_macro_attribute] 8 + pub fn bitfield(attr: TokenStream, item: TokenStream) -> TokenStream { 9 + let bit_size = parse_macro_input!(attr as LitInt) 10 + .base10_parse::<u32>() 11 + .unwrap_or(32); 12 + 13 + let bit_type = match bit_size { 14 + 8 => quote! { u8 }, 15 + 16 => quote! { u16 }, 16 + 32 => quote! { u32 }, 17 + 64 => quote! { u64 }, 18 + _ => quote! { u32 }, 19 + }; 20 + 21 + // Parse the struct 22 + let input = parse_macro_input!(item as ItemStruct); 23 + 24 + // Extract fields and generate layout doc string 25 + let mut layout_lines = Vec::new(); 26 + layout_lines.push("struct BitField {".to_string()); 27 + for field in input.fields.iter() { 28 + let ident = &field.ident; 29 + let ident = quote! {#ident}.to_string(); 30 + 31 + let ty = &field.ty; 32 + let ty = quote! {#ty}.to_string(); 33 + 34 + layout_lines.push(format!(" {ident}: {ty}")); 35 + } 36 + layout_lines.push("}".to_string()); 37 + 38 + let layout_doc = layout_lines.join("\n"); 39 + 40 + let struct_name = &input.ident; 41 + let vis = &input.vis; 42 + let attrs = &input.attrs; 43 + let fields = &input.fields; 44 + 45 + let expanded = quote! { 46 + #(#attrs)* 47 + #[doc = "A bit field."] 48 + #[doc = "```rs"] 49 + #[doc = #layout_doc] 50 + #[doc = "```"] 51 + #[bitsize(#bit_size)] 52 + #[derive(FromBits, DebugBits, BinRead, BinWrite, PartialEq, Clone, Copy)] 53 + #[br(map = #bit_type::into)] 54 + #[bw(map = |&x| #bit_type::from(x))] 55 + #vis struct #struct_name #fields 56 + }; 57 + 58 + TokenStream::from(expanded) 59 + }
resources_here/j0.ffsd

This is a binary file and will not be displayed.

+72
testbed/ffl_res_high.hexpat
··· 1 + struct ResourcePartsInfo { 2 + u32 dataPos; 3 + u32 dataSize; 4 + u32 compressedSize; 5 + u8 compressLevel; 6 + u8 windowBits; 7 + u8 memoryLevel; 8 + u8 strategy; 9 + }; 10 + 11 + struct ResourceShapeHeader { 12 + u32 maxSize[12]; 13 + ResourcePartsInfo beard[4]; 14 + ResourcePartsInfo hatNormal[132]; 15 + ResourcePartsInfo hatCap[132]; 16 + ResourcePartsInfo faceline[12]; 17 + ResourcePartsInfo glass[1]; 18 + ResourcePartsInfo mask[12]; 19 + ResourcePartsInfo noseline[18]; 20 + ResourcePartsInfo nose[18]; 21 + ResourcePartsInfo hairNormal[132]; 22 + ResourcePartsInfo hairCap[132]; 23 + ResourcePartsInfo foreheadNormal[132]; 24 + ResourcePartsInfo foreheadCap[132]; 25 + }; 26 + 27 + struct ResourceTextureHeader { 28 + u32 maxSize[11]; 29 + ResourcePartsInfo beard[3]; 30 + ResourcePartsInfo hat[132]; 31 + ResourcePartsInfo eye[62]; // [80] in AFL 32 + ResourcePartsInfo eyebrow[24]; // [28] in AFL 33 + ResourcePartsInfo faceline[12]; 34 + ResourcePartsInfo faceMakeup[12]; 35 + ResourcePartsInfo glass[9]; // [20] in AFL 36 + ResourcePartsInfo mole[2]; 37 + ResourcePartsInfo mouth[37]; // [52] in AFL 38 + ResourcePartsInfo mustache[6]; 39 + ResourcePartsInfo noseline[18]; 40 + }; 41 + 42 + struct ResourceHeader { 43 + u32 signature; // "FFRA" 44 + u32 version; 45 + u32 uncompressedBufferSize; 46 + u32 expandedBufferSize; 47 + u32 expanded; 48 + ResourceTextureHeader textureHeader; 49 + ResourceShapeHeader shapeHeader; 50 + u32 unknown[12]; // Always 0x0? 51 + }; 52 + 53 + 54 + ResourceHeader resourceheader_at_0x00 @ 0x00; 55 + 56 + // struct Vec3 { 57 + // float x; 58 + // float y; 59 + // float z; 60 + // }; 61 + 62 + // struct BoundingBox { 63 + // Vec3 min; 64 + // Vec3 max; 65 + // }; 66 + 67 + // struct ResourceShapeDataHeader { 68 + // u32 elementPos[6]; 69 + // u32 elementSize[6]; 70 + // BoundingBox boundingBox; 71 + // float transform[144]; // resshapehairtransform.size / 4 72 + // };