Skip to main content

petro_meg/
lib.rs

1//! # Petroglyph Meg file library
2//!
3//! MEGA files are a format used by Petroglyph for various games including Star Wars: Empire at War,
4//! Universe at War: Earth Assault, Guardians of Graxia, Rise of Immortals, Grey Goo, and Great War:
5//! Western Front.
6//!
7//! This library provides tools for extracting their contents or authoring them. It is based on the
8//! documentation an research by Mike Lankamp found on
9//! [modtools.petrolution.net](https://modtools.petrolution.net/docs/MegFileFormat)
10//!
11//! There are 3 different MEGA file versions, and the version used depends on the game. Based on the
12//! [Petrolution Mod Tools Game List](https://modtools.petrolution.net/docs/Games), the following
13//! versions are used for each of these games:
14//!
15//! *   Version 1:
16//!     *   Star Wars: Empire at War
17//!     *   Star Wars: Empire at War: Forces of Corruption
18//!     *   Universe at War: Earth Assault
19//! *   Version 2:
20//!     *   Guardians of Graxia
21//! *   Version 3:
22//!     *   Rise of Immortals
23//!     *   Grey Goo
24//!     *   Great War: Western Front
25//!
26//! The different versions are represented by types in the [`version`] module. There are two ways to
27//! work with versions. If you know what version you need to work with at compile time, you can use
28//! the explicit structs [`MegV1`][version::MegV1], [`MegV2`][version::MegV2], and
29//! [`MegV3`][version::MegV3]. If you need to dynamically support more than one MEGA file version,
30//! you can use the enum [`MegVersion`][version::MegVersion].
31//!
32//! Note that this project has undergone very minimal testing, especially on the writer side. It
33//! definitely reads at least some of the MEGA files from Empire at War and Great War: Western
34//! Front, including encrypted ones, but I have not yet tested whether those games will accept MEGA
35//! files encoded by it.
36//!
37//! ## Reading
38//!
39//! For reading, the mega file versions implement the [`ReadMegMega`][reader::ReadMegMeta] trait.
40//! This provides methods to read the metadata from the MEGA file. The metadata in the file consists
41//! of a list of [`FileEntry`s][reader::FileEntry] which contain the file names and the files'
42//! positions within the original MEGA file.
43//!
44//! ```
45//! use petro_meg::reader::ReadMegMeta;
46//! use petro_meg::version::MegV1;
47//! let data: &[u8] = // ...
48//! #    include_bytes!("example.meg");
49//! let files = MegV1.read_meg_meta(data).unwrap();
50//! ```
51//!
52//! `read_meg_meta` works on any type which implements `read`.
53//!
54//! The [`FileEntry`][reader::FileEntry] does not contain the actual file contents. To get the
55//! contents, you have two options depending on what your file source is. If you have the complete
56//! file contents in a slice, you can use [`range`][reader::FileEntry::range] to get a range that
57//! will slice to the file's content.
58//!
59//! ```
60//! # use petro_meg::reader::ReadMegMeta;
61//! # use petro_meg::version::MegV1;
62//! let data: &[u8] = // ...
63//! #    include_bytes!("example.meg");
64//! let files = MegV1.read_meg_meta(data).unwrap();
65//! for file in files {
66//!     let content = &data[file.range()];
67//! }
68//! ```
69//!
70//! If instead your data is a type which implements Seek, you can use
71//! [`extract_from`][reader::FileEntry::extract_from] to seek to the start and get a reader which
72//! will limit to just the contents.
73//!
74//! ```
75//! # use std::io::{Read, Cursor};
76//! # use petro_meg::reader::{ReadMegMeta, MegReadOptions};
77//! # use petro_meg::version::MegV1;
78//! let options = MegReadOptions::new();
79//! let mut mega_file = // ...
80//! #     Cursor::new(include_bytes!("example.meg"));
81//! let files = MegV1.read_meg_meta(&mut mega_file).unwrap();
82//! for file in files {
83//!     let mut content_reader = file.extract_from(&mut mega_file, &options).unwrap();
84//!     let mut content_bytes = Vec::with_capacity(file.size() as usize);
85//!     content_reader.read_to_end(&mut content_bytes).unwrap();
86//! }
87//! ```
88//!
89//! To extract Version 3 MEGA files which are encrypted, you must provide a
90//! [`MegReadOptions`][reader::MegReadOptions] with the appropriate [`Key`][crypto::Key]. A key
91//! consists of 16 bytes, and depends on the game you are extracting from. I do not provide keys,
92//! however you can find keys for some petroglyph games on the bottom of the [Petrolution Mod Tools
93//! MEGA File page](https://modtools.petrolution.net/docs/MegFileFormat).
94//!
95//! ### Format Guessing
96//!
97//! There is no clear flag in the MEGA file format for which version it is, however it is possible
98//! to guess, potentially somewhat unreliably, based on the header format. To guess formats, we
99//! provide the special version [`GuessVersion`][version::GuessVersion]. `GuessVersion` only works
100//! for reads. Additionally, `Option<MegVersion>` implements [`ReadMegMeta`][reader::ReadMegMeta],
101//! using the provided version when the option is `Some` or `GuessVersion` when it is `None`.
102//!
103//! ## Writing
104//!
105//! For writing files, the various [`version`] types (excluding `GuessVersion`) implement the
106//! [`BuildMeg`][writer::BuildMeg] trait, which provides a method to construct a
107//! [`MegBuilder`][writer::MegBuilder] for that version. The
108//! [`BuildMeg::builder`][writer::BuildMeg::builder] is generic on the type of files to put in the
109//! builder.
110//!
111//! ```
112//! # use std::fs::File;
113//! use petro_meg::writer::BuildMeg;
114//! use petro_meg::version::MegV1;
115//! let builder = MegV1.builder::<File>();
116//! ```
117//!
118//! You can put files into the builder with [`insert`][writer::MegBuilder::insert]. This takes a
119//! [`MegPathBuf`][path::MegPathBuf] to define the path that the file is inserted under. Unlike
120//! [`PathBuf`][std::path::PathBuf], `MegPathBuf` is validated. It enforces that the path is a
121//! relative path with no double slashes or relative elements like `..`. `MegPathBuf` and the
122//! corresponding [`MegPath`][path::MegPath] also implement case-insensitive comparisons and treat
123//! `/` and `\` the same, even on Unix-like systems. Note that when the MEGA file is built, all
124//! paths will be converted to ASCII uppercase and path separators will be normalized to `\`.
125//!
126//! ```
127//! # use std::fs::File;
128//! # use petro_meg::writer::BuildMeg;
129//! # use petro_meg::version::MegV1;
130//! # use petro_meg::path::MegPath;
131//! let mut builder = MegV1.builder();
132//! let path = MegPath::from_str("some/file.txt").unwrap();
133//! let data: &[u8] = // ...
134//! #   b"Some File Contents";
135//! builder.insert(path.to_owned(), data);
136//! ```
137//!
138//! The files inserted into the `MegBuilder` must implement the [`FileContent`][writer::FileContent]
139//! trait in addition to [`Read`][std::io::Read]. `FileContent` provides a
140//! [`file_len`][writer::FileContent::file_len] method which allows the builder to determine the
141//! size of the file while computing the MEGA file's metadata. This allows it to write the output
142//! file sequentially, rather than needing to pad the file for the MEGA file header, write the
143//! contents, then seek back to the beginning to fill in the header. `FileContent` is currently
144//! implemented for [`File`][std::fs::File], `&[u8]`, [`Cursor`][std::io::Cursor] and a few other
145//! types, such as `Box<T>` where `T: FileCursor`.
146//!
147//! Once you have inserted all the files you want into the `MegBuilder`, you can write it to an
148//! output using [`MegBuilder::build`][writer::MegBuilder::build].
149//!
150//! ```
151//! # use std::fs::File;
152//! # use petro_meg::writer::BuildMeg;
153//! # use petro_meg::version::MegV1;
154//! # use petro_meg::path::MegPath;
155//! # let mut builder = MegV1.builder();
156//! # let path = MegPath::from_str("some/file.txt").unwrap();
157//! # let data: &[u8] = b"Some File Contents";
158//! # builder.insert(path.to_owned(), data);
159//! let mut out = // ...
160//! #   Vec::new();
161//! builder.build(&mut out).unwrap();
162//! ```
163//!
164//! For V3 MEGA files, `MegBuilder` provides a
165//! [`set_encryption`][writer::MegBuilder::set_encryption] method which can be used to set an
166//! encryption key to use to encrypt the MEGA file's contents.
167
168pub mod crypto;
169pub mod path;
170pub mod reader;
171pub mod version;
172pub mod writer;