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#[repr(u8)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CompressionMethod {
12 Cpy = 0,
14 Asc = 1,
16 Hsc = 2,
18 Dir = 0x0E,
20 Special = 0x0F,
22 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#[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}