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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! # What are pointers
//! When building GameMaker data files, you frequently need to write "pointers".
//! "Pointer" in the context of GameMaker internals: a 32-bit integer that
//! indicates the absolute data position at which a resource is located.
//! For example, strings are (almost) always stored as pointers, allowing the
//! runner to do a simple addition to get to the UTF-8 string:
//! `data_start_mem_address + pointer = string_mem_address`.
//!
//! # Why placeholders are needed when building data
//! GameMaker chunks and elements are (mostly) written in an arbitrary order.
//! Any element in any chunk can refer to any other element in any chunk.
//! They can refer to elements previously declared in the file or to elements
//! that are "not yet" declared. They can even refer to themselves!
//! It's practically impossible to predict where an element (by ID)
//! will be written to.
//! Placeholders solve this issue:
//! Instead of writing the resource data position immediately,
//! you just write a placeholder/stub (`0xDEADC0DE`) for now.
//! The pointer placeholder is remembered in
//! `DataBuilder.pointer_placeholder_positions` with an associated **memory
//! address**. Then, when an element (that may be pointed to) is written,
//! you store the resolved data position of that element in
//! `DataBuilder.pointer_resource_positions`. (Note: this may happen before or
//! after any placeholder to this element was written.) At the very end, when
//! everything has been built, you can match the pointer placeholders to
//! their resolved positions by comparing (hashing) element memory addresses.
//! The `0xDEADC0DE` bytes are then overwritten with the actual resource
//! position.
//!
//!
//! # Why memory addresses
//! It's the simplest and most maintainable way
//! of keep track of which GameMaker element is which.
//!
//! You could use an enum instead that keeps track of element type and index.
//! I actually used this previously before I switched to memory addresses!
//! This enum sucks for multiple reasons though:
//! * Abstraction: when I rewrote this library with proper traits (`GMElement`),
//! I lost context of GameMaker lists; the index is no longer known when
//! (de)serializing a list element.
//! * Maintainability: Every pointer somewhere down the line needs an enum
//! variant with N stacked indices. etc.
//! * Performance: Using memory addresses is faster than these weird enums; it's
//! literally just `lea ebx, [eax+FIELD_OFFSET]` in x86.
//!
//! # Drawbacks / Things to note
//! These memory addresses seem unstable, right?
//! And they are, if under the wrong conditions.
//! Using memory addresses as an identifier for GameMaker elements requires:
//! * The ID should always be the same for the same element instance, while the
//! serialization is in progress.
//! * The ID should be unique; no other elements (that can be pointed to) should
//! ever get the same ID.
//!
//! # Why this is still sound
//! The way I use memory addresses fulfils these requirements:
//! * Memory address stays the same, as long as the struct is not moved or
//! reallocated.
//! * Nothing ever gets moved or reallocated, because [`GMData`] is borrowed
//! immutably: `&gm_data`. This disables moving (taking ownership) and
//! prevents reallocation, for example because of vector pushes.
//! * Memory addresses are unique for each struct field, as long as their size
//! is greater than zero (ZSTs are not used anywhere in [`GMData`]).
//!
//! One key part is missing here:
//! You could use both a struct and a field of struct as a pointer placeholder.
//! If you use both, their addresses could be the same:
//! `struct_mem_address + field_offset = field_mem_address` could backfire if
//! `offset` is zero. You need to be careful here: Rust does not guarantee
//! struct layout by default and may reorder fields to optimize space.
//!
//! If you encounter a struct that is used as a pointer placeholder and one of
//! its fields is too:
//! 1. Annotate the struct with `#[repr(C)]`
//! 2. Make sure that the pointer placeholder field is not the first one in
//! definition
//!
//! This prevents address collisions.
use HashMap;
use fmt;
use crate*;
use crateStopwatch;
use cratetypename;
use crateDataBuilder;