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>> {
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>> {
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(
72 &self,
73 reader: Box<dyn ReadSeek>,
74 _filename: &str,
75 _encoding: Encoding,
76 archive_encoding: Encoding,
77 config: &ExtraConfig,
78 _archive: Option<&Box<dyn Script>>,
79 ) -> Result<Box<dyn Script>> {
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
122struct Entry<T: Read + Seek> {
123 header: PckFileHeader,
124 reader: Arc<Mutex<T>>,
125 pos: usize,
126 script_type: Option<ScriptType>,
127}
128
129impl<T: Read + Seek> ArchiveContent for Entry<T> {
130 fn name(&self) -> &str {
131 &self.header.name
132 }
133
134 fn script_type(&self) -> Option<&ScriptType> {
135 self.script_type.as_ref()
136 }
137}
138
139impl<T: Read + Seek> Read for Entry<T> {
140 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
141 let mut reader = self.reader.lock().map_err(|e| {
142 std::io::Error::new(
143 std::io::ErrorKind::Other,
144 format!("Failed to lock mutex: {}", e),
145 )
146 })?;
147 reader.seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
148 let bytes_read = buf.len().min(self.header.size as usize - self.pos);
149 if bytes_read == 0 {
150 return Ok(0);
151 }
152 let bytes_read = reader.read(&mut buf[..bytes_read])?;
153 self.pos += bytes_read;
154 Ok(bytes_read)
155 }
156}
157
158impl<T: Read + Seek> Seek for Entry<T> {
159 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
160 let new_pos = match pos {
161 SeekFrom::Start(offset) => offset as usize,
162 SeekFrom::End(offset) => {
163 if offset < 0 {
164 if (-offset) as usize > self.header.size as usize {
165 return Err(std::io::Error::new(
166 std::io::ErrorKind::InvalidInput,
167 "Seek from end exceeds file length",
168 ));
169 }
170 self.header.size as usize - (-offset) as usize
171 } else {
172 self.header.size as usize + offset as usize
173 }
174 }
175 SeekFrom::Current(offset) => {
176 if offset < 0 {
177 if (-offset) as usize > self.pos {
178 return Err(std::io::Error::new(
179 std::io::ErrorKind::InvalidInput,
180 "Seek from current exceeds current position",
181 ));
182 }
183 self.pos.saturating_sub((-offset) as usize)
184 } else {
185 self.pos + offset as usize
186 }
187 }
188 };
189 self.pos = new_pos;
190 Ok(self.pos as u64)
191 }
192
193 fn stream_position(&mut self) -> std::io::Result<u64> {
194 Ok(self.pos as u64)
195 }
196}
197
198#[derive(Debug)]
199pub struct PckArchive<T: Read + Seek + std::fmt::Debug> {
201 reader: Arc<Mutex<T>>,
202 entries: Vec<PckFileHeader>,
203}
204
205impl<T: Read + Seek + std::fmt::Debug> PckArchive<T> {
206 pub fn new(mut reader: T, archive_encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
212 let file_count = reader.read_u32()?;
213 let mut offset_list = Vec::with_capacity(file_count as usize);
215 for _ in 0..file_count {
216 let offset = reader.read_u32()?;
217 let size = reader.read_u32()?;
218 offset_list.push((offset, size));
219 }
220 for i in 1..file_count as usize {
221 let (prev_offset, prev_size) = offset_list[i - 1];
222 let offset = offset_list[i].0;
223 if prev_offset + prev_size > offset {
224 return Err(anyhow::anyhow!(
225 "PckArchive: Overlapping entries detected at index {}: previous entry ends at {}, current entry starts at {}",
226 i - 1,
227 prev_offset + prev_size,
228 offset
229 ));
230 }
231 }
232 let mut entries = Vec::with_capacity(file_count as usize);
233 for (i, (offset, size)) in offset_list.into_iter().enumerate() {
234 let header: PckFileHeader = reader.read_struct(false, archive_encoding)?;
235 if header.offset != offset {
236 return Err(anyhow::anyhow!(
237 "PckArchive: Header offset mismatch at entry {}: expected {}, got {}",
238 i,
239 offset,
240 header.offset
241 ));
242 }
243 if header.size != size {
244 return Err(anyhow::anyhow!(
245 "PckArchive: Header size mismatch at entry {}: expected {}, got {}",
246 i,
247 size,
248 header.size
249 ));
250 }
251 entries.push(header);
252 }
253 Ok(Self {
254 reader: Arc::new(Mutex::new(reader)),
255 entries,
256 })
257 }
258}
259
260impl<T: Read + Seek + std::fmt::Debug + 'static> Script for PckArchive<T> {
261 fn default_output_script_type(&self) -> OutputScriptType {
262 OutputScriptType::Json
263 }
264
265 fn default_format_type(&self) -> FormatOptions {
266 FormatOptions::None
267 }
268
269 fn is_archive(&self) -> bool {
270 true
271 }
272
273 fn iter_archive_filename<'a>(
274 &'a self,
275 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
276 Ok(Box::new(self.entries.iter().map(|e| Ok(e.name.clone()))))
277 }
278
279 fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
280 Ok(Box::new(self.entries.iter().map(|e| Ok(e.offset as u64))))
281 }
282
283 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + 'a>> {
284 if index >= self.entries.len() {
285 return Err(anyhow::anyhow!(
286 "Index out of bounds: {} (max: {})",
287 index,
288 self.entries.len()
289 ));
290 }
291 let entry = &self.entries[index];
292 let mut entry = Entry {
293 header: entry.clone(),
294 reader: self.reader.clone(),
295 pos: 0,
296 script_type: None,
297 };
298 let mut buf = [0; 32];
299 let readed = match entry.read(&mut buf) {
300 Ok(readed) => readed,
301 Err(e) => {
302 return Err(anyhow::anyhow!(
303 "Failed to read entry '{}': {}",
304 entry.header.name,
305 e
306 ));
307 }
308 };
309 entry.pos = 0;
310 entry.script_type = detect_script_type(&buf, readed, &entry.header.name);
311 Ok(Box::new(entry))
312 }
313}
314
315fn detect_script_type(_buf: &[u8], _buf_len: usize, _filename: &str) -> Option<ScriptType> {
316 #[cfg(feature = "circus-img")]
317 if _buf_len >= 4 && _buf.starts_with(b"CRXG") {
318 return Some(ScriptType::CircusCrx);
319 }
320 #[cfg(feature = "circus-audio")]
321 if _buf_len >= 4 && _buf.starts_with(b"XPCM") {
322 return Some(ScriptType::CircusPcm);
323 }
324 None
325}
326
327pub struct PckArchiveWriter<T: Write + Seek> {
329 writer: T,
330 headers: HashMap<String, PckFileHeader>,
331 encoding: Encoding,
332}
333
334impl<T: Write + Seek> PckArchiveWriter<T> {
335 pub fn new(
342 mut writer: T,
343 files: &[&str],
344 encoding: Encoding,
345 _config: &ExtraConfig,
346 ) -> Result<Self> {
347 let file_count = files.len() as u32;
348 writer.write_u32(file_count)?;
349 let mut headers = HashMap::new();
350 for _ in 0..file_count {
351 writer.write_u32(0)?; writer.write_u32(0)?; }
354 for file in files {
355 let header = PckFileHeader {
356 name: file.to_string(),
357 offset: 0,
358 size: 0,
359 };
360 header.pack(&mut writer, false, encoding)?;
361 headers.insert(file.to_string(), header);
362 }
363 Ok(PckArchiveWriter {
364 writer,
365 headers,
366 encoding,
367 })
368 }
369}
370
371impl<T: Write + Seek> Archive for PckArchiveWriter<T> {
372 fn new_file<'a>(&'a mut self, name: &str) -> Result<Box<dyn WriteSeek + 'a>> {
373 let entry = self
374 .headers
375 .get_mut(name)
376 .ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?;
377 if entry.offset != 0 || entry.size != 0 {
378 return Err(anyhow::anyhow!("File '{}' already exists in archive", name));
379 }
380 self.writer.seek(SeekFrom::End(0))?;
381 entry.offset = self.writer.stream_position()? as u32;
382 let file = PckArchiveFile {
383 header: entry,
384 writer: &mut self.writer,
385 pos: 0,
386 };
387 Ok(Box::new(file))
388 }
389
390 fn write_header(&mut self) -> Result<()> {
391 self.writer.seek(SeekFrom::Start(0x4))?;
392 let mut files = self.headers.iter().map(|(_, d)| d).collect::<Vec<_>>();
393 files.sort_by_key(|f| f.offset);
394 for file in files.iter() {
395 self.writer.write_u32(file.offset)?;
396 self.writer.write_u32(file.size)?;
397 }
398 for file in files {
399 file.pack(&mut self.writer, false, self.encoding)?;
400 }
401 Ok(())
402 }
403}
404
405pub struct PckArchiveFile<'a, T: Write + Seek> {
407 header: &'a mut PckFileHeader,
408 writer: &'a mut T,
409 pos: usize,
410}
411
412impl<'a, T: Write + Seek> Write for PckArchiveFile<'a, T> {
413 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
414 self.writer
415 .seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
416 let bytes_written = self.writer.write(buf)?;
417 self.pos += bytes_written;
418 self.header.size = self.header.size.max(self.pos as u32);
419 Ok(bytes_written)
420 }
421
422 fn flush(&mut self) -> std::io::Result<()> {
423 self.writer.flush()
424 }
425}
426
427impl<'a, T: Write + Seek> Seek for PckArchiveFile<'a, T> {
428 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
429 let new_pos = match pos {
430 SeekFrom::Start(offset) => offset as usize,
431 SeekFrom::End(offset) => {
432 if offset < 0 {
433 if (-offset) as usize > self.header.size as usize {
434 return Err(std::io::Error::new(
435 std::io::ErrorKind::InvalidInput,
436 "Seek from end exceeds file length",
437 ));
438 }
439 self.header.size as usize - (-offset) as usize
440 } else {
441 self.header.size as usize + offset as usize
442 }
443 }
444 SeekFrom::Current(offset) => {
445 if offset < 0 {
446 if (-offset) as usize > self.pos {
447 return Err(std::io::Error::new(
448 std::io::ErrorKind::InvalidInput,
449 "Seek from current exceeds current position",
450 ));
451 }
452 self.pos.saturating_sub((-offset) as usize)
453 } else {
454 self.pos + offset as usize
455 }
456 }
457 };
458 self.pos = new_pos;
459 Ok(self.pos as u64)
460 }
461}
462
463pub fn is_this_format(buf: &[u8]) -> Result<u8> {
465 let mut reader = MemReaderRef::new(buf);
466 let count = reader.read_u32()? as usize;
467 let mut score = if count > 0 && count < 0x40000 { 5 } else { 0 };
468 let avail_count = ((buf.len() - 4) / 0x8).min(count);
469 score += ((avail_count / 2).min(10)) as u8;
470 if avail_count == 0 {
471 return Err(anyhow::anyhow!("No valid entries found in PCK archive"));
472 }
473 let mut prev_off = reader.read_u32()?;
474 let mut prev_size = reader.read_u32()?;
475 let mut index = 1;
476 while index < avail_count {
477 let off = reader.read_u32()?;
478 let size = reader.read_u32()?;
479 if off < prev_off || prev_off + prev_size != off {
480 return Err(anyhow::anyhow!("Invalid offset."));
481 }
482 prev_off = off;
483 prev_size = size;
484 index += 1;
485 }
486 Ok(score)
487}