1use crate::header::{Directory, Entry, FileMetadata};
2use crate::private::Sealed;
3use crate::{cfg_fs, cfg_integrity, split_path};
4use async_trait::async_trait;
5use pin_project::pin_project;
6use std::io::{Cursor, SeekFrom};
7use std::pin::Pin;
8use std::task::{Context, Poll};
9use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, Take};
10
11cfg_fs! {
12 use std::path::{Path, PathBuf};
13 use tokio::fs::File as TokioFile;
14}
15
16cfg_integrity! {
17 use sha2::digest::Digest;
18 use sha2::Sha256;
19}
20
21#[derive(Debug)]
27pub struct Archive<R: AsyncRead + AsyncSeek + Unpin> {
28 pub(crate) offset: u64,
29 pub(crate) header: Directory,
30 pub(crate) reader: R,
31}
32
33pub async fn check_asar_format(reader: &mut (impl AsyncRead + Unpin)) -> io::Result<Option<u32>> {
37 let [mut four, mut i1, mut i2, mut header_len] = [0; 4];
38 for x in [&mut four, &mut i1, &mut i2, &mut header_len] {
39 *x = reader.read_u32_le().await?;
40 }
41
42 let padding = match header_len % 4 {
43 0 => 0,
44 r => 4 - r,
45 };
46
47 let i1_e = header_len + padding + 8;
48 let i2_e = header_len + padding + 4;
49
50 if four == 4 && i1 == i1_e && i2 == i2_e {
51 Ok(Some(header_len))
52 } else {
53 Ok(None)
54 }
55}
56
57impl<R: AsyncRead + AsyncSeek + Unpin> Archive<R> {
58 pub async fn new(mut reader: R) -> io::Result<Self> {
60 let header_len = check_asar_format(&mut reader)
61 .await?
62 .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "file format check failed"))?;
63
64 let mut header_bytes = vec![0; header_len as _];
65 reader.read_exact(&mut header_bytes).await?;
66
67 let header = serde_json::from_slice(&header_bytes).map_err(io::Error::from)?;
68 let offset = match header_len % 4 {
69 0 => header_len + 16,
70 r => header_len + 16 + 4 - r,
71 } as u64;
72
73 Ok(Self {
74 offset,
75 header,
76 reader,
77 })
78 }
79
80 pub fn reader(&self) -> &R {
82 &self.reader
83 }
84
85 pub fn reader_mut(&mut self) -> &mut R {
91 &mut self.reader
92 }
93
94 pub fn into_reader(self) -> R {
96 self.reader
97 }
98}
99
100cfg_fs! {
101 impl Archive<DuplicableFile> {
102 pub async fn new_from_file(path: impl Into<PathBuf>) -> io::Result<Self> {
104 Self::new(DuplicableFile::open(path).await?).await
105 }
106 }
107}
108
109impl<R: AsyncRead + AsyncSeek + Unpin> Archive<R> {
110 pub async fn get(&mut self, path: &str) -> io::Result<File<&mut R>> {
112 let entry = self.header.search_segments(&split_path(path));
113 match entry {
114 Some(Entry::File(metadata)) => {
115 (self.reader)
116 .seek(SeekFrom::Start(self.offset + metadata.offset()?))
117 .await?;
118 Ok(File {
119 offset: self.offset,
120 metadata: metadata.clone(),
121 content: (&mut self.reader).take(metadata.size),
122 })
123 }
124 Some(Entry::Directory(_)) => Err(io::Error::from_raw_os_error(libc::EISDIR)),
125 None => Err(io::ErrorKind::NotFound.into()),
126 }
127 }
128
129 pub fn get_entry(&self, path: &str) -> Option<&Entry> {
131 self.header.search_segments(&split_path(path))
132 }
133}
134
135macro_rules! impl_get_owned {
136 (
137 $(#[$attr:ident $($args:tt)*])*
138 $get_owned:ident,
139 $duplicate:ident $(,)?
140 ) => {
141 impl<R: AsyncRead + AsyncSeek + $duplicate + Unpin> Archive<R> {
142 $(#[$attr $($args)*])*
143 pub async fn $get_owned(&self, path: &str) -> io::Result<File<R>> {
144 let entry = self.header.search_segments(&split_path(path));
145 match entry {
146 Some(Entry::File(metadata)) => {
147 let mut file = self.reader.duplicate().await?;
148 let seek_from = SeekFrom::Start(self.offset + metadata.offset()?);
149 file.seek(seek_from).await?;
150 Ok(File {
151 offset: self.offset,
152 metadata: metadata.clone(),
153 content: file.take(metadata.size),
154 })
155 }
156 Some(_) => Err(io::Error::from_raw_os_error(libc::EISDIR)),
157 None => Err(io::Error::from_raw_os_error(libc::ENOENT)),
158 }
159 }
160 }
161 }
162}
163
164impl_get_owned! {
165 get_owned,
171 Duplicable,
172}
173
174impl_get_owned! {
175 get_owned_local,
179 LocalDuplicable,
180}
181
182cfg_fs! {
183 impl<R: AsyncRead + AsyncSeek + Send + Unpin> Archive<R> {
184 pub async fn extract(&mut self, path: impl AsRef<Path>) -> io::Result<()> {
186 let path = path.as_ref();
187 for (name, entry) in self.header.files.iter() {
188 crate::extract::extract_entry(&mut self.reader, self.offset, name, entry, path).await?;
189 }
190 Ok(())
191 }
192 }
193
194 impl<R: AsyncRead + AsyncSeek + Unpin> Archive<R> {
195 pub async fn extract_local(&mut self, path: impl AsRef<Path>) -> io::Result<()> {
200 let path = path.as_ref();
201 for (name, entry) in self.header.files.iter() {
202 crate::extract::extract_entry_local(&mut self.reader, self.offset, name, entry, path).await?;
203 }
204 Ok(())
205 }
206 }
207}
208
209#[pin_project]
211pub struct File<R: AsyncRead + AsyncSeek + Unpin> {
212 pub(crate) offset: u64,
213 pub(crate) metadata: FileMetadata,
214 #[pin]
215 pub(crate) content: Take<R>,
216}
217
218impl<R: AsyncRead + AsyncSeek + Unpin> File<R> {
219 pub fn metadata(&self) -> &FileMetadata {
221 &self.metadata
222 }
223
224 cfg_integrity! {
225 pub async fn check_integrity(&mut self) -> io::Result<bool> {
226 if let Some(integrity) = &self.metadata.integrity {
227 let block_size = integrity.block_size;
228 let mut block = Vec::with_capacity(block_size as _);
229 let mut global_state = Sha256::new();
230 let mut size = 0;
231
232 for block_hash in integrity.blocks.iter() {
233 let read_size = (&mut self.content)
234 .take(block_size as _)
235 .read_to_end(&mut block)
236 .await?;
237 if read_size == 0 || *Sha256::digest(&block) != **block_hash {
238 self.rewind().await?;
239 return Ok(false);
240 }
241 size += read_size;
242 global_state.update(&block);
243 block.clear();
244 }
245 if self.metadata.size != size as u64 || *global_state.finalize() != *integrity.hash {
246 self.rewind().await?;
247 return Ok(false);
248 }
249
250 self.rewind().await?;
251 }
252 Ok(true)
253 }
254 }
255}
256
257impl<R: AsyncRead + AsyncSeek + Unpin> AsyncRead for File<R> {
258 fn poll_read(
259 self: Pin<&mut Self>,
260 cx: &mut Context<'_>,
261 buf: &mut io::ReadBuf<'_>,
262 ) -> Poll<io::Result<()>> {
263 self.project().content.poll_read(cx, buf)
264 }
265}
266
267impl<R: AsyncRead + AsyncSeek + Unpin> AsyncSeek for File<R> {
268 fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
269 let current_relative_pos = self.metadata.size - self.content.limit();
270 let offset = self.offset + self.metadata.offset()?;
271 let absolute_pos = match position {
272 SeekFrom::Start(pos) => SeekFrom::Start(offset + self.metadata.size.min(pos)),
273 SeekFrom::Current(pos) if -pos as u64 > current_relative_pos => {
274 return Err(io::Error::from_raw_os_error(libc::EINVAL))
275 }
276 SeekFrom::Current(pos) => {
277 let relative_pos = pos.min((self.metadata.size - current_relative_pos) as i64);
278 SeekFrom::Current(relative_pos)
279 }
280 SeekFrom::End(pos) if pos > 0 => SeekFrom::Start(offset + self.metadata.size),
281 SeekFrom::End(pos) if -pos as u64 > self.metadata.size => {
282 return Err(io::Error::from_raw_os_error(libc::EINVAL))
283 }
284 SeekFrom::End(pos) => SeekFrom::Start(offset + self.metadata.size - (-pos as u64)),
285 };
286 Pin::new(self.content.get_mut()).start_seek(absolute_pos)
287 }
288
289 fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
290 let result = Pin::new(self.content.get_mut()).poll_complete(cx);
291 match result {
292 Poll::Ready(Ok(result)) => {
293 let new_relative_pos = result - self.offset - self.metadata.offset()?;
294 let new_limit = self.metadata.size - new_relative_pos;
295 self.content.set_limit(new_limit);
296 Poll::Ready(Ok(new_relative_pos))
297 }
298 other => other,
299 }
300 }
301}
302
303#[async_trait]
311pub trait Duplicable: Sealed + Sized {
312 async fn duplicate(&self) -> io::Result<Self>;
313}
314
315#[async_trait]
316impl<T: Clone + Sync> Duplicable for Cursor<T> {
317 async fn duplicate(&self) -> io::Result<Self> {
318 Ok(self.clone())
319 }
320}
321
322#[async_trait(?Send)]
326pub trait LocalDuplicable: Sealed + Sized {
327 async fn duplicate(&self) -> io::Result<Self>;
328}
329
330#[async_trait(?Send)]
331impl<T: Clone> LocalDuplicable for Cursor<T> {
332 async fn duplicate(&self) -> io::Result<Self> {
333 Ok(self.clone())
334 }
335}
336
337cfg_fs! {
338 #[pin_project]
345 pub struct DuplicableFile {
346 #[pin]
347 inner: TokioFile,
348 path: PathBuf,
349 }
350
351 impl DuplicableFile {
352 pub async fn open(path: impl Into<PathBuf>) -> io::Result<Self> {
353 let path = path.into();
354 let inner = TokioFile::open(&path).await?;
355 Ok(Self { inner, path })
356 }
357
358 pub async fn path(&self) -> &Path {
359 &self.path
360 }
361
362 pub fn into_inner(self) -> (TokioFile, PathBuf) {
363 (self.inner, self.path)
364 }
365
366 pub async fn rename(&mut self, new_path: impl Into<PathBuf>) -> io::Result<()> {
367 let new_path = new_path.into();
368 tokio::fs::rename(&self.path, &new_path).await?;
369 self.path = new_path;
370 Ok(())
371 }
372 }
373
374 impl AsyncRead for DuplicableFile {
375 fn poll_read(
376 self: Pin<&mut Self>,
377 cx: &mut Context<'_>,
378 buf: &mut io::ReadBuf<'_>,
379 ) -> Poll<std::io::Result<()>> {
380 self.project().inner.poll_read(cx, buf)
381 }
382 }
383
384 impl AsyncSeek for DuplicableFile {
385 fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
386 self.project().inner.start_seek(position)
387 }
388
389 fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<u64>> {
390 self.project().inner.poll_complete(cx)
391 }
392 }
393
394 #[async_trait]
395 impl Duplicable for DuplicableFile {
396 async fn duplicate(&self) -> io::Result<Self> {
397 Ok(Self {
398 inner: TokioFile::open(&self.path).await?,
399 path: self.path.clone(),
400 })
401 }
402 }
403
404 #[async_trait(?Send)]
405 impl LocalDuplicable for DuplicableFile {
406 async fn duplicate(&self) -> io::Result<Self> {
407 Ok(Self {
408 inner: TokioFile::open(&self.path).await?,
409 path: self.path.clone(),
410 })
411 }
412 }
413}