+72
-1
Cargo.lock
+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
+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
+2
crates/vee_parse/Cargo.toml
+176
crates/vee_parse/src/cafe.rs
+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
+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
+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
+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
+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
resources_here/j0.ffsd
This is a binary file and will not be displayed.
+72
testbed/ffl_res_high.hexpat
+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
+
// };