use std::io::{self, Seek, Read, SeekFrom, BufReader};
use std::sync::Arc;
use std::fmt;
use crate::util::io::WgReadExt;
#[allow(unused)]
const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50;
const END_OF_CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x06054b50;
pub struct PackageReader<R: Read + Seek> {
inner: R,
name_buffer: Arc<str>,
file_infos: Arc<[PackageFileInfo]>,
}
#[derive(Debug)]
struct PackageFileInfo {
name_offset: u32,
name_len: u16,
header_offset: u32,
}
impl<R: Read + Seek> PackageReader<R> {
pub fn new(mut reader: R) -> io::Result<Self> {
const HEADER_MIN_SIZE: u64 = 22;
const HEADER_MAX_SIZE: u64 = 22 + u16::MAX as u64;
let file_length = reader.seek(SeekFrom::End(0))?;
let mut eocd_pos = file_length.checked_sub(HEADER_MIN_SIZE)
.ok_or(io::Error::from(io::ErrorKind::InvalidData))?;
let eocd_pos_bound = file_length.saturating_sub(HEADER_MAX_SIZE);
loop {
reader.seek(SeekFrom::Start(eocd_pos))?;
if reader.read_u32()? == END_OF_CENTRAL_DIRECTORY_SIGNATURE {
break;
}
if eocd_pos == eocd_pos_bound {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
eocd_pos = eocd_pos.checked_sub(1)
.ok_or(io::Error::from(io::ErrorKind::InvalidData))?;
}
let disk_number = reader.read_u16()?;
let disk_with_central_directory = reader.read_u16()?;
if disk_number != disk_with_central_directory {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
let number_of_files_on_this_disk = reader.read_u16()?;
let number_of_files = reader.read_u16()?;
if number_of_files_on_this_disk != number_of_files {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
let _central_directory_size = reader.read_u32()?;
let central_directory_offset = reader.read_u32()?;
let comment_length = reader.read_u16()?;
if comment_length != 0 {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
reader.seek(SeekFrom::Start(central_directory_offset as u64))?;
let mut reader = BufReader::new(reader);
let mut name_buffer = Vec::with_capacity(number_of_files as usize * 70);
let mut file_infos = Vec::with_capacity(number_of_files as usize);
for _ in 0..number_of_files {
if reader.read_u32()? != CENTRAL_DIRECTORY_HEADER_SIGNATURE {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
reader.seek_relative(24)?;
let file_name_len = reader.read_u16()?;
let extra_field_file_comment_len = reader.read_u32()?;
reader.seek_relative(8)?;
let relative_offset = reader.read_u32()?;
if extra_field_file_comment_len != 0 {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
let name_offset = name_buffer.len() as u32; name_buffer.resize(name_buffer.len() + file_name_len as usize, 0);
let this_name_buffer = &mut name_buffer[name_offset as usize..][..file_name_len as usize];
reader.read_exact(this_name_buffer)?;
if let None | Some(b'/') = this_name_buffer.last() {
name_buffer.truncate(name_offset as usize);
continue;
}
file_infos.push(PackageFileInfo {
name_offset,
name_len: file_name_len,
header_offset: relative_offset,
});
}
let name_buffer = String::from_utf8(name_buffer).unwrap();
Ok(Self {
inner: reader.into_inner(),
name_buffer: Arc::from(name_buffer),
file_infos: Arc::from(file_infos),
})
}
pub fn clone_with<NewR: Read + Seek>(&self, reader: NewR) -> PackageReader<NewR> {
PackageReader {
inner: reader,
name_buffer: Arc::clone(&self.name_buffer),
file_infos: Arc::clone(&self.file_infos),
}
}
#[inline]
pub fn len(&self) -> usize {
self.file_infos.len()
}
pub fn names(&self) -> impl Iterator<Item = &'_ str> + '_ {
self.file_infos.iter().map(|file| {
&self.name_buffer[file.name_offset as usize..][..file.name_len as usize]
})
}
pub fn index_by_name(&self, file_name: &str) -> Option<usize> {
self.names().position(|check| check == file_name)
}
pub fn read_by_name(&mut self, file_name: &str) -> io::Result<PackageFileReader<&'_ mut R>> {
let file_index = self.index_by_name(file_name)
.ok_or(io::Error::from(io::ErrorKind::NotFound))?;
self.read_by_index(file_index)
}
pub fn read_by_index(&mut self, file_index: usize) -> io::Result<PackageFileReader<&'_ mut R>> {
let info = self.file_infos.get(file_index)
.ok_or(io::Error::from(io::ErrorKind::NotFound))?;
self.inner.seek(SeekFrom::Start(info.header_offset as u64))?;
if self.inner.read_u32()? != LOCAL_FILE_HEADER_SIGNATURE {
return Err(io::ErrorKind::InvalidData.into());
}
self.inner.seek(SeekFrom::Current(2))?;
let flags = self.inner.read_u16()?;
let compression_method = self.inner.read_u16()?;
self.inner.seek(SeekFrom::Current(2 + 2 + 4))?;
let compressed_size = self.inner.read_u32()?;
let uncompressed_size = self.inner.read_u32()?;
self.inner.seek(SeekFrom::Current(4 + info.name_len as i64))?;
if flags != 0 {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
if compression_method != 0 || compressed_size != uncompressed_size {
return Err(io::Error::from(io::ErrorKind::InvalidData));
}
Ok(PackageFileReader {
inner: &mut self.inner,
initial_len: compressed_size,
remaining_len: compressed_size,
})
}
}
#[derive(Debug)]
pub struct PackageFileReader<R: Read + Seek> {
inner: R,
initial_len: u32,
remaining_len: u32,
}
impl<R: Read + Seek> PackageFileReader<R> {
pub fn try_clone_with<NewR: Read + Seek>(&mut self, mut reader: NewR) -> io::Result<PackageFileReader<NewR>> {
reader.seek(SeekFrom::Start(self.inner.stream_position()?))?;
Ok(PackageFileReader {
inner: reader,
initial_len: self.initial_len,
remaining_len: self.remaining_len,
})
}
}
impl<R: Read + Seek> Read for PackageFileReader<R> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = buf.len().min(self.remaining_len as usize);
let len = self.inner.read(&mut buf[..len])?;
self.remaining_len -= len as u32;
Ok(len)
}
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
if (self.remaining_len as usize) < buf.len() {
return Err(io::ErrorKind::UnexpectedEof.into());
}
self.inner.read_exact(buf)?;
self.remaining_len -= buf.len() as u32;
Ok(())
}
}
impl<R: Read + Seek> Seek for PackageFileReader<R> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let position = self.initial_len - self.remaining_len;
let delta = match pos {
SeekFrom::Start(offset) => {
if (self.initial_len as u64) < offset {
return Err(io::ErrorKind::InvalidInput.into());
}
-(position as i64) + offset as i64
}
SeekFrom::End(offset) => {
if offset > 0 || offset < -(self.initial_len as i64) {
return Err(io::ErrorKind::InvalidInput.into());
}
(self.remaining_len as i64) + offset
}
SeekFrom::Current(offset) => {
if offset > 0 && (self.remaining_len as i64) < offset {
return Err(io::ErrorKind::InvalidInput.into());
} else if offset < 0 && (position as i64) < -offset {
return Err(io::ErrorKind::InvalidInput.into());
}
offset
}
};
self.inner.seek(SeekFrom::Current(delta))?;
self.remaining_len = (self.remaining_len as i64 - delta) as u32;
Ok((self.initial_len - self.remaining_len) as u64)
}
#[inline]
fn stream_position(&mut self) -> io::Result<u64> {
Ok((self.initial_len - self.remaining_len) as u64)
}
}
impl<R: Read + Seek + fmt::Debug> fmt::Debug for PackageReader<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PackageReader")
.field("inner", &self.inner)
.field("name_buffer", &self.name_buffer.len())
.field("file_infos", &self.file_infos.len()).finish()
}
}