gma_lite/
lib.rs

1//! Minimal GMA Library.
2//!
3//! Format:
4//! - "GMAD" header (4 bytes)
5//! - VERSION (int8) == 3
6//! - steam_id64 (little-endian i64) [ignored]
7//! - timestamp (little-endian u64) [ignored]
8//! - required content (u8 = 0) [ignored]
9//! - addon name (C string)
10//! - addon description (C string)
11//! - addon author (C string)
12//! - addon version (little-endian i32) [ignored]
13//! - Repeated file metadata entries until idx == 0:
14//!     * idx (u32, 1-based; 0 terminates the list)
15//!     * name (C string)
16//!     * size (i64)
17//!     * crc32 (u32) [ignored]
18//! - File contents, concatenated in metadata order
19//! - trailing u32 zero
20//!
21
22use std::fmt;
23use std::io::{self};
24
25/// Magic header for GMA files.
26pub const HEADER: &[u8; 4] = b"GMAD";
27
28/// File format version.
29pub const VERSION: i8 = 3;
30
31mod reader;
32pub use reader::read;
33
34mod builder;
35pub use builder::Builder;
36
37/// One entry (file) contained in a GMA.
38#[derive(Clone, Debug, PartialEq, Eq, Default)]
39pub struct GMAFile {
40    pub name: String,
41    pub content: Vec<u8>,
42    pub size: i64,
43}
44
45/// Errors that can occur while reading a GMA.
46#[derive(Debug)]
47pub enum GmaError {
48    Io(io::Error),
49    InvalidHeader([u8; 4]),
50    InvalidVersion(i8),
51    MissingNullTerminator, // for C-strings
52    SizeOutOfRange(i64),
53    TrailingMarkerMismatch(u32),
54}
55
56impl fmt::Display for GmaError {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            GmaError::Io(e) => write!(f, "io error: {e}"),
60            GmaError::InvalidHeader(got) => {
61                write!(f, "invalid header: {:?}", String::from_utf8_lossy(got))
62            }
63            GmaError::InvalidVersion(v) => write!(f, "invalid version: {v}"),
64            GmaError::MissingNullTerminator => write!(f, "missing null terminator in C string"),
65            GmaError::SizeOutOfRange(sz) => write!(f, "negative or invalid size: {sz}"),
66            GmaError::TrailingMarkerMismatch(v) => {
67                write!(f, "expected trailing 0 u32 marker, got {v}")
68            }
69        }
70    }
71}
72
73impl std::error::Error for GmaError {
74    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
75        if let GmaError::Io(e) = self {
76            Some(e)
77        } else {
78            None
79        }
80    }
81}
82
83impl From<io::Error> for GmaError {
84    fn from(e: io::Error) -> Self {
85        GmaError::Io(e)
86    }
87}