1#![allow(clippy::unnecessary_fallible_conversions)] use std::io::Cursor;
7
8use crate::common::Platform;
9use crate::common_file_operations::{Half1, Half2, Half3, null_terminated_utf8};
10use crate::mtrl::ColorDyeTable::{
11 DawntrailColorDyeTable, LegacyColorDyeTable, OpaqueColorDyeTable,
12};
13use crate::mtrl::ColorTable::{DawntrailColorTable, LegacyColorTable, OpaqueColorTable};
14use crate::{ByteBuffer, ByteSpan, ReadableFile, WritableFile};
15use binrw::{BinRead, BinResult, BinWrite, binrw};
16
17#[binrw]
18#[derive(Debug)]
19#[allow(dead_code)]
20struct MaterialFileHeader {
21 version: u32,
22 file_size: u16,
23 data_set_size: u16,
24 string_table_size: u16,
25 shader_package_name_offset: u16,
26 texture_count: u8,
27 uv_set_count: u8,
28 color_set_count: u8,
29 additional_data_size: u8,
30}
31
32#[binrw]
33#[derive(Debug)]
34struct MaterialHeader {
35 shader_value_list_size: u16,
36 shader_key_count: u16,
37 constant_count: u16,
38 sampler_count: u16,
39 flags: u32,
40}
41
42#[binrw]
43#[derive(Debug)]
44#[allow(dead_code)]
45struct ColorSet {
46 name_offset: u16,
47 index: u16,
48}
49
50#[binrw]
51#[derive(Debug, Clone, Copy)]
52#[repr(C)]
53#[allow(dead_code)]
54pub struct DawntrailColorTableRow {
55 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
56 pub diffuse_color: [f32; 3],
57
58 #[br(map = |x: Half1| { x.value.to_f32() })]
59 pub unknown1: f32,
60
61 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
62 pub specular_color: [f32; 3],
63
64 #[br(map = |x: Half1| { x.value.to_f32() })]
65 pub unknown2: f32,
66
67 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
68 pub emissive_color: [f32; 3],
69
70 #[br(map = |x: Half1| { x.value.to_f32() })]
71 pub unknown3: f32,
72
73 #[br(map = |x: Half1| { x.value.to_f32() })]
74 pub sheen_rate: f32,
75
76 #[br(map = |x: Half1| { x.value.to_f32() })]
77 pub sheen_tint: f32,
78
79 #[br(map = |x: Half1| { x.value.to_f32() })]
80 pub sheen_aperture: f32,
81
82 #[br(map = |x: Half1| { x.value.to_f32() })]
83 pub unknown4: f32,
84
85 #[br(map = |x: Half1| { x.value.to_f32() })]
86 pub roughness: f32,
87
88 #[br(map = |x: Half1| { x.value.to_f32() })]
89 pub unknown5: f32,
90
91 #[br(map = |x: Half1| { x.value.to_f32() })]
92 pub metalness: f32,
93
94 #[br(map = |x: Half1| { x.value.to_f32() })]
95 pub anisotropy: f32,
96
97 #[br(map = |x: Half1| { x.value.to_f32() })]
98 pub unknown6: f32,
99
100 #[br(map = |x: Half1| { x.value.to_f32() })]
101 pub sphere_mask: f32,
102
103 #[br(map = |x: Half1| { x.value.to_f32() })]
104 pub unknown7: f32,
105
106 #[br(map = |x: Half1| { x.value.to_f32() })]
107 pub unknown8: f32,
108
109 pub shader_index: u16,
110
111 pub tile_set: u16,
112
113 #[br(map = |x: Half1| { x.value.to_f32() })]
114 pub tile_alpha: f32,
115
116 pub sphere_index: u16,
117
118 #[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
119 pub material_repeat: [f32; 2],
120
121 #[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
122 pub material_skew: [f32; 2],
123}
124
125#[binrw]
126#[derive(Debug, Clone, Copy)]
127#[repr(C)]
128#[allow(dead_code)]
129pub struct LegacyColorTableRow {
130 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
131 pub diffuse_color: [f32; 3],
132
133 #[br(map = |x: Half1| { x.value.to_f32() })]
134 pub specular_strength: f32,
135
136 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
137 pub specular_color: [f32; 3],
138
139 #[br(map = |x: Half1| { x.value.to_f32() })]
140 pub gloss_strength: f32,
141
142 #[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
143 pub emissive_color: [f32; 3],
144
145 pub tile_set: u16,
146
147 #[br(map = |x: Half1| { x.value.to_f32() })]
148 pub material_repeat_x: f32,
149
150 #[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
151 pub material_skew: [f32; 2],
152
153 #[br(map = |x: Half1| { x.value.to_f32() })]
154 pub material_repeat_y: f32,
155}
156
157#[binrw]
158#[derive(Debug, Clone, Default)]
159#[allow(dead_code)]
160pub struct LegacyColorTableData {
161 #[br(count = 16)]
162 pub rows: Vec<LegacyColorTableRow>,
163}
164
165#[binrw]
166#[derive(Debug, Clone, Default)]
167#[allow(dead_code)]
168pub struct DawntrailColorTableData {
169 #[br(count = 32)]
170 pub rows: Vec<DawntrailColorTableRow>,
171}
172
173#[binrw]
174#[derive(Debug, Clone, Default)]
175#[allow(dead_code)]
176pub struct OpaqueColorTableData {
177 }
179
180#[binrw]
181#[derive(Debug, Clone)]
182#[allow(dead_code)]
183pub enum ColorTable {
184 LegacyColorTable(LegacyColorTableData),
185 DawntrailColorTable(DawntrailColorTableData),
186 OpaqueColorTable(OpaqueColorTableData),
187}
188
189#[binrw]
190#[derive(Debug, Clone)]
191#[allow(dead_code)]
192pub struct LegacyColorDyeTableRow {
193 #[br(temp)]
194 #[bw(calc = 0)] data: u16,
196
197 #[br(calc = data >> 5)]
198 #[bw(ignore)]
199 pub template: u16,
200
201 #[br(calc = (data & 0x01) != 0)]
202 #[bw(ignore)]
203 pub diffuse: bool,
204
205 #[br(calc = (data & 0x02) != 0)]
206 #[bw(ignore)]
207 pub specular: bool,
208
209 #[br(calc = (data & 0x04) != 0)]
210 #[bw(ignore)]
211 pub emissive: bool,
212
213 #[br(calc = (data & 0x08) != 0)]
214 #[bw(ignore)]
215 pub gloss: bool,
216
217 #[br(calc = (data & 0x10) != 0)]
218 #[bw(ignore)]
219 pub specular_strength: bool,
220}
221
222#[binrw]
223#[derive(Debug, Clone)]
224#[allow(dead_code)]
225pub struct DawntrailColorDyeTableRow {
226 #[br(temp)]
227 #[bw(calc = 0)] data: u32,
229
230 #[br(calc = ((data >> 16) & 0x7FF) as u16)]
231 #[bw(ignore)]
232 pub template: u16,
233
234 #[br(calc = ((data >> 27) & 0x3) as u8)]
235 #[bw(ignore)]
236 pub channel: u8,
237
238 #[br(calc = (data & 0x0001) != 0)]
239 #[bw(ignore)]
240 pub diffuse: bool,
241
242 #[br(calc = (data & 0x0002) != 0)]
243 #[bw(ignore)]
244 pub specular: bool,
245
246 #[br(calc = (data & 0x0004) != 0)]
247 #[bw(ignore)]
248 pub emissive: bool,
249
250 #[br(calc = (data & 0x0008) != 0)]
251 #[bw(ignore)]
252 pub scalar3: bool,
253
254 #[br(calc = (data & 0x0010) != 0)]
255 #[bw(ignore)]
256 pub metalness: bool,
257
258 #[br(calc = (data & 0x0020) != 0)]
259 #[bw(ignore)]
260 pub roughness: bool,
261
262 #[br(calc = (data & 0x0040) != 0)]
263 #[bw(ignore)]
264 pub sheen_rate: bool,
265
266 #[br(calc = (data & 0x0080) != 0)]
267 #[bw(ignore)]
268 pub sheen_tint_rate: bool,
269
270 #[br(calc = (data & 0x0100) != 0)]
271 #[bw(ignore)]
272 pub sheen_aperture: bool,
273
274 #[br(calc = (data & 0x0200) != 0)]
275 #[bw(ignore)]
276 pub anisotropy: bool,
277
278 #[br(calc = (data & 0x0400) != 0)]
279 #[bw(ignore)]
280 pub sphere_map_index: bool,
281
282 #[br(calc = (data & 0x0800) != 0)]
283 #[bw(ignore)]
284 pub sphere_map_mask: bool,
285}
286
287#[binrw]
288#[derive(Debug, Clone)]
289#[allow(dead_code)]
290pub struct LegacyColorDyeTableData {
291 #[br(count = 16)]
292 pub rows: Vec<LegacyColorDyeTableRow>,
293}
294
295#[binrw]
296#[derive(Debug, Clone)]
297#[allow(dead_code)]
298pub struct DawntrailColorDyeTableData {
299 #[br(count = 32)]
300 pub rows: Vec<DawntrailColorDyeTableRow>,
301}
302
303#[binrw]
304#[derive(Debug, Clone)]
305#[allow(dead_code)]
306pub struct OpaqueColorDyeTableData {
307 }
309
310#[binrw]
311#[derive(Debug, Clone)]
312#[allow(dead_code)]
313pub enum ColorDyeTable {
314 LegacyColorDyeTable(LegacyColorDyeTableData),
315 DawntrailColorDyeTable(DawntrailColorDyeTableData),
316 OpaqueColorDyeTable(OpaqueColorDyeTableData),
317}
318
319#[binrw]
320#[derive(Debug, Clone, Copy)]
321#[repr(C)]
322#[allow(dead_code)]
323pub struct ShaderKey {
324 pub category: u32,
325 pub value: u32,
326}
327
328#[binrw]
329#[derive(Debug, Clone, Copy)]
330#[allow(dead_code)]
331pub struct ConstantStruct {
332 constant_id: u32,
333 value_offset: u16,
334 value_size: u16,
335}
336
337#[derive(Debug, Clone)]
338#[repr(C)]
339#[allow(dead_code)]
340pub struct Constant {
341 id: u32,
342 num_values: u32,
343 values: [f32; 4],
344}
345
346#[binrw]
347#[derive(Debug, Clone, Copy)]
348#[repr(C)]
349#[allow(dead_code)]
350pub struct Sampler {
351 pub texture_usage: u32,
353 flags: u32, #[brw(pad_after = 3)] texture_index: u8,
356}
357
358#[binrw::parser(reader, endian)]
359fn parse_color_table(table_dimension_logs: u8) -> BinResult<Option<ColorTable>> {
360 Ok(Some(match table_dimension_logs {
362 0 | 0x42 => LegacyColorTable(
363 LegacyColorTableData::read_options(reader, endian, ()).unwrap_or_default(),
364 ),
365 0x53 => DawntrailColorTable(
366 DawntrailColorTableData::read_options(reader, endian, ()).unwrap_or_default(),
367 ),
368 _ => OpaqueColorTable(
369 OpaqueColorTableData::read_options(reader, endian, ()).unwrap_or_default(),
370 ),
371 }))
372}
373
374#[binrw::parser(reader, endian)]
375fn parse_color_dye_table(table_dimension_logs: u8) -> BinResult<Option<ColorDyeTable>> {
376 Ok(Some(match table_dimension_logs {
377 0 => LegacyColorDyeTable(LegacyColorDyeTableData::read_options(reader, endian, ())?),
378 0x50...0x5F => DawntrailColorDyeTable(DawntrailColorDyeTableData::read_options(
379 reader,
380 endian,
381 (),
382 )?),
383 _ => OpaqueColorDyeTable(OpaqueColorDyeTableData::read_options(reader, endian, ())?),
384 }))
385}
386
387#[binrw]
388#[derive(Debug)]
389#[allow(dead_code)]
390struct MaterialData {
391 file_header: MaterialFileHeader,
392
393 #[br(count = file_header.texture_count)]
394 offsets: Vec<u32>,
395
396 #[br(count = file_header.uv_set_count)]
397 uv_color_sets: Vec<ColorSet>,
398
399 #[br(count = file_header.color_set_count)]
400 color_sets: Vec<ColorSet>,
401
402 #[br(count = file_header.string_table_size)]
403 strings: Vec<u8>,
404
405 #[br(count = file_header.additional_data_size)]
406 additional_data: Vec<u8>,
407
408 #[bw(ignore)]
409 #[br(calc = if additional_data.len() >= 4 { u32::from_le_bytes(additional_data[0..4].try_into().unwrap()) } else { 0 })]
410 table_flags: u32,
411
412 #[br(calc = (table_flags & 0x4) != 0)]
413 #[bw(ignore)]
414 has_table: bool,
415
416 #[br(calc = (table_flags & 0x8) != 0)]
417 #[bw(ignore)]
418 has_dye_table: bool,
419
420 #[br(calc = ((table_flags >> 4) & 0xF) as u8)]
421 #[bw(ignore)]
422 table_width_log: u8,
423
424 #[br(calc = ((table_flags >> 8) & 0xF) as u8)]
425 #[bw(ignore)]
426 table_height_log: u8,
427
428 #[br(calc = (table_flags >> 4) as u8)]
429 #[bw(ignore)]
430 table_dimension_logs: u8,
431
432 #[br(calc = !has_table || table_width_log != 0 && table_height_log != 0)]
433 #[bw(ignore)]
434 is_dawntrail: bool,
435
436 #[br(if(has_table))]
437 #[br(parse_with = parse_color_table)]
438 #[br(args(table_dimension_logs))]
439 #[bw(if(*has_table))]
440 color_table: Option<ColorTable>,
441
442 #[br(if(has_dye_table))]
443 #[br(parse_with = parse_color_dye_table)]
444 #[br(args(table_dimension_logs))]
445 #[bw(if(*has_dye_table))]
446 color_dye_table: Option<ColorDyeTable>,
447
448 header: MaterialHeader,
449
450 #[br(count = header.shader_key_count)]
451 shader_keys: Vec<ShaderKey>,
452 #[br(count = header.constant_count)]
453 constants: Vec<ConstantStruct>,
454 #[br(count = header.sampler_count)]
455 samplers: Vec<Sampler>,
456 #[br(count = header.shader_value_list_size as usize / std::mem::size_of::<f32>())]
457 shader_values: Vec<f32>,
458}
459
460#[derive(Debug)]
464pub struct Material {
465 mat_data: MaterialData,
466 pub shader_package_name: String,
467 pub texture_paths: Vec<String>,
468 pub shader_keys: Vec<ShaderKey>,
469 pub constants: Vec<Constant>,
470 pub samplers: Vec<Sampler>,
471 pub color_table: Option<ColorTable>,
472 pub color_dye_table: Option<ColorDyeTable>,
473}
474
475impl ReadableFile for Material {
476 fn from_existing(platform: Platform, buffer: ByteSpan) -> Option<Material> {
477 let mut cursor = Cursor::new(buffer);
478 let mat_data = MaterialData::read_options(&mut cursor, platform.endianness(), ()).ok()?;
479
480 let mut texture_paths = vec![];
481
482 if mat_data.strings.is_empty() {
484 return None;
485 }
486
487 let mut offset = 0;
488 for _ in 0..mat_data.file_header.texture_count {
489 let (s, next) = null_terminated_utf8(&mat_data.strings, offset);
490 texture_paths.push(s);
491 offset = next;
492 }
493
494 let (shader_package_name, _) = null_terminated_utf8(
495 &mat_data.strings,
496 mat_data.file_header.shader_package_name_offset as usize,
497 );
498
499 let mut constants = Vec::new();
502 const VALUE_SIZE: u16 = std::mem::size_of::<f32>() as u16;
503 if mat_data.header.shader_value_list_size % VALUE_SIZE == 0 {
504 for constant in &mat_data.constants {
505 let mut values: [f32; 4] = [0.0; 4];
506
507 let num_floats = constant.value_size / VALUE_SIZE;
509 for (i, value) in values.iter_mut().enumerate().take(num_floats as usize) {
510 *value = mat_data.shader_values[(constant.value_offset as usize / 4) + i];
511 }
512
513 constants.push(Constant {
514 id: constant.constant_id,
515 num_values: num_floats as u32,
516 values,
517 });
518 }
519 }
520
521 let shader_keys = mat_data.shader_keys.clone();
522 let samplers = mat_data.samplers.clone();
523 let color_table = mat_data.color_table.clone();
524 let color_dye_table = mat_data.color_dye_table.clone();
525
526 Some(Material {
527 mat_data,
528 shader_package_name,
529 texture_paths,
530 shader_keys,
531 constants,
532 samplers,
533 color_table,
534 color_dye_table,
535 })
536 }
537}
538
539impl WritableFile for Material {
540 fn write_to_buffer(&self, platform: Platform) -> Option<ByteBuffer> {
541 let mut buffer = ByteBuffer::new();
542
543 {
544 let mut cursor = Cursor::new(&mut buffer);
545 self.mat_data
546 .write_options(&mut cursor, platform.endianness(), ())
547 .ok()?;
548 }
549
550 Some(buffer)
551 }
552}
553
554impl Material {
555 pub fn gear_material_path(gear_id: i32, gear_version: i32, material_name: &str) -> String {
557 format!("chara/equipment/e{gear_id:04}/material/v{gear_version:04}{material_name}")
558 }
559
560 pub fn skin_material_path(race_code: i32, body_code: i32, material_name: &str) -> String {
562 format!(
563 "chara/human/c{race_code:04}/obj/body/b{body_code:04}/material/v0001{material_name}"
564 )
565 }
566
567 pub fn face_material_path(race_code: i32, face_code: i32, material_name: &str) -> String {
569 format!("chara/human/c{race_code:04}/obj/face/f{face_code:04}/material{material_name}")
570 }
571
572 pub fn hair_material_path(race_code: i32, hair_code: i32, material_name: &str) -> String {
574 format!(
575 "chara/human/c{race_code:04}/obj/hair/h{hair_code:04}/material/v0001{material_name}"
576 )
577 }
578
579 pub fn ear_material_path(race_code: i32, ear_code: i32, material_name: &str) -> String {
581 format!("chara/human/c{race_code:04}/obj/ear/e{ear_code:04}/material/v0001{material_name}")
582 }
583
584 pub fn tail_material_path(race_code: i32, tail_code: i32, material_name: &str) -> String {
586 format!(
587 "chara/human/c{race_code:04}/obj/tail/t{tail_code:04}/material/v0001{material_name}"
588 )
589 }
590}
591
592#[cfg(test)]
593mod tests {
594 use crate::pass_random_invalid;
595
596 use super::*;
597
598 #[test]
599 fn test_invalid() {
600 pass_random_invalid::<Material>();
601 }
602}