use std::path::PathBuf;
use crate::prelude::*;
use crate::util::bench::Stopwatch;
use crate::wad::Blob;
use crate::wad::GMVersion;
use crate::wad::chunk::ChunkName;
use crate::wad::elem::animation_curve::AnimationCurves;
use crate::wad::elem::audio::Audios;
use crate::wad::elem::audio_group::AudioGroup;
use crate::wad::elem::audio_group::AudioGroups;
use crate::wad::elem::background::Tilesets;
use crate::wad::elem::code::Codes;
use crate::wad::elem::data_file::DataFiles;
use crate::wad::elem::embedded_image::EmbeddedImages;
use crate::wad::elem::extension::Extensions;
use crate::wad::elem::feature_flag::FeatureFlags;
use crate::wad::elem::filter_effect::FilterEffects;
use crate::wad::elem::font::Fonts;
use crate::wad::elem::function::Functions;
use crate::wad::elem::game_end::GameEndScripts;
use crate::wad::elem::game_object::GameObjects;
use crate::wad::elem::general_info::Flags;
use crate::wad::elem::general_info::FunctionClassifications;
use crate::wad::elem::general_info::GMS2Data;
use crate::wad::elem::general_info::GeneralInfo;
use crate::wad::elem::global_init::GlobalInitScripts;
use crate::wad::elem::language::LanguageInfo;
use crate::wad::elem::options::Constant;
use crate::wad::elem::options::OptionFlags;
use crate::wad::elem::options::Options;
use crate::wad::elem::particle_emitter::ParticleEmitters;
use crate::wad::elem::particle_system::ParticleSystems;
use crate::wad::elem::path::Paths;
use crate::wad::elem::room::Room;
use crate::wad::elem::room::Rooms;
use crate::wad::elem::script::Scripts;
use crate::wad::elem::sequence::Sequences;
use crate::wad::elem::shader::Shaders;
use crate::wad::elem::sound::Sounds;
use crate::wad::elem::sprite::Sprites;
use crate::wad::elem::string::Strings;
use crate::wad::elem::tag::Tags;
use crate::wad::elem::texture_group_info::TextureGroupInfos;
use crate::wad::elem::texture_page::TexturePages;
use crate::wad::elem::texture_page_item::TexturePageItems;
use crate::wad::elem::timeline::Timelines;
use crate::wad::elem::ui_node::UINodes;
use crate::wad::elem::validate_names;
use crate::wad::elem::variable::ModernHeader;
use crate::wad::elem::variable::Variables;
use crate::wad::version::LtsBranch;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Endianness {
#[default]
Little,
Big,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Metadata {
pub location: Option<PathBuf>,
pub chunk_padding: u32,
pub endianness: Endianness,
pub original_data_size: u32,
pub(crate) chunk_order: Vec<ChunkName>,
}
impl Default for Metadata {
fn default() -> Self {
Self {
location: None,
chunk_padding: 16,
endianness: Endianness::Little,
original_data_size: 0,
chunk_order: Vec::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct GMData {
pub meta: Metadata,
pub animation_curves: AnimationCurves, pub audio_groups: AudioGroups, pub audios: Audios, pub codes: Codes, pub data_files: DataFiles, pub embedded_images: EmbeddedImages, pub extensions: Extensions, pub feature_flags: FeatureFlags, pub filter_effects: FilterEffects, pub fonts: Fonts, pub functions: Functions, pub game_end_scripts: GameEndScripts, pub game_objects: GameObjects, pub general_info: GeneralInfo, pub global_init_scripts: GlobalInitScripts, pub language_info: LanguageInfo, pub options: Options, pub particle_emitters: ParticleEmitters, pub particle_systems: ParticleSystems, pub paths: Paths, pub rooms: Rooms, pub ui_nodes: UINodes, pub scripts: Scripts, pub sequences: Sequences, pub shaders: Shaders, pub sounds: Sounds, pub sprites: Sprites, pub strings: Strings, pub tags: Tags, pub texture_group_infos: TextureGroupInfos, pub texture_page_items: TexturePageItems, pub texture_pages: TexturePages, pub tilesets: Tilesets, pub timelines: Timelines, pub variables: Variables, }
impl Default for GMData {
fn default() -> Self {
use ChunkName::*;
let mut strings = Strings::default();
strings.exists = true;
let meta = Metadata {
location: None,
chunk_padding: 16,
endianness: Endianness::Little,
original_data_size: 5000,
chunk_order: vec![
GEN8, OPTN, LANG, EXTN, SOND, AGRP, SPRT, BGND, PATH, SCPT, GLOB, SHDR, FONT, TMLN,
OBJT, FEDS, ACRV, SEQN, TAGS, ROOM, UILR, DAFL, EMBI, PSEM, PSYS, TPAG, TGIN, CODE,
VARI, FUNC, FEAT, STRG, TXTR, AUDO,
],
};
let animation_curves = AnimationCurves { elems: Vec::new(), exists: true };
let mut audio_groups = AudioGroups { elems: Vec::new(), exists: true };
audio_groups.push(AudioGroup {
name: strings.make("audiogroup_default"),
path: strings.make("audiogroup_default.dat"),
});
let audios = Audios { elems: Vec::new(), exists: true };
let codes = Codes { elems: Vec::new(), exists: true };
let data_files = DataFiles { exists: true };
let embedded_images = EmbeddedImages { elems: Vec::new(), exists: true };
let extensions = Extensions { elems: Vec::new(), exists: true };
let feature_flags = FeatureFlags { elems: Vec::new(), exists: true };
let filter_effects = FilterEffects { elems: Vec::new(), exists: true };
let fonts = Fonts { exists: true, ..Default::default() };
let functions = Functions { exists: true, ..Default::default() };
let game_end_scripts = GameEndScripts { elems: Vec::new(), exists: true };
let game_objects = GameObjects { elems: Vec::new(), exists: true };
let general_info = GeneralInfo {
debugger_enabled: false,
wad_version: 17,
unknown_value: 0,
game_file_name: strings.make("libgm_game"),
config: strings.make("Default"),
last_object_id: 100_000,
last_tile_id: 10_000_000,
game_id: 1337,
directplay_guid: Blob([0u8; 16]),
game_name: strings.make("LibGM"),
version: GMVersion::new(2026, 0, 0, 0, LtsBranch::PostLts),
window_width: 640,
window_height: 480,
flags: Flags::SCALE | Flags::SHOW_CURSOR,
license_crc32: 1337,
license_md5: Blob(*b"GnuPublicLicense"),
creation_timestamp: Default::default(), display_name: strings.make("LibGM: The Game"),
function_classifications: FunctionClassifications::empty(),
steam_appid: 0,
debugger_port: 0,
room_order: vec![GMRef::new(0)],
gms2_data: Some(GMS2Data::default()),
exists: true,
};
let global_init_scripts = GlobalInitScripts { elems: Vec::new(), exists: true };
let language_info = LanguageInfo {
unknown1: 1,
exists: true,
..Default::default()
};
let options = Options {
is_new_format: true,
flags: OptionFlags::SHOW_CURSOR
| OptionFlags::VARIABLE_ERRORS
| OptionFlags::LEGACY_JSON_PARSING
| OptionFlags::LEGACY_NUMBER_CONVERSION
| OptionFlags::LEGACY_OTHER_BEHAVIOR
| OptionFlags::AUDIO_ERROR_BEHAVIOR
| OptionFlags::ALLOW_INSTANCE_CHANGE,
window_scale: 0,
window_color: 0,
color_depth: 0,
resolution: 0,
frequency: 0,
vertex_sync: 0,
priority: 0,
back_image: GMRef::none(),
front_image: GMRef::none(),
load_image: GMRef::none(),
load_alpha: 0,
constants: vec![
Constant::new("@@SleepMargin", "0", &mut strings),
Constant::new("@@DrawColour", "4294967295", &mut strings),
Constant::new("@@VersionMajor", "1", &mut strings),
Constant::new("@@VersionMinor", "0", &mut strings),
Constant::new("@@VersionBuild", "", &mut strings),
Constant::new("@@VersionRevision", "0", &mut strings),
],
exists: true,
};
let particle_emitters = ParticleEmitters { elems: Vec::new(), exists: true };
let particle_systems = ParticleSystems { elems: Vec::new(), exists: true };
let paths = Paths { elems: Vec::new(), exists: true };
let mut rooms = Rooms { elems: Vec::new(), exists: true };
rooms.push(Room {
name: strings.make("room0"),
..Default::default()
});
let ui_nodes = UINodes { elems: Vec::new(), exists: true };
let scripts = Scripts { elems: Vec::new(), exists: true };
let sequences = Sequences { elems: Vec::new(), exists: true };
let shaders = Shaders { elems: Vec::new(), exists: true };
let sounds = Sounds { elems: Vec::new(), exists: true };
let sprites = Sprites { elems: Vec::new(), exists: true };
let tags = Tags { exists: true, ..Default::default() };
let texture_group_infos = TextureGroupInfos { elems: Vec::new(), exists: true };
let texture_page_items = TexturePageItems { elems: Vec::new(), exists: true };
let texture_pages = TexturePages { elems: Vec::new(), exists: true };
let tilesets = Tilesets {
elems: Vec::new(),
align: true,
exists: true,
};
let timelines = Timelines { elems: Vec::new(), exists: true };
let variables = Variables {
elems: Vec::new(),
modern_header: Some(ModernHeader {
var_count1: 0,
var_count2: 0,
max_local_var_count: 0,
}),
exists: true,
};
GMData {
meta,
animation_curves,
audio_groups,
audios,
codes,
data_files,
embedded_images,
extensions,
feature_flags,
filter_effects,
fonts,
functions,
game_end_scripts,
game_objects,
general_info,
global_init_scripts,
language_info,
options,
particle_emitters,
particle_systems,
paths,
rooms,
ui_nodes,
scripts,
sequences,
shaders,
sounds,
sprites,
strings,
tags,
texture_group_infos,
texture_page_items,
texture_pages,
tilesets,
timelines,
variables,
}
}
}
impl GMData {
pub fn validate_names(&self) -> Result<()> {
let stopwatch = Stopwatch::start();
validate_names(&self.animation_curves, &self.strings)?;
validate_names(&self.audio_groups, &self.strings)?;
validate_names(&self.codes, &self.strings)?;
validate_names(&self.filter_effects, &self.strings)?;
validate_names(&self.fonts, &self.strings)?;
validate_names(&self.functions, &self.strings)?;
validate_names(&self.game_objects, &self.strings)?;
validate_names(&self.particle_systems, &self.strings)?;
validate_names(&self.paths, &self.strings)?;
validate_names(&self.rooms, &self.strings)?;
validate_names(&self.scripts, &self.strings)?;
validate_names(&self.sequences, &self.strings)?;
validate_names(&self.shaders, &self.strings)?;
validate_names(&self.sounds, &self.strings)?;
validate_names(&self.sprites, &self.strings)?;
validate_names(&self.texture_group_infos, &self.strings)?;
validate_names(&self.tilesets, &self.strings)?;
validate_names(&self.timelines, &self.strings)?;
self.variables.validate_names(&self.strings)?;
log::trace!("Validating all names took {stopwatch}");
Ok(())
}
pub fn deserialize_all_textures(&mut self) -> Result<()> {
let stopwatch = Stopwatch::start();
for texture_page in self.texture_pages.elements_mut() {
let Some(image) = &mut texture_page.image else {
continue;
};
let error: &str = "deserializing all embedded texture pages";
image.deserialize().ctx(error)?;
}
log::trace!(
"Deserializing all {} texture pages took {}",
self.texture_pages.len(),
stopwatch,
);
Ok(())
}
pub fn post_deserialize(&mut self) -> Result<()> {
self.validate_names()?;
self.deserialize_all_textures()?;
self.optimize_memory();
Ok(())
}
}