1#![warn(clippy::as_conversions)]
2
3mod entry;
5pub mod file_time;
7mod pak;
9pub(crate) mod reader;
11pub(crate) mod writer;
13
14pub use crate::entry::Entry;
15pub use crate::file_time::FileTime;
16pub use crate::pak::Pak;
17
18pub(crate) const MAGIC: &[u8] = &[0xc0, 0x4a, 0xc0, 0xba];
23pub(crate) const VERSION: &[u8] = &[0; 4];
27
28const FILEFLAGS_DEFAULT: u8 = 0x00;
30const FILEFLAGS_END: u8 = 0x80;
32
33#[derive(Debug)]
35pub enum PakError {
36 Io(std::io::Error),
38
39 InvalidMagic([u8; 4]),
43
44 InvalidVersion([u8; 4]),
48
49 InvalidFileNameLength {
53 length: usize,
54 error: std::num::TryFromIntError,
55 },
56
57 InvalidFileDataLength {
61 length: usize,
62 error: std::num::TryFromIntError,
63 },
64
65 InvalidRecordFileSize {
67 file_size: u32,
68 error: std::num::TryFromIntError,
69 },
70}
71
72impl From<std::io::Error> for PakError {
73 fn from(e: std::io::Error) -> Self {
74 Self::Io(e)
75 }
76}
77
78impl std::fmt::Display for PakError {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 Self::Io(e) => e.fmt(f),
82 Self::InvalidMagic(magic) => {
83 write!(f, "invalid magic number '{magic:?}', expected '{MAGIC:?}'",)
84 }
85 Self::InvalidVersion(version) => {
86 write!(f, "invalid version '{version:?}', expected '{VERSION:?}'",)
87 }
88 Self::InvalidFileNameLength { length, .. } => {
89 write!(f, "invalid file name length '{length}'")
90 }
91 Self::InvalidFileDataLength { length, .. } => {
92 write!(f, "invalid file data length '{length}'")
93 }
94 Self::InvalidRecordFileSize { file_size, .. } => {
95 write!(f, "invalid record file size '{file_size}'")
96 }
97 }
98 }
99}
100
101impl std::error::Error for PakError {
102 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
103 match self {
104 Self::Io(error) => Some(error),
105 Self::InvalidFileNameLength { error, .. } => Some(error),
106 Self::InvalidFileDataLength { error, .. } => Some(error),
107 Self::InvalidRecordFileSize { error, .. } => Some(error),
108 _ => None,
109 }
110 }
111}
112
113#[derive(Debug)]
115struct Record {
116 pub name: Vec<u8>,
118 pub file_size: u32,
120 pub file_time: FileTime,
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use std::path::Path;
128
129 const EXTRACT_PATH: &str = "test-extract";
130 const PAK_PATH: &str = "test_data/Simple Building.pak";
131
132 fn extract(pak: &mut Pak, extract_dir: &Path) {
133 let _ = std::fs::remove_dir_all(extract_dir);
134
135 for entry in pak.entries.iter_mut() {
136 let entry_path = entry.path_str().unwrap();
137 let entry_dir = entry.dir_str().transpose().unwrap();
138 let entry_name = entry.name_str().unwrap();
139
140 let (expected_entry_dir, expected_entry_name) = entry_path
141 .rsplit_once(['/', '\\'])
142 .map(|(dir, name)| (Some(dir), name))
143 .unwrap_or((None, entry_path));
144 assert!(
145 expected_entry_name == entry_name,
146 "{expected_entry_name} != {entry_name}"
147 );
148 assert!(
149 expected_entry_dir == entry_dir,
150 "{expected_entry_dir:?} != {entry_dir:?}"
151 );
152
153 println!("Extracting '{}'...", entry_path);
154 if let Some(dir) = entry_dir {
155 let entry_extract_dir = extract_dir.join(dir);
156 std::fs::create_dir_all(&entry_extract_dir).unwrap();
157 }
158
159 let entry_extract_path = extract_dir.join(entry_path);
160 let mut f = std::fs::File::create(&entry_extract_path).unwrap();
161 std::io::copy(entry, &mut f).unwrap();
162 }
163 }
164
165 #[test]
166 fn extract_read() {
167 let f = std::fs::File::open(PAK_PATH).unwrap();
168 let mut p = Pak::from_read(f).unwrap();
169 let extract_dir = Path::new(EXTRACT_PATH).join("read");
170
171 extract(&mut p, &extract_dir);
172 }
173
174 #[test]
175 fn extract_bytes() {
176 let data = std::fs::read(PAK_PATH).unwrap();
177 let mut p = Pak::from_bytes(&data).unwrap();
178 let extract_dir = Path::new(EXTRACT_PATH).join("bytes");
179
180 extract(&mut p, &extract_dir);
181 }
182
183 #[test]
184 fn bytes_vs_read() {
185 let data = std::fs::read(PAK_PATH).unwrap();
186 let read = Pak::from_read(std::io::Cursor::new(&data)).unwrap();
187 let bytes = Pak::from_bytes(&data).unwrap();
188 assert_eq!(bytes, read);
189 }
190
191 #[test]
192 fn round_trip() {
193 let original = std::fs::read(PAK_PATH).unwrap();
194 let mut pak = Pak::from_read(std::io::Cursor::new(&original)).unwrap();
195 let mut round = Vec::new();
196 pak.write_to(&mut round).unwrap();
197 assert_eq!(&round, &original);
198 let pak2 = Pak::from_read(std::io::Cursor::new(&round)).unwrap();
199 assert_eq!(pak, pak2);
200 }
201}