mod flags;
mod function_classifications;
mod gms2;
use std::fmt;
#[cfg(feature = "game-creation-timestamp")]
use chrono::DateTime;
#[cfg(feature = "game-creation-timestamp")]
use chrono::Utc;
pub use flags::Flags;
pub use function_classifications::FunctionClassifications;
pub use gms2::GMS2Data;
use crate::prelude::*;
use crate::wad::chunk::ChunkName;
use crate::wad::deserialize::reader::DataReader;
use crate::wad::elements::GMChunk;
use crate::wad::elements::GMElement;
use crate::wad::elements::room::GMRoom;
use crate::wad::reference::GMRef;
use crate::wad::serialize::builder::DataBuilder;
use crate::wad::version::GMVersion;
use crate::wad::version::GMVersionReq;
#[derive(Clone, PartialEq)]
pub struct GMGeneralInfo {
pub is_debugger_disabled: bool,
pub wad_version: u8,
unknown_value: u16,
pub game_file_name: String,
pub config: String,
pub last_object_id: u32,
pub last_tile_id: u32,
pub game_id: u32,
pub directplay_guid: [u8; 16],
pub game_name: String,
pub version: GMVersion,
pub default_window_width: u32,
pub default_window_height: u32,
pub flags: Flags,
pub license_crc32: u32,
pub license_md5: [u8; 16],
#[cfg(feature = "game-creation-timestamp")]
pub creation_timestamp: DateTime<Utc>,
#[cfg(not(feature = "game-creation-timestamp"))]
creation_timestamp: i64,
pub display_name: String,
pub function_classifications: FunctionClassifications,
pub steam_appid: i32,
pub debugger_port: u32,
pub room_order: Vec<GMRef<GMRoom>>,
pub gms2_data: Option<GMS2Data>,
pub(crate) exists: bool,
}
impl GMChunk for GMGeneralInfo {
const NAME: ChunkName = ChunkName::new("GEN8");
fn exists(&self) -> bool {
self.exists
}
}
impl GMElement for GMGeneralInfo {
fn deserialize(reader: &mut DataReader) -> Result<Self> {
let is_debugger_disabled: bool = match reader.read_u8()? {
0 => false,
1 => true,
other => {
bail!("Invalid u8 bool {other} while reading general info \"is debugger disabled\"")
}
};
let wad_version = reader.read_u8()?;
let unknown_value = reader.read_u16()?;
let game_file_name: String = reader.read_gm_string()?;
let config: String = reader.read_gm_string()?;
let last_object_id = reader.read_u32()?;
let last_tile_id = reader.read_u32()?;
let game_id = reader.read_u32()?;
let directplay_guid: [u8; 16] = *reader.read_bytes_const().context("reading GUID")?;
let game_name: String = reader.read_gm_string()?;
let version = GMVersion::deserialize(reader)?;
let default_window_width = reader.read_u32()?;
let default_window_height = reader.read_u32()?;
let flags_raw = reader.read_u32()?;
let flags = Flags::parse(flags_raw);
let license_crc32 = reader.read_u32()?;
let license_md5: [u8; 16] = *reader.read_bytes_const().context("reading license (MD5)")?;
let creation_timestamp = reader.read_i64()?;
#[cfg(feature = "game-creation-timestamp")]
let creation_timestamp: DateTime<Utc> =
DateTime::from_timestamp_secs(creation_timestamp)
.ok_or_else(|| format!("Invalid Creation Timestamp {creation_timestamp}"))?;
let display_name: String = reader.read_gm_string()?;
let active_targets = reader.read_u64()?;
reader.assert_int(active_targets, 0, "Active Targets")?;
let function_classifications = FunctionClassifications::deserialize(reader)?;
let steam_appid = reader.read_i32()?;
let debugger_port: u32 = reader.deserialize_if_wad_version(14)?.unwrap_or(0);
let room_order: Vec<GMRef<GMRoom>> = reader.read_simple_list()?;
let mut general_info = Self {
is_debugger_disabled,
wad_version,
unknown_value,
game_file_name,
config,
last_object_id,
last_tile_id,
game_id,
directplay_guid,
game_name,
version,
default_window_width,
default_window_height,
flags,
license_crc32,
license_md5,
creation_timestamp,
display_name,
function_classifications,
steam_appid,
debugger_port,
room_order,
gms2_data: None,
exists: true,
};
if general_info.version.major >= 2 {
let gms2 = general_info.read_gms2_data(reader)?;
general_info.gms2_data = Some(gms2);
}
Ok(general_info)
}
fn serialize(&self, builder: &mut DataBuilder) -> Result<()> {
if !self.exists {
bail!("General info is a required chunk (internal error)");
}
builder.write_u8(self.is_debugger_disabled.into());
builder.write_u8(self.wad_version);
builder.write_u16(self.unknown_value);
builder.write_gm_string(&self.game_file_name);
builder.write_gm_string(&self.config);
builder.write_u32(self.last_object_id);
builder.write_u32(self.last_tile_id);
builder.write_u32(self.game_id);
builder.write_bytes(&self.directplay_guid);
builder.write_gm_string(&self.game_name);
let version = if self.version.major == 1 {
&self.version
} else {
&GMVersion::GMS2
};
version.serialize(builder)?;
builder.write_u32(self.default_window_width);
builder.write_u32(self.default_window_height);
self.flags.serialize(builder)?;
builder.write_u32(self.license_crc32);
builder.write_bytes(&self.license_md5);
builder.write_i64(self.timestamp());
builder.write_gm_string(&self.display_name);
builder.write_u64(0); self.function_classifications.serialize(builder)?;
builder.write_i32(self.steam_appid);
if self.wad_version >= 14 {
builder.write_u32(self.debugger_port);
}
builder.write_simple_list(&self.room_order)?;
if builder.is_version_at_least((2, 0)) {
self.write_gms2_data(builder)?;
}
Ok(())
}
}
impl Default for GMGeneralInfo {
#[allow(clippy::default_trait_access)] fn default() -> Self {
Self {
is_debugger_disabled: true,
wad_version: 17,
unknown_value: 0,
game_file_name: "NewLibGMGame".to_owned(),
config: "Default".to_owned(),
last_object_id: 100_000,
last_tile_id: 10_000_000,
game_id: 1337,
directplay_guid: [0u8; 16],
game_name: "NewLibGMGame".to_owned(),
version: GMVersion::default(),
default_window_width: 1337,
default_window_height: 1337,
flags: Flags::default(),
license_crc32: 69420,
license_md5: [69; 16],
creation_timestamp: Default::default(),
display_name: "New LibGM Game (default stub)".to_owned(),
function_classifications: FunctionClassifications::default(),
steam_appid: 0,
debugger_port: 0,
room_order: vec![],
gms2_data: Some(GMS2Data::default()),
exists: false,
}
}
}
impl fmt::Debug for GMGeneralInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GMGeneralInfo")
.field("is_debugger_disabled", &self.is_debugger_disabled)
.field("wad_version", &self.wad_version)
.field("game_file_name", &self.game_file_name)
.field("config", &self.config)
.field("last_object_id", &self.last_object_id)
.field("last_tile_id", &self.last_tile_id)
.field("game_id", &self.game_id)
.field("game_name", &self.game_name)
.field("version", &self.version)
.field("default_window_width", &self.default_window_width)
.field("default_window_height", &self.default_window_height)
.field("creation_timestamp", &self.creation_timestamp)
.field("display_name", &self.display_name)
.field("steam_appid", &self.steam_appid)
.field("debugger_port", &self.debugger_port)
.field("gms2_data", &self.gms2_data)
.field("exists", &self.exists)
.finish_non_exhaustive()
}
}
impl GMGeneralInfo {
#[must_use]
pub fn is_version_at_least(&self, req: impl Into<GMVersionReq>) -> bool {
self.version.is_version_at_least(req)
}
pub fn set_version_at_least(&mut self, req: impl Into<GMVersionReq>) -> Result<()> {
self.version.set_version_at_least(req)
}
pub fn set_version(&mut self, req: impl Into<GMVersionReq>) {
self.version.set_version(req);
}
#[must_use]
const fn timestamp(&self) -> i64 {
#[cfg(feature = "game-creation-timestamp")]
{
self.creation_timestamp.timestamp()
}
#[cfg(not(feature = "game-creation-timestamp"))]
{
self.creation_timestamp
}
}
}