msg_tool\scripts\artemis\archive/
pf2.rs1use super::detect_script_type;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
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 ArtemisPf2Builder {}
16
17impl ArtemisPf2Builder {
18 pub fn new() -> Self {
20 ArtemisPf2Builder {}
21 }
22}
23
24impl ScriptBuilder for ArtemisPf2Builder {
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 buf: 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(ArtemisPf2::new(
43 MemReader::new(buf),
44 archive_encoding,
45 config,
46 filename,
47 )?))
48 }
49
50 fn build_script_from_file(
51 &self,
52 filename: &str,
53 _encoding: Encoding,
54 archive_encoding: Encoding,
55 config: &ExtraConfig,
56 _archive: Option<&Box<dyn Script>>,
57 ) -> Result<Box<dyn Script>> {
58 let f = std::fs::File::open(filename)?;
59 let f = std::io::BufReader::new(f);
60 Ok(Box::new(ArtemisPf2::new(
61 f,
62 archive_encoding,
63 config,
64 filename,
65 )?))
66 }
67
68 fn build_script_from_reader(
69 &self,
70 reader: Box<dyn ReadSeek>,
71 filename: &str,
72 _encoding: Encoding,
73 archive_encoding: Encoding,
74 config: &ExtraConfig,
75 _archive: Option<&Box<dyn Script>>,
76 ) -> Result<Box<dyn Script>> {
77 Ok(Box::new(ArtemisPf2::new(
78 reader,
79 archive_encoding,
80 config,
81 filename,
82 )?))
83 }
84
85 fn extensions(&self) -> &'static [&'static str] {
86 gen_artemis_arc_ext!()
87 }
88
89 fn is_archive(&self) -> bool {
90 true
91 }
92
93 fn script_type(&self) -> &'static ScriptType {
94 &ScriptType::ArtemisPf2
95 }
96
97 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
98 if buf_len >= 3 && buf.starts_with(b"pf2") {
99 return Some(20);
100 }
101 None
102 }
103
104 fn create_archive(
105 &self,
106 filename: &str,
107 files: &[&str],
108 encoding: Encoding,
109 _config: &ExtraConfig,
110 ) -> Result<Box<dyn Archive>> {
111 let f = std::fs::File::options()
112 .write(true)
113 .read(true)
114 .create(true)
115 .truncate(true)
116 .open(filename)?;
117 Ok(Box::new(ArtemisPf2Writer::new(f, files, encoding)?))
118 }
119}
120
121#[derive(Debug, Clone, StructPack, StructUnpack)]
122struct Pf2EntryHeader {
123 #[pstring(u32)]
124 name: String,
125 _unk1: u32,
127 _unk2: u32,
128 _unk3: u32,
129 offset: u32,
130 size: u32,
131}
132
133#[derive(Debug)]
134pub struct ArtemisPf2<T: Read + Seek + std::fmt::Debug> {
136 reader: Arc<Mutex<T>>,
137 entries: Vec<Pf2EntryHeader>,
138 output_ext: Option<String>,
139}
140
141impl<T: Read + Seek + std::fmt::Debug> ArtemisPf2<T> {
142 pub fn new(
149 mut reader: T,
150 archive_encoding: Encoding,
151 _config: &ExtraConfig,
152 filename: &str,
153 ) -> Result<Self> {
154 let mut magic = [0; 2];
155 reader.read_exact(&mut magic)?;
156 if &magic != b"pf" {
157 return Err(anyhow::anyhow!(
158 "Invalid Artemis PF2 archive magic: {:?}",
159 magic
160 ));
161 }
162 let version = reader.read_u8()?;
163 if version != b'2' {
164 return Err(anyhow::anyhow!(
165 "Unsupported Artemis PF2 archive version: {}",
166 version
167 ));
168 }
169 let _index_size = reader.read_u32()?;
170 let _reserved = reader.read_u32()?;
171 let file_count = reader.read_u32()?;
172 let mut entries = Vec::with_capacity(file_count as usize);
173 for _ in 0..file_count {
174 let header = reader.read_struct(false, archive_encoding)?;
175 entries.push(header);
176 }
177 let output_ext = std::path::Path::new(filename)
178 .extension()
179 .filter(|s| *s != "pfs")
180 .map(|s| s.to_string_lossy().to_string());
181 Ok(ArtemisPf2 {
182 reader: Arc::new(Mutex::new(reader)),
183 entries,
184 output_ext,
185 })
186 }
187}
188
189impl<T: Read + Seek + std::fmt::Debug + 'static> Script for ArtemisPf2<T> {
190 fn default_output_script_type(&self) -> OutputScriptType {
191 OutputScriptType::Json
192 }
193
194 fn default_format_type(&self) -> FormatOptions {
195 FormatOptions::None
196 }
197
198 fn is_archive(&self) -> bool {
199 true
200 }
201
202 fn iter_archive_filename<'a>(
203 &'a self,
204 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
205 Ok(Box::new(
206 self.entries.iter().map(|header| Ok(header.name.clone())),
207 ))
208 }
209
210 fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
211 Ok(Box::new(
212 self.entries.iter().map(|header| Ok(header.offset as u64)),
213 ))
214 }
215
216 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + 'a>> {
217 if index >= self.entries.len() {
218 return Err(anyhow::anyhow!(
219 "Index out of bounds: {} (max: {})",
220 index,
221 self.entries.len()
222 ));
223 }
224 let header = &self.entries[index];
225 let mut entry = Pf2Entry {
226 header: header.clone(),
227 reader: self.reader.clone(),
228 pos: 0,
229 script_type: None,
230 };
231 let mut header_buf = [0; 0x20];
232 let readed = entry.read(&mut header_buf)?;
233 entry.pos = 0;
234 entry.script_type = detect_script_type(&header_buf, readed, &entry.header.name);
235 Ok(Box::new(entry))
236 }
237
238 fn archive_output_ext<'a>(&'a self) -> Option<&'a str> {
239 self.output_ext.as_deref()
240 }
241}
242
243struct Pf2Entry<T: Read + Seek> {
244 header: Pf2EntryHeader,
245 reader: Arc<Mutex<T>>,
246 pos: u64,
247 script_type: Option<ScriptType>,
248}
249
250impl<T: Read + Seek> ArchiveContent for Pf2Entry<T> {
251 fn name(&self) -> &str {
252 &self.header.name
253 }
254
255 fn script_type(&self) -> Option<&ScriptType> {
256 self.script_type.as_ref()
257 }
258}
259
260impl<T: Read + Seek> Read for Pf2Entry<T> {
261 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
262 let mut reader = self.reader.lock().map_err(|e| {
263 std::io::Error::new(
264 std::io::ErrorKind::Other,
265 format!("Failed to lock mutex: {}", e),
266 )
267 })?;
268 reader.seek(SeekFrom::Start(self.header.offset as u64 + self.pos))?;
269 let remaining = (self.header.size as u64).saturating_sub(self.pos);
270 if remaining == 0 {
271 return Ok(0);
272 }
273 let bytes_to_read = buf.len().min(remaining as usize);
274 let bytes_read = reader.read(&mut buf[..bytes_to_read])?;
275 self.pos += bytes_read as u64;
276 Ok(bytes_read)
277 }
278}
279
280impl<T: Read + Seek> Seek for Pf2Entry<T> {
281 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
282 let new_pos = match pos {
283 SeekFrom::Start(offset) => offset,
284 SeekFrom::End(offset) => {
285 if offset < 0 {
286 if (-offset) as u64 > self.header.size as u64 {
287 return Err(std::io::Error::new(
288 std::io::ErrorKind::InvalidInput,
289 "Seek from end exceeds file length",
290 ));
291 }
292 self.header.size as u64 - (-offset) as u64
293 } else {
294 self.header.size as u64 + offset as u64
295 }
296 }
297 SeekFrom::Current(offset) => {
298 if offset < 0 {
299 if (-offset) as u64 > self.pos {
300 return Err(std::io::Error::new(
301 std::io::ErrorKind::InvalidInput,
302 "Seek from current exceeds current position",
303 ));
304 }
305 self.pos.saturating_sub((-offset) as u64)
306 } else {
307 self.pos + offset as u64
308 }
309 }
310 };
311 self.pos = new_pos;
312 Ok(self.pos)
313 }
314
315 fn stream_position(&mut self) -> std::io::Result<u64> {
316 Ok(self.pos)
317 }
318}
319
320pub struct ArtemisPf2Writer<T: Write + Seek + Read> {
322 writer: T,
323 headers: HashMap<String, Pf2EntryHeader>,
324 encoding: Encoding,
325 index_size: u32,
326}
327
328impl<T: Write + Seek + Read> ArtemisPf2Writer<T> {
329 pub fn new(mut writer: T, files: &[&str], encoding: Encoding) -> Result<Self> {
335 writer.write_all(b"pf2")?;
336 writer.write_u32(0)?; writer.write_u32(0)?; writer.write_u32(files.len() as u32)?;
339 let mut headers = HashMap::new();
340 for file in files {
341 let header = Pf2EntryHeader {
342 name: file.to_string(),
343 _unk1: 0x10,
344 _unk2: 0,
345 _unk3: 0,
346 offset: 0,
347 size: 0,
348 };
349 header.pack(&mut writer, false, encoding)?;
350 headers.insert(file.to_string(), header);
351 }
352 let size = writer.stream_position()?;
353 let index_size = size as u32 - 7;
354 writer.write_u32_at(3, index_size)?;
355 writer.write_u32_at(7, 0)?;
356 Ok(ArtemisPf2Writer {
357 writer,
358 headers,
359 encoding,
360 index_size,
361 })
362 }
363}
364
365impl<T: Write + Seek + Read> Archive for ArtemisPf2Writer<T> {
366 fn new_file<'a>(
367 &'a mut self,
368 name: &str,
369 _size: Option<u64>,
370 ) -> Result<Box<dyn WriteSeek + 'a>> {
371 let entry = self
372 .headers
373 .get_mut(name)
374 .ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?;
375 if entry.offset != 0 || entry.size != 0 {
376 return Err(anyhow::anyhow!("File '{}' already exists in archive", name));
377 }
378 self.writer.seek(SeekFrom::End(0))?;
379 entry.offset = self.writer.stream_position()? as u32;
380 let file = ArtemisPf2File {
381 header: entry,
382 writer: &mut self.writer,
383 pos: 0,
384 };
385 Ok(Box::new(file))
386 }
387
388 fn write_header(&mut self) -> Result<()> {
389 self.writer.seek(SeekFrom::Start(15))?;
390 let mut files = self.headers.values().collect::<Vec<_>>();
391 files.sort_by_key(|d| d.offset);
392 for file in files.iter() {
393 file.pack(&mut self.writer, false, self.encoding)?;
394 }
395 self.writer.write_u32_at(3, self.index_size)?;
396 self.writer.write_u32_at(7, 0)?;
397 Ok(())
398 }
399}
400
401pub struct ArtemisPf2File<'a, T: Write + Seek> {
403 header: &'a mut Pf2EntryHeader,
404 writer: &'a mut T,
405 pos: u64,
406}
407
408impl<'a, T: Write + Seek> Write for ArtemisPf2File<'a, T> {
409 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
410 self.writer
411 .seek(SeekFrom::Start(self.header.offset as u64 + self.pos))?;
412 let bytes_written = self.writer.write(buf)?;
413 self.pos += bytes_written as u64;
414 self.header.size = self.header.size.max(self.pos as u32);
415 Ok(bytes_written)
416 }
417
418 fn flush(&mut self) -> std::io::Result<()> {
419 self.writer.flush()
420 }
421}
422
423impl<'a, T: Write + Seek> Seek for ArtemisPf2File<'a, T> {
424 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
425 let new_pos = match pos {
426 SeekFrom::Start(offset) => offset,
427 SeekFrom::End(offset) => {
428 if offset < 0 {
429 if (-offset) as u64 > self.header.size as u64 {
430 return Err(std::io::Error::new(
431 std::io::ErrorKind::InvalidInput,
432 "Seek from end exceeds file length",
433 ));
434 }
435 self.header.size as u64 - (-offset) as u64
436 } else {
437 self.header.size as u64 + offset as u64
438 }
439 }
440 SeekFrom::Current(offset) => {
441 if offset < 0 {
442 if (-offset) as u64 > self.pos {
443 return Err(std::io::Error::new(
444 std::io::ErrorKind::InvalidInput,
445 "Seek from current exceeds current position",
446 ));
447 }
448 self.pos.saturating_sub((-offset) as u64)
449 } else {
450 self.pos + offset as u64
451 }
452 }
453 };
454 self.pos = new_pos;
455 Ok(self.pos)
456 }
457
458 fn stream_position(&mut self) -> std::io::Result<u64> {
459 Ok(self.pos)
460 }
461}