From 0e6f2c3f782deccad0fbb2645ed0f378ccd8e0fd Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sat, 27 Apr 2024 21:27:42 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20Add=20invalid=5Fnbt=20flag.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + schemsearch-cli/Cargo.toml | 2 +- schemsearch-cli/src/main.rs | 89 ++++++++++------ schemsearch-cli/src/types.rs | 17 +++- schemsearch-lib/Cargo.toml | 3 +- schemsearch-lib/src/blocks.txt | 163 ++++++++++++++++++++++++++++++ schemsearch-lib/src/lib.rs | 103 +++---------------- schemsearch-lib/src/nbt_search.rs | 27 +++++ schemsearch-lib/src/search.rs | 87 ++++++++++++++++ 9 files changed, 366 insertions(+), 126 deletions(-) mode change 100644 => 100755 Cargo.toml mode change 100644 => 100755 schemsearch-cli/Cargo.toml mode change 100644 => 100755 schemsearch-cli/src/main.rs mode change 100644 => 100755 schemsearch-cli/src/types.rs mode change 100644 => 100755 schemsearch-lib/Cargo.toml create mode 100755 schemsearch-lib/src/blocks.txt mode change 100644 => 100755 schemsearch-lib/src/lib.rs create mode 100755 schemsearch-lib/src/nbt_search.rs create mode 100755 schemsearch-lib/src/search.rs diff --git a/Cargo.toml b/Cargo.toml old mode 100644 new mode 100755 index 03d056b..079b6d2 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "schemsearch-sql", "schemsearch-java" ] +resolver = "2" [profile.small] inherits = "release" diff --git a/schemsearch-cli/Cargo.toml b/schemsearch-cli/Cargo.toml old mode 100644 new mode 100755 index 73e4dbb..a28a424 --- a/schemsearch-cli/Cargo.toml +++ b/schemsearch-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "schemsearch-cli" -version = "0.1.6" +version = "0.1.7" edition = "2021" license = "AGPL-3.0-or-later" diff --git a/schemsearch-cli/src/main.rs b/schemsearch-cli/src/main.rs old mode 100644 new mode 100755 index 313ea22..3e62897 --- a/schemsearch-cli/src/main.rs +++ b/schemsearch-cli/src/main.rs @@ -26,8 +26,8 @@ use clap::{command, Arg, ArgAction, ValueHint}; use std::path::PathBuf; use std::str::FromStr; use clap::error::ErrorKind; -use schemsearch_lib::{Match, search, SearchBehavior}; -use crate::types::{PathSchematicSupplier, SchematicSupplierType}; +use schemsearch_lib::{Match, SearchBehavior}; +use crate::types::{PathSchematicSupplier, SchematicSupplier, SchematicSupplierType}; #[cfg(feature = "sql")] use futures::executor::block_on; use rayon::prelude::*; @@ -42,15 +42,17 @@ use indicatif::*; use schemsearch_files::SpongeSchematic; use crate::sinks::{OutputFormat, OutputSink}; use crate::stderr::MaschineStdErr; +use schemsearch_lib::nbt_search::has_invalid_nbt; +use schemsearch_lib::search::search; fn main() { #[allow(unused_mut)] - let mut cmd = command!("schemsearch") + let mut cmd = command!("schemsearch") .arg( Arg::new("pattern") .help("The pattern to search for") - .required(true) .value_hint(ValueHint::FilePath) + .required_unless_present("invalid-nbt") .action(ArgAction::Set), ) .arg( @@ -94,6 +96,13 @@ fn main() { .long("air-as-any") .action(ArgAction::SetTrue), ) + .arg( + Arg::new("invalid-nbt") + .help("Search for Schematics with Invalid or missing NBT data") + .short('I') + .long("invalid-nbt") + .action(ArgAction::SetTrue), + ) .arg( Arg::new("output") .help("The output format and path [Format:Path] available formats: text, json, csv; available paths: std, err, (file path)") @@ -134,7 +143,7 @@ fn main() { ) .arg( Arg::new("threads") - .help("The number of threads to use [0 = Available Threads]") + .help("The number of threads to use [0 = all Available Threads]") .short('T') .long("threads") .action(ArgAction::Set) @@ -163,9 +172,9 @@ fn main() { .bin_name("schemsearch"); #[cfg(feature = "sql")] - let mut cmd = cmd + let mut cmd = cmd .arg( - Arg::new("sql") + Arg::new("sql") .help("Use the SteamWar SQL Database") .short('s') .long("sql") @@ -204,18 +213,22 @@ fn main() { air_as_any: matches.get_flag("air-as-any"), ignore_entities: matches.get_flag("ignore-entities"), threshold: *matches.get_one::("threshold").expect("Couldn't get threshold"), + invalid_nbt: matches.get_flag("invalid-nbt"), }; - let pattern = match SpongeSchematic::load(&PathBuf::from(matches.get_one::("pattern").unwrap())) { - Ok(x) => x, - Err(e) => { - cmd.error(ErrorKind::Io, format!("Error while loading Pattern: {}", e.to_string())).exit(); - } + let pattern = match matches.get_one::("pattern") { + Some(p) => match SpongeSchematic::load(&PathBuf::from(p)) { + Ok(x) => Some(x), + Err(e) => { + cmd.error(ErrorKind::Io, format!("Error while loading Pattern: {}", e.to_string())).exit(); + } + }, + None => None, }; let mut schematics: Vec = Vec::new(); match matches.get_many::("schematic") { - None => {}, + None => {} Some(x) => { let paths = x.map(|x| PathBuf::from(x)); for path in paths { @@ -226,12 +239,12 @@ fn main() { .filter(|x| x.path().is_file()) .filter(|x| x.path().extension().unwrap().to_str().unwrap() == "schem") .for_each(|x| { - schematics.push(SchematicSupplierType::PATH(Box::new(PathSchematicSupplier { + schematics.push(SchematicSupplierType::PATH(PathSchematicSupplier { path: x.path(), - }))) + })) }); } else if path.extension().unwrap().to_str().unwrap() == "schem" { - schematics.push(SchematicSupplierType::PATH(Box::new(PathSchematicSupplier { path }))); + schematics.push(SchematicSupplierType::PATH(PathSchematicSupplier { path })); } } } @@ -247,7 +260,7 @@ fn main() { filter = filter.name(x.collect()); } for schem in block_on(load_all_schematics(filter)) { - schematics.push(SchematicSupplierType::SQL(SqlSchematicSupplier{ + schematics.push(SchematicSupplierType::SQL(SqlSchematicSupplier { node: schem })) }; @@ -282,28 +295,20 @@ fn main() { Some(x) => x, None => return SearchResult { name: schem.get_name(), - matches: Vec::default() + matches: Vec::default(), } }; - SearchResult { - name: schem.get_name(), - matches: search(schematic, &pattern, search_behavior) - } + search_in_schem(schematic, pattern.as_ref(), search_behavior, schem) } #[cfg(feature = "sql")] SchematicSupplierType::SQL(schem) => { match schem.get_schematic() { - Ok(schematic) => { - SearchResult { - name: schem.get_name(), - matches: search(schematic, &pattern, search_behavior) - } - } + Ok(schematic) => search_in_schem(schematic, pattern.as_ref(), search_behavior, schem), Err(e) => { eprintln!("Error while loading schematic ({}): {}", schem.get_name(), e.to_string()); SearchResult { name: schem.get_name(), - matches: Vec::default() + matches: Vec::default(), } } } @@ -334,6 +339,32 @@ fn main() { } } +fn search_in_schem(schematic: SpongeSchematic, pattern: Option<&SpongeSchematic>, search_behavior: SearchBehavior, schem: &impl SchematicSupplier) -> SearchResult { + if search_behavior.invalid_nbt { + if has_invalid_nbt(schematic) { + SearchResult { + name: schem.get_name(), + matches: vec![Match { + x: 0, + y: 0, + z: 0, + percent: 1.0, + }], + } + } else { + SearchResult { + name: schem.get_name(), + matches: vec![], + } + } + } else { + SearchResult { + name: schem.get_name(), + matches: search(schematic, pattern.unwrap(), search_behavior), + } + } +} + fn load_schem(schem_path: &PathBuf) -> Option { match SpongeSchematic::load(schem_path) { Ok(x) => Some(x), diff --git a/schemsearch-cli/src/types.rs b/schemsearch-cli/src/types.rs old mode 100644 new mode 100755 index ba9a93e..6346831 --- a/schemsearch-cli/src/types.rs +++ b/schemsearch-cli/src/types.rs @@ -26,17 +26,21 @@ use schemsearch_files::SpongeSchematic; use schemsearch_sql::{load_schemdata, SchematicNode}; pub enum SchematicSupplierType { - PATH(Box), + PATH(PathSchematicSupplier), #[cfg(feature = "sql")] SQL(SqlSchematicSupplier), } +pub trait SchematicSupplier { + fn get_name(&self) -> String; +} + pub struct PathSchematicSupplier { pub path: PathBuf, } -impl PathSchematicSupplier { - pub fn get_name(&self) -> String { +impl SchematicSupplier for PathSchematicSupplier { + fn get_name(&self) -> String { self.path.file_stem().unwrap().to_str().unwrap().to_string() } } @@ -52,8 +56,13 @@ impl SqlSchematicSupplier { let mut schemdata = block_on(load_schemdata(self.node.id)); SpongeSchematic::load_data(&mut Cursor::new(schemdata.as_mut_slice())) } +} - pub fn get_name(&self) -> String { +#[cfg(feature = "sql")] +impl SchematicSupplier for SqlSchematicSupplier { + fn get_name(&self) -> String { format!("{} ({})", self.node.name, self.node.id) } } + + diff --git a/schemsearch-lib/Cargo.toml b/schemsearch-lib/Cargo.toml old mode 100644 new mode 100755 index b55e846..0ff6766 --- a/schemsearch-lib/Cargo.toml +++ b/schemsearch-lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "schemsearch-lib" -version = "0.1.6" +version = "0.1.7" edition = "2021" license = "AGPL-3.0-or-later" @@ -11,3 +11,4 @@ serde = { version = "1.0.160", features = ["derive"] } schemsearch-files = { path = "../schemsearch-files" } named-binary-tag = "0.6" libmath = "0.2.1" +lazy_static = "1.4.0" diff --git a/schemsearch-lib/src/blocks.txt b/schemsearch-lib/src/blocks.txt new file mode 100755 index 0000000..dcbc7b4 --- /dev/null +++ b/schemsearch-lib/src/blocks.txt @@ -0,0 +1,163 @@ +oak_sign +oak_wall_sign +oak_hanging_sign +oak_wall_hanging_sign +birch_sign +birch_wall_sign +birch_hanging_sign +birch_wall_hanging_sign +spruce_sign +spruce_wall_sign +spruce_hanging_sign +spruce_wall_hanging_sign +jungle_sign +jungle_wall_sign +jungle_hanging_sign +jungle_wall_hanging_sign +dark_oak_sign +dark_oak_wall_sign +dark_oak_hanging_sign +dark_oak_wall_hanging_sign +acacia_sign +acacia_wall_sign +acacia_hanging_sign +acacia_wall_hanging_sign +mangrove_sign +mangrove_wall_sign +mangrove_hanging_sign +mangrove_wall_hanging_sign +cherry_sign +cherry_wall_sign +cherry_hanging_sign +cherry_wall_hanging_sign +bamboo_sign +bamboo_wall_sign +bamboo_hanging_sign +bamboo_wall_hanging_sign +warped_sign +warped_wall_sign +warped_hanging_sign +warped_wall_hanging_sign +crimson_sign +crimson_wall_sign +crimson_hanging_sign +crimson_wall_hanging_sign +suspicious_gravel +suspicious_sand +white_banner +light_gray_banner +gray_banner +black_banner +brown_banner +red_banner +orange_banner +yellow_banner +lime_banner +green_banner +cyan_banner +light_blue_banner +blue_banner +purple_banner +magenta_banner +pink_banner +white_wall_banner +light_gray_wall_banner +gray_wall_banner +black_wall_banner +brown_wall_banner +red_wall_banner +orange_wall_banner +yellow_wall_banner +lime_wall_banner +green_wall_banner +cyan_wall_banner +light_blue_wall_banner +blue_wall_banner +purple_wall_banner +magenta_wall_banner +pink_wall_banner +white_bed +light_gray_bed +gray_bed +black_bed +brown_bed +red_bed +orange_bed +yellow_bed +lime_bed +green_bed +cyan_bed +light_blue_bed +blue_bed +purple_bed +magenta_bed +pink_bed +shulker_box +white_shulker_box +light_gray_shulker_box +gray_shulker_box +black_shulker_box +brown_shulker_box +red_shulker_box +orange_shulker_box +yellow_shulker_box +lime_shulker_box +green_shulker_box +cyan_shulker_box +light_blue_shulker_box +blue_shulker_box +purple_shulker_box +magenta_shulker_box +pink_shulker_box +furnace +blast_furnace +smoker +chest +trapped_chest +ender_chest +enchanting_table +barrel +lectern +jukebox +bell +brewing_stand +bee_nest +beehive +decorated_pot +beacon +conduit +campfire +soul_campfire +redstone_comparator +hopper +dispenser +dropper +moving_piston +daylight_detector +sculk_sensor +calibrated_sculk_sensor +sculk_catalyst +sculk_shrieker +player_head +player_wall_head +wither_skeleton_skull +wither_skeleton_wall_skull +zombie_head +zombie_wall_head +skeleton_skull +skeleton_wall_skull +creeper_head +creeper_wall_head +piglin_head +piglin_wall_head +dragon_head +dragon_wall_head +chiseled_bookshelf +command_block +chain_command_block +repeating_command_block +structure_block +jigsaw_block +end_portal +end_gateway +monster_spawner \ No newline at end of file diff --git a/schemsearch-lib/src/lib.rs b/schemsearch-lib/src/lib.rs old mode 100644 new mode 100755 index d3edec1..b2f7206 --- a/schemsearch-lib/src/lib.rs +++ b/schemsearch-lib/src/lib.rs @@ -16,12 +16,10 @@ */ pub mod pattern_mapper; +pub mod search; +pub mod nbt_search; use serde::{Serialize, Deserialize}; -use pattern_mapper::match_palette; -use schemsearch_files::SpongeSchematic; -use crate::pattern_mapper::match_palette_adapt; -use math::round::ceil; #[derive(Debug, Clone, Copy, Deserialize, Serialize)] pub struct SearchBehavior { @@ -31,89 +29,7 @@ pub struct SearchBehavior { pub air_as_any: bool, pub ignore_entities: bool, pub threshold: f32, -} - -pub fn search( - schem: SpongeSchematic, - pattern_schem: &SpongeSchematic, - search_behavior: SearchBehavior, -) -> Vec { - if schem.width < pattern_schem.width || schem.height < pattern_schem.height || schem.length < pattern_schem.length { - return Vec::new(); - } - - if pattern_schem.palette.len() > schem.palette.len() { - return Vec::new(); - } - - let pattern_schem = match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data); - - let mut matches: Vec = Vec::with_capacity(4); - - let pattern_data = pattern_schem.block_data.as_ptr(); - - let schem_data = if search_behavior.ignore_block_data { - match_palette_adapt(&schem, &pattern_schem.palette, search_behavior.ignore_block_data) - } else { - schem.block_data - }; - - let schem_data = schem_data.as_ptr(); - - let air_id = if search_behavior.ignore_air || search_behavior.air_as_any { pattern_schem.palette.get("minecraft:air").unwrap_or(&-1) } else { &-1}; - - let pattern_blocks = pattern_schem.block_data.len() as f32; - let i_pattern_blocks = pattern_blocks as i32; - - let pattern_width = pattern_schem.width as usize; - let pattern_height = pattern_schem.height as usize; - let pattern_length = pattern_schem.length as usize; - - let schem_width = schem.width as usize; - let schem_height = schem.height as usize; - let schem_length = schem.length as usize; - - let skip_amount = ceil((pattern_blocks * (1.0 - search_behavior.threshold)) as f64, 0) as i32; - - for y in 0..=schem_height - pattern_height { - for z in 0..=schem_length - pattern_length { - for x in 0..=schem_width - pattern_width { - let mut not_matching = 0; - 'outer: - for j in 0..pattern_height { - for k in 0..pattern_length { - 'inner: - for i in 0..pattern_width { - let index = (x + i) + schem_width * ((z + k) + (y + j) * schem_length); - let pattern_index = i + pattern_width * (k + j * pattern_length); - let data = unsafe { *schem_data.add(index) }; - let pattern_data = unsafe { *pattern_data.add(pattern_index) }; - if (search_behavior.ignore_air && data != *air_id) || (search_behavior.air_as_any && pattern_data != *air_id) { - continue 'inner; - } - if data != pattern_data { - not_matching += 1; - if not_matching >= skip_amount { - break 'outer; - } - } - } - } - } - - if not_matching < skip_amount { - matches.push(Match { - x: x as u16, - y: y as u16, - z: z as u16, - percent: (i_pattern_blocks - not_matching) as f32 / pattern_blocks, - }); - } - } - } - } - - return matches; + pub invalid_nbt: bool, } #[derive(Debug, Clone, Copy, Default, Deserialize, Serialize)] @@ -137,7 +53,9 @@ pub fn normalize_data(data: &str, ignore_data: bool) -> &str { #[cfg(test)] mod tests { use std::path::{Path, PathBuf}; - use crate::pattern_mapper::strip_data; + use schemsearch_files::SpongeSchematic; + use crate::pattern_mapper::{match_palette, strip_data}; + use crate::search::search; use super::*; #[test] @@ -191,7 +109,8 @@ mod tests { ignore_entities: true, ignore_air: false, air_as_any: false, - threshold: 0.9 + threshold: 0.9, + invalid_nbt: false }); } @@ -206,7 +125,8 @@ mod tests { ignore_entities: true, ignore_air: false, air_as_any: false, - threshold: 0.9 + threshold: 0.9, + invalid_nbt: false }); assert_eq!(matches.len(), 1); @@ -227,7 +147,8 @@ mod tests { ignore_entities: false, ignore_air: false, air_as_any: false, - threshold: 0.9 + threshold: 0.9, + invalid_nbt: false }); assert_eq!(matches.len(), 1); diff --git a/schemsearch-lib/src/nbt_search.rs b/schemsearch-lib/src/nbt_search.rs new file mode 100755 index 0000000..9fda1b0 --- /dev/null +++ b/schemsearch-lib/src/nbt_search.rs @@ -0,0 +1,27 @@ +use std::borrow::ToOwned; +use std::collections::HashSet; +use std::iter::Iterator; +use lazy_static::lazy_static; +use schemsearch_files::SpongeSchematic; + +const NBT_BLOCKS: &str = include_str!("blocks.txt"); + +lazy_static! { + static ref NBT_BLOCKS_SET: HashSet = NBT_BLOCKS.lines().map(ToOwned::to_owned).collect(); +} + +pub fn has_invalid_nbt(schem: SpongeSchematic) -> bool { + if schem.block_entities.is_empty() && schem.palette.keys().any(|v| NBT_BLOCKS_SET.contains(v)) { + return true; + } + + let nbt_blocks = schem.palette.iter().filter(|(k, _)| NBT_BLOCKS_SET.contains(*k)).map(|(_, v)| *v).collect::>(); + + for block_entity in schem.block_data.iter() { + if nbt_blocks.contains(block_entity) { + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/schemsearch-lib/src/search.rs b/schemsearch-lib/src/search.rs new file mode 100755 index 0000000..e24fa0f --- /dev/null +++ b/schemsearch-lib/src/search.rs @@ -0,0 +1,87 @@ +use math::round::ceil; +use schemsearch_files::SpongeSchematic; +use crate::{Match, SearchBehavior}; +use crate::pattern_mapper::{match_palette, match_palette_adapt}; + +pub fn search( + schem: SpongeSchematic, + pattern_schem: &SpongeSchematic, + search_behavior: SearchBehavior, +) -> Vec { + if schem.width < pattern_schem.width || schem.height < pattern_schem.height || schem.length < pattern_schem.length { + return Vec::new(); + } + + if pattern_schem.palette.len() > schem.palette.len() { + return Vec::new(); + } + + let pattern_schem = match_palette(&schem, &pattern_schem, search_behavior.ignore_block_data); + + let mut matches: Vec = Vec::with_capacity(4); + + let pattern_data = pattern_schem.block_data.as_ptr(); + + let schem_data = if search_behavior.ignore_block_data { + match_palette_adapt(&schem, &pattern_schem.palette, search_behavior.ignore_block_data) + } else { + schem.block_data + }; + + let schem_data = schem_data.as_ptr(); + + let air_id = if search_behavior.ignore_air || search_behavior.air_as_any { pattern_schem.palette.get("minecraft:air").unwrap_or(&-1) } else { &-1}; + + let pattern_blocks = pattern_schem.block_data.len() as f32; + let i_pattern_blocks = pattern_blocks as i32; + + let pattern_width = pattern_schem.width as usize; + let pattern_height = pattern_schem.height as usize; + let pattern_length = pattern_schem.length as usize; + + let schem_width = schem.width as usize; + let schem_height = schem.height as usize; + let schem_length = schem.length as usize; + + let skip_amount = ceil((pattern_blocks * (1.0 - search_behavior.threshold)) as f64, 0) as i32; + + for y in 0..=schem_height - pattern_height { + for z in 0..=schem_length - pattern_length { + for x in 0..=schem_width - pattern_width { + let mut not_matching = 0; + 'outer: + for j in 0..pattern_height { + for k in 0..pattern_length { + 'inner: + for i in 0..pattern_width { + let index = (x + i) + schem_width * ((z + k) + (y + j) * schem_length); + let pattern_index = i + pattern_width * (k + j * pattern_length); + let data = unsafe { *schem_data.add(index) }; + let pattern_data = unsafe { *pattern_data.add(pattern_index) }; + if (search_behavior.ignore_air && data != *air_id) || (search_behavior.air_as_any && pattern_data != *air_id) { + continue 'inner; + } + if data != pattern_data { + not_matching += 1; + if not_matching >= skip_amount { + break 'outer; + } + } + } + } + } + + if not_matching < skip_amount { + matches.push(Match { + x: x as u16, + y: y as u16, + z: z as u16, + percent: (i_pattern_blocks - not_matching) as f32 / pattern_blocks, + }); + } + } + } + } + + return matches; +} \ No newline at end of file