use std::convert::TryFrom;
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use flate2::read::ZlibEncoder;
use flate2::Compression;
use num_enum::TryFromPrimitive;
use crate::{Error, Result};
pub(crate) const SECTOR_SIZE: usize = 4096;
pub(crate) const REGION_HEADER_SIZE: usize = 2 * SECTOR_SIZE;
pub(crate) const CHUNK_HEADER_SIZE: usize = 5;
#[derive(Clone)]
pub struct Region<S> {
stream: S,
offsets: Vec<u64>,
}
impl<S> Region<S>
where
S: Read + Seek,
{
pub fn from_stream(stream: S) -> Result<Self> {
let mut tmp = Self {
stream,
offsets: vec![],
};
let mut max_offset = 0;
let mut max_offsets_sector_count = 0;
for z in 0..32 {
for x in 0..32 {
let Some(loc) = tmp.location(x, z)? else {
continue;
};
tmp.offsets.push(loc.offset);
if loc.offset > max_offset {
max_offset = loc.offset;
max_offsets_sector_count = loc.sectors;
}
}
}
tmp.offsets.sort_unstable();
tmp.offsets.push(max_offset + max_offsets_sector_count);
Ok(tmp)
}
pub fn read_chunk(&mut self, x: usize, z: usize) -> Result<Option<Vec<u8>>> {
self.compression_scheme(x, z)?
.map(|scheme| match scheme {
CompressionScheme::Zlib => {
let mut decoder = flate2::write::ZlibDecoder::new(vec![]);
self.read_compressed_chunk(x, z, &mut decoder)?;
Ok(decoder.finish()?)
}
CompressionScheme::Gzip => {
let mut decoder = flate2::write::GzDecoder::new(vec![]);
self.read_compressed_chunk(x, z, &mut decoder)?;
Ok(decoder.finish()?)
}
CompressionScheme::Uncompressed => {
let mut buf = vec![];
self.read_compressed_chunk(x, z, &mut buf)?;
Ok(buf)
}
CompressionScheme::Lz4 => {
let mut decoder = Lz4DecoderWrapper::new(vec![]);
self.read_compressed_chunk(x, z, &mut decoder)?;
Ok(decoder.finish()?)
}
})
.transpose()
}
pub(crate) fn location(&mut self, x: usize, z: usize) -> Result<Option<ChunkLocation>> {
if x >= 32 || z >= 32 {
return Err(Error::InvalidOffset(x as isize, z as isize));
}
self.stream.seek(SeekFrom::Start(header_pos(x, z)))?;
let mut buf = [0u8; 4];
self.stream.read_exact(&mut buf[..])?;
let mut offset = 0u64;
offset |= (buf[0] as u64) << 16;
offset |= (buf[1] as u64) << 8;
offset |= buf[2] as u64;
let sectors = buf[3] as u64;
Ok((offset != 0 || sectors != 0).then_some(ChunkLocation { offset, sectors }))
}
fn read_compressed_chunk(
&mut self,
x: usize,
z: usize,
writer: &mut dyn Write,
) -> Result<bool> {
let Some(loc) = self.location(x, z)? else {
return Ok(false);
};
self.stream
.seek(SeekFrom::Start(loc.offset * SECTOR_SIZE as u64))?;
let mut buf = [0u8; 5];
self.stream.read_exact(&mut buf)?;
let metadata = ChunkMeta::new(&buf)?;
let mut adapted = (&mut self.stream).take(metadata.compressed_len as u64);
io::copy(&mut adapted, writer)?;
Ok(true)
}
pub fn into_inner(mut self) -> io::Result<S> {
self.offsets.pop().unwrap();
let Some(offset) = self.offsets.pop() else {
self.stream
.seek(SeekFrom::Start(REGION_HEADER_SIZE as u64))?;
return Ok(self.stream);
};
self.stream
.seek(SeekFrom::Start(offset * SECTOR_SIZE as u64))?;
let chunk_length = self.stream.read_u32::<BigEndian>()? + 4;
let logical_end = unstable_div_ceil(
offset as usize * SECTOR_SIZE + chunk_length as usize,
SECTOR_SIZE,
) * SECTOR_SIZE;
self.stream.seek(SeekFrom::Start(logical_end as u64))?;
Ok(self.stream)
}
fn compression_scheme(&mut self, x: usize, z: usize) -> Result<Option<CompressionScheme>> {
if x >= 32 || z >= 32 {
return Err(Error::InvalidOffset(x as isize, z as isize));
}
let Some(loc) = self.location(x, z)? else {
return Ok(None);
};
self.stream
.seek(SeekFrom::Start(loc.offset * SECTOR_SIZE as u64))?;
let mut buf = [0u8; 5];
self.stream.read_exact(&mut buf)?;
let metadata = ChunkMeta::new(&buf)?;
Ok(Some(metadata.compression_scheme))
}
pub fn iter(&mut self) -> RegionIter<'_, S> {
RegionIter::new(self)
}
fn chunk_meta(&self, compressed_chunk_size: u32, scheme: CompressionScheme) -> [u8; 5] {
let mut buf = [0u8; 5];
let mut c = Cursor::new(buf.as_mut_slice());
c.write_u32::<BigEndian>(compressed_chunk_size + 1).unwrap();
c.write_u8(scheme as u8).unwrap();
buf
}
}
impl<S> Region<S>
where
S: Read + Write + Seek,
{
pub fn create(mut stream: S) -> Result<Self> {
stream.rewind()?;
stream.write_all(&[0; REGION_HEADER_SIZE])?;
Ok(Self {
stream,
offsets: vec![2], })
}
pub fn write_chunk(&mut self, x: usize, z: usize, uncompressed_chunk: &[u8]) -> Result<()> {
let mut buf = vec![];
let mut enc = ZlibEncoder::new(uncompressed_chunk, Compression::fast());
enc.read_to_end(&mut buf)?;
self.write_compressed_chunk(x, z, CompressionScheme::Zlib, &buf)
}
pub fn write_compressed_chunk(
&mut self,
x: usize,
z: usize,
scheme: CompressionScheme,
compressed_chunk: &[u8],
) -> Result<()> {
let loc = self.location(x, z)?;
let required_sectors =
unstable_div_ceil(CHUNK_HEADER_SIZE + compressed_chunk.len(), SECTOR_SIZE);
if let Some(loc) = loc {
let i = self.offsets.binary_search(&loc.offset).unwrap();
let start_offset = self.offsets[i];
let end_offset = self.offsets[i + 1];
let available_sectors = (end_offset - start_offset) as usize;
if required_sectors <= available_sectors {
self.set_chunk(start_offset, scheme, compressed_chunk)?;
self.set_header(x, z, start_offset, required_sectors)?;
} else {
self.offsets.remove(i); let offset = *self.offsets.last().unwrap();
self.offsets.push(offset + required_sectors as u64);
self.set_chunk(offset, scheme, compressed_chunk)?;
self.pad()?;
self.set_header(x, z, offset, required_sectors)?;
}
} else {
let offset = *self.offsets.last().expect("offset should always exist");
self.offsets.push(offset + required_sectors as u64);
self.set_chunk(offset, scheme, compressed_chunk)?;
self.pad()?;
self.set_header(x, z, offset, required_sectors)?;
}
Ok(())
}
pub fn remove_chunk(&mut self, x: usize, z: usize) -> Result<()> {
let Some(loc) = self.location(x, z)? else {
return Ok(());
};
self.set_header(x, z, 0, 0)?;
let i = self.offsets.binary_search(&loc.offset).unwrap();
self.offsets.remove(i);
Ok(())
}
fn set_chunk(&mut self, offset: u64, scheme: CompressionScheme, chunk: &[u8]) -> Result<()> {
self.stream
.seek(SeekFrom::Start(offset * SECTOR_SIZE as u64))?;
self.stream.write_all(&self.chunk_meta(
chunk.len() as u32, scheme,
))?;
self.stream.write_all(chunk)?;
Ok(())
}
fn pad(&mut self) -> Result<()> {
let current_end = unstable_stream_len(&mut self.stream)? as usize;
let padded_end = unstable_div_ceil(current_end, SECTOR_SIZE) * SECTOR_SIZE;
let pad_len = padded_end - current_end;
self.stream.write_all(&vec![0; pad_len])?;
Ok(())
}
fn set_header(
&mut self,
x: usize,
z: usize,
offset: u64,
new_sector_count: usize,
) -> Result<()> {
if new_sector_count > 255 {
return Err(Error::ChunkTooLarge);
}
let mut buf = [0u8; 4];
buf[0] = ((offset & 0xFF0000) >> 16) as u8;
buf[1] = ((offset & 0x00FF00) >> 8) as u8;
buf[2] = (offset & 0x0000FF) as u8;
buf[3] = new_sector_count as u8;
self.stream.seek(SeekFrom::Start(header_pos(x, z)))?;
self.stream.write_all(&buf)?;
Ok(())
}
}
#[derive(Debug, TryFromPrimitive)]
#[repr(u8)]
pub enum CompressionScheme {
Gzip = 1,
Zlib = 2,
Uncompressed = 3,
Lz4 = 4,
}
pub struct RegionIter<'a, S>
where
S: Read + Seek,
{
inner: &'a mut Region<S>,
index: usize,
}
impl<'a, S> RegionIter<'a, S>
where
S: Read + Seek,
{
fn new(inner: &'a mut Region<S>) -> Self {
Self { inner, index: 0 }
}
fn next_xz(&mut self) -> Option<(usize, usize)> {
let index = self.index;
self.index += 1;
if index == 32 * 32 {
return None;
}
let x = index % 32;
let z = index / 32;
Some((x, z))
}
}
pub struct ChunkData {
pub x: usize,
pub z: usize,
pub data: Vec<u8>,
}
impl<'a, S> Iterator for RegionIter<'a, S>
where
S: Read + Seek,
{
type Item = Result<ChunkData>;
fn next(&mut self) -> Option<Self::Item> {
while let Some((x, z)) = self.next_xz() {
let c = self.inner.read_chunk(x, z);
match c {
Ok(Some(c)) => return Some(Ok(ChunkData { x, z, data: c })),
Ok(None) => {} Err(e) => return Some(Err(e)),
}
}
None
}
}
pub const fn unstable_div_ceil(lhs: usize, rhs: usize) -> usize {
let d = lhs / rhs;
let r = lhs % rhs;
if r > 0 && rhs > 0 {
d + 1
} else {
d
}
}
fn unstable_stream_len(seek: &mut impl Seek) -> Result<u64> {
let old_pos = seek.stream_position()?;
let len = seek.seek(SeekFrom::End(0))?;
if old_pos != len {
seek.seek(SeekFrom::Start(old_pos))?;
}
Ok(len)
}
fn header_pos(x: usize, z: usize) -> u64 {
(4 * ((x % 32) + (z % 32) * 32)) as u64
}
#[derive(Debug)]
pub struct ChunkLocation {
pub offset: u64,
pub sectors: u64,
}
#[derive(Debug)]
struct ChunkMeta {
pub compressed_len: u32,
pub compression_scheme: CompressionScheme,
}
impl ChunkMeta {
fn new(mut data: &[u8]) -> Result<Self> {
let len = data.read_u32::<BigEndian>()?;
let scheme = data.read_u8()?;
let scheme =
CompressionScheme::try_from(scheme).map_err(|_| Error::UnknownCompression(scheme))?;
Ok(Self {
compressed_len: len - 1, compression_scheme: scheme,
})
}
}
pub struct Lz4DecoderWrapper<W: Write> {
buf: Vec<u8>,
write_to: W,
}
impl<W: Write> Lz4DecoderWrapper<W> {
pub fn new(write_to: W) -> Self {
Self {
buf: vec![],
write_to,
}
}
pub fn finish(mut self) -> Result<W> {
let mut decoder = lz4_java_wrc::Lz4BlockInput::new(Cursor::new(self.buf));
io::copy(&mut decoder, &mut self.write_to)?;
Ok(self.write_to)
}
}
impl<W: Write> Write for Lz4DecoderWrapper<W> {
fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
self.buf.extend_from_slice(data);
Ok(data.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}