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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// SPDX-License-Identifier: GPL-3.0-only
//! A data parsing and building library for GameMaker data files (`data.win` / `game.unx`).
//!
//! This library provides structs and functions to
//! handle GameMaker game assets in a meaningful way.
//!
//! It provides a powerful API to the data file, which yields you full control of
//! GameMaker's internals while still providing you with abstractions to handle game assets conveniently.
//!
//! LibGM does not free you from all redundancies found in the data file format.
//! It does not aim to restore a data file into a classic GameMaker project structure.
//! This tradeoff makes parsing and building faster, allows you to view and edit slightly malformed
//! data files, and stays as close to the original data file layout when serializing.
//! Perhaps there can be a high-level crate in the future that abstracts on this library.
//!
//! ## Usage
//! For most purposes, using the [`parse_file`] and [`build_file`] functions is enough.
//! If you need more control, you can use [`parse_bytes`], [`build_bytes`] or [`ParsingOptions`].
//!
//! It is recommended to import the prelude so important types and traits are available to use:
//! ```
//! use libgm::prelude::*;
//! ```
//!
//! This is an example of how to open a data file stored on disk
//! and extract some basic information:
//! ```no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use libgm::prelude::*;
//!
//! let path = "C:/Program Files (x86)/Steam/steamapps/common/Undertale/data.win";
//! let data: GMData = libgm::wad::parse_file(path)?;
//!
//! let name: &str = data.strings.by_ref(data.general_info.display_name)?;
//! let creation: chrono::DateTime<chrono::Utc> = data.general_info.creation_timestamp;
//! println!("Opened {name}!");
//! println!("Game was created at {creation:?}.");
//! println!("This data file has {} sprites.", data.sprites.len());
//! # Ok(()) }
//! ```
//!
//! Modifiying parts of the game:
//! ```no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use libgm::prelude::*;
//! use libgm::wad::elem::game_object::GameObject;
//! use libgm::wad::elem::sprite::Sprite;
//!
//! let mut data: GMData = libgm::wad::parse_file("./game.unx")?;
//! let object: &mut GameObject = data
//! .game_objects
//! .by_name_mut("obj_mysteryman", &data.strings)?;
//! object.solid = true;
//! object.depth = 66666;
//!
//! let sprite: &Sprite = data.sprites.by_ref(object.sprite)?;
//! let w: u32 = sprite.width;
//! let h: u32 = sprite.width;
//! println!("Mysteryman's sprite dimensions are {w}x{h} pixels");
//!
//! libgm::wad::build_file(&data, "./game_modded.unx")?;
//! # Ok(()) }
//! ```
//!
//! More complicated parsing for data files stored in memory:
//! ```no_run
//! # fn some_sophisticated_source() -> &'static [u8] { &[] }
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use libgm::prelude::*;
//! use libgm::wad::elem::audio::Audio;
//! use libgm::wad::elem::sound::Flags;
//! use libgm::wad::parse::ParsingOptions;
//! let raw_data: &[u8] = some_sophisticated_source();
//! let parser = ParsingOptions::new()
//! .verify_constants(false)
//! .verify_alignment(true);
//! let gm_data: GMData = parser.parse_bytes(raw_data)?;
//! std::fs::create_dir_all("exported_sounds")?;
//!
//! for sound in gm_data.sounds.elements() {
//! if sound.flags.contains(Flags::EMBEDDED) {
//! let name = gm_data.strings.by_ref(sound.name)?;
//! let audio: &Audio = gm_data.audios.by_ref(sound.audio)?;
//! let path = format!("exported_sounds/{name}.wav");
//! std::fs::write(path, &audio.data)?;
//! }
//! }
//! # Ok(()) }
//! ```
//!
//! For more information on the GameMaker specifics, check out the [`wad`] module.
//!
//! ## Disclaimer
//! This library is mainly tested against different Undertale and Deltarune versions.
//! Other games may encounter some issues.
//! Please report them to the attached Codeberg repository.
//!
//! LibGM is designed to roundtrip: Parsing a data file and then building it
//! without any modifications should produce the same exact output file.
//! If this assertion fails for some game, you can report this issue as well.
//!
//! If you have any questions or concerns about my code
//! or documentation, please contact me via either:
//! - Discord DM: `@biotomate.de`
//! - [Codeberg Issue](https://codeberg.org/BioTomateDE/LibGM/issues/new)
//! - [Email](mailto:biotomatede@proton.me?Subject=LibGM%20Question)
//!
//! ## Panicking
//! This library *should* never panic.
//! All malformed data files are caught into LibGM's custom error type.
//! However, since this library is not entirely mature yet, there might still be a few
//! bugs. For GUI applications, I would definitely recommend to enable the
//! `catch-panic` crate feature (which is enabled by default anyway).
//!
//! ## Missing features
//! The following features are not yet supported by LibGM:
//! - Special Vector Sprites
//! - Only partial pre WAD version 15 support (pre 2016)
//! - Only partial/untested big endian support
//!
//! ## Breaking changes
//! Some things in this library are **not** considered "breaking changes" and
//! may be modified in SemVer patch updates. These could bring unwanted change
//! of behavior to your program if you don't have a `Cargo.lock` set/commited.
//! Some of these things include:
//! - All log messages (using the [`log`](https://crates.io/crates/log) crate), including:
//! - Timing
//! - Code Origin/Location
//! - Message string
//! - All error contents:
//! - Error message string
//! - Context chain
//! - All structs and enums marked with `#[non_exhaustive]`
//! - Implementing traits like `GMChunk` (These traits are only meant for
//! writing generic code, not for implementing it for your own types)
//!
//! There might be some other struct fields or type names
//! with docstrings saying "this may change in the future".
//! These changes will still require a SemVer minor increment, though.
//! In other words, they are definitely version-safe to use,
//! but they might be renamed or reworked soon.
//!
//! ## Credits
//! This project is effectively a Rust port of
//! [UndertaleModTool](https://github.com/UnderminersTeam/UndertaleModTool).
//! Version detection code, some element docstrings and other parts are taken from there.
//! Huge shoutout to the Underminers Team!
// Activate all lint groups.
// Pedantic is really strong, so many lints will be whitelisted (with a reason).
//
//
// Const assertion for soundness
const _: = assert!;
use ;
// Private modules
// Public modules
// Convenience re-exports
pub use Error;
pub use Result;
// === Some TODOs for the entire library ===
//
// When Rust finally drops Macros 2.0:
// * Migrate all `macro_rules!` to `macro`s and remove exporting from crate root.
// Reference: https://github.com/rust-lang/rust/issues/39412
//
// When most traits (`Into`, `TryInto`, `Iterator`, `PartialEq`) are const-stable:
// * Clean up all `const-hack`s