msg_tool\scripts\bgi\archive/
v2.rs

1//! Buriko General Interpreter/Ethornell Archive File Version 2 (.arc)
2use super::bse::*;
3use super::dsc::*;
4use crate::ext::io::*;
5use crate::ext::mutex::*;
6use crate::scripts::base::*;
7use crate::types::*;
8use crate::utils::encoding::encode_string;
9use crate::utils::struct_pack::*;
10use crate::utils::threadpool::*;
11use anyhow::Result;
12use msg_tool_macro::*;
13use std::collections::HashMap;
14use std::io::{Read, Seek, SeekFrom, Write};
15use std::ops::DerefMut;
16use std::sync::{Arc, Mutex};
17
18#[derive(Debug)]
19/// Builder for BGI Archive Version 2
20pub struct BgiArchiveBuilder {}
21
22impl BgiArchiveBuilder {
23    /// Creates a new instance of `BgiArchiveBuilder`.
24    pub const fn new() -> Self {
25        BgiArchiveBuilder {}
26    }
27}
28
29impl ScriptBuilder for BgiArchiveBuilder {
30    fn default_encoding(&self) -> Encoding {
31        Encoding::Cp932
32    }
33
34    fn default_archive_encoding(&self) -> Option<Encoding> {
35        Some(Encoding::Cp932)
36    }
37
38    fn build_script(
39        &self,
40        data: Vec<u8>,
41        filename: &str,
42        _encoding: Encoding,
43        archive_encoding: Encoding,
44        config: &ExtraConfig,
45        _archive: Option<&Box<dyn Script>>,
46    ) -> Result<Box<dyn Script + Send + Sync>> {
47        Ok(Box::new(BgiArchive::new(
48            MemReader::new(data),
49            archive_encoding,
50            config,
51            filename,
52        )?))
53    }
54
55    fn build_script_from_file(
56        &self,
57        filename: &str,
58        _encoding: Encoding,
59        archive_encoding: Encoding,
60        config: &ExtraConfig,
61        _archive: Option<&Box<dyn Script>>,
62    ) -> Result<Box<dyn Script + Send + Sync>> {
63        if filename == "-" {
64            let data = crate::utils::files::read_file(filename)?;
65            Ok(Box::new(BgiArchive::new(
66                MemReader::new(data),
67                archive_encoding,
68                config,
69                filename,
70            )?))
71        } else {
72            let f = std::fs::File::open(filename)?;
73            let reader = std::io::BufReader::new(f);
74            Ok(Box::new(BgiArchive::new(
75                reader,
76                archive_encoding,
77                config,
78                filename,
79            )?))
80        }
81    }
82
83    fn build_script_from_reader<'a>(
84        &self,
85        reader: Box<dyn ReadSeek + Send + Sync + 'a>,
86        filename: &str,
87        _encoding: Encoding,
88        archive_encoding: Encoding,
89        config: &ExtraConfig,
90        _archive: Option<&Box<dyn Script>>,
91    ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
92        Ok(Box::new(BgiArchive::new(
93            reader,
94            archive_encoding,
95            config,
96            filename,
97        )?))
98    }
99
100    fn extensions(&self) -> &'static [&'static str] {
101        &["arc"]
102    }
103
104    fn script_type(&self) -> &'static ScriptType {
105        &ScriptType::BGIArcV2
106    }
107
108    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
109        if buf_len >= 12 && buf.starts_with(b"BURIKO ARC20") {
110            return Some(255);
111        }
112        None
113    }
114
115    fn is_archive(&self) -> bool {
116        true
117    }
118
119    fn create_archive(
120        &self,
121        filename: &str,
122        files: &[&str],
123        encoding: Encoding,
124        config: &ExtraConfig,
125    ) -> Result<Box<dyn Archive>> {
126        let f = std::fs::File::create(filename)?;
127        let writer = std::io::BufWriter::new(f);
128        Ok(Box::new(BgiArchiveWriter::new(
129            writer, files, encoding, config,
130        )?))
131    }
132}
133
134#[derive(Clone, Debug, StructPack, StructUnpack)]
135struct BgiFileHeader {
136    #[fstring = 0x60]
137    filename: String,
138    offset: u32,
139    size: u32,
140    #[fvec = 8]
141    _unk: Vec<u8>,
142    #[fvec = 16]
143    _padding: Vec<u8>,
144}
145
146#[derive(Debug)]
147struct Entry<T: Read + Seek + std::fmt::Debug> {
148    header: BgiFileHeader,
149    reader: Arc<Mutex<T>>,
150    pos: usize,
151    base_offset: u64,
152    script_type: Option<ScriptType>,
153}
154
155impl<T: Read + Seek + std::fmt::Debug + Send + Sync> ArchiveContent for Entry<T> {
156    fn name(&self) -> &str {
157        &self.header.filename
158    }
159
160    fn size(&self) -> Option<u64> {
161        Some(self.header.size as u64)
162    }
163
164    fn script_type(&self) -> Option<&ScriptType> {
165        self.script_type.as_ref()
166    }
167
168    fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
169        Ok(Box::new(self))
170    }
171}
172
173impl<T: Read + Seek + std::fmt::Debug> Read for Entry<T> {
174    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
175        let mut reader = self.reader.lock().map_err(|e| {
176            std::io::Error::new(
177                std::io::ErrorKind::Other,
178                format!("Failed to lock mutex: {}", e),
179            )
180        })?;
181        reader.seek(SeekFrom::Start(
182            self.base_offset + self.header.offset as u64 + self.pos as u64,
183        ))?;
184        let bytes_read = buf.len().min(self.header.size as usize - self.pos);
185        if bytes_read == 0 {
186            return Ok(0);
187        }
188        let bytes_read = reader.read(&mut buf[..bytes_read])?;
189        self.pos += bytes_read;
190        Ok(bytes_read)
191    }
192}
193
194impl<T: Read + Seek + std::fmt::Debug> Seek for Entry<T> {
195    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
196        let new_pos = match pos {
197            SeekFrom::Start(offset) => offset as usize,
198            SeekFrom::End(offset) => {
199                if offset < 0 {
200                    if (-offset) as usize > self.header.size as usize {
201                        return Err(std::io::Error::new(
202                            std::io::ErrorKind::InvalidInput,
203                            "Seek from end exceeds file length",
204                        ));
205                    }
206                    self.header.size as usize - (-offset) as usize
207                } else {
208                    self.header.size as usize + offset as usize
209                }
210            }
211            SeekFrom::Current(offset) => {
212                if offset < 0 {
213                    if (-offset) as usize > self.pos {
214                        return Err(std::io::Error::new(
215                            std::io::ErrorKind::InvalidInput,
216                            "Seek from current exceeds current position",
217                        ));
218                    }
219                    self.pos.saturating_sub((-offset) as usize)
220                } else {
221                    self.pos + offset as usize
222                }
223            }
224        };
225        self.pos = new_pos;
226        Ok(self.pos as u64)
227    }
228
229    fn stream_position(&mut self) -> std::io::Result<u64> {
230        Ok(self.pos as u64)
231    }
232}
233
234struct MemEntry<F: Fn(&[u8], usize, &str) -> Option<&'static ScriptType>> {
235    name: String,
236    data: MemReader,
237    detect: F,
238}
239
240impl<F: Fn(&[u8], usize, &str) -> Option<&'static ScriptType>> Read for MemEntry<F> {
241    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
242        self.data.read(buf)
243    }
244}
245
246impl<F: Fn(&[u8], usize, &str) -> Option<&'static ScriptType>> ArchiveContent for MemEntry<F> {
247    fn name(&self) -> &str {
248        &self.name
249    }
250
251    fn size(&self) -> Option<u64> {
252        Some(self.data.data.len() as u64)
253    }
254
255    fn script_type(&self) -> Option<&ScriptType> {
256        (self.detect)(&self.data.data, self.data.data.len(), &self.name)
257    }
258
259    fn data(&mut self) -> Result<Vec<u8>> {
260        Ok(self.data.data.clone())
261    }
262
263    fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + 'a + Send + Sync>> {
264        Ok(Box::new(&mut self.data))
265    }
266}
267
268#[derive(Debug)]
269/// BGI Archive Version 2
270pub struct BgiArchive<'b, T: Read + Seek + std::fmt::Debug + 'b> {
271    reader: Arc<Mutex<T>>,
272    entries: Vec<BgiFileHeader>,
273    base_offset: u64,
274    #[cfg(feature = "bgi-img")]
275    is_sysgrp_arc: bool,
276    _mark: std::marker::PhantomData<&'b ()>,
277}
278
279impl<'b, T: Read + Seek + std::fmt::Debug + 'b> BgiArchive<'b, T> {
280    /// Creates a new BGI Archive from a reader.
281    ///
282    /// * `reader` - The reader to read the archive from.
283    /// * `archive_encoding` - The encoding used for the archive.
284    /// * `config` - Extra configuration options.
285    /// * `filename` - The name of the archive file (used for detecting sysgrp.arc).
286    pub fn new(
287        mut reader: T,
288        archive_encoding: Encoding,
289        _config: &ExtraConfig,
290        _filename: &str,
291    ) -> Result<Self> {
292        let mut header = [0u8; 12];
293        reader.read_exact(&mut header)?;
294        if !header.starts_with(b"BURIKO ARC20") {
295            return Err(anyhow::anyhow!("Invalid BGI archive header"));
296        }
297
298        let file_count = reader.read_u32()?;
299        let mut entries = Vec::with_capacity(file_count as usize);
300        for _ in 0..file_count {
301            let entry = BgiFileHeader::unpack(&mut reader, false, archive_encoding, &None)?;
302            entries.push(entry);
303        }
304
305        #[cfg(feature = "bgi-img")]
306        let is_sysgrp_arc = _config.bgi_is_sysgrp_arc.unwrap_or_else(|| {
307            std::path::Path::new(&_filename.to_lowercase())
308                .file_name()
309                .map(|f| f == "sysgrp.arc")
310                .unwrap_or(false)
311        });
312
313        Ok(BgiArchive {
314            reader: Arc::new(Mutex::new(reader)),
315            entries,
316            base_offset: 16 + (file_count as u64 * 0x80),
317            #[cfg(feature = "bgi-img")]
318            is_sysgrp_arc,
319            _mark: std::marker::PhantomData,
320        })
321    }
322}
323
324impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script for BgiArchive<'b, T> {
325    fn default_output_script_type(&self) -> OutputScriptType {
326        OutputScriptType::Json
327    }
328
329    fn default_format_type(&self) -> FormatOptions {
330        FormatOptions::None
331    }
332
333    fn is_archive(&self) -> bool {
334        true
335    }
336
337    fn iter_archive_filename<'a>(
338        &'a self,
339    ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
340        Ok(Box::new(
341            self.entries.iter().map(|e| Ok(e.filename.clone())),
342        ))
343    }
344
345    fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
346        Ok(Box::new(self.entries.iter().map(|e| Ok(e.offset as u64))))
347    }
348
349    fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
350        if index >= self.entries.len() {
351            return Err(anyhow::anyhow!(
352                "Index out of bounds: {} (max: {})",
353                index,
354                self.entries.len()
355            ));
356        }
357        let entry = &self.entries[index];
358        let mut entry = Entry {
359            header: entry.clone(),
360            reader: self.reader.clone(),
361            pos: 0,
362            base_offset: self.base_offset,
363            script_type: None,
364        };
365        let mut buf = [0u8; 32];
366        match entry.read(&mut buf) {
367            Ok(_) => {}
368            Err(e) => {
369                return Err(anyhow::anyhow!(
370                    "Failed to read entry '{}': {}",
371                    entry.header.filename,
372                    e
373                ));
374            }
375        }
376        entry.pos = 0;
377        if buf.starts_with(b"DSC FORMAT 1.00") {
378            let data = match entry.data() {
379                Ok(data) => data,
380                Err(e) => {
381                    return Err(anyhow::anyhow!(
382                        "Failed to read DSC data for '{}': {}",
383                        entry.header.filename,
384                        e
385                    ));
386                }
387            };
388            entry.pos = 0;
389            let dsc = match DscDecoder::new(&data) {
390                Ok(dsc) => dsc,
391                Err(e) => {
392                    return Err(anyhow::anyhow!(
393                        "Failed to create DSC decoder for '{}': {}",
394                        entry.header.filename,
395                        e
396                    ));
397                }
398            };
399            let decoded = match dsc.unpack() {
400                Ok(decoded) => decoded,
401                Err(e) => {
402                    return Err(anyhow::anyhow!(
403                        "Failed to unpack DSC data for '{}': {}",
404                        entry.header.filename,
405                        e
406                    ));
407                }
408            };
409            let reader = MemReader::new(decoded);
410            if reader.data.starts_with(b"BSE 1.") {
411                match BseReader::new(reader, detect_script_type, &entry.header.filename) {
412                    Ok(bse_reader) => {
413                        return Ok(Box::new(bse_reader));
414                    }
415                    Err(e) => {
416                        return Err(anyhow::anyhow!(
417                            "Failed to create BSE reader for '{}': {}",
418                            entry.header.filename,
419                            e
420                        ));
421                    }
422                };
423            }
424            return Ok(Box::new(MemEntry {
425                name: entry.header.filename.clone(),
426                data: reader,
427                #[cfg(feature = "bgi-img")]
428                detect: if self.is_sysgrp_arc {
429                    detect_script_type_sysgrp
430                } else {
431                    detect_script_type
432                },
433                #[cfg(not(feature = "bgi-img"))]
434                detect: detect_script_type,
435            }));
436        }
437        if buf.starts_with(b"BSE 1.") {
438            let filename = entry.header.filename.clone();
439            #[cfg(feature = "bgi-img")]
440            let detect = if self.is_sysgrp_arc {
441                detect_script_type_sysgrp
442            } else {
443                detect_script_type
444            };
445            #[cfg(not(feature = "bgi-img"))]
446            let detect = detect_script_type;
447            match BseReader::new(entry, detect, &filename) {
448                Ok(mut bse_reader) => {
449                    if bse_reader.is_dsc() {
450                        let data = match bse_reader.data() {
451                            Ok(data) => data,
452                            Err(e) => {
453                                return Err(anyhow::anyhow!(
454                                    "Failed to read BSE data for '{}': {}",
455                                    &filename,
456                                    e
457                                ));
458                            }
459                        };
460                        let dsc = match DscDecoder::new(&data) {
461                            Ok(dsc) => dsc,
462                            Err(e) => {
463                                return Err(anyhow::anyhow!(
464                                    "Failed to create DSC decoder for '{}': {}",
465                                    &filename,
466                                    e
467                                ));
468                            }
469                        };
470                        let decoded = match dsc.unpack() {
471                            Ok(decoded) => decoded,
472                            Err(e) => {
473                                return Err(anyhow::anyhow!(
474                                    "Failed to unpack DSC data for '{}': {}",
475                                    &filename,
476                                    e
477                                ));
478                            }
479                        };
480                        let reader = MemReader::new(decoded);
481                        return Ok(Box::new(MemEntry {
482                            name: filename,
483                            data: reader,
484                            detect,
485                        }));
486                    }
487                    return Ok(Box::new(bse_reader));
488                }
489                Err(e) => {
490                    return Err(anyhow::anyhow!(
491                        "Failed to create BSE reader for '{}': {}",
492                        &filename,
493                        e
494                    ));
495                }
496            };
497        }
498        #[cfg(feature = "bgi-img")]
499        if self.is_sysgrp_arc {
500            entry.script_type = Some(ScriptType::BGIImg);
501        } else {
502            entry.script_type =
503                detect_script_type(&buf, buf.len(), &entry.header.filename).cloned();
504        }
505        #[cfg(not(feature = "bgi-img"))]
506        {
507            entry.script_type =
508                detect_script_type(&buf, buf.len(), &entry.header.filename).cloned();
509        }
510        Ok(Box::new(entry))
511    }
512}
513
514fn detect_script_type(buf: &[u8], buf_len: usize, filename: &str) -> Option<&'static ScriptType> {
515    if buf_len >= 28 && buf.starts_with(b"BurikoCompiledScriptVer1.00\0") {
516        return Some(&ScriptType::BGI);
517    }
518    #[cfg(feature = "bgi-img")]
519    if buf_len >= 16 && buf.starts_with(b"CompressedBG___") {
520        return Some(&ScriptType::BGICbg);
521    }
522    #[cfg(feature = "bgi-audio")]
523    if buf_len >= 8 && buf[4..].starts_with(b"bw  ") {
524        return Some(&ScriptType::BGIAudio);
525    }
526    let filename = filename.to_lowercase();
527    if filename.ends_with("._bp") {
528        return Some(&ScriptType::BGIBp);
529    } else if filename.ends_with("._bsi") {
530        return Some(&ScriptType::BGIBsi);
531    }
532    None
533}
534
535#[cfg(feature = "bgi-img")]
536fn detect_script_type_sysgrp(
537    _buf: &[u8],
538    _buf_len: usize,
539    _filename: &str,
540) -> Option<&'static ScriptType> {
541    Some(&ScriptType::BGIImg)
542}
543
544/// BGI Archive Writer for Version 2
545pub struct BgiArchiveWriter<T: Write + Seek> {
546    writer: Arc<Mutex<T>>,
547    headers: Arc<Mutex<HashMap<String, BgiFileHeader>>>,
548    compress_file: bool,
549    encoding: Encoding,
550    compress_level: u8,
551    runner: ThreadPool<Result<()>>,
552}
553
554impl<T: Write + Seek> BgiArchiveWriter<T> {
555    /// Creates a new BGI Archive Writer.
556    ///
557    /// * `writer` - The writer to write the archive to.
558    /// * `files` - The list of files to include in the archive.
559    /// * `encoding` - The encoding used for the archive.
560    /// * `config` - Extra configuration options.
561    pub fn new(
562        mut writer: T,
563        files: &[&str],
564        encoding: Encoding,
565        config: &ExtraConfig,
566    ) -> Result<Self> {
567        writer.write_all(b"BURIKO ARC20")?;
568        let file_count = files.len();
569        writer.write_u32(file_count as u32)?;
570        let mut headers = HashMap::new();
571        for file in files {
572            let header = BgiFileHeader {
573                filename: file.to_string(),
574                offset: 0,
575                size: 0,
576                _unk: vec![0; 8],
577                _padding: vec![0; 16],
578            };
579            header.pack(&mut writer, false, encoding, &None)?;
580            headers.insert(file.to_string(), header);
581        }
582        Ok(BgiArchiveWriter {
583            writer: Arc::new(Mutex::new(writer)),
584            headers: Arc::new(Mutex::new(headers)),
585            compress_file: config.bgi_compress_file,
586            encoding,
587            compress_level: config.bgi_compress_level,
588            runner: ThreadPool::new(
589                if config.bgi_compress_file {
590                    config.bgi_arc_workers
591                } else {
592                    1
593                },
594                Some("bgi-arc-writer"),
595                false,
596            )?,
597        })
598    }
599}
600
601impl<T: Write + Seek + Send + Sync + 'static> Archive for BgiArchiveWriter<T> {
602    fn new_file<'a>(
603        &'a mut self,
604        name: &str,
605        size: Option<u64>,
606    ) -> Result<Box<dyn WriteSeek + 'a>> {
607        let mut entry = self
608            .headers
609            .lock_blocking()
610            .get(name)
611            .ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?
612            .clone();
613        if self.compress_file {
614            let inner = self.new_file_non_seek(name, size)?;
615            Ok(Box::new(Writer {
616                inner,
617                mem: MemWriter::new(),
618            }))
619        } else {
620            let mut writer = self.writer.lock_blocking();
621            let offset = writer.seek(SeekFrom::End(0))?;
622            entry.offset = offset as u32;
623            Ok(Box::new(BgiArchiveFile {
624                header: entry,
625                writer: self.writer.clone(),
626                pos: 0,
627                headers: self.headers.clone(),
628                name: name.to_owned(),
629            }))
630        }
631    }
632
633    fn new_file_non_seek<'a>(
634        &'a mut self,
635        name: &str,
636        size: Option<u64>,
637    ) -> Result<Box<dyn Write + 'a>> {
638        if !self.compress_file {
639            return Ok(Box::new(self.new_file(name, size)?));
640        }
641        for err in self.runner.take_results() {
642            err?;
643        }
644        let mut entry = self
645            .headers
646            .lock_blocking()
647            .get(name)
648            .ok_or_else(|| anyhow::anyhow!("File '{}' not found in archive", name))?
649            .clone();
650        let (reader, writer) = std::io::pipe()?;
651        let file = self.writer.clone();
652        let headers = self.headers.clone();
653        let compress_level = self.compress_level;
654        let name = name.to_owned();
655        self.runner.execute(
656            move |_| {
657                let mut reader = reader;
658                let mut data = Vec::new();
659                reader.read_to_end(&mut data)?;
660                let mut buf = MemWriter::new();
661                {
662                    let mut b = std::io::BufWriter::new(&mut buf);
663                    DscEncoder::new(&mut b, compress_level).pack(&data)?;
664                }
665                let mut writer = file.lock_blocking();
666                let offset = writer.seek(SeekFrom::End(0))?;
667                entry.offset = offset as u32;
668                writer.write_all(&buf.data)?;
669                entry.size = buf.data.len() as u32;
670                headers.lock_blocking().insert(name, entry);
671                Ok(())
672            },
673            true,
674        )?;
675        Ok(Box::new(writer))
676    }
677
678    fn write_header(&mut self) -> Result<()> {
679        self.runner.join();
680        for err in self.runner.take_results() {
681            err?;
682        }
683        let mut writer = self.writer.lock_blocking();
684        let mut headers = self.headers.lock_blocking();
685        writer.seek(SeekFrom::Start(0x10))?;
686        let base_offset = headers.len() as u32 * 0x80 + 16;
687        let mut files = headers.iter_mut().map(|(_, d)| d).collect::<Vec<_>>();
688        files.sort_by_key(|f| f.offset);
689        for file in files {
690            file.offset -= base_offset;
691            file.pack(writer.deref_mut(), false, self.encoding, &None)?;
692        }
693        Ok(())
694    }
695}
696
697/// BGI Archive File Writer (Not compressed)
698pub struct BgiArchiveFile<T: Write + Seek> {
699    header: BgiFileHeader,
700    writer: Arc<Mutex<T>>,
701    pos: usize,
702    headers: Arc<Mutex<HashMap<String, BgiFileHeader>>>,
703    name: String,
704}
705
706impl<T: Write + Seek> Write for BgiArchiveFile<T> {
707    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
708        let mut writer = self.writer.lock_blocking();
709        writer.seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
710        let bytes_written = writer.write(buf)?;
711        self.pos += bytes_written;
712        self.header.size = self.header.size.max(self.pos as u32);
713        Ok(bytes_written)
714    }
715
716    fn flush(&mut self) -> std::io::Result<()> {
717        self.writer.lock_blocking().flush()
718    }
719}
720
721impl<T: Write + Seek> Seek for BgiArchiveFile<T> {
722    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
723        let new_pos = match pos {
724            SeekFrom::Start(offset) => offset as usize,
725            SeekFrom::End(offset) => {
726                if offset < 0 {
727                    if (-offset) as usize > self.header.size as usize {
728                        return Err(std::io::Error::new(
729                            std::io::ErrorKind::InvalidInput,
730                            "Seek from end exceeds file length",
731                        ));
732                    }
733                    self.header.size as usize - (-offset) as usize
734                } else {
735                    self.header.size as usize + offset as usize
736                }
737            }
738            SeekFrom::Current(offset) => {
739                if offset < 0 {
740                    if (-offset) as usize > self.pos {
741                        return Err(std::io::Error::new(
742                            std::io::ErrorKind::InvalidInput,
743                            "Seek from current exceeds current position",
744                        ));
745                    }
746                    self.pos.saturating_sub((-offset) as usize)
747                } else {
748                    self.pos + offset as usize
749                }
750            }
751        };
752        self.pos = new_pos;
753        Ok(self.pos as u64)
754    }
755}
756
757impl<T: Write + Seek> Drop for BgiArchiveFile<T> {
758    fn drop(&mut self) {
759        self.headers
760            .lock_blocking()
761            .insert(self.name.clone(), self.header.clone());
762    }
763}
764struct Writer<'a> {
765    inner: Box<dyn Write + 'a>,
766    mem: MemWriter,
767}
768
769impl std::fmt::Debug for Writer<'_> {
770    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
771        f.debug_struct("Writer").field("mem", &self.mem).finish()
772    }
773}
774
775impl<'a> Write for Writer<'a> {
776    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
777        self.mem.write(buf)
778    }
779
780    fn flush(&mut self) -> std::io::Result<()> {
781        self.mem.flush()
782    }
783}
784
785impl<'a> Seek for Writer<'a> {
786    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
787        self.mem.seek(pos)
788    }
789
790    fn stream_position(&mut self) -> std::io::Result<u64> {
791        self.mem.stream_position()
792    }
793
794    fn rewind(&mut self) -> std::io::Result<()> {
795        self.mem.rewind()
796    }
797}
798
799impl<'a> Drop for Writer<'a> {
800    fn drop(&mut self) {
801        let _ = self.inner.write_all(&self.mem.data);
802        let _ = self.inner.flush();
803    }
804}