1#![allow(clippy::unnecessary_fallible_conversions)] use std::io::Cursor;
7
8use crate::ByteSpan;
9use crate::common_file_operations::{Half1, Half2, Half3};
10use crate::mtrl::ColorDyeTable::{
11 DawntrailColorDyeTable, LegacyColorDyeTable, OpaqueColorDyeTable,
12};
13use crate::mtrl::ColorTable::{DawntrailColorTable, LegacyColorTable, OpaqueColorTable};
14use binrw::{BinRead, BinResult, binread, binrw};
15
16#[binrw]
17#[derive(Debug)]
18#[allow(dead_code)]
19struct MaterialFileHeader {
20 version: u32,
21 file_size: u16,
22 data_set_size: u16,
23 string_table_size: u16,
24 shader_package_name_offset: u16,
25 texture_count: u8,
26 uv_set_count: u8,
27 color_set_count: u8,
28 additional_data_size: u8,
29}
30
31#[binrw]
32#[derive(Debug)]
33struct MaterialHeader {
34 shader_value_list_size: u16,
35 shader_key_count: u16,
36 constant_count: u16,
37 sampler_count: u16,
38 flags: u32,
39}
40
41#[binrw]
42#[derive(Debug)]
43#[allow(dead_code)]
44struct ColorSet {
45 name_offset: u16,
46 index: u16,
47}
48
49#[binread]
50#[derive(Debug, Clone, Copy)]
51#[repr(C)]
52#[allow(dead_code)]
53pub struct DawntrailColorTableRow {
54 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
55 pub diffuse_color: [f32; 3],
56
57 #[br(map = |x: Half1| { x.value.to_f32() })]
58 pub unknown1: f32,
59
60 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
61 pub specular_color: [f32; 3],
62
63 #[br(map = |x: Half1| { x.value.to_f32() })]
64 pub unknown2: f32,
65
66 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
67 pub emissive_color: [f32; 3],
68
69 #[br(map = |x: Half1| { x.value.to_f32() })]
70 pub unknown3: f32,
71
72 #[br(map = |x: Half1| { x.value.to_f32() })]
73 pub sheen_rate: f32,
74
75 #[br(map = |x: Half1| { x.value.to_f32() })]
76 pub sheen_tint: f32,
77
78 #[br(map = |x: Half1| { x.value.to_f32() })]
79 pub sheen_aperture: f32,
80
81 #[br(map = |x: Half1| { x.value.to_f32() })]
82 pub unknown4: f32,
83
84 #[br(map = |x: Half1| { x.value.to_f32() })]
85 pub roughness: f32,
86
87 #[br(map = |x: Half1| { x.value.to_f32() })]
88 pub unknown5: f32,
89
90 #[br(map = |x: Half1| { x.value.to_f32() })]
91 pub metalness: f32,
92
93 #[br(map = |x: Half1| { x.value.to_f32() })]
94 pub anisotropy: f32,
95
96 #[br(map = |x: Half1| { x.value.to_f32() })]
97 pub unknown6: f32,
98
99 #[br(map = |x: Half1| { x.value.to_f32() })]
100 pub sphere_mask: f32,
101
102 #[br(map = |x: Half1| { x.value.to_f32() })]
103 pub unknown7: f32,
104
105 #[br(map = |x: Half1| { x.value.to_f32() })]
106 pub unknown8: f32,
107
108 pub shader_index: u16,
109
110 pub tile_set: u16,
111
112 #[br(map = |x: Half1| { x.value.to_f32() })]
113 pub tile_alpha: f32,
114
115 pub sphere_index: u16,
116
117 #[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
118 pub material_repeat: [f32; 2],
119
120 #[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
121 pub material_skew: [f32; 2],
122}
123
124#[binread]
125#[derive(Debug, Clone, Copy)]
126#[repr(C)]
127#[allow(dead_code)]
128pub struct LegacyColorTableRow {
129 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
130 pub diffuse_color: [f32; 3],
131
132 #[br(map = |x: Half1| { x.value.to_f32() })]
133 pub specular_strength: f32,
134
135 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
136 pub specular_color: [f32; 3],
137
138 #[br(map = |x: Half1| { x.value.to_f32() })]
139 pub gloss_strength: f32,
140
141 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
142 pub emissive_color: [f32; 3],
143
144 pub tile_set: u16,
145
146 #[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
147 pub material_repeat: [f32; 2],
148
149 #[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
150 pub material_skew: [f32; 2],
151}
152
153#[binread]
154#[derive(Debug)]
155#[allow(dead_code)]
156pub struct LegacyColorTableData {
157 #[br(count = 16)]
158 pub rows: Vec<LegacyColorTableRow>,
159}
160
161#[binread]
162#[derive(Debug)]
163#[allow(dead_code)]
164pub struct DawntrailColorTableData {
165 #[br(count = 32)]
166 pub rows: Vec<DawntrailColorTableRow>,
167}
168
169#[binread]
170#[derive(Debug)]
171#[allow(dead_code)]
172pub struct OpaqueColorTableData {
173 }
175
176#[binread]
177#[derive(Debug)]
178#[allow(dead_code)]
179pub enum ColorTable {
180 LegacyColorTable(LegacyColorTableData),
181 DawntrailColorTable(DawntrailColorTableData),
182 OpaqueColorTable(OpaqueColorTableData),
183}
184
185#[binread]
186#[derive(Debug)]
187#[allow(dead_code)]
188pub struct LegacyColorDyeTableRow {
189 #[br(temp)]
190 data: u16,
191
192 #[br(calc = data >> 5)]
193 pub template: u16,
194
195 #[br(calc = (data & 0x01) != 0)]
196 pub diffuse: bool,
197
198 #[br(calc = (data & 0x02) != 0)]
199 pub specular: bool,
200
201 #[br(calc = (data & 0x04) != 0)]
202 pub emissive: bool,
203
204 #[br(calc = (data & 0x08) != 0)]
205 pub gloss: bool,
206
207 #[br(calc = (data & 0x10) != 0)]
208 pub specular_strength: bool,
209}
210
211#[binread]
212#[derive(Debug)]
213#[allow(dead_code)]
214pub struct DawntrailColorDyeTableRow {
215 #[br(temp)]
216 data: u32,
217
218 #[br(calc = ((data >> 16) & 0x7FF) as u16)]
219 pub template: u16,
220
221 #[br(calc = ((data >> 27) & 0x3) as u8)]
222 pub channel: u8,
223
224 #[br(calc = (data & 0x0001) != 0)]
225 pub diffuse: bool,
226
227 #[br(calc = (data & 0x0002) != 0)]
228 pub specular: bool,
229
230 #[br(calc = (data & 0x0004) != 0)]
231 pub emissive: bool,
232
233 #[br(calc = (data & 0x0008) != 0)]
234 pub scalar3: bool,
235
236 #[br(calc = (data & 0x0010) != 0)]
237 pub metalness: bool,
238
239 #[br(calc = (data & 0x0020) != 0)]
240 pub roughness: bool,
241
242 #[br(calc = (data & 0x0040) != 0)]
243 pub sheen_rate: bool,
244
245 #[br(calc = (data & 0x0080) != 0)]
246 pub sheen_tint_rate: bool,
247
248 #[br(calc = (data & 0x0100) != 0)]
249 pub sheen_aperture: bool,
250
251 #[br(calc = (data & 0x0200) != 0)]
252 pub anisotropy: bool,
253
254 #[br(calc = (data & 0x0400) != 0)]
255 pub sphere_map_index: bool,
256
257 #[br(calc = (data & 0x0800) != 0)]
258 pub sphere_map_mask: bool,
259}
260
261#[binread]
262#[derive(Debug)]
263#[allow(dead_code)]
264pub struct LegacyColorDyeTableData {
265 #[br(count = 16)]
266 pub rows: Vec<LegacyColorDyeTableRow>,
267}
268
269#[binread]
270#[derive(Debug)]
271#[allow(dead_code)]
272pub struct DawntrailColorDyeTableData {
273 #[br(count = 32)]
274 pub rows: Vec<DawntrailColorDyeTableRow>,
275}
276
277#[binread]
278#[derive(Debug)]
279#[allow(dead_code)]
280pub struct OpaqueColorDyeTableData {
281 }
283
284#[binread]
285#[derive(Debug)]
286#[allow(dead_code)]
287pub enum ColorDyeTable {
288 LegacyColorDyeTable(LegacyColorDyeTableData),
289 DawntrailColorDyeTable(DawntrailColorDyeTableData),
290 OpaqueColorDyeTable(OpaqueColorDyeTableData),
291}
292
293#[binrw]
294#[derive(Debug, Clone, Copy)]
295#[repr(C)]
296#[allow(dead_code)]
297pub struct ShaderKey {
298 pub category: u32,
299 pub value: u32,
300}
301
302#[binrw]
303#[derive(Debug, Clone, Copy)]
304#[allow(dead_code)]
305pub struct ConstantStruct {
306 constant_id: u32,
307 value_offset: u16,
308 value_size: u16,
309}
310
311#[derive(Debug, Clone)]
312#[repr(C)]
313#[allow(dead_code)]
314pub struct Constant {
315 id: u32,
316 num_values: u32,
317 values: [f32; 4],
318}
319
320#[binrw]
321#[derive(Debug, Clone, Copy)]
322#[repr(C)]
323#[allow(dead_code)]
324pub struct Sampler {
325 pub texture_usage: u32,
327 flags: u32, texture_index: u8,
329 unknown1: u8,
330 unknown2: u8,
331 unknown3: u8,
332}
333
334#[binrw::parser(reader, endian)]
335fn parse_color_table(table_dimension_logs: u8) -> BinResult<Option<ColorTable>> {
336 Ok(Some(match table_dimension_logs {
337 0 | 0x42 => LegacyColorTable(LegacyColorTableData::read_options(reader, endian, ())?),
338 0x53 => DawntrailColorTable(DawntrailColorTableData::read_options(reader, endian, ())?),
339 _ => OpaqueColorTable(OpaqueColorTableData::read_options(reader, endian, ())?),
340 }))
341}
342
343#[binrw::parser(reader, endian)]
344fn parse_color_dye_table(table_dimension_logs: u8) -> BinResult<Option<ColorDyeTable>> {
345 Ok(Some(match table_dimension_logs {
346 0 => LegacyColorDyeTable(LegacyColorDyeTableData::read_options(reader, endian, ())?),
347 0x50...0x5F => DawntrailColorDyeTable(DawntrailColorDyeTableData::read_options(
348 reader,
349 endian,
350 (),
351 )?),
352 _ => OpaqueColorDyeTable(OpaqueColorDyeTableData::read_options(reader, endian, ())?),
353 }))
354}
355
356#[binrw]
357#[derive(Debug)]
358#[allow(dead_code)]
359#[br(little)]
360struct MaterialData {
361 file_header: MaterialFileHeader,
362
363 #[br(count = file_header.texture_count)]
364 offsets: Vec<u32>,
365
366 #[br(count = file_header.uv_set_count)]
367 uv_color_sets: Vec<ColorSet>,
368
369 #[br(count = file_header.color_set_count)]
370 color_sets: Vec<ColorSet>,
371
372 #[br(count = file_header.string_table_size)]
373 strings: Vec<u8>,
374
375 #[br(count = file_header.additional_data_size)]
376 #[br(pad_size_to = 4)]
377 #[br(map = |x: Vec<u8>| u32::from_le_bytes(x[0..4].try_into().unwrap()))]
378 table_flags: u32,
379
380 #[br(calc = (table_flags & 0x4) != 0)]
381 #[bw(ignore)]
382 has_table: bool,
383
384 #[br(calc = (table_flags & 0x8) != 0)]
385 #[bw(ignore)]
386 has_dye_table: bool,
387
388 #[br(calc = ((table_flags >> 4) & 0xF) as u8)]
389 #[bw(ignore)]
390 table_width_log: u8,
391
392 #[br(calc = ((table_flags >> 8) & 0xF) as u8)]
393 #[bw(ignore)]
394 table_height_log: u8,
395
396 #[br(calc = (table_flags >> 4) as u8)]
397 #[bw(ignore)]
398 table_dimension_logs: u8,
399
400 #[br(calc = !has_table || table_width_log != 0 && table_height_log != 0)]
401 #[bw(ignore)]
402 is_dawntrail: bool,
403
404 #[br(if(has_table))]
405 #[br(parse_with = parse_color_table)]
406 #[br(args(table_dimension_logs))]
407 #[bw(ignore)]
408 color_table: Option<ColorTable>,
409
410 #[br(if(has_dye_table))]
411 #[br(parse_with = parse_color_dye_table)]
412 #[br(args(table_dimension_logs))]
413 #[bw(ignore)]
414 color_dye_table: Option<ColorDyeTable>,
415
416 header: MaterialHeader,
417
418 #[br(count = header.shader_key_count)]
419 shader_keys: Vec<ShaderKey>,
420 #[br(count = header.constant_count)]
421 constants: Vec<ConstantStruct>,
422 #[br(count = header.sampler_count)]
423 samplers: Vec<Sampler>,
424 #[br(count = header.shader_value_list_size / 4)]
425 shader_values: Vec<f32>,
426}
427
428#[derive(Debug)]
429pub struct Material {
430 pub shader_package_name: String,
431 pub texture_paths: Vec<String>,
432 pub shader_keys: Vec<ShaderKey>,
433 pub constants: Vec<Constant>,
434 pub samplers: Vec<Sampler>,
435 pub color_table: Option<ColorTable>,
436 pub color_dye_table: Option<ColorDyeTable>,
437}
438
439impl Material {
440 pub fn from_existing(buffer: ByteSpan) -> Option<Material> {
441 let mut cursor = Cursor::new(buffer);
442 let mat_data = MaterialData::read(&mut cursor).ok()?;
443
444 let mut texture_paths = vec![];
445
446 let mut offset = 0;
447 for _ in 0..mat_data.file_header.texture_count {
448 let mut string = String::new();
449
450 let mut next_char = mat_data.strings[offset] as char;
451 while next_char != '\0' {
452 string.push(next_char);
453 offset += 1;
454 next_char = mat_data.strings[offset] as char;
455 }
456
457 texture_paths.push(string);
458
459 offset += 1;
460 }
461
462 let mut shader_package_name = String::new();
464
465 offset = mat_data.file_header.shader_package_name_offset as usize;
466
467 let mut next_char = mat_data.strings[offset] as char;
468 while next_char != '\0' {
469 shader_package_name.push(next_char);
470 offset += 1;
471 next_char = mat_data.strings[offset] as char;
472 }
473
474 let mut constants = Vec::new();
475 for constant in mat_data.constants {
476 let mut values: [f32; 4] = [0.0; 4];
477
478 let num_floats = constant.value_size / 4;
480 for i in 0..num_floats as usize {
481 values[i] = mat_data.shader_values[(constant.value_offset as usize / 4) + i];
482 }
483
484 constants.push(Constant {
485 id: constant.constant_id,
486 num_values: num_floats as u32,
487 values,
488 });
489 }
490
491 Some(Material {
492 shader_package_name,
493 texture_paths,
494 shader_keys: mat_data.shader_keys,
495 constants,
496 samplers: mat_data.samplers,
497 color_table: mat_data.color_table,
498 color_dye_table: mat_data.color_dye_table,
499 })
500 }
501}
502
503#[cfg(test)]
504mod tests {
505 use std::fs::read;
506 use std::path::PathBuf;
507
508 use super::*;
509
510 #[test]
511 fn test_invalid() {
512 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
513 d.push("resources/tests");
514 d.push("random");
515
516 Material::from_existing(&read(d).unwrap());
518 }
519}