2023-08-06 08:25:04 -04:00
|
|
|
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
2024-04-20 13:17:11 -04:00
|
|
|
#![allow(clippy::unnecessary_fallible_conversions)] // This wrongly trips on binrw code
|
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
use std::io::Cursor;
|
2022-08-11 15:40:14 -04:00
|
|
|
|
2023-08-06 08:25:04 -04:00
|
|
|
use binrw::{BinRead, binrw};
|
2023-10-13 16:16:04 -04:00
|
|
|
use crate::ByteSpan;
|
2023-08-06 08:25:04 -04:00
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-08-11 15:40:14 -04:00
|
|
|
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,
|
2022-08-16 11:52:07 -04:00
|
|
|
additional_data_size: u8,
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct MaterialHeader {
|
|
|
|
shader_value_list_size: u16,
|
|
|
|
shader_key_count: u16,
|
|
|
|
constant_count: u16,
|
|
|
|
#[br(pad_after = 4)]
|
2022-08-16 11:52:07 -04:00
|
|
|
sampler_count: u16,
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-08-11 15:40:14 -04:00
|
|
|
struct ColorSet {
|
|
|
|
name_offset: u16,
|
|
|
|
#[br(pad_after = 1)]
|
2022-08-16 11:52:07 -04:00
|
|
|
index: u8,
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2024-04-17 21:40:32 -04:00
|
|
|
#[br(import {set_count: usize})]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-08-11 15:40:14 -04:00
|
|
|
struct ColorSetInfo {
|
2024-04-17 21:40:32 -04:00
|
|
|
#[br(count = set_count)]
|
2022-08-16 11:52:07 -04:00
|
|
|
data: Vec<u16>,
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-08-11 15:40:14 -04:00
|
|
|
struct ColorSetDyeInfo {
|
|
|
|
#[br(count = 16)]
|
2022-08-16 11:52:07 -04:00
|
|
|
data: Vec<u16>,
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2024-01-29 15:07:59 -05:00
|
|
|
pub struct ShaderKey {
|
|
|
|
pub category: u32,
|
|
|
|
pub value: u32,
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-08-11 15:40:14 -04:00
|
|
|
struct Constant {
|
|
|
|
constant_id: u32,
|
|
|
|
value_offset: u16,
|
2022-08-16 11:52:07 -04:00
|
|
|
value_size: u16,
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
2024-01-29 15:07:59 -05:00
|
|
|
// from https://github.com/NotAdam/Lumina/blob/master/src/Lumina/Data/Parsing/MtrlStructs.cs
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
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)]
|
2024-04-17 20:23:44 -04:00
|
|
|
SamplerWhitecapMap,
|
|
|
|
|
|
|
|
#[brw(magic = 0x565f8fd8u32)]
|
|
|
|
UnknownDawntrail1
|
2024-01-29 15:07:59 -05:00
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-08-11 15:40:14 -04:00
|
|
|
struct Sampler {
|
2024-01-29 15:07:59 -05:00
|
|
|
texture_usage: TextureUsage,
|
2022-08-11 15:40:14 -04:00
|
|
|
flags: u32, // TODO: unknown
|
|
|
|
#[br(pad_after = 3)]
|
|
|
|
texture_index: u8,
|
|
|
|
}
|
|
|
|
|
2022-10-13 17:11:03 -04:00
|
|
|
#[binrw]
|
2022-08-11 15:40:14 -04:00
|
|
|
#[derive(Debug)]
|
2022-09-15 16:26:31 -04:00
|
|
|
#[allow(dead_code)]
|
2022-10-13 16:03:46 -04:00
|
|
|
#[br(little)]
|
2022-08-11 15:40:14 -04:00
|
|
|
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)]
|
2023-03-31 21:31:54 -04:00
|
|
|
#[br(pad_after = file_header.additional_data_size)]
|
2022-08-11 15:40:14 -04:00
|
|
|
strings: Vec<u8>,
|
|
|
|
|
2024-04-17 21:40:32 -04:00
|
|
|
#[br(if(file_header.data_set_size > 0))]
|
|
|
|
// Dawntrail doubled the amount of color sets.
|
|
|
|
// The MTRL version is the same (why square enix?) so we check the data set size instead
|
|
|
|
#[br(args { set_count: if file_header.data_set_size < 2048 { 256 } else { 1024 } })]
|
2022-08-11 15:40:14 -04:00
|
|
|
color_set_info: Option<ColorSetInfo>,
|
|
|
|
|
2024-04-17 21:40:32 -04:00
|
|
|
#[br(if(file_header.data_set_size >
|
|
|
|
if file_header.data_set_size < 2048 { 512 } else { 2048 }
|
|
|
|
))]
|
2022-08-11 15:40:14 -04:00
|
|
|
color_set_due_info: Option<ColorSetDyeInfo>,
|
|
|
|
|
|
|
|
header: MaterialHeader,
|
|
|
|
|
|
|
|
#[br(count = header.shader_key_count)]
|
|
|
|
shader_keys: Vec<ShaderKey>,
|
|
|
|
#[br(count = header.constant_count)]
|
|
|
|
constants: Vec<Constant>,
|
|
|
|
#[br(count = header.sampler_count)]
|
|
|
|
samplers: Vec<Sampler>,
|
|
|
|
#[br(count = header.shader_value_list_size / 4)]
|
2022-08-16 11:52:07 -04:00
|
|
|
shader_values: Vec<f32>,
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Material {
|
2024-01-29 15:07:59 -05:00
|
|
|
pub shader_package_name: String,
|
2022-08-16 11:52:07 -04:00
|
|
|
pub texture_paths: Vec<String>,
|
2024-01-29 15:07:59 -05:00
|
|
|
pub shader_keys: Vec<ShaderKey>
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Material {
|
2023-10-13 16:16:04 -04:00
|
|
|
pub fn from_existing(buffer: ByteSpan) -> Option<Material> {
|
2022-08-11 15:40:14 -04:00
|
|
|
let mut cursor = Cursor::new(buffer);
|
|
|
|
let mat_data = MaterialData::read(&mut cursor).ok()?;
|
2024-04-17 20:23:44 -04:00
|
|
|
|
2022-08-11 15:40:14 -04:00
|
|
|
let mut texture_paths = vec![];
|
|
|
|
|
|
|
|
let mut offset = 0;
|
|
|
|
for _ in 0..mat_data.file_header.texture_count {
|
|
|
|
let mut string = String::new();
|
|
|
|
|
2024-04-20 13:17:11 -04:00
|
|
|
let mut next_char = mat_data.strings[offset] as char;
|
2022-08-11 15:40:14 -04:00
|
|
|
while next_char != '\0' {
|
|
|
|
string.push(next_char);
|
|
|
|
offset += 1;
|
2024-04-20 13:17:11 -04:00
|
|
|
next_char = mat_data.strings[offset] as char;
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
texture_paths.push(string);
|
|
|
|
|
|
|
|
offset += 1;
|
|
|
|
}
|
|
|
|
|
2024-01-29 15:07:59 -05:00
|
|
|
// TODO: move to reusable function
|
|
|
|
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;
|
2024-04-20 13:17:11 -04:00
|
|
|
next_char = mat_data.strings[offset] as char;
|
2024-01-29 15:07:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Some(Material {
|
|
|
|
shader_package_name,
|
|
|
|
texture_paths,
|
|
|
|
shader_keys: mat_data.shader_keys
|
|
|
|
})
|
2022-08-11 15:40:14 -04:00
|
|
|
}
|
2022-08-16 11:52:07 -04:00
|
|
|
}
|
2024-04-16 22:05:59 -04:00
|
|
|
|
|
|
|
#[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");
|
|
|
|
|
|
|
|
// Feeding it invalid data should not panic
|
|
|
|
Material::from_existing(&read(d).unwrap());
|
|
|
|
}
|
|
|
|
}
|