Skip to main content

unarc_rs/ace/
header.rs

1//! ACE archive header structures
2
3use crate::date_time::DosDateTime;
4
5/// ACE main header magic bytes
6pub const ACE_MAGIC: &[u8; 7] = b"**ACE**";
7
8/// Header type constants
9#[allow(dead_code)]
10pub mod header_type {
11    pub const MAIN: u8 = 0;
12    pub const FILE: u8 = 1;
13    pub const RECOVERY32: u8 = 2;
14    pub const RECOVERY64A: u8 = 3;
15    pub const RECOVERY64B: u8 = 4;
16}
17
18/// Header flags for MAIN header
19#[allow(dead_code)]
20pub mod header_flags {
21    pub const ADDSIZE: u16 = 0x0001;
22    pub const COMMENT: u16 = 0x0002;
23    pub const MEMORY_64BIT: u16 = 0x0004;
24    pub const AV_STRING: u16 = 0x0008;
25    pub const SOLID: u16 = 0x0010;
26    pub const LOCKED: u16 = 0x0020;
27    pub const PROTECTED: u16 = 0x0040;
28    pub const PASSWORD: u16 = 0x0080;
29    // Main header specific flags
30    pub const V20FORMAT: u16 = 0x0100;
31    pub const SFX: u16 = 0x0200;
32    pub const LIMITSFXJR: u16 = 0x0400;
33    pub const MULTIVOLUME: u16 = 0x0800;
34    pub const ADVERT: u16 = 0x1000;
35    pub const RECOVERY: u16 = 0x2000;
36    pub const LOCKED_MAIN: u16 = 0x4000;
37    pub const SOLID_MAIN: u16 = 0x8000;
38    // File header specific flags (different meaning for same bits)
39    pub const NTSECURITY: u16 = 0x0400;
40    pub const CONTINUED_PREV: u16 = 0x1000;
41    pub const CONTINUED_NEXT: u16 = 0x2000;
42}
43
44/// Compression types
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum CompressionType {
47    /// Stored (no compression)
48    Stored,
49    /// LZ77 compression (ACE 1.0)
50    Lz77,
51    /// Blocked compression (ACE 2.0)
52    Blocked,
53    /// Unknown compression type
54    Unknown(u8),
55}
56
57impl From<u8> for CompressionType {
58    fn from(value: u8) -> Self {
59        match value {
60            0 => CompressionType::Stored,
61            1 => CompressionType::Lz77,
62            2 => CompressionType::Blocked,
63            n => CompressionType::Unknown(n),
64        }
65    }
66}
67
68impl std::fmt::Display for CompressionType {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            CompressionType::Stored => write!(f, "Stored"),
72            CompressionType::Lz77 => write!(f, "LZ77"),
73            CompressionType::Blocked => write!(f, "Blocked"),
74            CompressionType::Unknown(n) => write!(f, "Unknown({})", n),
75        }
76    }
77}
78
79/// Compression quality
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum CompressionQuality {
82    None,
83    Fastest,
84    Fast,
85    Normal,
86    Good,
87    Best,
88    Unknown(u8),
89}
90
91impl From<u8> for CompressionQuality {
92    fn from(value: u8) -> Self {
93        match value {
94            0 => CompressionQuality::None,
95            1 => CompressionQuality::Fastest,
96            2 => CompressionQuality::Fast,
97            3 => CompressionQuality::Normal,
98            4 => CompressionQuality::Good,
99            5 => CompressionQuality::Best,
100            n => CompressionQuality::Unknown(n),
101        }
102    }
103}
104
105/// Host OS type
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum HostOs {
108    MsDos,
109    Os2,
110    Windows,
111    Unix,
112    MacOs,
113    WinNt,
114    Primos,
115    AppleGs,
116    Atari,
117    Vax,
118    Amiga,
119    Next,
120    Unknown(u8),
121}
122
123impl From<u8> for HostOs {
124    fn from(value: u8) -> Self {
125        match value {
126            0 => HostOs::MsDos,
127            1 => HostOs::Os2,
128            2 => HostOs::Windows,
129            3 => HostOs::Unix,
130            4 => HostOs::MacOs,
131            5 => HostOs::WinNt,
132            6 => HostOs::Primos,
133            7 => HostOs::AppleGs,
134            8 => HostOs::Atari,
135            9 => HostOs::Vax,
136            10 => HostOs::Amiga,
137            11 => HostOs::Next,
138            n => HostOs::Unknown(n),
139        }
140    }
141}
142
143/// ACE main archive header
144#[derive(Debug)]
145pub struct MainHeader {
146    pub header_crc: u16,
147    pub header_size: u16,
148    pub header_type: u8,
149    pub header_flags: u16,
150    pub extract_version: u8,
151    pub creator_version: u8,
152    pub host_os: HostOs,
153    pub volume_number: u8,
154    pub datetime: DosDateTime,
155    pub advert: String,
156    pub comment: Vec<u8>,
157}
158
159impl MainHeader {
160    /// Check if archive uses 64-bit sizes
161    pub fn is_64bit(&self) -> bool {
162        self.header_flags & header_flags::MEMORY_64BIT != 0
163    }
164
165    /// Check if archive is solid
166    pub fn is_solid(&self) -> bool {
167        self.header_flags & header_flags::SOLID != 0
168    }
169
170    /// Check if archive is password protected
171    pub fn is_encrypted(&self) -> bool {
172        self.header_flags & header_flags::PASSWORD != 0
173    }
174
175    /// Check if archive is multi-volume
176    pub fn is_multivolume(&self) -> bool {
177        self.header_flags & header_flags::MULTIVOLUME != 0
178    }
179}
180
181/// ACE file entry header
182#[derive(Debug, Clone)]
183pub struct FileHeader {
184    pub header_crc: u16,
185    pub header_size: u16,
186    pub header_type: u8,
187    pub header_flags: u16,
188    pub packed_size: u64,
189    pub original_size: u64,
190    pub datetime: DosDateTime,
191    pub attributes: u32,
192    pub crc32: u32,
193    pub compression_type: CompressionType,
194    pub compression_quality: CompressionQuality,
195    pub parameters: u16,
196    pub filename: String,
197    pub comment: Vec<u8>,
198    /// File data offset in the archive
199    pub data_offset: u64,
200}
201
202impl FileHeader {
203    /// Check if entry is a directory
204    pub fn is_directory(&self) -> bool {
205        self.attributes & 0x10 != 0
206    }
207
208    /// Check if entry is encrypted
209    pub fn is_encrypted(&self) -> bool {
210        self.header_flags & header_flags::PASSWORD != 0
211    }
212
213    /// Check if entry continues from previous volume
214    pub fn is_continued_from_prev(&self) -> bool {
215        self.header_flags & header_flags::CONTINUED_PREV != 0
216    }
217
218    /// Check if entry continues to next volume
219    pub fn is_continued_to_next(&self) -> bool {
220        self.header_flags & header_flags::CONTINUED_NEXT != 0
221    }
222
223    /// Get dictionary size in bytes
224    pub fn dictionary_size(&self) -> usize {
225        let bits = (self.parameters & 0x0F) as usize;
226        if bits < 10 {
227            1024 // minimum 1K
228        } else {
229            1 << bits
230        }
231    }
232}