msg_tool\scripts\circus\archive/
pck.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use msg_tool_macro::*;
9use std::collections::HashMap;
10use std::io::{Read, Seek, SeekFrom, Write};
11use std::sync::{Arc, Mutex};
12
13#[derive(Debug)]
14pub struct PckArchiveBuilder {}
16
17impl PckArchiveBuilder {
18 pub const fn new() -> Self {
20 Self {}
21 }
22}
23
24impl ScriptBuilder for PckArchiveBuilder {
25 fn default_encoding(&self) -> Encoding {
26 Encoding::Cp932
27 }
28
29 fn default_archive_encoding(&self) -> Option<Encoding> {
30 Some(Encoding::Cp932)
31 }
32
33 fn build_script(
34 &self,
35 data: Vec<u8>,
36 _filename: &str,
37 _encoding: Encoding,
38 archive_encoding: Encoding,
39 config: &ExtraConfig,
40 _archive: Option<&Box<dyn Script>>,
41 ) -> Result<Box<dyn Script + Send + Sync>> {
42 Ok(Box::new(PckArchive::new(
43 MemReader::new(data),
44 archive_encoding,
45 config,
46 )?))
47 }
48
49 fn build_script_from_file(
50 &self,
51 filename: &str,
52 _encoding: Encoding,
53 archive_encoding: Encoding,
54 config: &ExtraConfig,
55 _archive: Option<&Box<dyn Script>>,
56 ) -> Result<Box<dyn Script + Send + Sync>> {
57 if filename == "-" {
58 let data = crate::utils::files::read_file(filename)?;
59 Ok(Box::new(PckArchive::new(
60 MemReader::new(data),
61 archive_encoding,
62 config,
63 )?))
64 } else {
65 let f = std::fs::File::open(filename)?;
66 let reader = std::io::BufReader::new(f);
67 Ok(Box::new(PckArchive::new(reader, archive_encoding, 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(PckArchive::new(reader, archive_encoding, config)?))
81 }
82
83 fn extensions(&self) -> &'static [&'static str] {
84 &["pck", "dat"]
85 }
86
87 fn script_type(&self) -> &'static ScriptType {
88 &ScriptType::CircusPck
89 }
90
91 fn is_archive(&self) -> bool {
92 true
93 }
94
95 fn create_archive(
96 &self,
97 filename: &str,
98 files: &[&str],
99 encoding: Encoding,
100 config: &ExtraConfig,
101 ) -> Result<Box<dyn Archive>> {
102 let f = std::fs::File::create(filename)?;
103 let writer = std::io::BufWriter::new(f);
104 Ok(Box::new(PckArchiveWriter::new(
105 writer, files, encoding, config,
106 )?))
107 }
108
109 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
110 is_this_format(&buf[..buf_len]).ok()
111 }
112}
113
114#[derive(Debug, Clone, StructPack, StructUnpack)]
115struct PckFileHeader {
116 #[fstring = 0x38]
117 name: String,
118 offset: u32,
119 size: u32,
120}
121
122#[derive(Debug)]
123struct Entry<T: Read + Seek> {
124 header: PckFileHeader,
125 reader: Arc<Mutex<T>>,
126 pos: usize,
127 script_type: Option<ScriptType>,
128}
129
130impl<T: Read + Seek + std::fmt::Debug + Send + Sync> ArchiveContent for Entry<T> {
131 fn name(&self) -> &str {
132 &self.header.name
133 }
134
135 fn size(&self) -> Option<u64> {
136 Some(self.header.size as u64)
137 }
138
139 fn script_type(&self) -> Option<&ScriptType> {
140 self.script_type.as_ref()
141 }
142
143 fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
144 Ok(Box::new(self))
145 }
146}
147
148impl<T: Read + Seek> Read for Entry<T> {
149 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
150 let mut reader = self.reader.lock().map_err(|e| {
151 std::io::Error::new(
152 std::io::ErrorKind::Other,
153 format!("Failed to lock mutex: {}", e),
154 )
155 })?;
156 reader.seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
157 let bytes_read = buf.len().min(self.header.size as usize - self.pos);
158 if bytes_read == 0 {
159 return Ok(0);
160 }
161 let bytes_read = reader.read(&mut buf[..bytes_read])?;
162 self.pos += bytes_read;
163 Ok(bytes_read)
164 }
165}
166
167impl<T: Read + Seek> Seek for Entry<T> {
168 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
169 let new_pos = match pos {
170 SeekFrom::Start(offset) => offset as usize,
171 SeekFrom::End(offset) => {
172 if offset < 0 {
173 if (-offset) as usize > self.header.size as usize {
174 return Err(std::io::Error::new(
175 std::io::ErrorKind::InvalidInput,
176 "Seek from end exceeds file length",
177 ));
178 }
179 self.header.size as usize - (-offset) as usize
180 } else {
181 self.header.size as usize + offset as usize
182 }
183 }
184 SeekFrom::Current(offset) => {
185 if offset < 0 {
186 if (-offset) as usize > self.pos {
187 return Err(std::io::Error::new(
188 std::io::ErrorKind::InvalidInput,
189 "Seek from current exceeds current position",
190 ));
191 }
192 self.pos.saturating_sub((-offset) as usize)
193 } else {
194 self.pos + offset as usize
195 }
196 }
197 };
198 self.pos = new_pos;
199 Ok(self.pos as u64)
200 }
201
202 fn stream_position(&mut self) -> std::io::Result<u64> {
203 Ok(self.pos as u64)
204 }
205}
206
207#[derive(Debug)]
208pub struct PckArchive<'b, T: Read + Seek + std::fmt::Debug + 'b> {
210 reader: Arc<Mutex<T>>,
211 entries: Vec<PckFileHeader>,
212 _mark: std::marker::PhantomData<&'b ()>,
213}
214
215impl<'b, T: Read + Seek + std::fmt::Debug + 'b> PckArchive<'b, T> {
216 pub fn new(mut reader: T, archive_encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
222 let file_count = reader.read_u32()?;
223 let mut offset_list = Vec::with_capacity(file_count as usize);
225 for _ in 0..file_count {
226 let offset = reader.read_u32()?;
227 let size = reader.read_u32()?;
228 offset_list.push((offset, size));
229 }
230 for i in 1..file_count as usize {
231 let (prev_offset, prev_size) = offset_list[i - 1];
232 let offset = offset_list[i].0;
233 if prev_offset + prev_size > offset {
234 return Err(anyhow::anyhow!(
235 "PckArchive: Overlapping entries detected at index {}: previous entry ends at {}, current entry starts at {}",
236 i - 1,
237 prev_offset + prev_size,
238 offset
239 ));
240 }
241 }
242 let mut entries = Vec::with_capacity(file_count as usize);
243 for (i, (offset, size)) in offset_list.into_iter().enumerate() {
244 let header: PckFileHeader = reader.read_struct(false, archive_encoding, &None)?;
245 if header.offset != offset {
246 return Err(anyhow::anyhow!(
247 "PckArchive: Header offset mismatch at entry {}: expected {}, got {}",
248 i,
249 offset,
250 header.offset
251 ));
252 }
253 if header.size != size {
254 return Err(anyhow::anyhow!(
255 "PckArchive: Header size mismatch at entry {}: expected {}, got {}",
256 i,
257 size,
258 header.size
259 ));
260 }
261 entries.push(header);
262 }
263 Ok(Self {
264 reader: Arc::new(Mutex::new(reader)),
265 entries,
266 _mark: std::marker::PhantomData,
267 })
268 }
269}
270
271impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script for PckArchive<'b, T> {
272 fn default_output_script_type(&self) -> OutputScriptType {
273 OutputScriptType::Json
274 }
275
276 fn default_format_type(&self) -> FormatOptions {
277 FormatOptions::None
278 }
279
280 fn is_archive(&self) -> bool {
281 true
282 }
283
284 fn iter_archive_filename<'a>(
285 &'a self,
286 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
287 Ok(Box::new(self.entries.iter().map(|e| Ok(e.name.clone()))))
288 }
289
290 fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
291 Ok(Box::new(self.entries.iter().map(|e| Ok(e.offset as u64))))
292 }
293
294 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
295 if index >= self.entries.len() {
296 return Err(anyhow::anyhow!(
297 "Index out of bounds: {} (max: {})",
298 index,
299 self.entries.len()
300 ));
301 }
302 let entry = &self.entries[index];
303 let mut entry = Entry {
304 header: entry.clone(),
305 reader: self.reader.clone(),
306 pos: 0,
307 script_type: None,
308 };
309 let mut buf = [0; 32];
310 let readed = match entry.read(&mut buf) {
311 Ok(readed) => readed,
312 Err(e) => {
313 return Err(anyhow::anyhow!(
314 "Failed to read entry '{}': {}",
315 entry.header.name,
316 e
317 ));
318 }
319 };
320 entry.pos = 0;
321 entry.script_type = detect_script_type(&buf, readed, &entry.header.name);
322 Ok(Box::new(entry))
323 }
324}
325
326fn detect_script_type(_buf: &[u8], _buf_len: usize, _filename: &str) -> Option<ScriptType> {
327 #[cfg(feature = "circus-img")]
328 if _buf_len >= 4 && _buf.starts_with(b"CRXG") {
329 return Some(ScriptType::CircusCrx);
330 }
331 #[cfg(feature = "circus-audio")]
332 if _buf_len >= 4 && _buf.starts_with(b"XPCM") {
333 return Some(ScriptType::CircusPcm);
334 }
335 None
336}
337
338pub struct PckArchiveWriter<T: Write + Seek> {
340 writer: T,
341 headers: HashMap<String, PckFileHeader>,
342 encoding: Encoding,
343}
344
345impl<T: Write + Seek> PckArchiveWriter<T> {
346 pub fn new(
353 mut writer: T,
354 files: &[&str],
355 encoding: Encoding,
356 _config: &ExtraConfig,
357 ) -> Result<Self> {
358 let file_count = files.len() as u32;
359 writer.write_u32(file_count)?;
360 let mut headers = HashMap::new();
361 for _ in 0..file_count {
362 writer.write_u32(0)?; writer.write_u32(0)?; }
365 for file in files {
366 let header = PckFileHeader {
367 name: file.to_string(),
368 offset: 0,
369 size: 0,
370 };
371 header.pack(&mut writer, false, encoding, &None)?;
372 headers.insert(file.to_string(), header);
373 }
374 Ok(PckArchiveWriter {
375 writer,
376 headers,
377 encoding,
378 })
379 }
380}
381
382impl<T: Write + Seek> Archive for PckArchiveWriter<T> {
383 fn new_file<'a>(
384 &'a mut self,
385 name: &str,
386 _size: Option<u64>,
387 ) -> Result<Box<dyn WriteSeek + 'a>> {
388 let entry = self
389 .headers
390 .get_mut(name)
391 .ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?;
392 if entry.offset != 0 || entry.size != 0 {
393 return Err(anyhow::anyhow!("File '{}' already exists in archive", name));
394 }
395 self.writer.seek(SeekFrom::End(0))?;
396 entry.offset = self.writer.stream_position()? as u32;
397 let file = PckArchiveFile {
398 header: entry,
399 writer: &mut self.writer,
400 pos: 0,
401 };
402 Ok(Box::new(file))
403 }
404
405 fn write_header(&mut self) -> Result<()> {
406 self.writer.seek(SeekFrom::Start(0x4))?;
407 let mut files = self.headers.iter().map(|(_, d)| d).collect::<Vec<_>>();
408 files.sort_by_key(|f| f.offset);
409 for file in files.iter() {
410 self.writer.write_u32(file.offset)?;
411 self.writer.write_u32(file.size)?;
412 }
413 for file in files {
414 file.pack(&mut self.writer, false, self.encoding, &None)?;
415 }
416 Ok(())
417 }
418}
419
420pub struct PckArchiveFile<'a, T: Write + Seek> {
422 header: &'a mut PckFileHeader,
423 writer: &'a mut T,
424 pos: usize,
425}
426
427impl<'a, T: Write + Seek> Write for PckArchiveFile<'a, T> {
428 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
429 self.writer
430 .seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
431 let bytes_written = self.writer.write(buf)?;
432 self.pos += bytes_written;
433 self.header.size = self.header.size.max(self.pos as u32);
434 Ok(bytes_written)
435 }
436
437 fn flush(&mut self) -> std::io::Result<()> {
438 self.writer.flush()
439 }
440}
441
442impl<'a, T: Write + Seek> Seek for PckArchiveFile<'a, T> {
443 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
444 let new_pos = match pos {
445 SeekFrom::Start(offset) => offset as usize,
446 SeekFrom::End(offset) => {
447 if offset < 0 {
448 if (-offset) as usize > self.header.size as usize {
449 return Err(std::io::Error::new(
450 std::io::ErrorKind::InvalidInput,
451 "Seek from end exceeds file length",
452 ));
453 }
454 self.header.size as usize - (-offset) as usize
455 } else {
456 self.header.size as usize + offset as usize
457 }
458 }
459 SeekFrom::Current(offset) => {
460 if offset < 0 {
461 if (-offset) as usize > self.pos {
462 return Err(std::io::Error::new(
463 std::io::ErrorKind::InvalidInput,
464 "Seek from current exceeds current position",
465 ));
466 }
467 self.pos.saturating_sub((-offset) as usize)
468 } else {
469 self.pos + offset as usize
470 }
471 }
472 };
473 self.pos = new_pos;
474 Ok(self.pos as u64)
475 }
476}
477
478pub fn is_this_format(buf: &[u8]) -> Result<u8> {
480 let mut reader = MemReaderRef::new(buf);
481 let count = reader.read_u32()? as usize;
482 let mut score = if count > 0 && count < 0x40000 { 5 } else { 0 };
483 let avail_count = ((buf.len() - 4) / 0x8).min(count);
484 score += ((avail_count / 2).min(10)) as u8;
485 if avail_count == 0 {
486 return Err(anyhow::anyhow!("No valid entries found in PCK archive"));
487 }
488 let mut prev_off = reader.read_u32()?;
489 let mut prev_size = reader.read_u32()?;
490 let mut index = 1;
491 while index < avail_count {
492 let off = reader.read_u32()?;
493 let size = reader.read_u32()?;
494 if off < prev_off
495 || prev_off
496 .checked_add(prev_size)
497 .ok_or_else(|| anyhow::anyhow!("Overflow in offset calculation"))?
498 != off
499 {
500 return Err(anyhow::anyhow!("Invalid offset."));
501 }
502 prev_off = off;
503 prev_size = size;
504 index += 1;
505 }
506 Ok(score)
507}