Skip to main content

unarc_rs/ha/
header.rs

1use crate::date_time::DosDateTime;
2use std::io::Read;
3
4use crate::error::{ArchiveError, Result};
5
6pub const HA_MAGIC: &[u8] = b"HA";
7
8/// Compression methods supported by HA archives
9#[repr(u8)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CompressionMethod {
12    /// Store (no compression)
13    Cpy = 0,
14    /// LZ77 + Arithmetic coding
15    Asc = 1,
16    /// PPM + Arithmetic coding
17    Hsc = 2,
18    /// Directory entry
19    Dir = 0x0E,
20    /// Special entry
21    Special = 0x0F,
22    /// Unknown method
23    Unknown(u8),
24}
25
26impl From<u8> for CompressionMethod {
27    fn from(value: u8) -> Self {
28        match value & 0x0F {
29            0 => CompressionMethod::Cpy,
30            1 => CompressionMethod::Asc,
31            2 => CompressionMethod::Hsc,
32            0x0E => CompressionMethod::Dir,
33            0x0F => CompressionMethod::Special,
34            v => CompressionMethod::Unknown(v),
35        }
36    }
37}
38
39#[derive(Debug, Clone)]
40pub struct ArchiveHeader {
41    pub file_count: u16,
42}
43
44impl ArchiveHeader {
45    pub fn load_from<R: Read>(reader: &mut R) -> Result<Self> {
46        let mut magic = [0u8; 2];
47        reader.read_exact(&mut magic)?;
48
49        if magic != HA_MAGIC {
50            return Err(ArchiveError::invalid_header("HA"));
51        }
52
53        let mut count_buf = [0u8; 2];
54        reader.read_exact(&mut count_buf)?;
55        let file_count = u16::from_le_bytes(count_buf);
56
57        Ok(Self { file_count })
58    }
59}
60
61/// HA file header structure
62#[derive(Debug, Clone)]
63pub struct FileHeader {
64    pub version: u8,
65    pub method: CompressionMethod,
66    pub compressed_size: u32,
67    pub original_size: u32,
68    pub crc32: u32,
69    pub timestamp: DosDateTime,
70    pub path: String,
71    pub name: String,
72    pub machine_info: Vec<u8>,
73}
74
75impl FileHeader {
76    pub fn load_from<R: Read>(reader: &mut R) -> Result<Self> {
77        let mut ver_type = [0u8; 1];
78        reader.read_exact(&mut ver_type)?;
79        let version = ver_type[0] >> 4;
80        let method = CompressionMethod::from(ver_type[0]);
81
82        let mut buf = [0u8; 4];
83        reader.read_exact(&mut buf)?;
84        let compressed_size = u32::from_le_bytes(buf);
85
86        reader.read_exact(&mut buf)?;
87        let original_size = u32::from_le_bytes(buf);
88
89        reader.read_exact(&mut buf)?;
90        let crc32 = u32::from_le_bytes(buf);
91
92        reader.read_exact(&mut buf)?;
93        let timestamp = DosDateTime::from(u32::from_le_bytes(buf));
94
95        let path = read_null_string(reader)?;
96        let name = read_null_string(reader)?;
97
98        let mut len_buf = [0u8; 1];
99        reader.read_exact(&mut len_buf)?;
100        let machine_info_len = len_buf[0] as usize;
101
102        let mut machine_info = vec![0u8; machine_info_len];
103        if machine_info_len > 0 {
104            reader.read_exact(&mut machine_info)?;
105        }
106
107        Ok(Self {
108            version,
109            method,
110            compressed_size,
111            original_size,
112            crc32,
113            timestamp,
114            path,
115            name,
116            machine_info,
117        })
118    }
119
120    pub fn full_path(&self) -> String {
121        if self.path.is_empty() {
122            self.name.clone()
123        } else {
124            format!("{}/{}", self.path, self.name)
125        }
126    }
127
128    pub fn is_directory(&self) -> bool {
129        self.method == CompressionMethod::Dir
130    }
131}
132
133fn read_null_string<R: Read>(reader: &mut R) -> Result<String> {
134    let mut bytes = Vec::new();
135    let mut byte = [0u8; 1];
136
137    loop {
138        reader.read_exact(&mut byte)?;
139        if byte[0] == 0 {
140            break;
141        }
142        bytes.push(byte[0]);
143    }
144
145    Ok(String::from_utf8_lossy(&bytes).into_owned())
146}