use crate::error::Result;
use byteorder::{LittleEndian, WriteBytesExt};
use std::io::{Seek, SeekFrom, Write};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FourCCFormat {
Node = 1,
String = 2,
Int = 3,
Binary = 8,
Guid = 0x0D,
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum FourCCNode {
Container {
fourcc: [u8; 4],
children: Vec<FourCCNode>,
},
String { fourcc: [u8; 4], value: String },
Int { fourcc: [u8; 4], value: u32 },
Binary { fourcc: [u8; 4], data: Vec<u8> },
Guid { fourcc: [u8; 4], guid: [u8; 16] },
}
impl FourCCNode {
#[must_use]
pub fn container(fourcc: [u8; 4]) -> Self {
Self::Container {
fourcc,
children: Vec::new(),
}
}
#[must_use]
pub fn string(fourcc: [u8; 4], value: impl Into<String>) -> Self {
Self::String {
fourcc,
value: value.into(),
}
}
#[must_use]
pub fn int(fourcc: [u8; 4], value: u32) -> Self {
Self::Int { fourcc, value }
}
#[must_use]
pub fn binary(fourcc: [u8; 4], data: Vec<u8>) -> Self {
Self::Binary { fourcc, data }
}
#[must_use]
pub fn guid(fourcc: [u8; 4], guid: [u8; 16]) -> Self {
Self::Guid { fourcc, guid }
}
pub fn add_child(&mut self, child: FourCCNode) {
if let Self::Container { children, .. } = self {
children.push(child);
}
}
}
#[derive(Debug, Clone, Default)]
pub struct FourCCTree {
root: Option<FourCCNode>,
}
impl FourCCTree {
#[must_use]
pub fn new() -> Self {
Self { root: None }
}
pub fn set_root(&mut self, node: FourCCNode) {
self.root = Some(node);
}
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<u32> {
let start_pos = writer.stream_position()?;
if let Some(ref root) = self.root {
Self::write_node(writer, root)?;
}
let end_pos = writer.stream_position()?;
Ok((end_pos - start_pos) as u32)
}
fn write_node<W: Write + Seek>(writer: &mut W, node: &FourCCNode) -> Result<()> {
match node {
FourCCNode::Container { fourcc, children } => {
let header_pos = writer.stream_position()?;
writer.write_all(fourcc)?;
writer.write_u8(FourCCFormat::Node as u8)?;
writer.write_u8(0)?; writer.write_u16::<LittleEndian>(0)?;
for child in children {
Self::write_node(writer, child)?;
}
let end_pos = writer.stream_position()?;
let content_len = end_pos - header_pos - 8;
writer.seek(SeekFrom::Start(header_pos + 6))?;
if content_len > 0xFFFF {
writer.seek(SeekFrom::Start(header_pos + 5))?;
writer.write_u8(1)?; writer.write_u16::<LittleEndian>((content_len >> 16) as u16)?;
writer.seek(SeekFrom::Start(header_pos + 6))?;
writer.write_u16::<LittleEndian>(content_len as u16)?;
} else {
writer.write_u16::<LittleEndian>(content_len as u16)?;
}
writer.seek(SeekFrom::Start(end_pos))?;
Self::align_to_4(writer)?;
}
FourCCNode::String { fourcc, value } => {
let utf16: Vec<u16> = value.encode_utf16().chain(std::iter::once(0)).collect();
let bytes: Vec<u8> = utf16.iter().flat_map(|&c| c.to_le_bytes()).collect();
writer.write_all(fourcc)?;
writer.write_u8(FourCCFormat::String as u8)?;
writer.write_u8(0)?;
writer.write_u16::<LittleEndian>(bytes.len() as u16)?;
writer.write_all(&bytes)?;
Self::align_to_4(writer)?;
}
FourCCNode::Int { fourcc, value } => {
writer.write_all(fourcc)?;
writer.write_u8(FourCCFormat::Int as u8)?;
writer.write_u8(0)?;
writer.write_u16::<LittleEndian>(4)?;
writer.write_u32::<LittleEndian>(*value)?;
}
FourCCNode::Binary { fourcc, data } => {
writer.write_all(fourcc)?;
writer.write_u8(FourCCFormat::Binary as u8)?;
writer.write_u8(0)?;
writer.write_u16::<LittleEndian>(data.len() as u16)?;
writer.write_all(data)?;
Self::align_to_4(writer)?;
}
FourCCNode::Guid { fourcc, guid } => {
writer.write_all(fourcc)?;
writer.write_u8(FourCCFormat::Guid as u8)?;
writer.write_u8(0)?;
writer.write_u16::<LittleEndian>(16)?;
writer.write_all(guid)?;
}
}
Ok(())
}
fn align_to_4<W: Write + Seek>(writer: &mut W) -> Result<()> {
let pos = writer.stream_position()?;
let padding = (4 - (pos % 4)) % 4;
for _ in 0..padding {
writer.write_u8(0)?;
}
Ok(())
}
}
pub fn build_metadata_tree(
texture_name: &str,
width: u32,
height: u32,
x: u32,
y: u32,
layers: &[(&str, &str)], guid: &[u8; 16],
) -> FourCCTree {
let mut tree = FourCCTree::new();
let mut meta = FourCCNode::container(*b"META");
let mut atls = FourCCNode::container(*b"ATLS");
let mut txts = FourCCNode::container(*b"TXTS");
let mut txtr = FourCCNode::container(*b"TXTR");
txtr.add_child(FourCCNode::string(*b"NAME", texture_name));
txtr.add_child(FourCCNode::int(*b"WDTH", width));
txtr.add_child(FourCCNode::int(*b"HGHT", height));
txtr.add_child(FourCCNode::int(*b"XXXX", x));
txtr.add_child(FourCCNode::int(*b"YYYY", y));
txtr.add_child(FourCCNode::string(*b"ADDR", ""));
txtr.add_child(FourCCNode::binary(*b"SRGB", vec![1, 0, 0, 0]));
txtr.add_child(FourCCNode::guid(*b"THMB", *guid));
txts.add_child(txtr);
atls.add_child(txts);
meta.add_child(atls);
meta.add_child(FourCCNode::container(*b"PROJ"));
let mut linf = FourCCNode::container(*b"LINF");
for (i, (name, layer_type)) in layers.iter().enumerate() {
let mut layr = FourCCNode::container(*b"LAYR");
layr.add_child(FourCCNode::int(*b"INDX", i as u32));
layr.add_child(FourCCNode::string(*b"TYPE", *layer_type));
layr.add_child(FourCCNode::string(*b"NAME", *name));
linf.add_child(layr);
}
meta.add_child(linf);
let mut info = FourCCNode::container(*b"INFO");
let mut comp = FourCCNode::container(*b"COMP");
comp.add_child(FourCCNode::binary(*b"CMPW", vec![1, 0])); comp.add_child(FourCCNode::binary(*b"BLDV", vec![1, 0, 0, 0, 0, 0]));
info.add_child(comp);
info.add_child(FourCCNode::string(*b"DATE", ""));
info.add_child(FourCCNode::string(*b"BLKS", ""));
info.add_child(FourCCNode::string(*b"TILE", ""));
info.add_child(FourCCNode::string(*b"BDPR", ""));
info.add_child(FourCCNode::int(*b"LTMP", 0));
meta.add_child(info);
tree.set_root(meta);
tree
}