libgm 0.7.0

A tool for unpacking and modding GameMaker games
Documentation
// SPDX-License-Identifier: GPL-3.0-only
use crate::prelude::*;
use crate::util::assert;
use crate::wad::build::builder::DataBuilder;
use crate::wad::chunk::gm_list_chunk;
use crate::wad::elem::GMElement;
use crate::wad::parse::reader::DataReader;

const ALIGNMENT: u32 = 4;

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Strings {
    pub elems: Vec<String>,
    pub align: bool,
    pub exists: bool,
}

gm_list_chunk!(STRG, Strings, String, direct);

impl GMElement for Strings {
    // be sure not to use `reader.general_info` here, as it is not initialized yet!
    // this includes methods like `deserialize_if_gm_version`.
    fn deserialize(reader: &mut DataReader) -> Result<Self> {
        let pointers: Vec<u32> = reader.read_simple_list()?;
        let align: bool = pointers.iter().all(|&p| p.is_multiple_of(ALIGNMENT));

        let mut elems: Vec<String> = Vec::with_capacity(pointers.len());

        for (i, pointer) in pointers.into_iter().enumerate() {
            if align {
                reader.align(ALIGNMENT)?;
            }
            reader.assert_pos(pointer, "String")?;
            reader
                .string_occurrences
                .insert(pointer + 4, GMRef::from(i));

            let string_length = reader.read_u32()?;
            let string: String = reader.read_literal_string(string_length)?;
            let byte = reader.read_u8()?;
            assert::int(byte, 0, "Null terminator byte after string")?;
            elems.push(string);
        }

        reader.align(0x80)?;
        Ok(Self { elems, align, exists: true })
    }

    fn serialize(&self, builder: &mut DataBuilder) -> Result<()> {
        let count = self.elems.len();

        builder.write_usize(count)?;
        let pointer_list_pos = builder.pos();
        for _ in 0..count {
            builder.write_u32(0xDEAD_C0DE);
        }

        for (idx, string) in self.elems.iter().enumerate() {
            if self.align {
                builder.align(ALIGNMENT);
            }
            builder.overwrite_pointer_with_cur_pos(pointer_list_pos, idx)?;
            builder.write_u32(string.len() as u32);
            builder.resolve_pointer(string)?;
            builder.write_bytes(string.as_bytes());
            builder.write_u8(0);
        }

        builder.align(0x80);
        Ok(())
    }
}

impl Strings {
    pub fn find(&self, string: &str) -> Result<GMRef<String>> {
        for (gm_ref, str) in self.element_refs() {
            if str == string {
                return Ok(gm_ref);
            }
        }
        Err(err!("Could not find existing string {string:?}"))
    }

    #[inline]
    pub fn make(&mut self, string: &str) -> GMRef<String> {
        for (gm_ref, str) in self.element_refs() {
            if str == string {
                return gm_ref;
            }
        }
        self.make_new(string.to_owned())
    }

    #[inline]
    pub fn make_new(&mut self, string: String) -> GMRef<String> {
        self.elems.push(string);
        GMRef::from(self.len() - 1)
    }
}