srcdmp 0.1.0-alpha.1

Amazing code snapshot tool and library
Documentation
use crate::raw::error::SdmpError;

/// Codec ID for SDMP Generic 7 (three-tier compression).
pub const CODEC_SDMP7: u8 = 0x07;

/// Codec ID for SDMP Generic 8 (raw UTF-8 passthrough).
pub const CODEC_SDMP8: u8 = 0x08;

/// Codec ID for SDMP Compact 4 (Huffman-tree compression).
pub const CODEC_C4: u8 = 0x04;

/// Codec ID for SDMP Compact 2 (reserved for future use).
pub const CODEC_C2: u8 = 0x02;

/// The four-byte magic bytes that every `.srcdmp` file starts with.
pub const MAGIC: [u8; 4] = *b"SDMP";

/// The global file header for a `.srcdmp` archive.
///
/// Appears exactly once at the beginning of every archive and carries
/// the codec ID, format version, original uncompressed size, and file count.
///
/// # Wire Format
///
/// | Offset | Size | Field |
/// | :--- | :--- | :--- |
/// | 0 | 4 | Magic bytes (`SDMP`) |
/// | 4 | 1 | Codec ID |
/// | 5 | 1 | Format version |
/// | 6 | 4 | Original size (little-endian `u32`) |
/// | 10 | 4 | File count (little-endian `u32`) |
///
/// Total header size is 14 bytes.
///
/// # Examples
///
/// ```rust
/// use srcdmp::raw::header::{Header, CODEC_C4};
///
/// let h = Header {
///     codec: CODEC_C4,
///     version: 1,
///     original_size: 123456,
///     file_count: 34,
/// };
///
/// let bytes = h.write();
/// let decoded = Header::read(&bytes).unwrap();
/// assert_eq!(decoded.codec, CODEC_C4);
/// assert_eq!(decoded.file_count, 34);
/// ```
pub struct Header {
    /// Codec ID byte identifying the compression scheme.
    pub codec: u8,
    /// Format version number (currently always `1`).
    pub version: u8,
    /// Total uncompressed size of all file content in bytes.
    pub original_size: u32,
    /// Number of file entries in this archive.
    pub file_count: u32,
}

impl Header {
    /// Size of the serialized header in bytes (always 14).
    pub const SIZE: usize = 14;

    /// Deserialize a header from a byte slice.
    ///
    /// Validates the magic bytes and codec ID.
    ///
    /// # Errors
    ///
    /// Returns [`SdmpError::UnexpectedEof`] if fewer than [`SIZE`](Self::SIZE) bytes are
    /// available. Returns [`SdmpError::NotAnSdmpFile`] if the magic bytes
    /// are missing. Returns [`SdmpError::UnknownCodec`] if the codec byte
    /// is not recognized.
    pub fn read(bytes: &[u8]) -> Result<Self, SdmpError> {
        if bytes.len() < Self::SIZE {
            return Err(SdmpError::UnexpectedEof);
        }

        if bytes[0..4] != MAGIC {
            return Err(SdmpError::NotAnSdmpFile);
        }

        let codec = bytes[4];

        if ![CODEC_SDMP7, CODEC_SDMP8, CODEC_C4, CODEC_C2].contains(&codec) {
            return Err(SdmpError::UnknownCodec(codec));
        }

        let version = bytes[5];

        let original_size = u32::from_le_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]);
        let file_count = u32::from_le_bytes([bytes[10], bytes[11], bytes[12], bytes[13]]);

        Ok(Self {
            codec,
            version,
            original_size,
            file_count,
        })
    }

    /// Serialize this header into a 14-byte array.
    #[must_use]
    pub fn write(&self) -> [u8; Self::SIZE] {
        let mut bytes = [0u8; Self::SIZE];

        bytes[0..4].copy_from_slice(&MAGIC);

        bytes[4] = self.codec;
        bytes[5] = self.version;

        bytes[6..10].copy_from_slice(&self.original_size.to_le_bytes());
        bytes[10..14].copy_from_slice(&self.file_count.to_le_bytes());

        bytes
    }
}