msg_tool\scripts\hexen_haus\archive/
arcc.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::decode_to_string;
6use anyhow::Result;
7use std::io::{Read, Seek, SeekFrom};
8use std::sync::{Arc, Mutex};
9
10#[derive(Debug)]
11pub struct HexenHausArccArchiveBuilder;
13
14impl HexenHausArccArchiveBuilder {
15 pub const fn new() -> Self {
17 HexenHausArccArchiveBuilder
18 }
19}
20
21impl ScriptBuilder for HexenHausArccArchiveBuilder {
22 fn default_encoding(&self) -> Encoding {
23 Encoding::Cp932
24 }
25
26 fn default_archive_encoding(&self) -> Option<Encoding> {
27 Some(Encoding::Cp932)
28 }
29
30 fn build_script(
31 &self,
32 buf: Vec<u8>,
33 _filename: &str,
34 _encoding: Encoding,
35 archive_encoding: Encoding,
36 config: &ExtraConfig,
37 _archive: Option<&Box<dyn Script>>,
38 ) -> Result<Box<dyn Script + Send + Sync>> {
39 Ok(Box::new(HexenHausArccArchive::new(
40 MemReader::new(buf),
41 archive_encoding,
42 config,
43 )?))
44 }
45
46 fn build_script_from_file(
47 &self,
48 filename: &str,
49 _encoding: Encoding,
50 archive_encoding: Encoding,
51 config: &ExtraConfig,
52 _archive: Option<&Box<dyn Script>>,
53 ) -> Result<Box<dyn Script + Send + Sync>> {
54 if filename == "-" {
55 let data = crate::utils::files::read_file(filename)?;
56 return Ok(Box::new(HexenHausArccArchive::new(
57 MemReader::new(data),
58 archive_encoding,
59 config,
60 )?));
61 }
62 let file = std::fs::File::open(filename)?;
63 let reader = std::io::BufReader::new(file);
64 Ok(Box::new(HexenHausArccArchive::new(
65 reader,
66 archive_encoding,
67 config,
68 )?))
69 }
70
71 fn build_script_from_reader<'a>(
72 &self,
73 reader: Box<dyn ReadSeek + Send + Sync + 'a>,
74 _filename: &str,
75 _encoding: Encoding,
76 archive_encoding: Encoding,
77 config: &ExtraConfig,
78 _archive: Option<&Box<dyn Script>>,
79 ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
80 Ok(Box::new(HexenHausArccArchive::new(
81 reader,
82 archive_encoding,
83 config,
84 )?))
85 }
86
87 fn extensions(&self) -> &'static [&'static str] {
88 &["arc"]
89 }
90
91 fn script_type(&self) -> &'static ScriptType {
92 &ScriptType::HexenHausArcc
93 }
94
95 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
96 if buf_len >= 4 && buf.starts_with(b"ARCC") {
97 Some(10)
98 } else {
99 None
100 }
101 }
102
103 fn is_archive(&self) -> bool {
104 true
105 }
106}
107
108#[derive(Debug, Clone)]
109struct HexenHausArccEntry {
110 name: String,
111 offset: u64,
112 size: u32,
113}
114
115#[derive(Debug)]
116pub struct HexenHausArccArchive<'a, T: Read + Seek + std::fmt::Debug + 'a> {
118 reader: Arc<Mutex<T>>,
119 entries: Vec<HexenHausArccEntry>,
120 _mark: std::marker::PhantomData<&'a ()>,
121}
122
123impl<'b, T: Read + Seek + std::fmt::Debug + 'b> HexenHausArccArchive<'b, T> {
124 pub fn new(mut reader: T, archive_encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
126 reader.seek(SeekFrom::Start(0))?;
127 let mut signature = [0u8; 4];
128 reader.read_exact(&mut signature)?;
129 if signature != *b"ARCC" {
130 return Err(anyhow::anyhow!("Invalid HexenHaus ARCC signature"));
131 }
132 reader.seek(SeekFrom::Start(0))?;
133 let reader = Arc::new(Mutex::new(reader));
134
135 let file_count = reader.cpeek_u32_at(0x14)?;
136 let entry_count = file_count as usize;
137
138 let mut index_offset = 0x2a_u64;
139 let mut tag = [0u8; 4];
140 reader.cpeek_exact_at(index_offset, &mut tag)?;
141 if &tag != b"NAME" {
142 return Err(anyhow::anyhow!("Missing NAME section in ARCC archive"));
143 }
144 let addr_offset = reader.cpeek_u64_at(index_offset + 4)?;
145 index_offset += 0x0e;
146
147 reader.cpeek_exact_at(index_offset, &mut tag)?;
148 if &tag != b"NIDX" {
149 return Err(anyhow::anyhow!("Missing NIDX section in ARCC archive"));
150 }
151 index_offset += 4;
152 for _ in 0..entry_count {
153 let _ = reader.cpeek_u32_at(index_offset + 2)?;
154 index_offset += 8;
155 }
156
157 reader.cpeek_exact_at(index_offset, &mut tag)?;
158 if &tag != b"EIDX" {
159 return Err(anyhow::anyhow!("Missing EIDX section in ARCC archive"));
160 }
161 index_offset += 4 + 8 * file_count as u64;
162
163 reader.cpeek_exact_at(index_offset, &mut tag)?;
164 if &tag != b"CINF" {
165 return Err(anyhow::anyhow!("Missing CINF section in ARCC archive"));
166 }
167 index_offset += 4;
168
169 let mut entries = Vec::with_capacity(entry_count);
170 for _ in 0..entry_count {
171 index_offset += 6;
172 let name_len = reader.cpeek_u16_at(index_offset)? as usize;
173 let mut name_buf = vec![0u8; name_len];
174 if name_len > 0 {
175 reader.cpeek_exact_at(index_offset + 4, &mut name_buf)?;
176 decrypt_name(&mut name_buf);
177 }
178 index_offset += 6 + name_len as u64;
179 let name = decode_to_string(archive_encoding, &name_buf, true)?;
180 entries.push(HexenHausArccEntry {
181 name,
182 offset: 0,
183 size: 0,
184 });
185 }
186
187 let mut addr_offset = addr_offset;
188 reader.cpeek_exact_at(addr_offset, &mut tag)?;
189 if &tag != b"ADDR" {
190 return Err(anyhow::anyhow!("Missing ADDR section in ARCC archive"));
191 }
192 addr_offset += 4;
193 for entry in &mut entries {
194 entry.offset = reader.cpeek_u64_at(addr_offset + 2)?;
195 addr_offset += 12;
196 }
197
198 for entry in &mut entries {
199 if reader.cpeek_and_equal_at(entry.offset, b"FILE").is_err() {
200 continue;
201 }
202 entry.size = reader.cpeek_u32_at(entry.offset + 0x18)?;
203 entry.offset += 0x22;
204 }
205
206 entries.retain(|entry| entry.size > 0);
207 if entries.is_empty() {
208 return Err(anyhow::anyhow!("ARCC archive contains no files"));
209 }
210
211 Ok(HexenHausArccArchive {
212 reader,
213 entries,
214 _mark: std::marker::PhantomData,
215 })
216 }
217}
218
219impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script
220 for HexenHausArccArchive<'b, T>
221{
222 fn default_output_script_type(&self) -> OutputScriptType {
223 OutputScriptType::Json
224 }
225
226 fn default_format_type(&self) -> FormatOptions {
227 FormatOptions::None
228 }
229
230 fn is_archive(&self) -> bool {
231 true
232 }
233
234 fn iter_archive_filename<'a>(
235 &'a self,
236 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
237 Ok(Box::new(
238 self.entries.iter().map(|entry| Ok(entry.name.clone())),
239 ))
240 }
241
242 fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
243 Ok(Box::new(self.entries.iter().map(|entry| Ok(entry.offset))))
244 }
245
246 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
247 if index >= self.entries.len() {
248 return Err(anyhow::anyhow!(
249 "Index out of bounds: {} (total files: {})",
250 index,
251 self.entries.len()
252 ));
253 }
254 let entry = &self.entries[index];
255 let header = self
256 .reader
257 .cpeek_at_vec(entry.offset, (entry.size as usize).min(16))?;
258 Ok(Box::new(Entry {
259 reader: self.reader.clone(),
260 header: entry.clone(),
261 pos: 0,
262 typ: super::detect_script_type(&entry.name, &header),
263 }))
264 }
265}
266
267#[derive(Debug)]
268struct Entry<T: Read + Seek> {
269 header: HexenHausArccEntry,
270 reader: Arc<Mutex<T>>,
271 pos: u64,
272 typ: Option<ScriptType>,
273}
274
275impl<T: Read + Seek + std::fmt::Debug + Send + Sync> ArchiveContent for Entry<T> {
276 fn name(&self) -> &str {
277 &self.header.name
278 }
279
280 fn size(&self) -> Option<u64> {
281 Some(self.header.size as u64)
282 }
283
284 fn script_type(&self) -> Option<&ScriptType> {
285 self.typ.as_ref()
286 }
287
288 fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
289 Ok(Box::new(self))
290 }
291}
292
293impl<T: Read + Seek> Read for Entry<T> {
294 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
295 let mut reader = self.reader.lock().map_err(|e| {
296 std::io::Error::new(
297 std::io::ErrorKind::Other,
298 format!("Failed to lock mutex: {}", e),
299 )
300 })?;
301 reader.seek(SeekFrom::Start(self.header.offset + self.pos))?;
302 let bytes_read = buf.len().min(self.header.size as usize - self.pos as usize);
303 if bytes_read == 0 {
304 return Ok(0);
305 }
306 let bytes_read = reader.read(&mut buf[..bytes_read])?;
307 self.pos += bytes_read as u64;
308 Ok(bytes_read)
309 }
310}
311
312impl<T: Read + Seek> Seek for Entry<T> {
313 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
314 let new_pos = match pos {
315 SeekFrom::Start(offset) => offset as u64,
316 SeekFrom::End(offset) => {
317 if offset < 0 {
318 if (-offset) as u64 > self.header.size as u64 {
319 return Err(std::io::Error::new(
320 std::io::ErrorKind::InvalidInput,
321 "Seek from end exceeds file length",
322 ));
323 }
324 self.header.size as u64 - (-offset) as u64
325 } else {
326 self.header.size as u64 + offset as u64
327 }
328 }
329 SeekFrom::Current(offset) => {
330 if offset < 0 {
331 if (-offset) as u64 > self.pos {
332 return Err(std::io::Error::new(
333 std::io::ErrorKind::InvalidInput,
334 "Seek from current exceeds current position",
335 ));
336 }
337 self.pos.saturating_sub((-offset) as u64)
338 } else {
339 self.pos + offset as u64
340 }
341 }
342 };
343 self.pos = new_pos;
344 Ok(self.pos)
345 }
346
347 fn stream_position(&mut self) -> std::io::Result<u64> {
348 Ok(self.pos)
349 }
350}
351
352fn decrypt_name(buf: &mut [u8]) {
353 for byte in buf.iter_mut() {
354 *byte ^= 0x69;
355 }
356}