Skip to main content

sga/entires/
file.rs

1use std::io::{BufRead, Read};
2
3use sga_macros::read_field;
4use thiserror::Error;
5
6#[derive(Error, Debug)]
7pub enum SgaFileEntryParseError {
8    #[error("Failed to parse number `{0}`")]
9    FailedToParseNumber(String),
10    #[error("Failed to read byte value `{0}`")]
11    FailedToParseByte(String),
12    #[error("Failed to parse storage type `{0}`")]
13    FailedToParseStorageType(String),
14    #[error("Failed to parse verification type `{0}`")]
15    FailedToParseVerificationType(String),
16}
17
18/// Describes how a file is verified when it's loaded.
19#[derive(Debug, Clone)]
20pub enum FileVerificationType {
21    /// No verification.
22    None,
23
24    /// CRC verification.
25    CRC,
26
27    /// CRC verification for blocks.
28    CRCBlocks,
29
30    /// MD5 verification for blocks.
31    MD5Blocks,
32
33    /// SHA1 verification for blocks.
34    SHA1Blocks,
35}
36
37impl FileVerificationType {
38    /// Parses a byte into a `FileVerificationType`.
39    pub fn from_u8(value: u8) -> Result<Self, String> {
40        match value {
41            0 => Ok(Self::None),
42            1 => Ok(Self::CRC),
43            2 => Ok(Self::CRCBlocks),
44            3 => Ok(Self::MD5Blocks),
45            4 => Ok(Self::SHA1Blocks),
46            _ => Err("Invalid file verification type".into()),
47        }
48    }
49
50    /// Converts the `FileVerificationType` into its corresponding byte value.
51    pub fn to_u8(self) -> u8 {
52        match self {
53            FileVerificationType::None => 0,
54            FileVerificationType::CRC => 1,
55            FileVerificationType::CRCBlocks => 2,
56            FileVerificationType::MD5Blocks => 3,
57            FileVerificationType::SHA1Blocks => 4,
58        }
59    }
60}
61
62/// Describes how a file is stored within an SGA archive.
63#[derive(Debug, Clone)]
64pub enum FileStorageType {
65    /// Stored plainly.
66    Store,
67
68    /// Stored compressed (stream compression).
69    StreamCompress,
70
71    /// Stored compressed (buffer compression).
72    BufferCompress,
73
74    /// Stored compressed (stream compression with Brotli).
75    StreamCompressBrotli,
76
77    /// Stored compressed (buffer compression with Brotli).
78    BufferCompressBrotli,
79
80    /// Unknown file type
81    Unknown(u8),
82}
83
84impl FileStorageType {
85    /// Parses a byte into a `FileStorageType`.
86    pub fn from_u8(value: u8) -> Self {
87        match value {
88            0 => Self::Store,
89            1 => Self::StreamCompress,
90            2 => Self::BufferCompress,
91            3 => Self::StreamCompressBrotli,
92            4 => Self::BufferCompressBrotli,
93            _ => Self::Unknown(value),
94        }
95    }
96
97    pub fn to_u8(self) -> u8 {
98        match self {
99            FileStorageType::Store => 0,
100            FileStorageType::StreamCompress => 1,
101            FileStorageType::BufferCompress => 2,
102            FileStorageType::StreamCompressBrotli => 3,
103            FileStorageType::BufferCompressBrotli => 4,
104            FileStorageType::Unknown(n) => n,
105        }
106    }
107}
108
109/// File entry of an SGA archive.
110#[derive(Debug, Clone)]
111pub struct SgaFileEntry {
112    /// Offset of the file's name in the SGA archive's string blob.
113    pub name_offset: u32,
114
115    /// Offset of the file's hash in the SGA archive's hash blob.
116    pub hash_offset: u32,
117
118    /// Offset of the file's data in the SGA archive's data blob.
119    pub data_offset: u64,
120
121    /// Size of the file in bytes as stored in the SGA archive.
122    pub compressed_length: u32,
123
124    /// Size of the file in bytes when uncompressed.
125    pub uncompressed_size: u32,
126
127    /// How the file should be verified when loaded.
128    pub verification_type: FileVerificationType,
129
130    /// How the file's data is stored.
131    pub storage_type: FileStorageType,
132
133    /// CRC32 checksum of the file's data.
134    pub crc: u32,
135}
136
137impl SgaFileEntry {
138    pub fn parse<T: Read + BufRead>(reader: &mut T) -> Result<Self, SgaFileEntryParseError> {
139        let name_offset = read_field!(reader, SgaFileEntryParseError::FailedToParseNumber, u32)?;
140        let hash_offset = read_field!(reader, SgaFileEntryParseError::FailedToParseNumber, u32)?;
141        let data_offset = read_field!(reader, SgaFileEntryParseError::FailedToParseNumber, u64)?;
142        let compressed_length = read_field!(reader, SgaFileEntryParseError::FailedToParseNumber, u32)?;
143        let uncompressed_size = read_field!(reader, SgaFileEntryParseError::FailedToParseNumber, u32)?;
144        let verification_type_byte = read_field!(reader, SgaFileEntryParseError::FailedToParseByte, u8)?;
145        let storage_type_byte = read_field!(reader, SgaFileEntryParseError::FailedToParseByte, u8)?;
146        let crc = read_field!(reader, SgaFileEntryParseError::FailedToParseNumber, u32)?;
147
148        
149        let verification_type = FileVerificationType::from_u8(verification_type_byte).map_err(|err| SgaFileEntryParseError::FailedToParseVerificationType(err.to_string()))?;
150        let storage_type = FileStorageType::from_u8(storage_type_byte);
151
152        Ok(Self {
153            name_offset,
154            hash_offset,
155            data_offset,
156            compressed_length,
157            uncompressed_size,
158            verification_type,
159            storage_type,
160            crc,
161        })
162    }
163}