msg_tool\scripts\kirikiri\archive\xp3/
read.rs

1use super::archive::*;
2use super::consts::*;
3use super::crypt::*;
4use crate::ext::io::*;
5use crate::types::*;
6use anyhow::Result;
7use std::io::{Read, Seek, SeekFrom};
8use std::sync::{Arc, Mutex};
9
10impl<'a> Xp3Archive<'a> {
11    pub fn new<T: Read + Seek + Send + Sync + std::fmt::Debug + 'a>(
12        stream: T,
13        config: &ExtraConfig,
14        filename: &str,
15        base_offset: u64,
16    ) -> Result<Self> {
17        #[allow(unused_mut)]
18        let mut crypt: Box<dyn Crypt + Send + Sync> =
19            if let Some(game_title) = &config.xp3_game_title {
20                query_crypt_schema(game_title)
21                    .ok_or_else(|| {
22                        anyhow::anyhow!("Unsupported game title for XP3 archive: {}", game_title)
23                    })?
24                    .create_crypt(filename, config)?
25            } else {
26                Box::new(NoCrypt::new())
27            };
28        let mut stream = Box::new(stream);
29        if base_offset != 0 {
30            stream.seek(SeekFrom::Start(base_offset))?;
31        }
32        stream
33            .read_and_equal(XP3_MAGIC)
34            .map_err(|e| anyhow::anyhow!("Invalid xp3 signature: {}", e))?;
35        let mut index_offset = stream.read_u64()?;
36        let mut minor_version = 0;
37        if index_offset == TVP_XP3_CURRENT_HEADER_VERSION {
38            minor_version = stream.read_u32()?;
39            let sig = stream.read_u8()?;
40            if sig != TVP_XP3_INDEX_CONTINUE {
41                anyhow::bail!("Unsupported XP3 index format: {} is not continue flag", sig);
42            }
43            let index_offset_offset = stream.read_i64()?;
44            if index_offset_offset != 0 {
45                stream.seek_relative(index_offset_offset)?;
46            }
47            index_offset = stream.read_u64()?;
48        }
49        index_offset += base_offset;
50        stream.seek(SeekFrom::Start(index_offset))?;
51        let mut entries = Vec::new();
52        let mut extras = Vec::new();
53        {
54            let mut index_stream = Self::get_index_stream(&mut stream)?;
55            let mut sig = [0u8; 4];
56            loop {
57                let readed = index_stream.read_most(&mut sig)?;
58                if readed == 0 {
59                    break;
60                }
61                if readed < 4 {
62                    anyhow::bail!("Invalid chunk signature in index");
63                }
64                let mut size = index_stream.read_u64()?;
65                if &sig == CHUNK_FILE {
66                    let mut name = None;
67                    let mut flags = None;
68                    let mut file_hash = None;
69                    let mut original_size = None;
70                    let mut archived_size = None;
71                    let mut timestamp = None;
72                    let mut segments = Vec::new();
73                    let mut seg_offset = 0;
74                    let mut entry_extras = Vec::new();
75                    while size > 0 {
76                        if size < 12 {
77                            anyhow::bail!("Invalid chunk size in index");
78                        }
79                        let mut chunk_sig = [0u8; 4];
80                        index_stream.read_exact(&mut chunk_sig)?;
81                        let mut chunk_size = index_stream.read_u64()?;
82                        size -= 12;
83                        if size < chunk_size {
84                            anyhow::bail!("Invalid chunk size in index");
85                        }
86                        size -= chunk_size;
87                        if &chunk_sig == CHUNK_INFO {
88                            if chunk_size < 20 {
89                                anyhow::bail!("Invalid info chunk size in index");
90                            }
91                            flags = Some(index_stream.read_u32()?);
92                            original_size = Some(index_stream.read_u64()?);
93                            archived_size = Some(index_stream.read_u64()?);
94                            chunk_size -= 20;
95                            let (n, s) = crypt.read_name(&mut index_stream)?;
96                            name = Some(n);
97                            chunk_size -= s;
98                        } else if &chunk_sig == CHUNK_ADLR {
99                            if chunk_size == 4 {
100                                file_hash = Some(index_stream.read_u32()?);
101                                chunk_size -= 4;
102                            }
103                        } else if &chunk_sig == CHUNK_SEGM {
104                            while chunk_size > 0 {
105                                if chunk_size < 0x1C {
106                                    anyhow::bail!("Invalid segm chunk size in index");
107                                }
108                                let seg_flags = index_stream.read_u32()?;
109                                let start = index_stream.read_u64()?;
110                                let original_size = index_stream.read_u64()?;
111                                let archived_size = index_stream.read_u64()?;
112                                chunk_size -= 0x1C;
113                                segments.push(Segment {
114                                    is_compressed: seg_flags != 0,
115                                    start,
116                                    offset_in_file: seg_offset,
117                                    original_size,
118                                    archived_size,
119                                });
120                                seg_offset += original_size;
121                            }
122                        } else if &chunk_sig == CHUNK_TIME {
123                            if chunk_size == 8 {
124                                timestamp = Some(index_stream.read_u64()?);
125                                chunk_size -= 8;
126                            }
127                        } else {
128                            let data = index_stream.read_exact_vec(chunk_size as usize)?;
129                            chunk_size = 0;
130                            entry_extras.push(ExtraProp {
131                                tag: chunk_sig.into(),
132                                data,
133                            });
134                        }
135                        if chunk_size > 0 {
136                            index_stream.skip(chunk_size)?;
137                        }
138                    }
139                    let mut entry = Xp3Entry {
140                        name: name
141                            .ok_or_else(|| anyhow::anyhow!("Missing name chunk in file entry"))?,
142                        flags: flags
143                            .ok_or_else(|| anyhow::anyhow!("Missing flags chunk in file entry"))?,
144                        file_hash: file_hash.unwrap_or(0),
145                        original_size: original_size.ok_or_else(|| {
146                            anyhow::anyhow!("Missing original size chunk in file entry")
147                        })?,
148                        archived_size: archived_size.ok_or_else(|| {
149                            anyhow::anyhow!("Missing archived size chunk in file entry")
150                        })?,
151                        timestamp,
152                        segments,
153                        extras: entry_extras,
154                        extra: None,
155                    };
156                    if entry.name == "startup.tjs"
157                        && entry.flags != 0
158                        && crypt.startup_tjs_not_encrypted()
159                    {
160                        entry.flags = 0;
161                    }
162                    entries.push(entry);
163                } else {
164                    let data = index_stream.read_exact_vec(size as usize)?;
165                    let tag = sig.into();
166                    if config.xp3_game_title.is_none() && tag == "Hxv4" {
167                        match Hxv4Crypt::new(filename, config) {
168                            Ok(c) => {
169                                crypt = Box::new(c);
170                            }
171                            Err(e) => {
172                                eprintln!("WARNING: Failed to load filelist.json: {}", e);
173                                crate::COUNTER.inc_warning();
174                            }
175                        }
176                    }
177                    extras.push(ExtraProp { tag, data });
178                }
179            }
180        }
181        let crypt = Arc::new(crypt);
182        let mut archive = Self {
183            inner: Arc::new(Mutex::new(stream)),
184            crypt: crypt.clone(),
185            base_offset,
186            index_offset,
187            minor_version,
188            entries,
189            extras,
190        };
191        crypt.init(&mut archive)?;
192        Ok(archive)
193    }
194
195    fn get_index_stream<'c, 'b, T: Read + Seek + std::fmt::Debug + 'b>(
196        stream: &'c mut Box<T>,
197    ) -> Result<Box<dyn Read + 'c>> {
198        let index_type = stream.read_u8()?;
199        Ok(match index_type {
200            TVP_XP3_INDEX_ENCODE_RAW => {
201                let index_size = stream.read_u64()?;
202                Box::new(StreamRegion::with_size(stream, index_size)?)
203            }
204            TVP_XP3_INDEX_ENCODE_ZLIB => {
205                let packed_size = stream.read_u64()?;
206                let _original_size = stream.read_u64()?;
207                let mut compressed_data = StreamRegion::with_size(stream, packed_size)?;
208                if compressed_data.peek_and_equal(ZSTD_SIGNATURE).is_ok() {
209                    Box::new(zstd::stream::read::Decoder::new(compressed_data)?)
210                } else {
211                    Box::new(flate2::read::ZlibDecoder::new(compressed_data))
212                }
213            }
214            _ => {
215                anyhow::bail!("Unsupported index type: {}", index_type);
216            }
217        })
218    }
219}