#![allow(clippy::unnecessary_fallible_conversions)] use std::io::Cursor;
use crate::common_file_operations::{Half1, Half2, Half3};
use crate::ByteSpan;
use binrw::{binread, binrw, BinRead, BinResult};
use crate::mtrl::ColorDyeTable::{DawntrailColorDyeTable, LegacyColorDyeTable, OpaqueColorDyeTable};
use crate::mtrl::ColorTable::{DawntrailColorTable, LegacyColorTable, OpaqueColorTable};
#[binrw]
#[derive(Debug)]
#[allow(dead_code)]
struct MaterialFileHeader {
version: u32,
file_size: u16,
data_set_size: u16,
string_table_size: u16,
shader_package_name_offset: u16,
texture_count: u8,
uv_set_count: u8,
color_set_count: u8,
additional_data_size: u8,
}
#[binrw]
#[derive(Debug)]
struct MaterialHeader {
shader_value_list_size: u16,
shader_key_count: u16,
constant_count: u16,
sampler_count: u16,
flags: u32,
}
#[binrw]
#[derive(Debug)]
#[allow(dead_code)]
struct ColorSet {
name_offset: u16,
index: u16,
}
#[binread]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
#[allow(dead_code)]
pub struct DawntrailColorTableRow {
#[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
pub diffuse_color: [f32; 3],
#[br(map = |x: Half1| { x.value.to_f32() })]
pub unknown1: f32,
#[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
pub specular_color: [f32; 3],
#[br(map = |x: Half1| { x.value.to_f32() })]
pub unknown2: f32,
#[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
pub emissive_color: [f32; 3],
#[br(map = |x: Half1| { x.value.to_f32() })]
pub unknown3: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub sheen_rate: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub sheen_tint: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub sheen_aperture: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub unknown4: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub roughness: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub unknown5: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub metalness: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub anisotropy: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub unknown6: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub sphere_mask: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub unknown7: f32,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub unknown8: f32,
pub shader_index: u16,
pub tile_set: u16,
#[br(map = |x: Half1| { x.value.to_f32() })]
pub tile_alpha: f32,
pub sphere_index: u16,
#[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
pub material_repeat: [f32; 2],
#[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
pub material_skew: [f32; 2],
}
#[binread]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
#[allow(dead_code)]
pub struct LegacyColorTableRow {
#[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
pub diffuse_color: [f32; 3],
#[br(map = |x: Half1| { x.value.to_f32() })]
pub specular_strength: f32,
#[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
pub specular_color: [f32; 3],
#[br(map = |x: Half1| { x.value.to_f32() })]
pub gloss_strength: f32,
#[br(map = |x: Half3| { [x.r.to_f32(), x.g.to_f32(), x.b.to_f32()] })]
pub emissive_color: [f32; 3],
pub tile_set: u16,
#[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
pub material_repeat: [f32; 2],
#[br(map = |x: Half2| { [x.x.to_f32(), x.y.to_f32()] })]
pub material_skew: [f32; 2],
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub struct LegacyColorTableData {
#[br(count = 16)]
pub rows: Vec<LegacyColorTableRow>,
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub struct DawntrailColorTableData {
#[br(count = 32)]
pub rows: Vec<DawntrailColorTableRow>,
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub struct OpaqueColorTableData {
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub enum ColorTable {
LegacyColorTable(LegacyColorTableData),
DawntrailColorTable(DawntrailColorTableData),
OpaqueColorTable(OpaqueColorTableData)
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub struct LegacyColorDyeTableRow {
#[br(temp)]
data: u16,
#[br(calc = data >> 5)]
pub template: u16,
#[br(calc = (data & 0x01) != 0)]
pub diffuse: bool,
#[br(calc = (data & 0x02) != 0)]
pub specular: bool,
#[br(calc = (data & 0x04) != 0)]
pub emissive: bool,
#[br(calc = (data & 0x08) != 0)]
pub gloss: bool,
#[br(calc = (data & 0x10) != 0)]
pub specular_strength: bool,
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub struct DawntrailColorDyeTableRow {
#[br(temp)]
data: u32,
#[br(calc = ((data >> 16) & 0x7FF) as u16)]
pub template: u16,
#[br(calc = ((data >> 27) & 0x3) as u8)]
pub channel: u8,
#[br(calc = (data & 0x0001) != 0)]
pub diffuse: bool,
#[br(calc = (data & 0x0002) != 0)]
pub specular: bool,
#[br(calc = (data & 0x0004) != 0)]
pub emissive: bool,
#[br(calc = (data & 0x0008) != 0)]
pub scalar3: bool,
#[br(calc = (data & 0x0010) != 0)]
pub metalness: bool,
#[br(calc = (data & 0x0020) != 0)]
pub roughness: bool,
#[br(calc = (data & 0x0040) != 0)]
pub sheen_rate: bool,
#[br(calc = (data & 0x0080) != 0)]
pub sheen_tint_rate: bool,
#[br(calc = (data & 0x0100) != 0)]
pub sheen_aperture: bool,
#[br(calc = (data & 0x0200) != 0)]
pub anisotropy: bool,
#[br(calc = (data & 0x0400) != 0)]
pub sphere_map_index: bool,
#[br(calc = (data & 0x0800) != 0)]
pub sphere_map_mask: bool,
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub struct LegacyColorDyeTableData {
#[br(count = 16)]
pub rows: Vec<LegacyColorDyeTableRow>,
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub struct DawntrailColorDyeTableData {
#[br(count = 32)]
pub rows: Vec<DawntrailColorDyeTableRow>,
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub struct OpaqueColorDyeTableData {
}
#[binread]
#[derive(Debug)]
#[allow(dead_code)]
pub enum ColorDyeTable {
LegacyColorDyeTable(LegacyColorDyeTableData),
DawntrailColorDyeTable(DawntrailColorDyeTableData),
OpaqueColorDyeTable(OpaqueColorDyeTableData)
}
#[binrw]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
#[allow(dead_code)]
pub struct ShaderKey {
pub category: u32,
pub value: u32,
}
#[binrw]
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct ConstantStruct {
constant_id: u32,
value_offset: u16,
value_size: u16,
}
#[derive(Debug, Clone)]
#[repr(C)]
#[allow(dead_code)]
pub struct Constant {
id: u32,
num_values: u32,
values: [f32; 4],
}
#[binrw]
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
pub enum TextureUsage {
#[brw(magic = 0x88408C04u32)]
Sampler,
#[brw(magic = 0x213CB439u32)]
Sampler0,
#[brw(magic = 0x563B84AFu32)]
Sampler1,
#[brw(magic = 0xFEA0F3D2u32)]
SamplerCatchlight,
#[brw(magic = 0x1E6FEF9Cu32)]
SamplerColorMap0,
#[brw(magic = 0x6968DF0Au32)]
SamplerColorMap1,
#[brw(magic = 0x115306BEu32)]
SamplerDiffuse,
#[brw(magic = 0xF8D7957Au32)]
SamplerEnvMap,
#[brw(magic = 0x8A4E82B6u32)]
SamplerMask,
#[brw(magic = 0x0C5EC1F1u32)]
SamplerNormal,
#[brw(magic = 0xAAB4D9E9u32)]
SamplerNormalMap0,
#[brw(magic = 0xDDB3E97Fu32)]
SamplerNormalMap1,
#[brw(magic = 0x87F6474Du32)]
SamplerReflection,
#[brw(magic = 0x2B99E025u32)]
SamplerSpecular,
#[brw(magic = 0x1BBC2F12u32)]
SamplerSpecularMap0,
#[brw(magic = 0x6CBB1F84u32)]
SamplerSpecularMap1,
#[brw(magic = 0xE6321AFCu32)]
SamplerWaveMap,
#[brw(magic = 0x574E22D6u32)]
SamplerWaveletMap0,
#[brw(magic = 0x20491240u32)]
SamplerWaveletMap1,
#[brw(magic = 0x95E1F64Du32)]
SamplerWhitecapMap,
#[brw(magic = 0x565f8fd8u32)]
UnknownDawntrail1,
#[brw(magic = 0xe5338c17u32)]
UnknownDawntrail2,
}
#[binrw]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
#[allow(dead_code)]
pub struct Sampler {
texture_usage: TextureUsage,
flags: u32, texture_index: u8,
unknown1: u8,
unknown2: u8,
unknown3: u8,
}
#[binrw::parser(reader, endian)]
fn parse_color_table(table_dimension_logs: u8) -> BinResult<Option<ColorTable>> {
Ok(Some(match table_dimension_logs {
0 | 0x42 => LegacyColorTable(LegacyColorTableData::read_options(reader, endian, ())?),
0x53 => DawntrailColorTable(DawntrailColorTableData::read_options(reader, endian, ())?),
_ => OpaqueColorTable(OpaqueColorTableData::read_options(reader, endian, ())?)
}))
}
#[binrw::parser(reader, endian)]
fn parse_color_dye_table(table_dimension_logs: u8) -> BinResult<Option<ColorDyeTable>> {
Ok(Some(match table_dimension_logs {
0 => LegacyColorDyeTable(LegacyColorDyeTableData::read_options(reader, endian, ())?),
0x50...0x5F => DawntrailColorDyeTable(DawntrailColorDyeTableData::read_options(reader, endian, ())?),
_ => OpaqueColorDyeTable(OpaqueColorDyeTableData::read_options(reader, endian, ())?)
}))
}
#[binrw]
#[derive(Debug)]
#[allow(dead_code)]
#[br(little)]
struct MaterialData {
file_header: MaterialFileHeader,
#[br(count = file_header.texture_count)]
offsets: Vec<u32>,
#[br(count = file_header.uv_set_count)]
uv_color_sets: Vec<ColorSet>,
#[br(count = file_header.color_set_count)]
color_sets: Vec<ColorSet>,
#[br(count = file_header.string_table_size)]
strings: Vec<u8>,
#[br(count = file_header.additional_data_size)]
#[br(pad_size_to = 4)]
#[br(map = |x: Vec<u8>| u32::from_le_bytes(x[0..4].try_into().unwrap()))]
table_flags: u32,
#[br(calc = (table_flags & 0x4) != 0)]
#[bw(ignore)]
has_table: bool,
#[br(calc = (table_flags & 0x8) != 0)]
#[bw(ignore)]
has_dye_table: bool,
#[br(calc = ((table_flags >> 4) & 0xF) as u8)]
#[bw(ignore)]
table_width_log: u8,
#[br(calc = ((table_flags >> 8) & 0xF) as u8)]
#[bw(ignore)]
table_height_log: u8,
#[br(calc = (table_flags >> 4) as u8)]
#[bw(ignore)]
table_dimension_logs: u8,
#[br(calc = !has_table || table_width_log != 0 && table_height_log != 0)]
#[bw(ignore)]
is_dawntrail: bool,
#[br(if(has_table))]
#[br(parse_with = parse_color_table)]
#[br(args(table_dimension_logs))]
#[bw(ignore)]
color_table: Option<ColorTable>,
#[br(if(has_dye_table))]
#[br(parse_with = parse_color_dye_table)]
#[br(args(table_dimension_logs))]
#[bw(ignore)]
color_dye_table: Option<ColorDyeTable>,
header: MaterialHeader,
#[br(count = header.shader_key_count)]
shader_keys: Vec<ShaderKey>,
#[br(count = header.constant_count)]
constants: Vec<ConstantStruct>,
#[br(count = header.sampler_count)]
samplers: Vec<Sampler>,
#[br(count = header.shader_value_list_size / 4)]
shader_values: Vec<f32>,
}
#[derive(Debug)]
pub struct Material {
pub shader_package_name: String,
pub texture_paths: Vec<String>,
pub shader_keys: Vec<ShaderKey>,
pub constants: Vec<Constant>,
pub samplers: Vec<Sampler>,
pub color_table: Option<ColorTable>,
pub color_dye_table: Option<ColorDyeTable>,
}
impl Material {
pub fn from_existing(buffer: ByteSpan) -> Option<Material> {
let mut cursor = Cursor::new(buffer);
let mat_data = MaterialData::read(&mut cursor).ok()?;
let mut texture_paths = vec![];
let mut offset = 0;
for _ in 0..mat_data.file_header.texture_count {
let mut string = String::new();
let mut next_char = mat_data.strings[offset] as char;
while next_char != '\0' {
string.push(next_char);
offset += 1;
next_char = mat_data.strings[offset] as char;
}
texture_paths.push(string);
offset += 1;
}
let mut shader_package_name = String::new();
offset = mat_data.file_header.shader_package_name_offset as usize;
let mut next_char = mat_data.strings[offset] as char;
while next_char != '\0' {
shader_package_name.push(next_char);
offset += 1;
next_char = mat_data.strings[offset] as char;
}
let mut constants = Vec::new();
for constant in mat_data.constants {
let mut values: [f32; 4] = [0.0; 4];
let num_floats = constant.value_size / 4;
for i in 0..num_floats as usize {
values[i] = mat_data.shader_values[(constant.value_offset as usize / 4) + i];
}
constants.push(Constant {
id: constant.constant_id,
num_values: num_floats as u32,
values,
});
}
Some(Material {
shader_package_name,
texture_paths,
shader_keys: mat_data.shader_keys,
constants,
samplers: mat_data.samplers,
color_table: mat_data.color_table,
color_dye_table: mat_data.color_dye_table,
})
}
}
#[cfg(test)]
mod tests {
use std::fs::read;
use std::path::PathBuf;
use super::*;
#[test]
fn test_invalid() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("random");
Material::from_existing(&read(d).unwrap());
}
}