From fb6cb1773a734aaf091dbbc840632905a2a40cad Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Wed, 1 Feb 2023 15:05:52 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 3 + Cargo.toml | 10 +++ src/lib.rs | 144 ++++++++++++++++++++++++++++++++++++++++++ src/pattern_mapper.rs | 118 ++++++++++++++++++++++++++++++++++ src/schematic.rs | 58 +++++++++++++++++ tests/endstone.schem | Bin 0 -> 204 bytes tests/simple.schem | Bin 0 -> 4920 bytes 7 files changed, 333 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/pattern_mapper.rs create mode 100644 src/schematic.rs create mode 100644 tests/endstone.schem create mode 100644 tests/simple.schem diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d11246 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +/.idea/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..03109d7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "schemsearch" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hematite-nbt = "0.5.2" +serde = "1.0.152" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..baecb33 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,144 @@ +#![allow(unused_variables)] + +use crate::pattern_mapper::match_palette; +use crate::schematic::Schematic; + +mod schematic; +mod pattern_mapper; + +#[derive(Debug, Clone, Copy)] +pub struct SearchBehavior { + ignore_block_data: bool, + ignore_block_entities: bool, + ignore_entities: bool, +} + +pub fn search( + data: &Vec, + pattern: &Vec, + search_behavior: SearchBehavior, +) -> Vec<(u16, u16, u16)> { + let schem: Schematic = parse_schematic(data); + let pattern_schem: Schematic = parse_schematic(pattern); + + if schem.width < pattern_schem.width || schem.height < pattern_schem.height || schem.length < pattern_schem.length { + return vec![]; + } + + if pattern_schem.palette.len() > schem.palette.len() { + return vec![]; + } + + let (schem, pattern_schem) = match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data); + + let mut matches: Vec<(u16, u16, u16)> = Vec::new(); + + for i in 0..schem.width - pattern_schem.width { + for j in 0..schem.height - pattern_schem.height { + for k in 0..schem.length - pattern_schem.length { + let mut match_found = true; + for x in 0..pattern_schem.width { + for y in 0..pattern_schem.height { + for z in 0..pattern_schem.length { + let index = (i + x) as usize + (j + y) as usize * schem.width as usize + (k + z) as usize * schem.width as usize * schem.height as usize; + let pattern_index = x as usize + y as usize * pattern_schem.width as usize + z as usize * pattern_schem.width as usize * pattern_schem.height as usize; + if schem.block_data[index] != pattern_schem.block_data[pattern_index] { + match_found = false; + break; + } + } + if !match_found { + break; + } + } + if !match_found { + break; + } + } + if match_found { + matches.push((i, j, k)); + } + } + } + } + + return matches; + +} + +pub(crate) fn normalize_data(data: &String, ignore_data: bool) -> String { + if ignore_data { + data.split('[').next().unwrap().to_string() + } else { + data.clone() + } +} + +fn parse_schematic(data: &Vec) -> Schematic { + if data[0] == 0x1f && data[1] == 0x8b { + // gzip + nbt::from_gzip_reader(data.as_slice()).unwrap() + } else { + // uncompressed + nbt::from_reader(data.as_slice()).unwrap() + } +} + +mod tests { + use std::path::Path; + use crate::pattern_mapper::{match_palette, strip_data}; + use super::*; + + #[test] + fn read_schematic() { + let schematic = Schematic::load(Path::new("tests/simple.schem")); + assert_eq!(schematic.width as usize * schematic.height as usize * schematic.length as usize, schematic.block_data.len()); + assert_eq!(schematic.palette_max, schematic.palette.len() as i32); + println!("{:?}", schematic); + } + + #[test] + fn test_parse_function() { + let file = std::fs::File::open("tests/simple.schem").expect("Failed to open file"); + let schematic: Schematic = parse_schematic(&std::io::Read::bytes(file).map(|b| b.unwrap()).collect()); + assert_eq!(schematic.width as usize * schematic.height as usize * schematic.length as usize, schematic.block_data.len()); + assert_eq!(schematic.palette_max, schematic.palette.len() as i32); + println!("{:?}", schematic); + } + + #[test] + fn test_strip_schem() { + let schematic = Schematic::load(Path::new("tests/simple.schem")); + let stripped = strip_data(&schematic); + + assert_eq!(stripped.palette.keys().any(|k| k.contains('[')), false); + println!("{:?}", stripped); + } + + #[test] + fn test_match_palette() { + let schematic = Schematic::load(Path::new("tests/simple.schem")); + let endstone = Schematic::load(Path::new("tests/endstone.schem")); + + let (matched_schematic, matched_endstone) = match_palette(&schematic, &endstone, true); + + println!("{:?}", matched_schematic); + println!("{:?}", matched_endstone); + } + + #[test] + fn test_search() { + let file = std::fs::File::open("tests/simple.schem").expect("Failed to open file"); + let schematic = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect(); + let file = std::fs::File::open("tests/endstone.schem").expect("Failed to open file"); + let pattern = &std::io::Read::bytes(file).map(|b| b.unwrap()).collect(); + + let matches = search(schematic, pattern, SearchBehavior { + ignore_block_data: true, + ignore_block_entities: true, + ignore_entities: true, + }); + + println!("{:?}", matches); + } +} diff --git a/src/pattern_mapper.rs b/src/pattern_mapper.rs new file mode 100644 index 0000000..8b3855a --- /dev/null +++ b/src/pattern_mapper.rs @@ -0,0 +1,118 @@ +use nbt::Map; +use crate::normalize_data; +use crate::schematic::Schematic; + +pub(crate) fn strip_data(schem: &Schematic) -> Schematic { + let mut data: Vec = Vec::new(); + + let mut palette: Map = Map::new(); + let mut palette_max: i32 = 0; + + for block in schem.block_data.iter() { + let block_name = schem.palette.iter().find(|(_, &v)| v == *block).expect("Invalid Schematic").0; + let block_name = block_name.split('[').next().unwrap().to_string(); + + let entry = palette.entry(block_name).or_insert_with(|| { + let value = palette_max; + palette_max += 1; + value + }); + data.push(*entry); + } + + Schematic { + version: schem.version, + data_version: schem.data_version, + palette, + palette_max, + block_data: data, + block_entities: schem.block_entities.clone(), + height: schem.height, + length: schem.length, + width: schem.width, + metadata: schem.metadata.clone(), + offset: schem.offset.clone(), + entities: None, + } +} + +fn match_palette_adapt(schem: &Schematic, matching_palette: Map, ignore_data: bool) -> Vec { + let mut data: Vec = Vec::new(); + + for x in schem.block_data.iter() { + let blockname = schem.palette.iter().find(|(_, &v)| v == *x).expect("Invalid Schematic").0; + let blockname = if ignore_data { normalize_data(&blockname, ignore_data) } else { blockname.clone() }; + let block_id = matching_palette.get(&blockname).unwrap_or(&-1); + data.push(*block_id); + } + + data +} + +pub(crate) fn match_palette( + schem: &Schematic, + pattern: &Schematic, + ignore_data: bool, +) -> (Schematic, Schematic) { + if ignore_data { + match_palette_internal(&strip_data(schem), &strip_data(pattern), ignore_data) + } else { + match_palette_internal(schem, pattern, ignore_data) + } +} + +fn match_palette_internal( + schem: &Schematic, + pattern: &Schematic, + ignore_data: bool, +) -> (Schematic, Schematic) { + + if schem.palette.len() < pattern.palette.len() { + panic!("Schematic palette is larger than pattern palette"); + } + + let mut matching_palette: Map = Map::new(); + let mut matching_palette_max: i32 = 0; + + for (block_name, block_id) in pattern.palette.iter() { + let block_name = normalize_data(block_name, true); + let schem_block_id = pattern.palette.get(&block_name).expect("Pattern block not found in schematic palette"); + matching_palette.insert(block_name, *schem_block_id); + matching_palette_max += 1; + } + + let mut data_schem: Vec = match_palette_adapt(&schem, matching_palette.clone(), true); + + let mut data_pattern: Vec = match_palette_adapt(&pattern, matching_palette.clone(), true); + + let schem = Schematic { + version: schem.version.clone(), + data_version: schem.data_version.clone(), + palette: matching_palette.clone(), + palette_max: matching_palette_max.clone(), + block_data: data_schem, + block_entities: schem.block_entities.clone(), + height: schem.height.clone(), + length: schem.length.clone(), + width: schem.width.clone(), + metadata: schem.metadata.clone(), + offset: schem.offset.clone(), + entities: None, + }; + let pattern = Schematic { + version: pattern.version.clone(), + data_version: pattern.data_version.clone(), + palette: matching_palette.clone(), + palette_max: matching_palette_max.clone(), + block_data: data_pattern, + block_entities: pattern.block_entities.clone(), + height: pattern.height.clone(), + length: pattern.length.clone(), + width: pattern.width.clone(), + metadata: pattern.metadata.clone(), + offset: pattern.offset.clone(), + entities: None, + }; + + (schem, pattern) +} \ No newline at end of file diff --git a/src/schematic.rs b/src/schematic.rs new file mode 100644 index 0000000..9a105ff --- /dev/null +++ b/src/schematic.rs @@ -0,0 +1,58 @@ +use std::path::Path; +use nbt::{Map, Value}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) struct Schematic { + #[serde(rename = "Version")] + pub(crate) version: i32, + #[serde(rename = "DataVersion")] + pub(crate) data_version: i32, + #[serde(rename = "Metadata")] + pub(crate) metadata: Map, + #[serde(rename = "Width")] + pub(crate) width: u16, + #[serde(rename = "Height")] + pub(crate) height: u16, + #[serde(rename = "Length")] + pub(crate) length: u16, + #[serde(rename = "Offset")] + pub(crate) offset: [i32; 3], + #[serde(rename = "PaletteMax")] + pub(crate) palette_max: i32, + #[serde(rename = "Palette")] + pub(crate) palette: Map, + #[serde(rename = "BlockData")] + pub(crate) block_data: Vec, + #[serde(rename = "BlockEntities")] + pub(crate) block_entities: Vec, + #[serde(rename = "Entities")] + pub(crate) entities: Option>, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct BlockEntity { + #[serde(rename = "Id")] + pub(crate) id: String, + #[serde(rename = "Pos")] + pub(crate) pos: [i32; 3], +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct Entity { + #[serde(rename = "Id")] + pub(crate) id: String, + #[serde(rename = "Pos")] + pub(crate) pos: [i32; 3], +} + +impl Schematic { + pub(crate) fn load(path: &Path) -> Schematic { + let file = std::fs::File::open(path).expect("Failed to open file"); + let schematic: Schematic = match nbt::from_gzip_reader(file) { + Ok(schem) => schem, + Err(e) => panic!("Failed to parse schematic: {}", e), + }; + schematic + } +} diff --git a/tests/endstone.schem b/tests/endstone.schem new file mode 100644 index 0000000000000000000000000000000000000000..f2d73ece70d83d756c0e9d68b50cf9fd0ae0169a GIT binary patch literal 204 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 literal 0 HcmV?d00001