pub(crate) mod builder;
mod chunk;
mod lists;
mod numbers;
mod pointers;
mod resources;
mod versioning;
use std::path::Path;
use builder::DataBuilder;
use crate::prelude::*;
use crate::util::bench::Stopwatch;
use crate::util::unwind;
use crate::wad::chunk::ChunkName;
use crate::wad::data::Endianness;
use crate::wad::data::GMData;
#[inline]
pub fn build_bytes(gm_data: &GMData) -> Result<Vec<u8>> {
build(gm_data).ctx("building GameMaker data bytes")
}
pub fn build_file(gm_data: &GMData, path: impl AsRef<Path>) -> Result<()> {
let path: &Path = path.as_ref();
let raw_data: Vec<u8> =
build(gm_data).ctx(|| format!("building GameMaker data file {}", path.display()))?;
let stopwatch = Stopwatch::start();
std::fs::write(path, raw_data).ctx_any("writing data file")?;
log::trace!("Writing data file took {stopwatch}");
Ok(())
}
#[inline]
fn build(gm_data: &GMData) -> Result<Vec<u8>> {
if cfg!(feature = "catch-panic") {
unwind::catch(|| build_impl(gm_data))
} else {
build_impl(gm_data)
}
}
fn build_impl(data: &GMData) -> Result<Vec<u8>> {
let stopwatch = Stopwatch::start();
let mut builder = DataBuilder::new(data);
let root_chunk = match data.meta.endianness {
Endianness::Little => b"FORM",
Endianness::Big => b"MROF",
};
builder.write_bytes(root_chunk);
builder.write_u32(0xDEAD_C0DE);
let chunk_order: Vec<ChunkName> = make_chunk_order(data);
if chunk_order.is_empty() {
bail!("Data file needs at least one chunk");
}
for chunk_name in chunk_order {
match chunk_name {
ChunkName::ACRV => builder.build_chunk(&data.animation_curves),
ChunkName::AGRP => builder.build_chunk(&data.audio_groups),
ChunkName::AUDO => builder.build_chunk(&data.audios),
ChunkName::BGND => builder.build_chunk(&data.tilesets),
ChunkName::CODE => builder.build_chunk(&data.codes),
ChunkName::DAFL => builder.build_chunk(&data.data_files),
ChunkName::EMBI => builder.build_chunk(&data.embedded_images),
ChunkName::EXTN => builder.build_chunk(&data.extensions),
ChunkName::FEAT => builder.build_chunk(&data.feature_flags),
ChunkName::FEDS => builder.build_chunk(&data.filter_effects),
ChunkName::FONT => builder.build_chunk(&data.fonts),
ChunkName::FUNC => builder.build_chunk(&data.functions),
ChunkName::GEN8 => builder.build_chunk(&data.general_info),
ChunkName::GLOB => builder.build_chunk(&data.global_init_scripts),
ChunkName::GMEN => builder.build_chunk(&data.game_end_scripts),
ChunkName::LANG => builder.build_chunk(&data.language_info),
ChunkName::OBJT => builder.build_chunk(&data.game_objects),
ChunkName::OPTN => builder.build_chunk(&data.options),
ChunkName::PATH => builder.build_chunk(&data.paths),
ChunkName::PSEM => builder.build_chunk(&data.particle_emitters),
ChunkName::PSYS => builder.build_chunk(&data.particle_systems),
ChunkName::ROOM => builder.build_chunk(&data.rooms),
ChunkName::SCPT => builder.build_chunk(&data.scripts),
ChunkName::SEQN => builder.build_chunk(&data.sequences),
ChunkName::SHDR => builder.build_chunk(&data.shaders),
ChunkName::SOND => builder.build_chunk(&data.sounds),
ChunkName::SPRT => builder.build_chunk(&data.sprites),
ChunkName::STRG => builder.build_chunk(&data.strings),
ChunkName::TAGS => builder.build_chunk(&data.tags),
ChunkName::TGIN => builder.build_chunk(&data.texture_group_infos),
ChunkName::TMLN => builder.build_chunk(&data.timelines),
ChunkName::TPAG => builder.build_chunk(&data.texture_page_items),
ChunkName::TXTR => builder.build_chunk(&data.texture_pages),
ChunkName::UILR => builder.build_chunk(&data.ui_nodes),
ChunkName::VARI => builder.build_chunk(&data.variables),
}?;
}
builder.remove_last_chunk_padding();
builder.connect_pointer_placeholders()?;
let data_length = builder.pos() - 8; builder.overwrite_u32(data_length, 4)?;
log::trace!("Building data file took {stopwatch}");
let raw_data: Vec<u8> = builder.finish();
if raw_data.len() >= i32::MAX as usize {
bail!("Data file is bigger than 2,147,483,646 bytes which will lead to bugs in the runner")
}
Ok(raw_data)
}
fn make_chunk_order(data: &GMData) -> Vec<ChunkName> {
let mut chunks = data.meta.chunk_order.clone();
macro_rules! yeah {
($chunkname:ident, $field:ident) => {
if data.$field.exists && !chunks.contains(&ChunkName::$chunkname) {
chunks.push(ChunkName::$chunkname);
}
};
}
yeah!(ACRV, animation_curves);
yeah!(AGRP, audio_groups);
yeah!(AUDO, audios);
yeah!(CODE, codes);
yeah!(DAFL, data_files);
yeah!(EMBI, embedded_images);
yeah!(EXTN, extensions);
yeah!(FEAT, feature_flags);
yeah!(FEDS, filter_effects);
yeah!(FONT, fonts);
yeah!(FUNC, functions);
yeah!(GMEN, game_end_scripts);
yeah!(OBJT, game_objects);
yeah!(GEN8, general_info);
yeah!(GLOB, global_init_scripts);
yeah!(LANG, language_info);
yeah!(OPTN, options);
yeah!(PSEM, particle_emitters);
yeah!(PSYS, particle_systems);
yeah!(PATH, paths);
yeah!(ROOM, rooms);
yeah!(UILR, ui_nodes);
yeah!(SCPT, scripts);
yeah!(SEQN, sequences);
yeah!(SHDR, shaders);
yeah!(SOND, sounds);
yeah!(SPRT, sprites);
yeah!(STRG, strings);
yeah!(TAGS, tags);
yeah!(TGIN, texture_group_infos);
yeah!(TPAG, texture_page_items);
yeah!(TXTR, texture_pages);
yeah!(BGND, tilesets);
yeah!(TMLN, timelines);
yeah!(VARI, variables);
chunks
}