unarc_rs/uc2/
mod.rs

1use std::io::{self, Read};
2
3mod consts;
4mod decompress;
5mod supermaster_decompressed;
6pub mod uc2_archive;
7
8/// Location in archive (volume + offset)
9#[derive(Debug, Clone, Copy, Default)]
10pub struct Location {
11    pub volume: u32,
12    pub offset: u32,
13}
14
15pub(crate) struct ExtendedHeader {
16    /// Start of CDIR
17    pub c_dir_loc: Location,
18}
19
20/// Extended header size: volume(4) + offset(4) + fletch(2) + is_busy(1) + versions(4) + reserved(1)
21const EXT_HEADER_SIZE: usize = 16;
22
23impl ExtendedHeader {
24    pub fn load_from<T: Read>(reader: &mut T) -> io::Result<Self> {
25        let mut header_bytes = [0u8; EXT_HEADER_SIZE];
26        reader.read_exact(&mut header_bytes)?;
27        let c_dir_loc_volume = u32::from_le_bytes([
28            header_bytes[0],
29            header_bytes[1],
30            header_bytes[2],
31            header_bytes[3],
32        ]);
33        let c_dir_loc_offset = u32::from_le_bytes([
34            header_bytes[4],
35            header_bytes[5],
36            header_bytes[6],
37            header_bytes[7],
38        ]);
39        // Remaining bytes (fletch, is_busy, versions, reserved) are unused
40        Ok(Self {
41            c_dir_loc: Location {
42                volume: c_dir_loc_volume,
43                offset: c_dir_loc_offset,
44            },
45        })
46    }
47}
48
49/// Compression info for a file or master
50#[derive(Debug, Clone, Default)]
51pub struct CompressInfo {
52    pub compressed_length: u32,
53    pub method: u16,
54    pub master_prefix: u32,
55}
56
57impl CompressInfo {
58    pub fn load_from<T: Read>(reader: &mut T) -> io::Result<Self> {
59        let mut buf = [0u8; 10];
60        reader.read_exact(&mut buf)?;
61        let mut data = buf.as_slice();
62        convert_u32!(compressed_length, data);
63        convert_u16!(method, data);
64        convert_u32!(master_prefix, data);
65        Ok(Self {
66            compressed_length,
67            method,
68            master_prefix,
69        })
70    }
71}
72
73/// Entry types in CDIR
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[repr(u8)]
76pub enum EntryType {
77    DirEntry = 1,
78    FileEntry = 2,
79    MasterEntry = 3,
80    EndOfCdir = 4,
81}
82
83impl TryFrom<u8> for EntryType {
84    type Error = io::Error;
85
86    fn try_from(value: u8) -> Result<Self, Self::Error> {
87        match value {
88            1 => Ok(EntryType::DirEntry),
89            2 => Ok(EntryType::FileEntry),
90            3 => Ok(EntryType::MasterEntry),
91            4 => Ok(EntryType::EndOfCdir),
92            _ => Err(io::Error::new(
93                io::ErrorKind::InvalidData,
94                format!("invalid entry type: {}", value),
95            )),
96        }
97    }
98}
99
100/// File attributes (MS-DOS style)
101#[derive(Debug, Clone, Copy, Default)]
102pub struct FileAttributes(u8);
103
104impl FileAttributes {
105    pub fn is_readonly(&self) -> bool {
106        self.0 & 0x01 != 0
107    }
108    pub fn is_hidden(&self) -> bool {
109        self.0 & 0x02 != 0
110    }
111    pub fn is_system(&self) -> bool {
112        self.0 & 0x04 != 0
113    }
114    pub fn is_directory(&self) -> bool {
115        self.0 & 0x10 != 0
116    }
117    pub fn is_archive(&self) -> bool {
118        self.0 & 0x20 != 0
119    }
120}
121
122impl From<u8> for FileAttributes {
123    fn from(value: u8) -> Self {
124        Self(value)
125    }
126}
127
128/// UC2 archive entry (file or directory)
129#[derive(Debug, Clone)]
130pub struct Uc2Entry {
131    /// Parent directory index (0 = root)
132    pub parent_dir: u32,
133    /// DOS file attributes
134    pub attributes: FileAttributes,
135    /// DOS time
136    pub dos_time: u32,
137    /// File name
138    pub name: String,
139}