Expand description
§Petroglyph Meg file library
MEGA files are a format used by Petroglyph for various games including Star Wars: Empire at War, Universe at War: Earth Assault, Guardians of Graxia, Rise of Immortals, Grey Goo, and Great War: Western Front.
This library provides tools for extracting their contents or authoring them. It is based on the documentation an research by Mike Lankamp found on modtools.petrolution.net
There are 3 different MEGA file versions, and the version used depends on the game. Based on the Petrolution Mod Tools Game List, the following versions are used for each of these games:
- Version 1:
- Star Wars: Empire at War
- Star Wars: Empire at War: Forces of Corruption
- Universe at War: Earth Assault
- Version 2:
- Guardians of Graxia
- Version 3:
- Rise of Immortals
- Grey Goo
- Great War: Western Front
The different versions are represented by types in the version module. There are two ways to
work with versions. If you know what version you need to work with at compile time, you can use
the explicit structs MegV1, MegV2, and
MegV3. If you need to dynamically support more than one MEGA file version,
you can use the enum MegVersion.
Note that this project has undergone very minimal testing, especially on the writer side. It definitely reads at least some of the MEGA files from Empire at War and Great War: Western Front, including encrypted ones, but I have not yet tested whether those games will accept MEGA files encoded by it.
§Reading
For reading, the mega file versions implement the ReadMegMega trait.
This provides methods to read the metadata from the MEGA file. The metadata in the file consists
of a list of FileEntrys which contain the file names and the files’
positions within the original MEGA file.
use petro_meg::reader::ReadMegMeta;
use petro_meg::version::MegV1;
let data: &[u8] = // ...
let files = MegV1.read_meg_meta(data).unwrap();read_meg_meta works on any type which implements read.
The FileEntry does not contain the actual file contents. To get the
contents, you have two options depending on what your file source is. If you have the complete
file contents in a slice, you can use range to get a range that
will slice to the file’s content.
let data: &[u8] = // ...
let files = MegV1.read_meg_meta(data).unwrap();
for file in files {
let content = &data[file.range()];
}If instead your data is a type which implements Seek, you can use
extract_from to seek to the start and get a reader which
will limit to just the contents.
let options = MegReadOptions::new();
let mut mega_file = // ...
let files = MegV1.read_meg_meta(&mut mega_file).unwrap();
for file in files {
let mut content_reader = file.extract_from(&mut mega_file, &options).unwrap();
let mut content_bytes = Vec::with_capacity(file.size() as usize);
content_reader.read_to_end(&mut content_bytes).unwrap();
}To extract Version 3 MEGA files which are encrypted, you must provide a
MegReadOptions with the appropriate Key. A key
consists of 16 bytes, and depends on the game you are extracting from. I do not provide keys,
however you can find keys for some petroglyph games on the bottom of the Petrolution Mod Tools
MEGA File page.
§Format Guessing
There is no clear flag in the MEGA file format for which version it is, however it is possible
to guess, potentially somewhat unreliably, based on the header format. To guess formats, we
provide the special version GuessVersion. GuessVersion only works
for reads. Additionally, Option<MegVersion> implements ReadMegMeta,
using the provided version when the option is Some or GuessVersion when it is None.
§Writing
For writing files, the various version types (excluding GuessVersion) implement the
BuildMeg trait, which provides a method to construct a
MegBuilder for that version. The
BuildMeg::builder is generic on the type of files to put in the
builder.
use petro_meg::writer::BuildMeg;
use petro_meg::version::MegV1;
let builder = MegV1.builder::<File>();You can put files into the builder with insert. This takes a
MegPathBuf to define the path that the file is inserted under. Unlike
PathBuf, MegPathBuf is validated. It enforces that the path is a
relative path with no double slashes or relative elements like ... MegPathBuf and the
corresponding MegPath also implement case-insensitive comparisons and treat
/ and \ the same, even on Unix-like systems. Note that when the MEGA file is built, all
paths will be converted to ASCII uppercase and path separators will be normalized to \.
let mut builder = MegV1.builder();
let path = MegPath::from_str("some/file.txt").unwrap();
let data: &[u8] = // ...
builder.insert(path.to_owned(), data);The files inserted into the MegBuilder must implement the FileContent
trait in addition to Read. FileContent provides a
file_len method which allows the builder to determine the
size of the file while computing the MEGA file’s metadata. This allows it to write the output
file sequentially, rather than needing to pad the file for the MEGA file header, write the
contents, then seek back to the beginning to fill in the header. FileContent is currently
implemented for File, &[u8], Cursor and a few other
types, such as Box<T> where T: FileCursor.
Once you have inserted all the files you want into the MegBuilder, you can write it to an
output using MegBuilder::build.
let mut out = // ...
builder.build(&mut out).unwrap();For V3 MEGA files, MegBuilder provides a
set_encryption method which can be used to set an
encryption key to use to encrypt the MEGA file’s contents.