libgm 0.5.1

A tool for modding, unpacking and decompiling GameMaker games
Documentation
use crate::gml::GMCode;
use crate::prelude::*;
use crate::wad::elements::validate_identifier;

impl GMNamedElement for GMCode {
    fn name(&self) -> &String {
        &self.name
    }

    fn name_mut(&mut self) -> &mut String {
        &mut self.name
    }

    fn validate_name(&self) -> Result<()> {
        let name: &str = &self.name;
        validate(name).with_context(|| format!("strictly validating code entry name {name:?}"))
    }
}

fn validate(mut name: &str) -> Result<()> {
    name = name
        .strip_prefix("gml_")
        .ok_or("Code entry name does not start with \"gml_\"")?;

    let split = name
        .split_once('_')
        .ok_or("Code entry name does not have a kind`:")?;
    let kind: &str = split.0;
    name = split.1;

    match kind {
        "Script" => {}
        "GlobalScript" => {
            // Some weird autogenerated(?) init scripts.
            if name.chars().all(|c| c.is_ascii_digit()) {
                return Ok(());
            }
        }
        "Room" => {
            let event_kind: &str;
            (name, event_kind) = name
                .rsplit_once('_')
                .ok_or("Missing event kind in Room code entry name")?;
            validate_room_event_kind(event_kind)?;
        }
        "RoomCC" => {
            let e = "RoomCC code entry name has too few parts (not enough underscores to be valid)";
            let (instance_id, event_kind) = last_three_parts(&mut name).ok_or(e)?;
            validate_room_event_kind(event_kind)?;
            instance_id
                .parse::<u16>()
                .context_src("parsing Instance ID in RoomCC code entry")?;
        }
        "Object" => {
            // Collision event has an object name as its subtype.
            // This is impossible to demangle, so just do a simple identifier validation and
            // return.
            if name.contains("_Collision_") {
                return validate_identifier(name);
            }

            let e = "Object code entry name has too few parts (not enough underscores to be valid)";
            let (event_type, event_subtype) = last_three_parts(&mut name).ok_or(e)?;
            let event_subtype: u32 = event_subtype
                .parse::<u32>()
                .context_src("parsing Event subtype in Object code entry")?;
            validate_event(event_type, event_subtype)?;
        }
        _ => bail!("Invalid code entry kind {kind:?}"),
    }

    // Lastly, validate the actual core (script/object/room) name.
    validate_identifier(name)
}

fn last_three_parts<'a>(string: &mut &'a str) -> Option<(&'a str, &'a str)> {
    let mut iter = string.rsplitn(3, '_');
    // `rsplit*` methods yield in reverse order
    let part3: &str = iter.next()?;
    let part2: &str = iter.next()?;
    let part1: &str = iter.next()?;
    *string = part1;
    Some((part2, part3))
}

fn validate_room_event_kind(event_kind: &str) -> Result<()> {
    match event_kind {
        "Create" | "PreCreate" => Ok(()),
        _ => Err(err!("Invalid Room event kind {event_kind:?}")),
    }
}

fn validate_event(event_type: &str, subtype: u32) -> Result<()> {
    use crate::wad::elements::game_object::event::EventSubtype;
    use crate::wad::elements::game_object::event::subtype::Alarm;
    use crate::wad::elements::game_object::event::subtype::Draw;
    use crate::wad::elements::game_object::event::subtype::Gesture;
    use crate::wad::elements::game_object::event::subtype::Key;
    use crate::wad::elements::game_object::event::subtype::Mouse;
    use crate::wad::elements::game_object::event::subtype::Other;
    use crate::wad::elements::game_object::event::subtype::Step;

    let error: Option<Error> = match event_type {
        "Create" => assert_zero(subtype),
        "Destroy" => assert_zero(subtype),
        "Alarm" => Alarm::parse(subtype).err(),
        "Step" => Step::parse(subtype).err(),
        // "Collision" => {},  // handled separately
        "Keyboard" => Key::parse(subtype).err(),
        "Mouse" => Mouse::parse(subtype).err(),
        "Other" => Other::parse(subtype).err(),
        "Draw" => Draw::parse(subtype).err(),
        "KeyPress" => Key::parse(subtype).err(),
        "KeyRelease" => Key::parse(subtype).err(),
        "Trigger" => assert_zero(subtype),
        "CleanUp" => assert_zero(subtype),
        "Gesture" => Gesture::parse(subtype).err(),
        "PreCreate" => assert_zero(subtype),
        _ => bail!("Invalid event type {event_type:?}"),
    };

    error.map_or(Ok(()), Err)
}

fn assert_zero(subtype: u32) -> Option<Error> {
    if subtype == 0 {
        None
    } else {
        Some(err!(
            "Expected event subtype 0 for this event type, got {subtype}"
        ))
    }
}