1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
use std::collections::HashMap;
use crate::gml::instruction::VariableType;
use crate::prelude::*;
use crate::wad::data::Endianness;
use crate::wad::data::GMData;
use crate::wad::elements::string::StringPlaceholder;
use crate::wad::serialize::pointers::Pointer;
use crate::wad::version::GMVersionReq;
// The Default value should never be read.
// This can only happen if there are zero existent chunks, though.
#[derive(Debug, Clone, Default)]
pub struct LastChunk {
pub length_pos: u32,
pub padding_start_pos: u32,
}
#[derive(Debug)]
pub struct DataBuilder<'a> {
/// The [`GMData`] to serialize.
pub gm_data: &'a GMData,
/// The raw data being generated.
pub raw_data: Vec<u8>,
/// Pairs data positions of pointer placeholders with the memory address of
/// the GameMaker element they're pointing to.
pub(super) pointer_placeholder_positions: Vec<(u32, Pointer)>,
/// Maps memory addresses of GameMaker elements to their resolved data
/// position.
pub(super) pointer_resource_positions: HashMap<Pointer, u32>,
/// Tracks where each function is used throughout the game data.
/// Will be populated when code is built.
/// - Outer Vec: Indexed by Function index from
/// `gm_data.functions.functions`
/// - Inner Vec: List of written positions for each occurrence
pub function_occurrences: Vec<Vec<u32>>,
/// Tracks where each variable is used throughout the game data.
/// Will be populated when code is built.
/// - Outer Vec: Indexed by Variable index from
/// `gm_data.variables.variables`
/// - Inner Vec: List of `(written_position, variable_type)` tuples for each
/// occurrence
pub variable_occurrences: Vec<Vec<(u32, VariableType)>>,
pub string_placeholders: Vec<StringPlaceholder>,
pub last_chunk: LastChunk,
}
impl<'a> DataBuilder<'a> {
pub fn new(gm_data: &'a GMData) -> Self {
let approximated_size: usize = (f64::from(gm_data.meta.original_data_size) * 1.05) as usize;
Self {
gm_data,
raw_data: Vec::with_capacity(approximated_size),
pointer_placeholder_positions: Vec::new(),
pointer_resource_positions: HashMap::new(),
function_occurrences: vec![Vec::new(); gm_data.functions.len()],
variable_occurrences: vec![Vec::new(); gm_data.variables.len()],
string_placeholders: Vec::new(),
last_chunk: LastChunk::default(),
}
}
#[inline]
#[must_use]
pub fn finish(self) -> Vec<u8> {
self.raw_data
}
/// The current length (aka. "position") of the internal buffer.
#[inline]
pub const fn len(&self) -> u32 {
self.raw_data.len() as u32
}
#[inline]
#[must_use]
pub fn is_version_at_least(&self, req: impl Into<GMVersionReq>) -> bool {
self.gm_data.general_info.version.is_version_at_least(req)
}
#[inline]
#[must_use]
pub const fn wad_version(&self) -> u8 {
self.gm_data.general_info.wad_version
}
/// Pads the internal buffer with zero bytes until its length is aligned to
/// `alignment`.
///
/// This adds zero bytes until `self.len()` is a multiple of `alignment`.
#[inline]
pub fn align(&mut self, alignment: u32) {
while !self.len().is_multiple_of(alignment) {
self.write_u8(0);
}
}
/// Appends the given bytes to the internal data buffer.
#[inline]
pub fn write_bytes(&mut self, data: &[u8]) {
// It's really stupid how this function is a one-liner whereas
// reading bytes needs like 50 lines to be handled correctly...
self.raw_data.extend_from_slice(data);
}
/// Overwrites bytes at the given position in the internal data buffer.
///
/// Useful for patching data like lengths or offsets after serialization.
fn overwrite_bytes(&mut self, bytes: &[u8], position: u32) -> Result<()> {
let start = position as usize;
let end = start + bytes.len();
if let Some(mut_slice) = self.raw_data.get_mut(start..end) {
mut_slice.copy_from_slice(bytes);
Ok(())
} else {
Err(err!(
"Could not overwrite {} bytes at position {} in data with length {}; out of bounds",
bytes.len(),
position,
self.raw_data.len(),
))
}
}
/// Overwrites a 32-bit unsigned integer at the specified written data
/// `position`.
///
/// Useful for patching fixed-size numeric values like lengths or offsets
/// after serialization. For writing regular pointer lists normally, see
/// [`Self::write_pointer_list`].
pub fn overwrite_u32(&mut self, number: u32, position: u32) -> Result<()> {
let bytes: [u8; 4] = match self.gm_data.meta.endianness {
Endianness::Little => number.to_le_bytes(),
Endianness::Big => number.to_be_bytes(),
};
self.overwrite_bytes(&bytes, position).with_context(|| {
format!("overwriting 32-bit number {number} at target data position {position}")
})
}
/// Overwrites a 32-bit number at the specified data position with the
/// current data position (length). The data position is calculated via
/// `pointer_list_pos + (4 * element_index)` which is suitable for
/// overwriting pointer in a pointer list.
///
/// For overwriting other numbers, see [`Self::overwrite_u32`].
/// For writing regular pointer lists normally, see
/// [`Self::write_pointer_list`].
#[inline]
pub fn overwrite_pointer_with_cur_pos(
&mut self,
pointer_list_pos: u32,
element_index: usize,
) -> Result<()> {
let number: u32 = self.len();
let position: u32 = pointer_list_pos + 4 * element_index as u32;
self.overwrite_u32(number, position).with_context(|| {
format!(
"overwriting pointer for element index {element_index} of pointer list with start \
position {pointer_list_pos}"
)
})
}
/// Write a GameMaker boolean as a 32-bit integer.
/// - If `true`, write `1_i32`.
/// - If `false`, write `0_i32`.
#[inline]
pub fn write_bool32(&mut self, boolean: bool) {
self.write_i32(boolean.into());
}
/// Write an actual character string.
///
/// This should only be used for literal strings in the `STRG` chunk.
/// For writing regular GameMaker string references, see
/// [`Self::write_gm_string`].
#[inline]
pub fn write_literal_string(&mut self, string: &str) {
self.write_bytes(string.as_bytes());
}
}