mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-03-05 17:44:52 +01:00
Refactor logic from new dump tools into library crate
This commit is contained in:
parent
5a364e2434
commit
5e96be3fda
8 changed files with 196 additions and 147 deletions
|
@ -3,6 +3,7 @@ name = "minedmap"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "BSD-2-Clause"
|
license = "BSD-2-Clause"
|
||||||
|
default-run = "minedmap"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use std::{fs::File, io::prelude::*, path::PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use flate2::read::GzDecoder;
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
@ -13,18 +12,8 @@ struct Args {
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let file = File::open(&args.file).context("Failed to open file")?;
|
let value: fastnbt::Value = minedmap::io::data::from_file(&args.file)?;
|
||||||
|
println!("{:#x?}", value);
|
||||||
let mut decoder = GzDecoder::new(file);
|
|
||||||
let mut buf = vec![];
|
|
||||||
decoder
|
|
||||||
.read_to_end(&mut buf)
|
|
||||||
.context("Failed to read file")?;
|
|
||||||
|
|
||||||
let nbt: fastnbt::Value =
|
|
||||||
fastnbt::from_bytes(buf.as_slice()).context("Failed to decode NBT data")?;
|
|
||||||
|
|
||||||
println!("{:#x?}", nbt);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,134 +1,7 @@
|
||||||
use std::{
|
use std::path::PathBuf;
|
||||||
collections::HashMap,
|
|
||||||
fs::File,
|
|
||||||
io::{prelude::*, SeekFrom},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use flate2::read::ZlibDecoder;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
|
|
||||||
const BLOCKSIZE: usize = 4096;
|
|
||||||
const CHUNKS_PER_REGION: u8 = 32;
|
|
||||||
|
|
||||||
/// A chunk X coordinate relative to a region
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct ChunkX(pub u8);
|
|
||||||
|
|
||||||
/// A chunk Z coordinate relative to a region
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct ChunkZ(pub u8);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ChunkDesc {
|
|
||||||
x: ChunkX,
|
|
||||||
z: ChunkZ,
|
|
||||||
len: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_header(header: &[u8; BLOCKSIZE]) -> HashMap<u32, ChunkDesc> {
|
|
||||||
let mut map = HashMap::new();
|
|
||||||
|
|
||||||
for z in 0..CHUNKS_PER_REGION {
|
|
||||||
for x in 0..CHUNKS_PER_REGION {
|
|
||||||
let chunk =
|
|
||||||
&header[(4 * (usize::from(CHUNKS_PER_REGION) * usize::from(z) + usize::from(x)))..];
|
|
||||||
|
|
||||||
let offset = u32::from(chunk[0]) << 16 | u32::from(chunk[1]) << 8 | u32::from(chunk[2]);
|
|
||||||
if offset == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = chunk[3];
|
|
||||||
|
|
||||||
map.insert(
|
|
||||||
offset,
|
|
||||||
ChunkDesc {
|
|
||||||
x: ChunkX(x),
|
|
||||||
z: ChunkZ(z),
|
|
||||||
len,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_chunk<T>(buf: &[u8]) -> Result<T>
|
|
||||||
where
|
|
||||||
T: DeserializeOwned,
|
|
||||||
{
|
|
||||||
let (len_bytes, buf) = buf.split_at(4);
|
|
||||||
let len = u32::from_be_bytes(
|
|
||||||
len_bytes
|
|
||||||
.try_into()
|
|
||||||
.context("Failed to decode chunk size")?,
|
|
||||||
) as usize;
|
|
||||||
|
|
||||||
let buf = &buf[..len];
|
|
||||||
let (format, buf) = buf.split_at(1);
|
|
||||||
if format.get(0) != Some(&2) {
|
|
||||||
bail!("Unknown chunk format");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut decoder = ZlibDecoder::new(&buf[..]);
|
|
||||||
let mut decode_buffer = vec![];
|
|
||||||
decoder
|
|
||||||
.read_to_end(&mut decode_buffer)
|
|
||||||
.context("Failed to decompress chunk data")?;
|
|
||||||
|
|
||||||
fastnbt::from_bytes(&decode_buffer).context("Failed to decode NBT data")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foreach_chunk<R, T, F>(mut reader: R, mut f: F) -> Result<()>
|
|
||||||
where
|
|
||||||
R: Read + Seek,
|
|
||||||
T: DeserializeOwned,
|
|
||||||
F: FnMut(ChunkX, ChunkZ, T),
|
|
||||||
{
|
|
||||||
let chunk_map = {
|
|
||||||
let mut header = [0u8; BLOCKSIZE];
|
|
||||||
reader
|
|
||||||
.read_exact(&mut header)
|
|
||||||
.context("Failed to read region header")?;
|
|
||||||
|
|
||||||
parse_header(&header)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut index = 1;
|
|
||||||
let mut count = 0;
|
|
||||||
let mut seen = [[false; CHUNKS_PER_REGION as usize]; CHUNKS_PER_REGION as usize];
|
|
||||||
|
|
||||||
while count < chunk_map.len() {
|
|
||||||
let Some(&ChunkDesc { x, z, len }) = chunk_map.get(&index) else {
|
|
||||||
reader.seek(SeekFrom::Current(BLOCKSIZE as i64)).context("Failed to seek chunk data")?;
|
|
||||||
index += 1;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let chunk_seen = &mut seen[x.0 as usize][z.0 as usize];
|
|
||||||
if *chunk_seen {
|
|
||||||
bail!("Duplicate chunk");
|
|
||||||
}
|
|
||||||
|
|
||||||
*chunk_seen = true;
|
|
||||||
count += 1;
|
|
||||||
|
|
||||||
let mut buffer = vec![0; (len as usize) * BLOCKSIZE];
|
|
||||||
reader
|
|
||||||
.read_exact(&mut buffer[..])
|
|
||||||
.context("Failed to read chunk data")?;
|
|
||||||
|
|
||||||
f(x, z, decode_chunk(&buffer[..])?);
|
|
||||||
|
|
||||||
index += len as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
@ -139,9 +12,7 @@ struct Args {
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let mut file = File::open(&args.file).context("Failed to open file")?;
|
minedmap::io::region::from_file(&args.file)?.foreach_chunk(|x, z, value: fastnbt::Value| {
|
||||||
|
|
||||||
foreach_chunk(&mut file, |x, z, value: fastnbt::Value| {
|
|
||||||
println!("Chunk({}, {}): {:#x?}", x.0, z.0, value);
|
println!("Chunk({}, {}): {:#x?}", x.0, z.0, value);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
28
src/io/data.rs
Normal file
28
src/io/data.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use std::{fs::File, io::prelude::*, path::Path};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
pub fn from_reader<R, T>(reader: R) -> Result<T>
|
||||||
|
where
|
||||||
|
R: Read,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let mut decoder = GzDecoder::new(reader);
|
||||||
|
let mut buf = vec![];
|
||||||
|
decoder
|
||||||
|
.read_to_end(&mut buf)
|
||||||
|
.context("Failed to read file")?;
|
||||||
|
|
||||||
|
fastnbt::from_bytes(&buf[..]).context("Failed to decode NBT data")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_file<P, T>(path: P) -> Result<T>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let file = File::open(path).context("Failed to open file")?;
|
||||||
|
from_reader(file)
|
||||||
|
}
|
2
src/io/mod.rs
Normal file
2
src/io/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod data;
|
||||||
|
pub mod region;
|
147
src/io/region.rs
Normal file
147
src/io/region.rs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs::File,
|
||||||
|
io::{prelude::*, SeekFrom},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use flate2::read::ZlibDecoder;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
use crate::types::*;
|
||||||
|
|
||||||
|
const BLOCKSIZE: usize = 4096;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ChunkDesc {
|
||||||
|
x: ChunkX,
|
||||||
|
z: ChunkZ,
|
||||||
|
len: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_header(header: &[u8; BLOCKSIZE]) -> HashMap<u32, ChunkDesc> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
for z in 0..CHUNKS_PER_REGION {
|
||||||
|
for x in 0..CHUNKS_PER_REGION {
|
||||||
|
let chunk =
|
||||||
|
&header[(4 * (usize::from(CHUNKS_PER_REGION) * usize::from(z) + usize::from(x)))..];
|
||||||
|
|
||||||
|
let offset = u32::from(chunk[0]) << 16 | u32::from(chunk[1]) << 8 | u32::from(chunk[2]);
|
||||||
|
if offset == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = chunk[3];
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
offset,
|
||||||
|
ChunkDesc {
|
||||||
|
x: ChunkX(x),
|
||||||
|
z: ChunkZ(z),
|
||||||
|
len,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_chunk<T>(buf: &[u8]) -> Result<T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let (len_bytes, buf) = buf.split_at(4);
|
||||||
|
let len = u32::from_be_bytes(
|
||||||
|
len_bytes
|
||||||
|
.try_into()
|
||||||
|
.context("Failed to decode chunk size")?,
|
||||||
|
) as usize;
|
||||||
|
|
||||||
|
let buf = &buf[..len];
|
||||||
|
let (format, buf) = buf.split_at(1);
|
||||||
|
if format.get(0) != Some(&2) {
|
||||||
|
bail!("Unknown chunk format");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut decoder = ZlibDecoder::new(&buf[..]);
|
||||||
|
let mut decode_buffer = vec![];
|
||||||
|
decoder
|
||||||
|
.read_to_end(&mut decode_buffer)
|
||||||
|
.context("Failed to decompress chunk data")?;
|
||||||
|
|
||||||
|
fastnbt::from_bytes(&decode_buffer).context("Failed to decode NBT data")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Region<R: Read + Seek> {
|
||||||
|
reader: R,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> Region<R> {
|
||||||
|
pub fn foreach_chunk<T, F>(self, mut f: F) -> Result<()>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
F: FnMut(ChunkX, ChunkZ, T),
|
||||||
|
{
|
||||||
|
let Region { mut reader } = self;
|
||||||
|
|
||||||
|
let chunk_map = {
|
||||||
|
let mut header = [0u8; BLOCKSIZE];
|
||||||
|
reader
|
||||||
|
.read_exact(&mut header)
|
||||||
|
.context("Failed to read region header")?;
|
||||||
|
|
||||||
|
parse_header(&header)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut index = 1;
|
||||||
|
let mut count = 0;
|
||||||
|
let mut seen = [[false; CHUNKS_PER_REGION as usize]; CHUNKS_PER_REGION as usize];
|
||||||
|
|
||||||
|
while count < chunk_map.len() {
|
||||||
|
let Some(&ChunkDesc { x, z, len }) = chunk_map.get(&index) else {
|
||||||
|
reader.seek(SeekFrom::Current(BLOCKSIZE as i64)).context("Failed to seek chunk data")?;
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let chunk_seen = &mut seen[x.0 as usize][z.0 as usize];
|
||||||
|
if *chunk_seen {
|
||||||
|
bail!("Duplicate chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
*chunk_seen = true;
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
let mut buffer = vec![0; (len as usize) * BLOCKSIZE];
|
||||||
|
reader
|
||||||
|
.read_exact(&mut buffer[..])
|
||||||
|
.context("Failed to read chunk data")?;
|
||||||
|
|
||||||
|
f(x, z, decode_chunk(&buffer[..])?);
|
||||||
|
|
||||||
|
index += len as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_reader<R>(reader: R) -> Region<R>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
Region { reader }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_file<P>(path: P) -> Result<Region<File>>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let file = File::open(path).context("Failed to open file")?;
|
||||||
|
Ok(from_reader(file))
|
||||||
|
}
|
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod io;
|
||||||
|
pub mod types;
|
9
src/types.rs
Normal file
9
src/types.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
pub const CHUNKS_PER_REGION: u8 = 32;
|
||||||
|
|
||||||
|
/// A chunk X coordinate relative to a region
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ChunkX(pub u8);
|
||||||
|
|
||||||
|
/// A chunk Z coordinate relative to a region
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ChunkZ(pub u8);
|
Loading…
Add table
Reference in a new issue