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]
322#[repr(u8)]
323#[derive(Debug, Clone, Copy)]
324pub enum TextureUsage {
325 #[brw(magic = 0x88408C04u32)]
326 Sampler,
327 #[brw(magic = 0x213CB439u32)]
328 Sampler0,
329 #[brw(magic = 0x563B84AFu32)]
330 Sampler1,
331 #[brw(magic = 0xFEA0F3D2u32)]
332 SamplerCatchlight,
333 #[brw(magic = 0x1E6FEF9Cu32)]
334 SamplerColorMap0,
335 #[brw(magic = 0x6968DF0Au32)]
336 SamplerColorMap1,
337 #[brw(magic = 0x115306BEu32)]
338 SamplerDiffuse,
339 #[brw(magic = 0xF8D7957Au32)]
340 SamplerEnvMap,
341 #[brw(magic = 0x8A4E82B6u32)]
342 SamplerMask,
343 #[brw(magic = 0x0C5EC1F1u32)]
344 SamplerNormal,
345 #[brw(magic = 0xAAB4D9E9u32)]
346 SamplerNormalMap0,
347 #[brw(magic = 0xDDB3E97Fu32)]
348 SamplerNormalMap1,
349 #[brw(magic = 0x87F6474Du32)]
350 SamplerReflection,
351 #[brw(magic = 0x2B99E025u32)]
352 SamplerSpecular,
353 #[brw(magic = 0x1BBC2F12u32)]
354 SamplerSpecularMap0,
355 #[brw(magic = 0x6CBB1F84u32)]
356 SamplerSpecularMap1,
357 #[brw(magic = 0xE6321AFCu32)]
358 SamplerWaveMap,
359 #[brw(magic = 0x574E22D6u32)]
360 SamplerWaveletMap0,
361 #[brw(magic = 0x20491240u32)]
362 SamplerWaveletMap1,
363 #[brw(magic = 0x95E1F64Du32)]
364 SamplerWhitecapMap,
365
366 #[brw(magic = 0x565f8fd8u32)]
367 UnknownDawntrail1,
368
369 #[brw(magic = 0xe5338c17u32)]
370 UnknownDawntrail2,
371}
372
373#[binrw]
374#[derive(Debug, Clone, Copy)]
375#[repr(C)]
376#[allow(dead_code)]
377pub struct Sampler {
378 texture_usage: TextureUsage,
379 flags: u32, texture_index: u8,
381 unknown1: u8,
382 unknown2: u8,
383 unknown3: u8,
384}
385
386#[binrw::parser(reader, endian)]
387fn parse_color_table(table_dimension_logs: u8) -> BinResult<Option<ColorTable>> {
388 Ok(Some(match table_dimension_logs {
389 0 | 0x42 => LegacyColorTable(LegacyColorTableData::read_options(reader, endian, ())?),
390 0x53 => DawntrailColorTable(DawntrailColorTableData::read_options(reader, endian, ())?),
391 _ => OpaqueColorTable(OpaqueColorTableData::read_options(reader, endian, ())?),
392 }))
393}
394
395#[binrw::parser(reader, endian)]
396fn parse_color_dye_table(table_dimension_logs: u8) -> BinResult<Option<ColorDyeTable>> {
397 Ok(Some(match table_dimension_logs {
398 0 => LegacyColorDyeTable(LegacyColorDyeTableData::read_options(reader, endian, ())?),
399 0x50...0x5F => DawntrailColorDyeTable(DawntrailColorDyeTableData::read_options(
400 reader,
401 endian,
402 (),
403 )?),
404 _ => OpaqueColorDyeTable(OpaqueColorDyeTableData::read_options(reader, endian, ())?),
405 }))
406}
407
408#[binrw]
409#[derive(Debug)]
410#[allow(dead_code)]
411#[br(little)]
412struct MaterialData {
413 file_header: MaterialFileHeader,
414
415 #[br(count = file_header.texture_count)]
416 offsets: Vec<u32>,
417
418 #[br(count = file_header.uv_set_count)]
419 uv_color_sets: Vec<ColorSet>,
420
421 #[br(count = file_header.color_set_count)]
422 color_sets: Vec<ColorSet>,
423
424 #[br(count = file_header.string_table_size)]
425 strings: Vec<u8>,
426
427 #[br(count = file_header.additional_data_size)]
428 #[br(pad_size_to = 4)]
429 #[br(map = |x: Vec<u8>| u32::from_le_bytes(x[0..4].try_into().unwrap()))]
430 table_flags: u32,
431
432 #[br(calc = (table_flags & 0x4) != 0)]
433 #[bw(ignore)]
434 has_table: bool,
435
436 #[br(calc = (table_flags & 0x8) != 0)]
437 #[bw(ignore)]
438 has_dye_table: bool,
439
440 #[br(calc = ((table_flags >> 4) & 0xF) as u8)]
441 #[bw(ignore)]
442 table_width_log: u8,
443
444 #[br(calc = ((table_flags >> 8) & 0xF) as u8)]
445 #[bw(ignore)]
446 table_height_log: u8,
447
448 #[br(calc = (table_flags >> 4) as u8)]
449 #[bw(ignore)]
450 table_dimension_logs: u8,
451
452 #[br(calc = !has_table || table_width_log != 0 && table_height_log != 0)]
453 #[bw(ignore)]
454 is_dawntrail: bool,
455
456 #[br(if(has_table))]
457 #[br(parse_with = parse_color_table)]
458 #[br(args(table_dimension_logs))]
459 #[bw(ignore)]
460 color_table: Option<ColorTable>,
461
462 #[br(if(has_dye_table))]
463 #[br(parse_with = parse_color_dye_table)]
464 #[br(args(table_dimension_logs))]
465 #[bw(ignore)]
466 color_dye_table: Option<ColorDyeTable>,
467
468 header: MaterialHeader,
469
470 #[br(count = header.shader_key_count)]
471 shader_keys: Vec<ShaderKey>,
472 #[br(count = header.constant_count)]
473 constants: Vec<ConstantStruct>,
474 #[br(count = header.sampler_count)]
475 samplers: Vec<Sampler>,
476 #[br(count = header.shader_value_list_size / 4)]
477 shader_values: Vec<f32>,
478}
479
480#[derive(Debug)]
481pub struct Material {
482 pub shader_package_name: String,
483 pub texture_paths: Vec<String>,
484 pub shader_keys: Vec<ShaderKey>,
485 pub constants: Vec<Constant>,
486 pub samplers: Vec<Sampler>,
487 pub color_table: Option<ColorTable>,
488 pub color_dye_table: Option<ColorDyeTable>,
489}
490
491impl Material {
492 pub fn from_existing(buffer: ByteSpan) -> Option<Material> {
493 let mut cursor = Cursor::new(buffer);
494 let mat_data = MaterialData::read(&mut cursor).ok()?;
495
496 let mut texture_paths = vec![];
497
498 let mut offset = 0;
499 for _ in 0..mat_data.file_header.texture_count {
500 let mut string = String::new();
501
502 let mut next_char = mat_data.strings[offset] as char;
503 while next_char != '\0' {
504 string.push(next_char);
505 offset += 1;
506 next_char = mat_data.strings[offset] as char;
507 }
508
509 texture_paths.push(string);
510
511 offset += 1;
512 }
513
514 let mut shader_package_name = String::new();
516
517 offset = mat_data.file_header.shader_package_name_offset as usize;
518
519 let mut next_char = mat_data.strings[offset] as char;
520 while next_char != '\0' {
521 shader_package_name.push(next_char);
522 offset += 1;
523 next_char = mat_data.strings[offset] as char;
524 }
525
526 let mut constants = Vec::new();
527 for constant in mat_data.constants {
528 let mut values: [f32; 4] = [0.0; 4];
529
530 let num_floats = constant.value_size / 4;
532 for i in 0..num_floats as usize {
533 values[i] = mat_data.shader_values[(constant.value_offset as usize / 4) + i];
534 }
535
536 constants.push(Constant {
537 id: constant.constant_id,
538 num_values: num_floats as u32,
539 values,
540 });
541 }
542
543 Some(Material {
544 shader_package_name,
545 texture_paths,
546 shader_keys: mat_data.shader_keys,
547 constants,
548 samplers: mat_data.samplers,
549 color_table: mat_data.color_table,
550 color_dye_table: mat_data.color_dye_table,
551 })
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use std::fs::read;
558 use std::path::PathBuf;
559
560 use super::*;
561
562 #[test]
563 fn test_invalid() {
564 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
565 d.push("resources/tests");
566 d.push("random");
567
568 Material::from_existing(&read(d).unwrap());
570 }
571}