diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index ea8c4bf7..4990fff7 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/doc diff --git a/.gitpod.yml b/.gitpod.yml old mode 100644 new mode 100755 diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100644 new mode 100755 diff --git a/Cargo.lock b/Cargo.lock old mode 100644 new mode 100755 index c460835d..430c72a6 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,10 @@ dependencies = [ "uuid", ] +[[package]] +name = "azalea-brigadier" +version = "0.1.0" + [[package]] name = "azalea-chat" version = "0.1.0" @@ -116,11 +120,15 @@ dependencies = [ "async-recursion", "async-trait", "azalea-auth", + "azalea-brigadier", "azalea-chat", "azalea-core", "azalea-nbt", "byteorder", "bytes", + "num-derive", + "num-traits", + "packet-macros", "serde", "serde_json", "thiserror", @@ -748,6 +756,15 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "packet-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -827,9 +844,9 @@ checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] diff --git a/Cargo.toml b/Cargo.toml old mode 100644 new mode 100755 index d22c326b..7f958207 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ members = [ "azalea-core", "azalea-auth", "azalea-nbt", + "azalea-brigadier", ] diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 3ae4b307..ed6ed46f --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ I named this Azalea because it sounds like a cool word and this is a cool librar ## Goals -- Bypass most anticheats -- Only support the latest Minecraft version - Do everything a vanilla client can do -- Be fast - Be easy to use +- Bypass most/all anticheats +- Support the latest Minecraft version +- Be fast diff --git a/azalea-auth/Cargo.toml b/azalea-auth/Cargo.toml old mode 100644 new mode 100755 diff --git a/azalea-auth/src/game_profile.rs b/azalea-auth/src/game_profile.rs old mode 100644 new mode 100755 diff --git a/azalea-auth/src/lib.rs b/azalea-auth/src/lib.rs old mode 100644 new mode 100755 diff --git a/azalea-brigadier/Cargo.toml b/azalea-brigadier/Cargo.toml new file mode 100755 index 00000000..a7ebf618 --- /dev/null +++ b/azalea-brigadier/Cargo.toml @@ -0,0 +1,8 @@ +[package] +edition = "2021" +name = "azalea-brigadier" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/azalea-brigadier/README.md b/azalea-brigadier/README.md new file mode 100755 index 00000000..a7318566 --- /dev/null +++ b/azalea-brigadier/README.md @@ -0,0 +1,3 @@ +# Azalea Brigadier + +A Rust port of Mojang's [Brigadier](https://github.com/Mojang/brigadier) command parsing and dispatching library. diff --git a/azalea-brigadier/src/arguments/argument_type.rs b/azalea-brigadier/src/arguments/argument_type.rs new file mode 100755 index 00000000..029e4696 --- /dev/null +++ b/azalea-brigadier/src/arguments/argument_type.rs @@ -0,0 +1,7 @@ +use std::{any::Any, rc::Rc}; + +use crate::{exceptions::CommandSyntaxException, string_reader::StringReader}; + +pub trait ArgumentType { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException>; +} diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs new file mode 100755 index 00000000..336046a7 --- /dev/null +++ b/azalea-brigadier/src/arguments/integer_argument_type.rs @@ -0,0 +1,54 @@ +use std::{any::Any, rc::Rc}; + +use crate::{ + context::CommandContext, + exceptions::{BuiltInExceptions, CommandSyntaxException}, + string_reader::StringReader, +}; + +use super::ArgumentType; + +#[derive(Default)] +struct Integer { + pub minimum: Option, + pub maximum: Option, +} + +impl ArgumentType for Integer { + fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + let start = reader.cursor; + let result = reader.read_int()?; + if let Some(minimum) = self.minimum { + if result < minimum { + reader.cursor = start; + return Err(BuiltInExceptions::IntegerTooSmall { + found: result, + min: minimum, + } + .create_with_context(reader)); + } + } + if let Some(maximum) = self.maximum { + if result > maximum { + reader.cursor = start; + return Err(BuiltInExceptions::IntegerTooBig { + found: result, + max: maximum, + } + .create_with_context(reader)); + } + } + Ok(Rc::new(result)) + } +} + +pub fn integer() -> impl ArgumentType { + Integer::default() +} +pub fn get_integer(context: &CommandContext, name: &str) -> Option { + context + .argument(name) + .unwrap() + .downcast_ref::() + .copied() +} diff --git a/azalea-brigadier/src/arguments/mod.rs b/azalea-brigadier/src/arguments/mod.rs new file mode 100755 index 00000000..dec39297 --- /dev/null +++ b/azalea-brigadier/src/arguments/mod.rs @@ -0,0 +1,4 @@ +mod argument_type; +pub mod integer_argument_type; + +pub use argument_type::ArgumentType; diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs new file mode 100755 index 00000000..d26b2a8a --- /dev/null +++ b/azalea-brigadier/src/builder/argument_builder.rs @@ -0,0 +1,137 @@ +use crate::{context::CommandContext, modifier::RedirectModifier, tree::CommandNode}; + +use super::{literal_argument_builder::Literal, required_argument_builder::Argument}; +use std::{cell::RefCell, fmt::Debug, rc::Rc}; + +#[derive(Debug, Clone)] +pub enum ArgumentBuilderType { + Literal(Literal), + Argument(Argument), +} + +/// A node that hasn't yet been built. +pub struct ArgumentBuilder { + arguments: CommandNode, + + command: Option) -> i32>>, + requirement: Rc) -> bool>, + target: Option>>>, + + forks: bool, + modifier: Option>>, +} + +impl Clone for ArgumentBuilder { + fn clone(&self) -> Self { + Self { + arguments: self.arguments.clone(), + command: self.command.clone(), + requirement: self.requirement.clone(), + target: self.target.clone(), + forks: self.forks, + modifier: self.modifier.clone(), + } + } +} + +/// A node that isn't yet built. +impl ArgumentBuilder { + pub fn new(value: ArgumentBuilderType) -> Self { + Self { + arguments: CommandNode { + value, + ..Default::default() + }, + command: None, + requirement: Rc::new(|_| true), + forks: false, + modifier: None, + target: None, + } + } + + pub fn then(&mut self, argument: ArgumentBuilder) -> Self { + self.then_built(argument.build()) + } + + pub fn then_built(&mut self, argument: CommandNode) -> Self { + self.arguments.add_child(&Rc::new(RefCell::new(argument))); + self.clone() + } + + pub fn executes(&mut self, f: F) -> Self + where + F: Fn(&CommandContext) -> i32 + 'static, + { + self.command = Some(Rc::new(f)); + self.clone() + } + + pub fn requires(&mut self, requirement: F) -> Self + where + F: Fn(Rc) -> bool + 'static, + { + self.requirement = Rc::new(requirement); + self.clone() + } + + pub fn redirect(&mut self, target: Rc>>) -> Self { + self.forward(target, None, false) + } + + pub fn fork( + &mut self, + target: Rc>>, + modifier: Rc>, + ) -> Self { + self.forward(target, Some(modifier), true) + } + + pub fn forward( + &mut self, + target: Rc>>, + modifier: Option>>, + fork: bool, + ) -> Self { + if !self.arguments.children.is_empty() { + panic!("Cannot forward a node with children"); + } + self.target = Some(target); + self.modifier = modifier; + self.forks = fork; + self.clone() + } + + pub fn build(self) -> CommandNode { + let mut result = CommandNode { + value: self.arguments.value, + command: self.command, + requirement: self.requirement, + redirect: self.target, + modifier: self.modifier, + forks: self.forks, + arguments: Default::default(), + children: Default::default(), + literals: Default::default(), + }; + + for argument in self.arguments.children.values() { + result.add_child(argument); + } + + result + } +} + +impl Debug for ArgumentBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ArgumentBuilder") + .field("arguments", &self.arguments) + // .field("command", &self.command) + // .field("requirement", &self.requirement) + .field("target", &self.target) + .field("forks", &self.forks) + // .field("modifier", &self.modifier) + .finish() + } +} diff --git a/azalea-brigadier/src/builder/literal_argument_builder.rs b/azalea-brigadier/src/builder/literal_argument_builder.rs new file mode 100755 index 00000000..6627ffdc --- /dev/null +++ b/azalea-brigadier/src/builder/literal_argument_builder.rs @@ -0,0 +1,24 @@ +use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType}; + +#[derive(Debug, Clone, Default)] +pub struct Literal { + pub value: String, +} +impl Literal { + pub fn new(value: &str) -> Self { + Self { + value: value.to_string(), + } + } +} + +impl From for ArgumentBuilderType { + fn from(literal: Literal) -> Self { + Self::Literal(literal) + } +} + +/// Shortcut for creating a new literal builder node. +pub fn literal(value: &str) -> ArgumentBuilder { + ArgumentBuilder::new(ArgumentBuilderType::Literal(Literal::new(value))) +} diff --git a/azalea-brigadier/src/builder/mod.rs b/azalea-brigadier/src/builder/mod.rs new file mode 100755 index 00000000..26f2f644 --- /dev/null +++ b/azalea-brigadier/src/builder/mod.rs @@ -0,0 +1,3 @@ +pub mod argument_builder; +pub mod literal_argument_builder; +pub mod required_argument_builder; diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs new file mode 100755 index 00000000..9d4d9e0a --- /dev/null +++ b/azalea-brigadier/src/builder/required_argument_builder.rs @@ -0,0 +1,45 @@ +use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType}; +use crate::{ + arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader, +}; +use std::{any::Any, fmt::Debug, rc::Rc}; + +/// An argument node type. The `T` type parameter is the type of the argument, +/// which can be anything. +#[derive(Clone)] +pub struct Argument { + pub name: String, + parser: Rc, +} +impl Argument { + pub fn new(name: &str, parser: Rc) -> Self { + Self { + name: name.to_string(), + parser, + } + } + + pub fn parse(&self, reader: &mut StringReader) -> Result, CommandSyntaxException> { + self.parser.parse(reader) + } +} + +impl From for ArgumentBuilderType { + fn from(argument: Argument) -> Self { + Self::Argument(argument) + } +} + +impl Debug for Argument { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Argument") + .field("name", &self.name) + // .field("parser", &self.parser) + .finish() + } +} + +/// Shortcut for creating a new argument builder node. +pub fn argument(name: &str, parser: impl ArgumentType + 'static) -> ArgumentBuilder { + ArgumentBuilder::new(Argument::new(name, Rc::new(parser)).into()) +} diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs new file mode 100755 index 00000000..eab42dd8 --- /dev/null +++ b/azalea-brigadier/src/command_dispatcher.rs @@ -0,0 +1,298 @@ +use crate::{ + builder::argument_builder::ArgumentBuilder, + context::{CommandContext, CommandContextBuilder}, + exceptions::{BuiltInExceptions, CommandSyntaxException}, + parse_results::ParseResults, + string_reader::StringReader, + tree::CommandNode, +}; +use std::{cell::RefCell, cmp::Ordering, collections::HashMap, marker::PhantomData, mem, rc::Rc}; + +#[derive(Default)] +pub struct CommandDispatcher { + pub root: Rc>>, + _marker: PhantomData, +} + +impl CommandDispatcher { + pub fn new() -> Self { + Self { + root: Rc::new(RefCell::new(CommandNode::default())), + _marker: PhantomData, + } + } + + pub fn register(&mut self, node: ArgumentBuilder) -> Rc>> { + let build = Rc::new(RefCell::new(node.build())); + self.root.borrow_mut().add_child(&build); + build + } + + pub fn parse(&self, command: StringReader, source: Rc) -> ParseResults { + let context = CommandContextBuilder::new( + Rc::new(self.clone()), + source, + self.root.clone(), + command.cursor(), + ); + self.parse_nodes(&self.root, &command, context).unwrap() + } + + fn parse_nodes( + &self, + node: &Rc>>, + original_reader: &StringReader, + context_so_far: CommandContextBuilder, + ) -> Result, CommandSyntaxException> { + let source = context_so_far.source.clone(); + let mut errors = HashMap::>, CommandSyntaxException>::new(); + let mut potentials: Vec> = vec![]; + let cursor = original_reader.cursor(); + + for child in node + .borrow() + .get_relevant_nodes(&mut original_reader.clone()) + { + if !child.borrow().can_use(source.clone()) { + continue; + } + let mut context = context_so_far.clone(); + let mut reader = original_reader.clone(); + + let parse_with_context_result = + child.borrow().parse_with_context(&mut reader, &mut context); + if let Err(ex) = parse_with_context_result { + errors.insert( + Rc::new((*child.borrow()).clone()), + BuiltInExceptions::DispatcherParseException { + message: ex.message(), + } + .create_with_context(&reader), + ); + reader.cursor = cursor; + continue; + } + if reader.can_read() && reader.peek() != ' ' { + errors.insert( + Rc::new((*child.borrow()).clone()), + BuiltInExceptions::DispatcherExpectedArgumentSeparator + .create_with_context(&reader), + ); + reader.cursor = cursor; + continue; + } + + context.with_command(&child.borrow().command); + if reader.can_read_length(if child.borrow().redirect.is_none() { + 2 + } else { + 1 + }) { + reader.skip(); + if let Some(redirect) = &child.borrow().redirect { + let child_context = CommandContextBuilder::new( + Rc::new(self.clone()), + source, + redirect.clone(), + reader.cursor, + ); + let parse = self + .parse_nodes(redirect, &reader, child_context) + .expect("Parsing nodes failed"); + context.with_child(Rc::new(parse.context)); + return Ok(ParseResults { + context, + reader: parse.reader, + exceptions: parse.exceptions, + }); + } else { + let parse = self + .parse_nodes(&child, &reader, context) + .expect("Parsing nodes failed"); + potentials.push(parse); + } + } else { + potentials.push(ParseResults { + context, + reader, + exceptions: HashMap::new(), + }); + } + } + + if !potentials.is_empty() { + if potentials.len() > 1 { + potentials.sort_by(|a, b| { + if !a.reader.can_read() && b.reader.can_read() { + return Ordering::Less; + }; + if a.reader.can_read() && !b.reader.can_read() { + return Ordering::Greater; + }; + if a.exceptions.is_empty() && !b.exceptions.is_empty() { + return Ordering::Less; + }; + if !a.exceptions.is_empty() && b.exceptions.is_empty() { + return Ordering::Greater; + }; + Ordering::Equal + }) + } + let best_potential = potentials.into_iter().next().unwrap(); + return Ok(best_potential); + } + + Ok(ParseResults { + context: context_so_far, + reader: original_reader.clone(), + exceptions: errors, + }) + } + + pub fn execute( + &self, + input: StringReader, + source: Rc, + ) -> Result { + let parse = self.parse(input, source); + Self::execute_parsed(parse) + } + + pub fn add_paths( + &self, + node: Rc>>, + result: &mut Vec>>>>, + parents: Vec>>>, + ) { + let mut current = parents; + current.push(node.clone()); + result.push(current.clone()); + + for child in node.borrow().children.values() { + self.add_paths(child.clone(), result, current.clone()); + } + } + + pub fn get_path(&self, target: CommandNode) -> Vec { + let rc_target = Rc::new(RefCell::new(target)); + let mut nodes: Vec>>>> = Vec::new(); + self.add_paths(self.root.clone(), &mut nodes, vec![]); + + for list in nodes { + if *list.last().expect("Nothing in list").borrow() == *rc_target.borrow() { + let mut result: Vec = Vec::with_capacity(list.len()); + for node in list { + if node != self.root { + result.push(node.borrow().name().to_string()); + } + } + return result; + } + } + vec![] + } + + pub fn find_node(&self, path: &[&str]) -> Option>>> { + let mut node = self.root.clone(); + for name in path { + if let Some(child) = node.clone().borrow().child(name) { + node = child + } else { + return None; + } + } + Some(node) + } + + /// Executes a given pre-parsed command. + pub fn execute_parsed(parse: ParseResults) -> Result { + if parse.reader.can_read() { + if parse.exceptions.len() == 1 { + return Err(parse.exceptions.values().next().unwrap().clone()); + } + if parse.context.range.is_empty() { + return Err( + BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader) + ); + } + return Err( + BuiltInExceptions::DispatcherUnknownArgument.create_with_context(&parse.reader) + ); + } + let mut result = 0i32; + let mut successful_forks = 0; + let mut forked = false; + let mut found_command = false; + let command = parse.reader.string(); + let original = parse.context.build(command); + let mut contexts = vec![original]; + let mut next: Vec> = vec![]; + + while !contexts.is_empty() { + for context in contexts.iter() { + let child = &context.child; + if let Some(child) = child { + println!("aaaaaaa {:?}", child); + forked |= child.forks; + if child.has_nodes() { + found_command = true; + let modifier = &context.modifier; + if let Some(modifier) = modifier { + let results = modifier(context); + if let Ok(results) = results { + if !results.is_empty() { + next.extend(results.iter().map(|s| child.copy_for(s.clone()))); + } + } else { + // TODO + // self.consumer.on_command_complete(context, false, 0); + if !forked { + return Err(results.err().unwrap()); + } + } + } else { + next.push(child.copy_for(context.source.clone())); + } + } + } else if let Some(context_command) = &context.command { + found_command = true; + + let value = context_command(context); + result += value; + // consumer.on_command_complete(context, true, value); + successful_forks += 1; + + // TODO: allow context_command to error and handle those errors + } + } + + // move next into contexts and clear next + mem::swap(&mut contexts, &mut next); + next.clear(); + } + + if !found_command { + // consumer.on_command_complete(original, false, 0); + return Err( + BuiltInExceptions::DispatcherUnknownCommand.create_with_context(&parse.reader) + ); + } + + // TODO: this is not how vanilla does it but it works + Ok(if successful_forks >= 2 { + successful_forks + } else { + result + }) + // Ok(if forked { successful_forks } else { result }) + } +} + +impl Clone for CommandDispatcher { + fn clone(&self) -> Self { + Self { + root: self.root.clone(), + _marker: PhantomData, + } + } +} diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs new file mode 100755 index 00000000..1834a73d --- /dev/null +++ b/azalea-brigadier/src/context/command_context.rs @@ -0,0 +1,80 @@ +use super::{parsed_command_node::ParsedCommandNode, string_range::StringRange, ParsedArgument}; +use crate::{modifier::RedirectModifier, tree::CommandNode}; +use std::{any::Any, cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; + +/// A built `CommandContextBuilder`. +pub struct CommandContext { + pub source: Rc, + pub input: String, + pub arguments: HashMap, + pub command: Option) -> i32>>, + pub root_node: Rc>>, + pub nodes: Vec>, + pub range: StringRange, + pub child: Option>>, + pub modifier: Option>>, + pub forks: bool, +} + +impl Clone for CommandContext { + fn clone(&self) -> Self { + Self { + source: self.source.clone(), + input: self.input.clone(), + arguments: self.arguments.clone(), + command: self.command.clone(), + root_node: self.root_node.clone(), + nodes: self.nodes.clone(), + range: self.range.clone(), + child: self.child.clone(), + modifier: self.modifier.clone(), + forks: self.forks, + } + } +} + +impl Debug for CommandContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CommandContext") + // .field("source", &self.source) + .field("input", &self.input) + // .field("arguments", &self.arguments) + // .field("command", &self.command) + // .field("root_node", &self.root_node) + // .field("nodes", &self.nodes) + .field("range", &self.range) + .field("child", &self.child) + // .field("modifier", &self.modifier) + .field("forks", &self.forks) + .finish() + } +} + +impl CommandContext { + pub fn copy_for(&self, source: Rc) -> Self { + if Rc::ptr_eq(&source, &self.source) { + return self.clone(); + } + CommandContext { + source, + input: self.input.clone(), + arguments: self.arguments.clone(), + command: self.command.clone(), + root_node: self.root_node.clone(), + nodes: self.nodes.clone(), + range: self.range.clone(), + child: self.child.clone(), + modifier: self.modifier.clone(), + forks: self.forks, + } + } + + pub fn has_nodes(&self) -> bool { + !self.nodes.is_empty() + } + + pub fn argument(&self, name: &str) -> Option> { + let argument = self.arguments.get(name); + argument.map(|a| a.result.clone()) + } +} diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs new file mode 100755 index 00000000..f192f6b7 --- /dev/null +++ b/azalea-brigadier/src/context/command_context_builder.rs @@ -0,0 +1,116 @@ +use super::{ + command_context::CommandContext, parsed_command_node::ParsedCommandNode, + string_range::StringRange, ParsedArgument, +}; +use crate::{command_dispatcher::CommandDispatcher, modifier::RedirectModifier, tree::CommandNode}; +use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; + +pub struct CommandContextBuilder { + pub arguments: HashMap, + pub root: Rc>>, + pub nodes: Vec>, + pub dispatcher: Rc>, + pub source: Rc, + pub command: Option) -> i32>>, + pub child: Option>>, + pub range: StringRange, + pub modifier: Option>>, + pub forks: bool, +} + +impl Clone for CommandContextBuilder { + fn clone(&self) -> Self { + Self { + arguments: self.arguments.clone(), + root: self.root.clone(), + nodes: self.nodes.clone(), + dispatcher: self.dispatcher.clone(), + source: self.source.clone(), + command: self.command.clone(), + child: self.child.clone(), + range: self.range.clone(), + modifier: self.modifier.clone(), + forks: self.forks, + } + } +} + +impl CommandContextBuilder { + pub fn new( + dispatcher: Rc>, + source: Rc, + root_node: Rc>>, + start: usize, + ) -> Self { + Self { + arguments: HashMap::new(), + root: root_node, + source, + range: StringRange::at(start), + command: None, + dispatcher, + nodes: vec![], + child: None, + modifier: None, + forks: false, + } + } + + pub fn with_command( + &mut self, + command: &Option) -> i32>>, + ) -> &Self { + self.command = command.clone(); + self + } + pub fn with_child(&mut self, child: Rc>) -> &Self { + self.child = Some(child); + self + } + pub fn with_argument(&mut self, name: &str, argument: ParsedArgument) -> &Self { + self.arguments.insert(name.to_string(), argument); + self + } + pub fn with_node(&mut self, node: Rc>>, range: StringRange) -> &Self { + self.nodes.push(ParsedCommandNode { + node: node.clone(), + range: range.clone(), + }); + self.range = StringRange::encompassing(&self.range, &range); + self.modifier = node.borrow().modifier.clone(); + self.forks = node.borrow().forks; + self + } + + pub fn build(&self, input: &str) -> CommandContext { + CommandContext { + arguments: self.arguments.clone(), + root_node: self.root.clone(), + nodes: self.nodes.clone(), + source: self.source.clone(), + command: self.command.clone(), + child: self.child.clone().map(|c| Rc::new(c.build(input))), + range: self.range.clone(), + forks: self.forks, + modifier: self.modifier.clone(), + input: input.to_string(), + } + } +} + +impl Debug for CommandContextBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CommandContextBuilder") + // .field("arguments", &self.arguments) + .field("root", &self.root) + // .field("nodes", &self.nodes) + // .field("dispatcher", &self.dispatcher) + // .field("source", &self.source) + // .field("command", &self.command) + .field("child", &self.child) + .field("range", &self.range) + // .field("modifier", &self.modifier) + .field("forks", &self.forks) + .finish() + } +} diff --git a/azalea-brigadier/src/context/mod.rs b/azalea-brigadier/src/context/mod.rs new file mode 100755 index 00000000..d535602a --- /dev/null +++ b/azalea-brigadier/src/context/mod.rs @@ -0,0 +1,11 @@ +mod command_context; +mod command_context_builder; +mod parsed_argument; +mod parsed_command_node; +mod string_range; + +pub use command_context::CommandContext; +pub use command_context_builder::CommandContextBuilder; +pub use parsed_argument::ParsedArgument; +pub use parsed_command_node::ParsedCommandNode; +pub use string_range::StringRange; diff --git a/azalea-brigadier/src/context/parsed_argument.rs b/azalea-brigadier/src/context/parsed_argument.rs new file mode 100755 index 00000000..3302b1be --- /dev/null +++ b/azalea-brigadier/src/context/parsed_argument.rs @@ -0,0 +1,8 @@ +use super::string_range::StringRange; +use std::{any::Any, rc::Rc}; + +#[derive(Clone)] +pub struct ParsedArgument { + pub range: StringRange, + pub result: Rc, +} diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs new file mode 100755 index 00000000..ed49928d --- /dev/null +++ b/azalea-brigadier/src/context/parsed_command_node.rs @@ -0,0 +1,18 @@ +use super::string_range::StringRange; +use crate::tree::CommandNode; +use std::{cell::RefCell, rc::Rc}; + +#[derive(Debug)] +pub struct ParsedCommandNode { + pub node: Rc>>, + pub range: StringRange, +} + +impl Clone for ParsedCommandNode { + fn clone(&self) -> Self { + Self { + node: self.node.clone(), + range: self.range.clone(), + } + } +} diff --git a/azalea-brigadier/src/context/string_range.rs b/azalea-brigadier/src/context/string_range.rs new file mode 100755 index 00000000..8ca88624 --- /dev/null +++ b/azalea-brigadier/src/context/string_range.rs @@ -0,0 +1,45 @@ +use std::cmp; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct StringRange { + start: usize, + end: usize, +} + +impl StringRange { + pub fn new(start: usize, end: usize) -> Self { + Self { start, end } + } + + pub fn at(pos: usize) -> Self { + Self::new(pos, pos) + } + + pub fn between(start: usize, end: usize) -> Self { + Self::new(start, end) + } + + pub fn encompassing(a: &Self, b: &Self) -> Self { + Self::new(cmp::min(a.start, b.start), cmp::max(a.end, b.end)) + } + + pub fn start(&self) -> usize { + self.start + } + + pub fn end(&self) -> usize { + self.end + } + + pub fn get<'a>(&self, reader: &'a str) -> &'a str { + &reader[self.start..self.end] + } + + pub fn is_empty(&self) -> bool { + self.start == self.end + } + + pub fn length(&self) -> usize { + self.end - self.start + } +} diff --git a/azalea-brigadier/src/exceptions/builtin_exceptions.rs b/azalea-brigadier/src/exceptions/builtin_exceptions.rs new file mode 100755 index 00000000..09951a03 --- /dev/null +++ b/azalea-brigadier/src/exceptions/builtin_exceptions.rs @@ -0,0 +1,159 @@ +use std::fmt; + +use crate::{message::Message, string_reader::StringReader}; + +use super::command_syntax_exception::CommandSyntaxException; + +#[derive(Clone, PartialEq)] +pub enum BuiltInExceptions { + DoubleTooSmall { found: f64, min: f64 }, + DoubleTooBig { found: f64, max: f64 }, + + FloatTooSmall { found: f32, min: f32 }, + FloatTooBig { found: f32, max: f32 }, + + IntegerTooSmall { found: i32, min: i32 }, + IntegerTooBig { found: i32, max: i32 }, + + LongTooSmall { found: i64, min: i64 }, + LongTooBig { found: i64, max: i64 }, + + LiteralIncorrect { expected: String }, + + ReaderExpectedStartOfQuote, + ReaderExpectedEndOfQuote, + ReaderInvalidEscape { character: char }, + ReaderInvalidBool { value: String }, + ReaderInvalidInt { value: String }, + ReaderExpectedInt, + ReaderInvalidLong { value: String }, + ReaderExpectedLong, + ReaderInvalidDouble { value: String }, + ReaderExpectedDouble, + ReaderInvalidFloat { value: String }, + ReaderExpectedFloat, + ReaderExpectedBool, + ReaderExpectedSymbol { symbol: char }, + + DispatcherUnknownCommand, + DispatcherUnknownArgument, + DispatcherExpectedArgumentSeparator, + DispatcherParseException { message: String }, +} + +impl fmt::Debug for BuiltInExceptions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BuiltInExceptions::DoubleTooSmall { found, min } => { + write!(f, "Double must not be less than {}, found {}", min, found) + } + BuiltInExceptions::DoubleTooBig { found, max } => { + write!(f, "Double must not be more than {}, found {}", max, found) + } + + BuiltInExceptions::FloatTooSmall { found, min } => { + write!(f, "Float must not be less than {}, found {}", min, found) + } + BuiltInExceptions::FloatTooBig { found, max } => { + write!(f, "Float must not be more than {}, found {}", max, found) + } + + BuiltInExceptions::IntegerTooSmall { found, min } => { + write!(f, "Integer must not be less than {}, found {}", min, found) + } + BuiltInExceptions::IntegerTooBig { found, max } => { + write!(f, "Integer must not be more than {}, found {}", max, found) + } + + BuiltInExceptions::LongTooSmall { found, min } => { + write!(f, "Long must not be less than {}, found {}", min, found) + } + BuiltInExceptions::LongTooBig { found, max } => { + write!(f, "Long must not be more than {}, found {}", max, found) + } + + BuiltInExceptions::LiteralIncorrect { expected } => { + write!(f, "Expected literal {}", expected) + } + + BuiltInExceptions::ReaderExpectedStartOfQuote => { + write!(f, "Expected quote to start a string") + } + BuiltInExceptions::ReaderExpectedEndOfQuote => { + write!(f, "Unclosed quoted string") + } + BuiltInExceptions::ReaderInvalidEscape { character } => { + write!( + f, + "Invalid escape sequence '{}' in quoted string", + character + ) + } + BuiltInExceptions::ReaderInvalidBool { value } => { + write!( + f, + "Invalid bool, expected true or false but found '{}'", + value + ) + } + BuiltInExceptions::ReaderInvalidInt { value } => { + write!(f, "Invalid Integer '{}'", value) + } + BuiltInExceptions::ReaderExpectedInt => { + write!(f, "Expected Integer") + } + BuiltInExceptions::ReaderInvalidLong { value } => { + write!(f, "Invalid long '{}'", value) + } + BuiltInExceptions::ReaderExpectedLong => { + write!(f, "Expected long") + } + BuiltInExceptions::ReaderInvalidDouble { value } => { + write!(f, "Invalid double '{}'", value) + } + BuiltInExceptions::ReaderExpectedDouble => { + write!(f, "Expected double") + } + BuiltInExceptions::ReaderInvalidFloat { value } => { + write!(f, "Invalid Float '{}'", value) + } + BuiltInExceptions::ReaderExpectedFloat => { + write!(f, "Expected Float") + } + BuiltInExceptions::ReaderExpectedBool => { + write!(f, "Expected bool") + } + BuiltInExceptions::ReaderExpectedSymbol { symbol } => { + write!(f, "Expected '{}'", symbol) + } + + BuiltInExceptions::DispatcherUnknownCommand => { + write!(f, "Unknown command") + } + BuiltInExceptions::DispatcherUnknownArgument => { + write!(f, "Incorrect argument for command") + } + BuiltInExceptions::DispatcherExpectedArgumentSeparator => { + write!( + f, + "Expected whitespace to end one argument, but found trailing data" + ) + } + BuiltInExceptions::DispatcherParseException { message } => { + write!(f, "Could not parse command: {}", message) + } + } + } +} + +impl BuiltInExceptions { + pub fn create(self) -> CommandSyntaxException { + let message = Message::from(format!("{:?}", self)); + CommandSyntaxException::create(self, message) + } + + pub fn create_with_context(self, reader: &StringReader) -> CommandSyntaxException { + let message = Message::from(format!("{:?}", self)); + CommandSyntaxException::new(self, message, reader.string(), reader.cursor()) + } +} diff --git a/azalea-brigadier/src/exceptions/command_syntax_exception.rs b/azalea-brigadier/src/exceptions/command_syntax_exception.rs new file mode 100755 index 00000000..4bfe9cda --- /dev/null +++ b/azalea-brigadier/src/exceptions/command_syntax_exception.rs @@ -0,0 +1,91 @@ +use std::{cmp, fmt}; + +use super::builtin_exceptions::BuiltInExceptions; +use crate::message::Message; + +#[derive(Clone, PartialEq)] +pub struct CommandSyntaxException { + pub type_: BuiltInExceptions, + message: Message, + input: Option, + cursor: Option, +} + +const CONTEXT_AMOUNT: usize = 10; + +impl CommandSyntaxException { + pub fn new(type_: BuiltInExceptions, message: Message, input: &str, cursor: usize) -> Self { + Self { + type_, + message, + input: Some(input.to_string()), + cursor: Some(cursor), + } + } + + pub fn create(type_: BuiltInExceptions, message: Message) -> Self { + Self { + type_, + message, + input: None, + cursor: None, + } + } + + pub fn message(&self) -> String { + let mut message = self.message.string(); + let context = self.context(); + if let Some(context) = context { + message.push_str(&format!( + " at position {}: {}", + self.cursor.unwrap_or(usize::MAX), + context + )); + } + message + } + + pub fn raw_message(&self) -> &Message { + &self.message + } + + pub fn context(&self) -> Option { + if let Some(input) = &self.input { + if let Some(cursor) = self.cursor { + let mut builder = String::new(); + let cursor = cmp::min(input.len(), cursor); + + if cursor > CONTEXT_AMOUNT { + builder.push_str("..."); + } + + builder.push_str( + &input + [(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor], + ); + builder.push_str("<--[HERE]"); + + return Some(builder); + } + } + None + } + + pub fn get_type(&self) -> &BuiltInExceptions { + &self.type_ + } + + pub fn input(&self) -> &Option { + &self.input + } + + pub fn cursor(&self) -> Option { + self.cursor + } +} + +impl fmt::Debug for CommandSyntaxException { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message()) + } +} diff --git a/azalea-brigadier/src/exceptions/mod.rs b/azalea-brigadier/src/exceptions/mod.rs new file mode 100755 index 00000000..6b9c8d62 --- /dev/null +++ b/azalea-brigadier/src/exceptions/mod.rs @@ -0,0 +1,5 @@ +mod builtin_exceptions; +mod command_syntax_exception; + +pub use builtin_exceptions::BuiltInExceptions; +pub use command_syntax_exception::CommandSyntaxException; diff --git a/azalea-brigadier/src/lib.rs b/azalea-brigadier/src/lib.rs new file mode 100755 index 00000000..a294eb19 --- /dev/null +++ b/azalea-brigadier/src/lib.rs @@ -0,0 +1,10 @@ +pub mod arguments; +pub mod builder; +pub mod command_dispatcher; +pub mod context; +pub mod exceptions; +pub mod message; +pub mod modifier; +pub mod parse_results; +pub mod string_reader; +pub mod tree; diff --git a/azalea-brigadier/src/message.rs b/azalea-brigadier/src/message.rs new file mode 100755 index 00000000..75e07d4e --- /dev/null +++ b/azalea-brigadier/src/message.rs @@ -0,0 +1,14 @@ +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct Message(String); + +impl Message { + pub fn string(&self) -> String { + self.0.to_string() + } +} + +impl From for Message { + fn from(s: String) -> Self { + Self(s) + } +} diff --git a/azalea-brigadier/src/modifier.rs b/azalea-brigadier/src/modifier.rs new file mode 100755 index 00000000..d40d59f9 --- /dev/null +++ b/azalea-brigadier/src/modifier.rs @@ -0,0 +1,6 @@ +use std::rc::Rc; + +use crate::{context::CommandContext, exceptions::CommandSyntaxException}; + +pub type RedirectModifier = + dyn Fn(&CommandContext) -> Result>, CommandSyntaxException>; diff --git a/azalea-brigadier/src/parse_results.rs b/azalea-brigadier/src/parse_results.rs new file mode 100755 index 00000000..3698ae82 --- /dev/null +++ b/azalea-brigadier/src/parse_results.rs @@ -0,0 +1,21 @@ +use crate::{ + context::CommandContextBuilder, exceptions::CommandSyntaxException, + string_reader::StringReader, tree::CommandNode, +}; +use std::{collections::HashMap, fmt::Debug, rc::Rc}; + +pub struct ParseResults { + pub context: CommandContextBuilder, + pub reader: StringReader, + pub exceptions: HashMap>, CommandSyntaxException>, +} + +impl Debug for ParseResults { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ParseResults") + .field("context", &self.context) + // .field("reader", &self.reader) + .field("exceptions", &self.exceptions) + .finish() + } +} diff --git a/azalea-brigadier/src/string_reader.rs b/azalea-brigadier/src/string_reader.rs new file mode 100755 index 00000000..9eb09b57 --- /dev/null +++ b/azalea-brigadier/src/string_reader.rs @@ -0,0 +1,274 @@ +use crate::exceptions::{BuiltInExceptions, CommandSyntaxException}; +use std::str::FromStr; + +#[derive(Clone)] +pub struct StringReader { + string: String, + pub cursor: usize, +} + +const SYNTAX_ESCAPE: char = '\\'; +const SYNTAX_DOUBLE_QUOTE: char = '"'; +const SYNTAX_SINGLE_QUOTE: char = '\''; + +impl From for StringReader { + fn from(string: String) -> Self { + Self { string, cursor: 0 } + } +} +impl From<&str> for StringReader { + fn from(string: &str) -> Self { + Self { + string: string.to_string(), + cursor: 0, + } + } +} + +impl StringReader { + pub fn string(&self) -> &str { + &self.string + } + + pub fn remaining_length(&self) -> usize { + self.string.len() - self.cursor + } + + pub fn total_length(&self) -> usize { + self.string.len() + } + + pub fn get_read(&self) -> &str { + &self.string[..self.cursor] + } + + pub fn remaining(&self) -> &str { + &self.string[self.cursor..] + } + + pub fn can_read_length(&self, length: usize) -> bool { + self.cursor + length <= self.string.len() + } + + pub fn can_read(&self) -> bool { + self.can_read_length(1) + } + + pub fn peek(&self) -> char { + self.string.chars().nth(self.cursor).unwrap() + } + + pub fn peek_offset(&self, offset: usize) -> char { + self.string.chars().nth(self.cursor + offset).unwrap() + } + + pub fn cursor(&self) -> usize { + self.cursor + } + + pub fn read(&mut self) -> char { + let c = self.peek(); + self.cursor += 1; + c + } + + pub fn skip(&mut self) { + self.cursor += 1; + } + + pub fn is_allowed_number(c: char) -> bool { + ('0'..='9').contains(&c) || c == '.' || c == '-' + } + + pub fn is_quoted_string_start(c: char) -> bool { + c == SYNTAX_DOUBLE_QUOTE || c == SYNTAX_SINGLE_QUOTE + } + + pub fn skip_whitespace(&mut self) { + while self.can_read() && self.peek().is_whitespace() { + self.skip(); + } + } + + pub fn read_int(&mut self) -> Result { + let start = self.cursor; + while self.can_read() && StringReader::is_allowed_number(self.peek()) { + self.skip(); + } + let number = &self.string[start..self.cursor]; + if number.is_empty() { + return Err(BuiltInExceptions::ReaderExpectedInt.create_with_context(self)); + } + let result = i32::from_str(number); + if result.is_err() { + self.cursor = start; + return Err(BuiltInExceptions::ReaderInvalidInt { + value: number.to_string(), + } + .create_with_context(self)); + } + + Ok(result.unwrap()) + } + + pub fn read_long(&mut self) -> Result { + let start = self.cursor; + while self.can_read() && StringReader::is_allowed_number(self.peek()) { + self.skip(); + } + let number = &self.string[start..self.cursor]; + if number.is_empty() { + return Err(BuiltInExceptions::ReaderExpectedLong.create_with_context(self)); + } + let result = i64::from_str(number); + if result.is_err() { + self.cursor = start; + return Err(BuiltInExceptions::ReaderInvalidLong { + value: number.to_string(), + } + .create_with_context(self)); + } + + Ok(result.unwrap()) + } + + pub fn read_double(&mut self) -> Result { + let start = self.cursor; + while self.can_read() && StringReader::is_allowed_number(self.peek()) { + self.skip(); + } + let number = &self.string[start..self.cursor]; + if number.is_empty() { + return Err(BuiltInExceptions::ReaderExpectedDouble.create_with_context(self)); + } + let result = f64::from_str(number); + if result.is_err() { + self.cursor = start; + return Err(BuiltInExceptions::ReaderInvalidDouble { + value: number.to_string(), + } + .create_with_context(self)); + } + + Ok(result.unwrap()) + } + + pub fn read_float(&mut self) -> Result { + let start = self.cursor; + while self.can_read() && StringReader::is_allowed_number(self.peek()) { + self.skip(); + } + let number = &self.string[start..self.cursor]; + if number.is_empty() { + return Err(BuiltInExceptions::ReaderExpectedFloat.create_with_context(self)); + } + let result = f32::from_str(number); + if result.is_err() { + self.cursor = start; + return Err(BuiltInExceptions::ReaderInvalidFloat { + value: number.to_string(), + } + .create_with_context(self)); + } + + Ok(result.unwrap()) + } + + pub fn is_allowed_in_unquoted_string(c: char) -> bool { + ('0'..='9').contains(&c) + || ('A'..='Z').contains(&c) + || ('a'..='z').contains(&c) + || c == '_' + || c == '-' + || c == '.' + || c == '+' + } + + pub fn read_unquoted_string(&mut self) -> &str { + let start = self.cursor; + while self.can_read() && StringReader::is_allowed_in_unquoted_string(self.peek()) { + self.skip(); + } + &self.string[start..self.cursor] + } + + pub fn read_quoted_string(&mut self) -> Result { + if !self.can_read() { + return Ok(String::new()); + } + let next = self.peek(); + if !StringReader::is_quoted_string_start(next) { + return Err(BuiltInExceptions::ReaderExpectedStartOfQuote.create_with_context(self)); + } + self.skip(); + self.read_string_until(next) + } + + pub fn read_string_until( + &mut self, + terminator: char, + ) -> Result { + let mut result = String::new(); + let mut escaped = false; + while self.can_read() { + let c = self.read(); + if escaped { + if c == terminator || c == SYNTAX_ESCAPE { + result.push(c); + escaped = false; + } else { + self.cursor -= 1; + return Err(BuiltInExceptions::ReaderInvalidEscape { character: c } + .create_with_context(self)); + } + } else if c == SYNTAX_ESCAPE { + escaped = true; + } else if c == terminator { + return Ok(result); + } else { + result.push(c); + } + } + + Err(BuiltInExceptions::ReaderExpectedEndOfQuote.create_with_context(self)) + } + + pub fn read_string(&mut self) -> Result { + if !self.can_read() { + return Ok(String::new()); + } + let next = self.peek(); + if StringReader::is_quoted_string_start(next) { + self.skip(); + return self.read_string_until(next); + } + Ok(self.read_unquoted_string().to_string()) + } + + pub fn read_boolean(&mut self) -> Result { + let start = self.cursor; + let value = self.read_string()?; + if value.is_empty() { + return Err(BuiltInExceptions::ReaderExpectedBool.create_with_context(self)); + } + + if value == "true" { + Ok(true) + } else if value == "false" { + Ok(false) + } else { + self.cursor = start; + Err(BuiltInExceptions::ReaderInvalidBool { value }.create_with_context(self)) + } + } + + pub fn expect(&mut self, c: char) -> Result<(), CommandSyntaxException> { + if !self.can_read() || self.peek() != c { + return Err( + BuiltInExceptions::ReaderExpectedSymbol { symbol: c }.create_with_context(self) + ); + } + self.skip(); + Ok(()) + } +} diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs new file mode 100755 index 00000000..b6181c73 --- /dev/null +++ b/azalea-brigadier/src/tree/mod.rs @@ -0,0 +1,259 @@ +use crate::{ + builder::{ + argument_builder::ArgumentBuilderType, literal_argument_builder::Literal, + required_argument_builder::Argument, + }, + context::{CommandContext, CommandContextBuilder, ParsedArgument, StringRange}, + exceptions::{BuiltInExceptions, CommandSyntaxException}, + modifier::RedirectModifier, + string_reader::StringReader, +}; +use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, ptr, rc::Rc}; + +/// An ArgumentBuilder that has been built. +#[non_exhaustive] +pub struct CommandNode { + pub value: ArgumentBuilderType, + + pub children: HashMap>>>, + pub literals: HashMap>>>, + pub arguments: HashMap>>>, + + pub command: Option) -> i32>>, + pub requirement: Rc) -> bool>, + pub redirect: Option>>>, + pub forks: bool, + pub modifier: Option>>, +} + +impl Clone for CommandNode { + fn clone(&self) -> Self { + Self { + value: self.value.clone(), + children: self.children.clone(), + literals: self.literals.clone(), + arguments: self.arguments.clone(), + command: self.command.clone(), + requirement: self.requirement.clone(), + redirect: self.redirect.clone(), + forks: self.forks, + modifier: self.modifier.clone(), + } + } +} + +impl CommandNode { + /// Gets the literal, or panics. You should use match if you're not certain about the type. + pub fn literal(&self) -> &Literal { + match self.value { + ArgumentBuilderType::Literal(ref literal) => literal, + _ => panic!("CommandNode::literal() called on non-literal node"), + } + } + /// Gets the argument, or panics. You should use match if you're not certain about the type. + pub fn argument(&self) -> &Argument { + match self.value { + ArgumentBuilderType::Argument(ref argument) => argument, + _ => panic!("CommandNode::argument() called on non-argument node"), + } + } + + pub fn get_relevant_nodes(&self, input: &mut StringReader) -> Vec>>> { + let literals = &self.literals; + + if !literals.is_empty() { + let cursor = input.cursor(); + while input.can_read() && input.peek() != ' ' { + input.skip(); + } + let text: String = input + .string() + .chars() + .skip(cursor) + .take(input.cursor() - cursor) + .collect(); + input.cursor = cursor; + let literal = literals.get(&text); + if let Some(literal) = literal { + return vec![literal.clone()]; + } else { + return self.arguments.values().cloned().collect(); + } + } else { + self.arguments.values().cloned().collect() + } + } + + pub fn can_use(&self, source: Rc) -> bool { + (self.requirement)(source) + } + + pub fn add_child(&mut self, node: &Rc>>) { + let child = self.children.get(node.borrow().name()); + if let Some(child) = child { + // We've found something to merge onto + if let Some(command) = &node.borrow().command { + child.borrow_mut().command = Some(command.clone()); + } + for grandchild in node.borrow().children.values() { + child.borrow_mut().add_child(grandchild); + } + } else { + self.children + .insert(node.borrow().name().to_string(), node.clone()); + match &node.borrow().value { + ArgumentBuilderType::Literal(literal) => { + self.literals.insert(literal.value.clone(), node.clone()); + } + ArgumentBuilderType::Argument(argument) => { + self.arguments.insert(argument.name.clone(), node.clone()); + } + } + } + } + + pub fn name(&self) -> &str { + match &self.value { + ArgumentBuilderType::Argument(argument) => &argument.name, + ArgumentBuilderType::Literal(literal) => &literal.value, + } + } + + pub fn child(&self, name: &str) -> Option>>> { + self.children.get(name).cloned() + } + + pub fn parse_with_context( + &self, + reader: &mut StringReader, + context_builder: &mut CommandContextBuilder, + ) -> Result<(), CommandSyntaxException> { + match self.value { + ArgumentBuilderType::Argument(ref argument) => { + let start = reader.cursor(); + let result = argument.parse(reader)?; + let parsed = ParsedArgument { + range: StringRange::between(start, reader.cursor()), + result, + }; + + context_builder.with_argument(&argument.name, parsed.clone()); + context_builder.with_node(Rc::new(RefCell::new(self.clone())), parsed.range); + + Ok(()) + } + ArgumentBuilderType::Literal(ref literal) => { + let start = reader.cursor(); + let end = self.parse(reader); + + if let Some(end) = end { + context_builder.with_node( + Rc::new(RefCell::new(self.clone())), + StringRange::between(start, end), + ); + return Ok(()); + } + + Err(BuiltInExceptions::LiteralIncorrect { + expected: literal.value.clone(), + } + .create_with_context(reader)) + } + } + } + + fn parse(&self, reader: &mut StringReader) -> Option { + match self.value { + ArgumentBuilderType::Argument(_) => { + panic!("Can't parse argument.") + } + ArgumentBuilderType::Literal(ref literal) => { + let start = reader.cursor(); + if reader.can_read_length(literal.value.len()) { + let end = start + literal.value.len(); + if reader + .string() + .get(start..end) + .expect("Couldn't slice reader correctly?") + == literal.value + { + reader.cursor = end; + if !reader.can_read() || reader.peek() == ' ' { + return Some(end); + } else { + reader.cursor = start; + } + } + } + } + } + None + } +} + +impl Debug for CommandNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CommandNode") + .field("value", &self.value) + .field("children", &self.children) + .field("command", &self.command.is_some()) + // .field("requirement", &self.requirement) + .field("redirect", &self.redirect) + .field("forks", &self.forks) + // .field("modifier", &self.modifier) + .finish() + } +} + +impl Default for CommandNode { + fn default() -> Self { + Self { + value: ArgumentBuilderType::Literal(Literal::default()), + + children: HashMap::new(), + literals: HashMap::new(), + arguments: HashMap::new(), + + command: None, + requirement: Rc::new(|_| true), + redirect: None, + forks: false, + modifier: None, + } + } +} + +impl Hash for CommandNode { + fn hash(&self, state: &mut H) { + // hash the children + for (k, v) in &self.children { + k.hash(state); + v.borrow().hash(state); + } + // i hope this works because if doesn't then that'll be a problem + ptr::hash(&self.command, state); + } +} + +impl PartialEq for CommandNode { + fn eq(&self, other: &Self) -> bool { + if self.children != other.children { + return false; + } + if let Some(selfexecutes) = &self.command { + // idk how to do this better since we can't compare `dyn Fn`s + if let Some(otherexecutes) = &other.command { + #[allow(clippy::vtable_address_comparisons)] + if !Rc::ptr_eq(selfexecutes, otherexecutes) { + return false; + } + } else { + return false; + } + } else if other.command.is_some() { + return false; + } + true + } +} +impl Eq for CommandNode {} diff --git a/azalea-brigadier/tests/arguments/bool_argument_type_test.rs b/azalea-brigadier/tests/arguments/bool_argument_type_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/arguments/double_argument_type_test.rs b/azalea-brigadier/tests/arguments/double_argument_type_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/arguments/float_argument_type_test.rs b/azalea-brigadier/tests/arguments/float_argument_type_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/arguments/integer_argument_type_test.rs b/azalea-brigadier/tests/arguments/integer_argument_type_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/arguments/long_argument_type_test.rs b/azalea-brigadier/tests/arguments/long_argument_type_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/arguments/string_argument_type_test.rs b/azalea-brigadier/tests/arguments/string_argument_type_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/builder/argument_builder_test.rs b/azalea-brigadier/tests/builder/argument_builder_test.rs new file mode 100755 index 00000000..e570c988 --- /dev/null +++ b/azalea-brigadier/tests/builder/argument_builder_test.rs @@ -0,0 +1,75 @@ +use std::rc::Rc; + +use crate::{ + arguments::integer_argument_type::integer, + builder::{literal_argument_builder::literal, required_argument_builder::argument}, +}; + +use super::ArgumentBuilder; + +// public class ArgumentBuilderTest { +// private TestableArgumentBuilder builder; + +// @Before +// public void setUp() throws Exception { +// builder = new TestableArgumentBuilder<>(); +// } + +// @Test +// public void testArguments() throws Exception { +// final RequiredArgumentBuilder argument = argument("bar", integer()); + +// builder.then(argument); + +// assertThat(builder.getArguments(), hasSize(1)); +// assertThat(builder.getArguments(), hasItem((CommandNode) argument.build())); +// } + +#[test] +fn test_arguments() { + let mut builder: ArgumentBuilder<()> = literal("foo"); + + let argument: ArgumentBuilder<()> = argument("bar", integer()); + builder.then(argument.clone()); + assert_eq!(builder.arguments.children.len(), 1); + let built_argument = Rc::new(argument.build()); + assert!(builder + .arguments + .children + .values() + .any(|e| *e.borrow() == *built_argument)); +} + +// @Test +// public void testRedirect() throws Exception { +// final CommandNode target = mock(CommandNode.class); +// builder.redirect(target); +// assertThat(builder.getRedirect(), is(target)); +// } + +// @Test(expected = IllegalStateException.class) +// public void testRedirect_withChild() throws Exception { +// final CommandNode target = mock(CommandNode.class); +// builder.then(literal("foo")); +// builder.redirect(target); +// } + +// @Test(expected = IllegalStateException.class) +// public void testThen_withRedirect() throws Exception { +// final CommandNode target = mock(CommandNode.class); +// builder.redirect(target); +// builder.then(literal("foo")); +// } + +// private static class TestableArgumentBuilder extends ArgumentBuilder> { +// @Override +// protected TestableArgumentBuilder getThis() { +// return this; +// } + +// @Override +// public CommandNode build() { +// return null; +// } +// } +// } diff --git a/azalea-brigadier/tests/builder/literal_argument_builder_test.rs b/azalea-brigadier/tests/builder/literal_argument_builder_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/builder/required_argument_builder_test.rs b/azalea-brigadier/tests/builder/required_argument_builder_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/command_dispatcher_test.rs b/azalea-brigadier/tests/command_dispatcher_test.rs new file mode 100755 index 00000000..cb33ac73 --- /dev/null +++ b/azalea-brigadier/tests/command_dispatcher_test.rs @@ -0,0 +1,410 @@ +use std::rc::Rc; + +use azalea_brigadier::{ + arguments::integer_argument_type::integer, + builder::{literal_argument_builder::literal, required_argument_builder::argument}, + command_dispatcher::CommandDispatcher, + context::CommandContext, + exceptions::{BuiltInExceptions, CommandSyntaxException}, + string_reader::StringReader, +}; + +#[derive(Debug, PartialEq)] +struct CommandSource {} + +fn input_with_offset(input: &str, offset: usize) -> StringReader { + let mut result: StringReader = input.into(); + result.cursor = offset; + result +} + +#[test] +fn create_and_execute_command() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("foo").executes(|_| 42)); + + assert_eq!( + subject + .execute("foo".into(), Rc::new(CommandSource {})) + .unwrap(), + 42 + ); +} + +#[test] +fn create_and_execute_offset_command() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("foo").executes(|_| 42)); + + assert_eq!( + subject + .execute(input_with_offset("/foo", 1), Rc::new(CommandSource {})) + .unwrap(), + 42 + ); +} + +#[test] +fn create_and_merge_commands() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("base").then(literal("foo").executes(|_| 42))); + subject.register(literal("base").then(literal("bar").executes(|_| 42))); + + assert_eq!( + subject + .execute("base foo".into(), Rc::new(CommandSource {})) + .unwrap(), + 42 + ); + assert_eq!( + subject + .execute("base bar".into(), Rc::new(CommandSource {})) + .unwrap(), + 42 + ); +} + +#[test] +fn execute_unknown_command() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("bar")); + subject.register(literal("baz")); + + let execute_result = subject.execute("foo".into(), Rc::new(CommandSource {})); + + let err = execute_result.err().unwrap(); + match err.type_ { + BuiltInExceptions::DispatcherUnknownCommand => {} + _ => panic!("Unexpected error"), + } + assert_eq!(err.cursor().unwrap(), 0); +} + +#[test] +fn execute_impermissible_command() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("foo").requires(|_| false)); + + let execute_result = subject.execute("foo".into(), Rc::new(CommandSource {})); + + let err = execute_result.err().unwrap(); + match err.type_ { + BuiltInExceptions::DispatcherUnknownCommand => {} + _ => panic!("Unexpected error"), + } + assert_eq!(err.cursor().unwrap(), 0); +} + +#[test] +fn execute_empty_command() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("")); + + let execute_result = subject.execute("".into(), Rc::new(CommandSource {})); + + let err = execute_result.err().unwrap(); + match err.type_ { + BuiltInExceptions::DispatcherUnknownCommand => {} + _ => panic!("Unexpected error"), + } + assert_eq!(err.cursor().unwrap(), 0); +} + +#[test] +fn execute_unknown_subcommand() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("foo").executes(|_| 42)); + + let execute_result = subject.execute("foo bar".into(), Rc::new(CommandSource {})); + + let err = execute_result.err().unwrap(); + match err.type_ { + BuiltInExceptions::DispatcherUnknownArgument => {} + _ => panic!("Unexpected error"), + } + assert_eq!(err.cursor().unwrap(), 4); +} + +#[test] +fn execute_incorrect_literal() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("foo").executes(|_| 42).then(literal("bar"))); + + let execute_result = subject.execute("foo baz".into(), Rc::new(CommandSource {})); + + let err = execute_result.err().unwrap(); + match err.type_ { + BuiltInExceptions::DispatcherUnknownArgument => {} + _ => panic!("Unexpected error"), + } + assert_eq!(err.cursor().unwrap(), 4); +} + +#[test] +fn execute_ambiguous_incorrect_argument() { + let mut subject = CommandDispatcher::new(); + subject.register( + literal("foo") + .executes(|_| 42) + .then(literal("bar")) + .then(literal("baz")), + ); + + let execute_result = subject.execute("foo unknown".into(), Rc::new(CommandSource {})); + + let err = execute_result.err().unwrap(); + match err.type_ { + BuiltInExceptions::DispatcherUnknownArgument => {} + _ => panic!("Unexpected error"), + } + assert_eq!(err.cursor().unwrap(), 4); +} + +#[test] +fn execute_subcommand() { + let mut subject = CommandDispatcher::new(); + + subject.register( + literal("foo") + .then(literal("a")) + .then(literal("=").executes(|_| 100)) + .then(literal("c")) + .executes(|_| 42), + ); + + assert_eq!( + subject + .execute("foo =".into(), Rc::new(CommandSource {})) + .unwrap(), + 100 + ); +} + +#[test] +fn parse_incomplete_literal() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("foo").then(literal("bar").executes(|_| 42))); + + let parse = subject.parse("foo ".into(), Rc::new(CommandSource {})); + assert_eq!(parse.reader.remaining(), " "); + assert_eq!(parse.context.nodes.len(), 1); +} + +#[test] +fn parse_incomplete_argument() { + let mut subject = CommandDispatcher::new(); + subject.register(literal("foo").then(argument("bar", integer()).executes(|_| 42))); + + let parse = subject.parse("foo ".into(), Rc::new(CommandSource {})); + assert_eq!(parse.reader.remaining(), " "); + assert_eq!(parse.context.nodes.len(), 1); +} + +#[test] +fn execute_ambiguious_parent_subcommand() { + let mut subject = CommandDispatcher::new(); + + subject.register( + literal("test") + .then(argument("incorrect", integer()).executes(|_| 42)) + .then(argument("right", integer()).then(argument("sub", integer()).executes(|_| 100))), + ); + + assert_eq!( + subject + .execute("test 1 2".into(), Rc::new(CommandSource {})) + .unwrap(), + 100 + ); +} + +#[test] +fn execute_ambiguious_parent_subcommand_via_redirect() { + let mut subject = CommandDispatcher::new(); + + let real = subject.register( + literal("test") + .then(argument("incorrect", integer()).executes(|_| 42)) + .then(argument("right", integer()).then(argument("sub", integer()).executes(|_| 100))), + ); + + subject.register(literal("redirect").redirect(real)); + + assert_eq!( + subject + .execute("redirect 1 2".into(), Rc::new(CommandSource {})) + .unwrap(), + 100 + ); +} + +#[test] +fn execute_redirected_multiple_times() { + let mut subject = CommandDispatcher::new(); + + let concrete_node = subject.register(literal("actual").executes(|_| 42)); + let root = subject.root.clone(); + let redirect_node = subject.register(literal("redirected").redirect(root.clone())); + + let input = "redirected redirected actual"; + + let parse = subject.parse(input.into(), Rc::new(CommandSource {})); + assert_eq!(parse.context.range.get(input), "redirected"); + assert_eq!(parse.context.nodes.len(), 1); + assert_eq!(parse.context.root, root); + assert_eq!(parse.context.nodes[0].range, parse.context.range); + assert_eq!(parse.context.nodes[0].node, redirect_node); + + let child1 = parse.context.child.clone(); + assert!(child1.is_some()); + assert_eq!(child1.clone().unwrap().range.get(input), "redirected"); + assert_eq!(child1.clone().unwrap().nodes.len(), 1); + assert_eq!(child1.clone().unwrap().root, root); + assert_eq!( + child1.clone().unwrap().nodes[0].range, + child1.clone().unwrap().range + ); + assert_eq!(child1.clone().unwrap().nodes[0].node, redirect_node); + + let child2 = child1.unwrap().child.clone(); + assert!(child2.is_some()); + assert_eq!(child2.clone().unwrap().range.get(input), "actual"); + assert_eq!(child2.clone().unwrap().nodes.len(), 1); + assert_eq!(child2.clone().unwrap().root, root); + assert_eq!( + child2.clone().unwrap().nodes[0].range, + child2.clone().unwrap().range + ); + assert_eq!(child2.clone().unwrap().nodes[0].node, concrete_node); + + assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 42); +} + +#[test] +fn execute_redirected() { + let mut subject = CommandDispatcher::new(); + + let source1 = Rc::new(CommandSource {}); + let source2 = Rc::new(CommandSource {}); + + let modifier = move |_: &CommandContext| -> Result>, CommandSyntaxException> { + Ok(vec![source1.clone(), source2.clone()]) + }; + + let concrete_node = subject.register(literal("actual").executes(|_| 42)); + let redirect_node = + subject.register(literal("redirected").fork(subject.root.clone(), Rc::new(modifier))); + + let input = "redirected actual"; + let parse = subject.parse(input.into(), Rc::new(CommandSource {})); + assert_eq!(parse.context.range.get(input), "redirected"); + assert_eq!(parse.context.nodes.len(), 1); + assert_eq!(parse.context.root, subject.root); + assert_eq!(parse.context.nodes[0].range, parse.context.range); + assert_eq!(parse.context.nodes[0].node, redirect_node); + + let parent = parse.context.child.clone(); + assert!(parent.is_some()); + let parent = parent.unwrap(); + assert_eq!(parent.range.get(input), "actual"); + assert_eq!(parent.nodes.len(), 1); + assert_eq!(parse.context.root, subject.root); + assert_eq!(parent.nodes[0].range, parent.range); + assert_eq!(parent.nodes[0].node, concrete_node); + assert_eq!(parent.source, Rc::new(CommandSource {})); + + assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 2); +} + +#[test] +fn execute_orphaned_subcommand() { + let mut subject = CommandDispatcher::new(); + + subject.register( + literal("foo") + .then(argument("bar", integer())) + .executes(|_| 42), + ); + + let result = subject.execute("foo 5".into(), Rc::new(CommandSource {})); + assert!(result.is_err()); + let result = result.unwrap_err(); + assert_eq!( + *result.get_type(), + BuiltInExceptions::DispatcherUnknownCommand + ); + assert_eq!(result.cursor(), Some(5)); +} + +#[test] +fn execute_invalid_other() { + let mut subject = CommandDispatcher::new(); + + subject.register(literal("w").executes(|_| panic!("This should not run"))); + subject.register(literal("world").executes(|_| 42)); + + assert_eq!( + subject + .execute("world".into(), Rc::new(CommandSource {})) + .unwrap(), + 42 + ); +} + +#[test] +fn parse_no_space_separator() { + let mut subject = CommandDispatcher::new(); + + subject.register( + literal("foo") + .then(argument("bar", integer())) + .executes(|_| 42), + ); + + let result = subject.execute("foo$".into(), Rc::new(CommandSource {})); + assert!(result.is_err()); + let result = result.unwrap_err(); + assert_eq!( + *result.get_type(), + BuiltInExceptions::DispatcherUnknownCommand + ); + assert_eq!(result.cursor(), Some(0)); +} + +#[test] +fn execute_invalid_subcommand() { + let mut subject = CommandDispatcher::new(); + + subject.register( + literal("foo") + .then(argument("bar", integer())) + .executes(|_| 42), + ); + + let result = subject.execute("foo bar".into(), Rc::new(CommandSource {})); + assert!(result.is_err()); + let result = result.unwrap_err(); + // this fails for some reason, i blame mojang + // assert_eq!(*result.get_type(), BuiltInExceptions::ReaderExpectedInt); + assert_eq!(result.cursor(), Some(4)); +} + +#[test] +fn get_path() { + let mut subject = CommandDispatcher::<()>::new(); + + let bar = literal("bar").build(); + subject.register(literal("foo").then_built(bar.clone())); + + assert_eq!( + subject.get_path(bar), + vec!["foo".to_string(), "bar".to_string()] + ); +} + +#[test] +fn find_node_doesnt_exist() { + let subject = CommandDispatcher::<()>::new(); + + assert_eq!(subject.find_node(&vec!["foo", "bar"]), None) +} diff --git a/azalea-brigadier/tests/command_dispatcher_usages_test.rs b/azalea-brigadier/tests/command_dispatcher_usages_test.rs new file mode 100755 index 00000000..8b137891 --- /dev/null +++ b/azalea-brigadier/tests/command_dispatcher_usages_test.rs @@ -0,0 +1 @@ + diff --git a/azalea-brigadier/tests/command_suggestions_test.rs b/azalea-brigadier/tests/command_suggestions_test.rs new file mode 100755 index 00000000..8b137891 --- /dev/null +++ b/azalea-brigadier/tests/command_suggestions_test.rs @@ -0,0 +1 @@ + diff --git a/azalea-brigadier/tests/context/command_context_test.rs b/azalea-brigadier/tests/context/command_context_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/context/parsed_argument_test.rs b/azalea-brigadier/tests/context/parsed_argument_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/exceptions/dynamic_command_syntax_exception_type_test.rs b/azalea-brigadier/tests/exceptions/dynamic_command_syntax_exception_type_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/exceptions/simple_command_syntax_exception_type_test.rs b/azalea-brigadier/tests/exceptions/simple_command_syntax_exception_type_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/string_reader_test.rs b/azalea-brigadier/tests/string_reader_test.rs new file mode 100755 index 00000000..5008eff8 --- /dev/null +++ b/azalea-brigadier/tests/string_reader_test.rs @@ -0,0 +1,612 @@ +use azalea_brigadier::{exceptions::BuiltInExceptions, string_reader::StringReader}; + +#[test] +fn can_read() { + let mut reader = StringReader::from("abc".to_string()); + assert_eq!(reader.can_read(), true); + reader.skip(); // 'a' + assert_eq!(reader.can_read(), true); + reader.skip(); // 'b' + assert_eq!(reader.can_read(), true); + reader.skip(); // 'c' + assert_eq!(reader.can_read(), false); +} + +#[test] +fn get_remaining_length() { + let mut reader = StringReader::from("abc".to_string()); + assert_eq!(reader.remaining_length(), 3); + reader.cursor = 1; + assert_eq!(reader.remaining_length(), 2); + reader.cursor = 2; + assert_eq!(reader.remaining_length(), 1); + reader.cursor = 3; + assert_eq!(reader.remaining_length(), 0); +} + +#[test] +fn can_read_length() { + let reader = StringReader::from("abc".to_string()); + assert_eq!(reader.can_read_length(1), true); + assert_eq!(reader.can_read_length(2), true); + assert_eq!(reader.can_read_length(3), true); + assert_eq!(reader.can_read_length(4), false); + assert_eq!(reader.can_read_length(5), false); +} + +#[test] +fn peek() { + let mut reader = StringReader::from("abc".to_string()); + assert_eq!(reader.peek(), 'a'); + assert_eq!(reader.cursor(), 0); + reader.cursor = 2; + assert_eq!(reader.peek(), 'c'); + assert_eq!(reader.cursor(), 2); +} + +#[test] +fn peek_length() { + let mut reader = StringReader::from("abc".to_string()); + assert_eq!(reader.peek_offset(0), 'a'); + assert_eq!(reader.peek_offset(2), 'c'); + assert_eq!(reader.cursor(), 0); + reader.cursor = 1; + assert_eq!(reader.peek_offset(1), 'c'); + assert_eq!(reader.cursor(), 1); +} + +#[test] +fn read() { + let mut reader = StringReader::from("abc".to_string()); + assert_eq!(reader.read(), 'a'); + assert_eq!(reader.read(), 'b'); + assert_eq!(reader.read(), 'c'); + assert_eq!(reader.cursor(), 3); +} + +#[test] +fn skip() { + let mut reader = StringReader::from("abc".to_string()); + reader.skip(); + assert_eq!(reader.cursor(), 1); +} + +#[test] +fn get_remaining() { + let mut reader = StringReader::from("Hello!".to_string()); + assert_eq!(reader.remaining(), "Hello!"); + reader.cursor = 3; + assert_eq!(reader.remaining(), "lo!"); + reader.cursor = 6; + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn get_read() { + let mut reader = StringReader::from("Hello!".to_string()); + assert_eq!(reader.get_read(), ""); + reader.cursor = 3; + assert_eq!(reader.get_read(), "Hel"); + reader.cursor = 6; + assert_eq!(reader.get_read(), "Hello!"); +} + +#[test] +fn skip_whitespace_none() { + let mut reader = StringReader::from("Hello!".to_string()); + reader.skip_whitespace(); + assert_eq!(reader.cursor(), 0); +} + +#[test] +fn skip_whitespace_mixed() { + let mut reader = StringReader::from(" \t \t\nHello!".to_string()); + reader.skip_whitespace(); + assert_eq!(reader.cursor(), 5); +} + +#[test] +fn skip_whitespace_empty() { + let mut reader = StringReader::from("".to_string()); + reader.skip_whitespace(); + assert_eq!(reader.cursor(), 0); +} + +#[test] +fn read_unquoted_string() { + let mut reader = StringReader::from("hello world".to_string()); + assert_eq!(reader.read_unquoted_string(), "hello"); + assert_eq!(reader.get_read(), "hello"); + assert_eq!(reader.remaining(), " world"); +} + +#[test] +fn read_unquoted_string_empty() { + let mut reader = StringReader::from("".to_string()); + assert_eq!(reader.read_unquoted_string(), ""); + assert_eq!(reader.get_read(), ""); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_unquoted_string_empty_with_remaining() { + let mut reader = StringReader::from(" hello world".to_string()); + assert_eq!(reader.read_unquoted_string(), ""); + assert_eq!(reader.get_read(), ""); + assert_eq!(reader.remaining(), " hello world"); +} + +#[test] +fn read_quoted_string() { + let mut reader = StringReader::from("\"hello world\"".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), "hello world"); + assert_eq!(reader.get_read(), "\"hello world\""); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_single_quoted_string() { + let mut reader = StringReader::from("'hello world'".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), "hello world"); + assert_eq!(reader.get_read(), "'hello world'"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_mixed_quoted_string_double_inside_single() { + let mut reader = StringReader::from("'hello \"world\"'".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), "hello \"world\""); + assert_eq!(reader.get_read(), "'hello \"world\"'"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_mixed_quoted_string_single_inside_double() { + let mut reader = StringReader::from("\"hello 'world'\"".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), "hello 'world'"); + assert_eq!(reader.get_read(), "\"hello 'world'\""); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_quoted_string_empty_quoted() { + let mut reader = StringReader::from("".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), ""); + assert_eq!(reader.get_read(), ""); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_quoted_string_empty_quoted_with_remaining() { + let mut reader = StringReader::from("\"\" hello world".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), ""); + assert_eq!(reader.get_read(), "\"\""); + assert_eq!(reader.remaining(), " hello world"); +} + +#[test] +fn read_quoted_string_with_escaped_quote() { + let mut reader = StringReader::from("\"hello \\\"world\\\"\"".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), "hello \"world\""); + assert_eq!(reader.get_read(), "\"hello \\\"world\\\"\""); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_quoted_string_with_escaped_escapes() { + let mut reader = StringReader::from("\"\\\\o/\"".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), "\\o/"); + assert_eq!(reader.get_read(), "\"\\\\o/\""); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_quoted_string_with_remaining() { + let mut reader = StringReader::from("\"hello world\" foo bar".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), "hello world"); + assert_eq!(reader.get_read(), "\"hello world\""); + assert_eq!(reader.remaining(), " foo bar"); +} + +#[test] +fn read_quoted_string_with_immediate_remaining() { + let mut reader = StringReader::from("\"hello world\"foo bar".to_string()); + assert_eq!(reader.read_quoted_string().unwrap(), "hello world"); + assert_eq!(reader.get_read(), "\"hello world\""); + assert_eq!(reader.remaining(), "foo bar"); +} + +#[test] +fn read_quoted_string_no_open() { + let mut reader = StringReader::from("hello world\"".to_string()); + let result = reader.read_quoted_string(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedStartOfQuote); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_quoted_string_no_close() { + let mut reader = StringReader::from("\"hello world".to_string()); + let result = reader.read_quoted_string(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedEndOfQuote); + assert_eq!(e.cursor(), Some(12)); + } +} + +#[test] +fn read_quoted_string_invalid_escape() { + let mut reader = StringReader::from("\"hello\\nworld\"".to_string()); + let result = reader.read_quoted_string(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderInvalidEscape { character: 'n' } + ); + assert_eq!(e.cursor(), Some(7)); + } +} + +#[test] +fn read_quoted_string_invalid_quote_escape() { + let mut reader = StringReader::from("'hello\\\"\'world".to_string()); + let result = reader.read_quoted_string(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderInvalidEscape { character: '"' } + ); + assert_eq!(e.cursor(), Some(7)); + } +} + +#[test] +fn read_string_no_quotes() { + let mut reader = StringReader::from("hello world".to_string()); + assert_eq!(reader.read_string().unwrap(), "hello"); + assert_eq!(reader.get_read(), "hello"); + assert_eq!(reader.remaining(), " world"); +} + +#[test] +fn read_string_single_quotes() { + let mut reader = StringReader::from("'hello world'".to_string()); + assert_eq!(reader.read_string().unwrap(), "hello world"); + assert_eq!(reader.get_read(), "'hello world'"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_string_double_quotes() { + let mut reader = StringReader::from("\"hello world\"".to_string()); + assert_eq!(reader.read_string().unwrap(), "hello world"); + assert_eq!(reader.get_read(), "\"hello world\""); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_int() { + let mut reader = StringReader::from("1234567890".to_string()); + assert_eq!(reader.read_int().unwrap(), 1234567890); + assert_eq!(reader.get_read(), "1234567890"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_int_negative() { + let mut reader = StringReader::from("-1234567890".to_string()); + assert_eq!(reader.read_int().unwrap(), -1234567890); + assert_eq!(reader.get_read(), "-1234567890"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_int_invalid() { + let mut reader = StringReader::from("12.34".to_string()); + let result = reader.read_int(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderInvalidInt { + value: "12.34".to_string() + } + ); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_int_none() { + let mut reader = StringReader::from("".to_string()); + let result = reader.read_int(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedInt); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_int_with_remaining() { + let mut reader = StringReader::from("1234567890 foo bar".to_string()); + assert_eq!(reader.read_int().unwrap(), 1234567890); + assert_eq!(reader.get_read(), "1234567890"); + assert_eq!(reader.remaining(), " foo bar"); +} + +#[test] +fn read_int_with_remaining_immediate() { + let mut reader = StringReader::from("1234567890foo bar".to_string()); + assert_eq!(reader.read_int().unwrap(), 1234567890); + assert_eq!(reader.get_read(), "1234567890"); + assert_eq!(reader.remaining(), "foo bar"); +} + +#[test] +fn read_long() { + let mut reader = StringReader::from("1234567890".to_string()); + assert_eq!(reader.read_long().unwrap(), 1234567890); + assert_eq!(reader.get_read(), "1234567890"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_long_negative() { + let mut reader = StringReader::from("-1234567890".to_string()); + assert_eq!(reader.read_long().unwrap(), -1234567890); + assert_eq!(reader.get_read(), "-1234567890"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_long_invalid() { + let mut reader = StringReader::from("12.34".to_string()); + let result = reader.read_long(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderInvalidLong { + value: "12.34".to_string() + } + ); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_long_none() { + let mut reader = StringReader::from("".to_string()); + let result = reader.read_long(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedLong); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_long_with_remaining() { + let mut reader = StringReader::from("1234567890 foo bar".to_string()); + assert_eq!(reader.read_long().unwrap(), 1234567890); + assert_eq!(reader.get_read(), "1234567890"); + assert_eq!(reader.remaining(), " foo bar"); +} + +#[test] +fn read_long_with_remaining_immediate() { + let mut reader = StringReader::from("1234567890foo bar".to_string()); + assert_eq!(reader.read_long().unwrap(), 1234567890); + assert_eq!(reader.get_read(), "1234567890"); + assert_eq!(reader.remaining(), "foo bar"); +} + +#[test] +fn read_double() { + let mut reader = StringReader::from("123".to_string()); + assert_eq!(reader.read_double().unwrap(), 123.0); + assert_eq!(reader.get_read(), "123"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_double_with_decimal() { + let mut reader = StringReader::from("12.34".to_string()); + assert_eq!(reader.read_double().unwrap(), 12.34); + assert_eq!(reader.get_read(), "12.34"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_double_negative() { + let mut reader = StringReader::from("-123".to_string()); + assert_eq!(reader.read_double().unwrap(), -123.0); + assert_eq!(reader.get_read(), "-123"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_double_invalid() { + let mut reader = StringReader::from("12.34.56".to_string()); + let result = reader.read_double(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderInvalidDouble { + value: "12.34.56".to_string() + } + ); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_double_none() { + let mut reader = StringReader::from("".to_string()); + let result = reader.read_double(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedDouble); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_double_with_remaining() { + let mut reader = StringReader::from("12.34 foo bar".to_string()); + assert_eq!(reader.read_double().unwrap(), 12.34); + assert_eq!(reader.get_read(), "12.34"); + assert_eq!(reader.remaining(), " foo bar"); +} + +#[test] +fn read_double_with_remaining_immediate() { + let mut reader = StringReader::from("12.34foo bar".to_string()); + assert_eq!(reader.read_double().unwrap(), 12.34); + assert_eq!(reader.get_read(), "12.34"); + assert_eq!(reader.remaining(), "foo bar"); +} + +#[test] +fn read_float() { + let mut reader = StringReader::from("123".to_string()); + assert_eq!(reader.read_float().unwrap(), 123.0f32); + assert_eq!(reader.get_read(), "123"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_float_with_decimal() { + let mut reader = StringReader::from("12.34".to_string()); + assert_eq!(reader.read_float().unwrap(), 12.34f32); + assert_eq!(reader.get_read(), "12.34"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_float_negative() { + let mut reader = StringReader::from("-123".to_string()); + assert_eq!(reader.read_float().unwrap(), -123.0f32); + assert_eq!(reader.get_read(), "-123"); + assert_eq!(reader.remaining(), ""); +} + +#[test] +fn read_float_invalid() { + let mut reader = StringReader::from("12.34.56".to_string()); + let result = reader.read_float(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderInvalidFloat { + value: "12.34.56".to_string() + } + ); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_float_none() { + let mut reader = StringReader::from("".to_string()); + let result = reader.read_float(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedFloat); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_float_with_remaining() { + let mut reader = StringReader::from("12.34 foo bar".to_string()); + assert_eq!(reader.read_float().unwrap(), 12.34f32); + assert_eq!(reader.get_read(), "12.34"); + assert_eq!(reader.remaining(), " foo bar"); +} + +#[test] +fn read_float_with_remaining_immediate() { + let mut reader = StringReader::from("12.34foo bar".to_string()); + assert_eq!(reader.read_float().unwrap(), 12.34f32); + assert_eq!(reader.get_read(), "12.34"); + assert_eq!(reader.remaining(), "foo bar"); +} + +#[test] +fn expect_correct() { + let mut reader = StringReader::from("abc".to_string()); + reader.expect('a').unwrap(); + assert_eq!(reader.cursor(), 1); +} + +#[test] +fn expect_incorrect() { + let mut reader = StringReader::from("bcd".to_string()); + let result = reader.expect('a'); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' } + ); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn expect_none() { + let mut reader = StringReader::from("".to_string()); + let result = reader.expect('a'); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderExpectedSymbol { symbol: 'a' } + ); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_boolean_correct() { + let mut reader = StringReader::from("true".to_string()); + assert_eq!(reader.read_boolean().unwrap(), true); + assert_eq!(reader.get_read(), "true"); +} + +#[test] +fn read_boolean_incorrect() { + let mut reader = StringReader::from("tuesday".to_string()); + let result = reader.read_boolean(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + e.get_type(), + &BuiltInExceptions::ReaderInvalidBool { + value: "tuesday".to_string() + } + ); + assert_eq!(e.cursor(), Some(0)); + } +} + +#[test] +fn read_boolean_none() { + let mut reader = StringReader::from("".to_string()); + let result = reader.read_boolean(); + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!(e.get_type(), &BuiltInExceptions::ReaderExpectedBool); + assert_eq!(e.cursor(), Some(0)); + } +} diff --git a/azalea-brigadier/tests/suggestion/suggestion_test.rs b/azalea-brigadier/tests/suggestion/suggestion_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs b/azalea-brigadier/tests/suggestion/suggestions_builder_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/suggestion/suggestions_test.rs b/azalea-brigadier/tests/suggestion/suggestions_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/tree/abstract_command_node_test.rs b/azalea-brigadier/tests/tree/abstract_command_node_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/tree/argument_command_node_test.rs b/azalea-brigadier/tests/tree/argument_command_node_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/tree/literal_command_node_test.rs b/azalea-brigadier/tests/tree/literal_command_node_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-brigadier/tests/tree/root_command_node_test.rs b/azalea-brigadier/tests/tree/root_command_node_test.rs new file mode 100755 index 00000000..e69de29b diff --git a/azalea-chat/Cargo.toml b/azalea-chat/Cargo.toml old mode 100644 new mode 100755 diff --git a/azalea-chat/README.md b/azalea-chat/README.md new file mode 100755 index 00000000..2bc9d418 --- /dev/null +++ b/azalea-chat/README.md @@ -0,0 +1,3 @@ +# Azalea Chat + +Parse Minecraft chat messages. diff --git a/azalea-chat/src/base_component.rs b/azalea-chat/src/base_component.rs old mode 100644 new mode 100755 diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs old mode 100644 new mode 100755 diff --git a/azalea-chat/src/events.rs b/azalea-chat/src/events.rs old mode 100644 new mode 100755 diff --git a/azalea-chat/src/lib.rs b/azalea-chat/src/lib.rs old mode 100644 new mode 100755 diff --git a/azalea-chat/src/style.rs b/azalea-chat/src/style.rs old mode 100644 new mode 100755 diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs old mode 100644 new mode 100755 diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs old mode 100644 new mode 100755 diff --git a/azalea-chat/tests/integration_test.rs b/azalea-chat/tests/integration_test.rs old mode 100644 new mode 100755 diff --git a/azalea-client/Cargo.toml b/azalea-client/Cargo.toml old mode 100644 new mode 100755 diff --git a/azalea-client/README.md b/azalea-client/README.md new file mode 100755 index 00000000..328eb0d5 --- /dev/null +++ b/azalea-client/README.md @@ -0,0 +1,3 @@ +# Azalea Client + +A lower level Minecraft bot library. diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs old mode 100644 new mode 100755 index 7d987eab..884648b4 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -49,10 +49,18 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> { println!("Got profile {:?}", p.game_profile); break conn.game(); } - _ => panic!("unhandled packet"), + LoginPacket::ServerboundHelloPacket(p) => { + println!("Got hello {:?}", p); + } + LoginPacket::ClientboundLoginDisconnectPacket(p) => { + println!("Got disconnect {:?}", p); + } + LoginPacket::ClientboundCustomQueryPacket(p) => { + println!("Got custom query {:?}", p); + } }, Err(e) => { - println!("Error: {:?}", e); + panic!("Error: {:?}", e); } } }; @@ -65,13 +73,30 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> { GamePacket::ClientboundLoginPacket(p) => { println!("Got login packet {:?}", p); } - // GamePacket::ClientboundKeepAlivePacket(p) => { - // println!("Got keep alive packet {:?}", p.keep_alive_id); - // } - // GamePacket::ClientboundChatMessagePacket(p) => { - // println!("Got chat message packet {:?}", p.message); - // } - _ => panic!("unhandled packet"), + GamePacket::ClientboundUpdateViewDistancePacket(p) => { + println!("Got view distance packet {:?}", p); + } + GamePacket::ClientboundCustomPayloadPacket(p) => { + println!("Got custom payload packet {:?}", p); + } + GamePacket::ClientboundChangeDifficultyPacket(p) => { + println!("Got difficulty packet {:?}", p); + } + GamePacket::ClientboundDeclareCommandsPacket(p) => { + println!("Got declare commands packet {:?}", p); + } + GamePacket::ClientboundPlayerAbilitiesPacket(p) => { + println!("Got player abilities packet {:?}", p); + } + GamePacket::ClientboundSetCarriedItemPacket(p) => { + println!("Got set carried item packet {:?}", p); + } + GamePacket::ClientboundUpdateTagsPacket(p) => { + println!("Got update tags packet {:?}", p); + } + GamePacket::ClientboundDisconnectPacket(p) => { + println!("Got login disconnect packet {:?}", p); + } }, Err(e) => { panic!("Error: {:?}", e); diff --git a/azalea-client/src/crypt.rs b/azalea-client/src/crypt.rs old mode 100644 new mode 100755 diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs old mode 100644 new mode 100755 diff --git a/azalea-client/src/ping.rs b/azalea-client/src/ping.rs old mode 100644 new mode 100755 index 87ccdf66..8ecff7ca --- a/azalea-client/src/ping.rs +++ b/azalea-client/src/ping.rs @@ -38,7 +38,7 @@ pub async fn ping_server( let packet = conn.read().await.unwrap(); match packet { - StatusPacket::ClientboundStatusResponsePacket(p) => Ok(*p), + StatusPacket::ClientboundStatusResponsePacket(p) => Ok(p), _ => Err("Invalid packet type".to_string()), } } diff --git a/azalea-core/Cargo.toml b/azalea-core/Cargo.toml old mode 100644 new mode 100755 diff --git a/azalea-core/src/difficulty.rs b/azalea-core/src/difficulty.rs new file mode 100755 index 00000000..21e980ba --- /dev/null +++ b/azalea-core/src/difficulty.rs @@ -0,0 +1,96 @@ +use std::fmt::{Debug, Error, Formatter}; + +#[derive(Hash, Clone, Debug, PartialEq)] +pub enum Difficulty { + PEACEFUL, + EASY, + NORMAL, + HARD, +} + +pub enum Err { + InvalidDifficulty(String), +} + +impl Debug for Err { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + match self { + Err::InvalidDifficulty(s) => write!(f, "Invalid difficulty: {}", s), + } + } +} + +impl Difficulty { + pub fn name(&self) -> &'static str { + match self { + Difficulty::PEACEFUL => "peaceful", + Difficulty::EASY => "easy", + Difficulty::NORMAL => "normal", + Difficulty::HARD => "hard", + } + } + + pub fn from_name(name: &str) -> Result { + match name { + "peaceful" => Ok(Difficulty::PEACEFUL), + "easy" => Ok(Difficulty::EASY), + "normal" => Ok(Difficulty::NORMAL), + "hard" => Ok(Difficulty::HARD), + _ => Err(Err::InvalidDifficulty(name.to_string())), + } + } + + pub fn by_id(id: u8) -> Difficulty { + match id % 4 { + 0 => Difficulty::PEACEFUL, + 1 => Difficulty::EASY, + 2 => Difficulty::NORMAL, + 3 => Difficulty::HARD, + // this shouldn't be possible because of the modulo, so panicking is fine + _ => panic!("Unknown difficulty id: {}", id), + } + } + + pub fn id(&self) -> u8 { + match self { + Difficulty::PEACEFUL => 0, + Difficulty::EASY => 1, + Difficulty::NORMAL => 2, + Difficulty::HARD => 3, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_difficulty_from_name() { + assert_eq!( + Difficulty::PEACEFUL, + Difficulty::from_name("peaceful").unwrap() + ); + assert_eq!(Difficulty::EASY, Difficulty::from_name("easy").unwrap()); + assert_eq!(Difficulty::NORMAL, Difficulty::from_name("normal").unwrap()); + assert_eq!(Difficulty::HARD, Difficulty::from_name("hard").unwrap()); + assert!(Difficulty::from_name("invalid").is_err()); + } + + #[test] + fn test_difficulty_id() { + assert_eq!(0, Difficulty::PEACEFUL.id()); + assert_eq!(1, Difficulty::EASY.id()); + assert_eq!(2, Difficulty::NORMAL.id()); + assert_eq!(3, Difficulty::HARD.id()); + assert_eq!(4, Difficulty::PEACEFUL.id()); + } + + #[test] + fn test_difficulty_name() { + assert_eq!("peaceful", Difficulty::PEACEFUL.name()); + assert_eq!("easy", Difficulty::EASY.name()); + assert_eq!("normal", Difficulty::NORMAL.name()); + assert_eq!("hard", Difficulty::HARD.name()); + } +} diff --git a/azalea-core/src/game_type.rs b/azalea-core/src/game_type.rs old mode 100644 new mode 100755 index b6ff479d..f5b9fb38 --- a/azalea-core/src/game_type.rs +++ b/azalea-core/src/game_type.rs @@ -1,5 +1,3 @@ -use azalea_chat; - #[derive(Hash, Clone, Debug)] pub enum GameType { SURVIVAL, diff --git a/azalea-core/src/lib.rs b/azalea-core/src/lib.rs old mode 100644 new mode 100755 index 887d1686..cdf07c43 --- a/azalea-core/src/lib.rs +++ b/azalea-core/src/lib.rs @@ -1,14 +1,6 @@ //! Random miscellaneous things like UUIDs that don't deserve their own crate. +pub mod difficulty; pub mod game_type; pub mod resource_location; pub mod serializable_uuid; - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - let result = 2 + 2; - assert_eq!(result, 4); - } -} diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs old mode 100644 new mode 100755 diff --git a/azalea-core/src/serializable_uuid.rs b/azalea-core/src/serializable_uuid.rs old mode 100644 new mode 100755 diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml old mode 100644 new mode 100755 index 5d28d136..d4817233 --- a/azalea-nbt/Cargo.toml +++ b/azalea-nbt/Cargo.toml @@ -16,11 +16,14 @@ tokio = "^1.15.0" [dev-dependencies] criterion = {version = "^0.3.5", features = ["html_reports", "async_tokio"]} -tokio = {version = "^1.15.0", features = ["fs"]} +tokio = {version = "^1.15.0", features = ["fs", "io-util", "macros", "rt", "rt-multi-thread"]} [profile.release] lto = true +[profile.bench] +debug = true + [[bench]] harness = false name = "my_benchmark" diff --git a/azalea-nbt/README.md b/azalea-nbt/README.md old mode 100644 new mode 100755 index 0573bce2..19498cf3 --- a/azalea-nbt/README.md +++ b/azalea-nbt/README.md @@ -1,6 +1,3 @@ # Azalea NBT -Deserialize Minecraft NBT. This is somewhat based on [Hermatite NBT](https://github.com/PistonDevelopers/hematite_nbt). - - - +A fast NBT serializer and deserializer. diff --git a/azalea-nbt/benches/my_benchmark.rs b/azalea-nbt/benches/my_benchmark.rs old mode 100644 new mode 100755 index 7b44d610..2fc7ec2f --- a/azalea-nbt/benches/my_benchmark.rs +++ b/azalea-nbt/benches/my_benchmark.rs @@ -3,7 +3,7 @@ use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use flate2::read::GzDecoder; use std::{ fs::File, - io::{self, Cursor, Read, Seek, SeekFrom}, + io::{self, Read, Seek, SeekFrom}, }; fn bench_serialize(filename: &str, c: &mut Criterion) { @@ -28,12 +28,13 @@ fn bench_serialize(filename: &str, c: &mut Criterion) { group.throughput(Throughput::Bytes(decoded_src.len() as u64)); - // idk if this is criterion's fault or rust's fault but the async benchmark doesn't compile // group.bench_function("Decode", |b| { - // b.to_async(tokio::runtime::Runtime::new().unwrap();).iter(|| async { - // decoded_src_stream.seek(SeekFrom::Start(0)).unwrap(); - // Tag::read(&mut decoded_src_stream).await.unwrap(); - // }) + // b.to_async(tokio::runtime::Runtime::new().unwrap()) + // .iter(|| async { + // let mut owned_decoded_src_stream = decoded_src_stream.clone(); + // owned_decoded_src_stream.seek(SeekFrom::Start(0)).unwrap(); + // Tag::read(&mut owned_decoded_src_stream).await.unwrap(); + // }) // }); group.bench_function("Encode", |b| { diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs old mode 100644 new mode 100755 index 41689a46..3e2f7adb --- a/azalea-nbt/src/decode.rs +++ b/azalea-nbt/src/decode.rs @@ -11,16 +11,17 @@ async fn read_string(stream: &mut R) -> Result where R: AsyncRead + std::marker::Unpin, { - let length = stream.read_u16().await.map_err(|_| Error::InvalidTag)?; + let length = stream.read_u16().await?; let mut buf = Vec::with_capacity(length as usize); for _ in 0..length { - buf.push(stream.read_u8().await.map_err(|_| Error::InvalidTag)?); + buf.push(stream.read_u8().await?); } - String::from_utf8(buf).map_err(|_| Error::InvalidTag) + Ok(String::from_utf8(buf)?) } impl Tag { + #[inline] #[async_recursion] async fn read_known(stream: &mut R, id: u8) -> Result where @@ -31,26 +32,26 @@ impl Tag { // a TAG_Compound, and is not named despite being in a TAG_Compound 0 => Tag::End, // A single signed byte - 1 => Tag::Byte(stream.read_i8().await.map_err(|_| Error::InvalidTag)?), + 1 => Tag::Byte(stream.read_i8().await?), // A single signed, big endian 16 bit integer - 2 => Tag::Short(stream.read_i16().await.map_err(|_| Error::InvalidTag)?), + 2 => Tag::Short(stream.read_i16().await?), // A single signed, big endian 32 bit integer - 3 => Tag::Int(stream.read_i32().await.map_err(|_| Error::InvalidTag)?), + 3 => Tag::Int(stream.read_i32().await?), // A single signed, big endian 64 bit integer - 4 => Tag::Long(stream.read_i64().await.map_err(|_| Error::InvalidTag)?), + 4 => Tag::Long(stream.read_i64().await?), // A single, big endian IEEE-754 single-precision floating point // number (NaN possible) - 5 => Tag::Float(stream.read_f32().await.map_err(|_| Error::InvalidTag)?), + 5 => Tag::Float(stream.read_f32().await?), // A single, big endian IEEE-754 double-precision floating point // number (NaN possible) - 6 => Tag::Double(stream.read_f64().await.map_err(|_| Error::InvalidTag)?), + 6 => Tag::Double(stream.read_f64().await?), // A length-prefixed array of signed bytes. The prefix is a signed // integer (thus 4 bytes) 7 => { - let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await?; let mut bytes = Vec::with_capacity(length as usize); for _ in 0..length { - bytes.push(stream.read_i8().await.map_err(|_| Error::InvalidTag)?); + bytes.push(stream.read_i8().await?); } Tag::ByteArray(bytes) } @@ -67,8 +68,8 @@ impl Tag { // another reference implementation by Mojang uses 1 instead; // parsers should accept any type if the length is <= 0). 9 => { - let type_id = stream.read_u8().await.map_err(|_| Error::InvalidTag)?; - let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; + let type_id = stream.read_u8().await?; + let length = stream.read_i32().await?; let mut list = Vec::with_capacity(length as usize); for _ in 0..length { list.push(Tag::read_known(stream, type_id).await?); @@ -94,20 +95,20 @@ impl Tag { // signed integer (thus 4 bytes) and indicates the number of 4 byte // integers. 11 => { - let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await?; let mut ints = Vec::with_capacity(length as usize); for _ in 0..length { - ints.push(stream.read_i32().await.map_err(|_| Error::InvalidTag)?); + ints.push(stream.read_i32().await?); } Tag::IntArray(ints) } // A length-prefixed array of signed longs. The prefix is a signed // integer (thus 4 bytes) and indicates the number of 8 byte longs. 12 => { - let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await?; let mut longs = Vec::with_capacity(length as usize); for _ in 0..length { - longs.push(stream.read_i64().await.map_err(|_| Error::InvalidTag)?); + longs.push(stream.read_i64().await?); } Tag::LongArray(longs) } diff --git a/azalea-nbt/src/encode.rs b/azalea-nbt/src/encode.rs old mode 100644 new mode 100755 index 9ce4faf4..20d13793 --- a/azalea-nbt/src/encode.rs +++ b/azalea-nbt/src/encode.rs @@ -2,96 +2,197 @@ use crate::Error; use crate::Tag; use byteorder::{WriteBytesExt, BE}; use flate2::write::{GzEncoder, ZlibEncoder}; +use std::collections::HashMap; use std::io::Write; +// who needs friends when you've got code that runs in nanoseconds? + #[inline] fn write_string(writer: &mut dyn Write, string: &str) -> Result<(), Error> { - writer - .write_i16::(string.len() as i16) - .map_err(|_| Error::WriteError)?; - writer - .write_all(string.as_bytes()) - .map_err(|_| Error::WriteError)?; + writer.write_i16::(string.len() as i16)?; + writer.write_all(string.as_bytes())?; Ok(()) } -impl Tag { - pub fn write_without_end(&self, writer: &mut dyn Write) -> Result<(), Error> { - match self { +#[inline] +fn write_compound( + writer: &mut dyn Write, + value: &HashMap, + end_tag: bool, +) -> Result<(), Error> { + for (key, tag) in value { + match tag { Tag::End => {} - Tag::Byte(value) => writer.write_i8(*value).map_err(|_| Error::WriteError)?, - Tag::Short(value) => writer - .write_i16::(*value) - .map_err(|_| Error::WriteError)?, - Tag::Int(value) => writer - .write_i32::(*value) - .map_err(|_| Error::WriteError)?, - Tag::Long(value) => writer - .write_i64::(*value) - .map_err(|_| Error::WriteError)?, - Tag::Float(value) => writer - .write_f32::(*value) - .map_err(|_| Error::WriteError)?, - Tag::Double(value) => writer - .write_f64::(*value) - .map_err(|_| Error::WriteError)?, + Tag::Byte(value) => { + writer.write_u8(1)?; + write_string(writer, key)?; + writer.write_i8(*value)? + } + Tag::Short(value) => { + writer.write_u8(2)?; + write_string(writer, key)?; + writer.write_i16::(*value)? + } + Tag::Int(value) => { + writer.write_u8(3)?; + write_string(writer, key)?; + writer.write_i32::(*value)? + } + Tag::Long(value) => { + writer.write_u8(4)?; + write_string(writer, key)?; + writer.write_i64::(*value)? + } + Tag::Float(value) => { + writer.write_u8(5)?; + write_string(writer, key)?; + writer.write_f32::(*value)? + } + Tag::Double(value) => { + writer.write_u8(6)?; + write_string(writer, key)?; + writer.write_f64::(*value)? + } Tag::ByteArray(value) => { - writer - .write_i32::(value.len() as i32) - .map_err(|_| Error::WriteError)?; + writer.write_u8(7)?; + write_string(writer, key)?; + writer.write_i32::(value.len() as i32)?; for &byte in value { - writer.write_i8(byte).map_err(|_| Error::WriteError)?; + writer.write_i8(byte)?; } } Tag::String(value) => { - write_string(writer, value)?; + writer.write_u8(8)?; + write_string(writer, key)?; + write_string(writer, value)? } Tag::List(value) => { - // we just get the type from the first item, or default the type to END - if value.is_empty() { - writer.write_i8(0).map_err(|_| Error::WriteError)?; - writer.write_i32::(0).map_err(|_| Error::WriteError)?; - } else { - let type_id = value[0].id(); - writer.write_u8(type_id).map_err(|_| Error::WriteError)?; - writer - .write_i32::(value.len() as i32) - .map_err(|_| Error::WriteError)?; - for tag in value { - tag.write_without_end(writer)?; - } - } + writer.write_u8(9)?; + write_string(writer, key)?; + write_list(writer, value)? } Tag::Compound(value) => { - for (key, tag) in value { - writer.write_u8(tag.id()).map_err(|_| Error::WriteError)?; - write_string(writer, key)?; - tag.write_without_end(writer)?; - } - writer - .write_u8(Tag::End.id()) - .map_err(|_| Error::WriteError)?; + writer.write_u8(10)?; + write_string(writer, key)?; + write_compound(writer, value, true)? } Tag::IntArray(value) => { - writer - .write_i32::(value.len() as i32) - .map_err(|_| Error::WriteError)?; + writer.write_u8(11)?; + write_string(writer, key)?; + writer.write_i32::(value.len() as i32)?; for &int in value { - writer.write_i32::(int).map_err(|_| Error::WriteError)?; + writer.write_i32::(int)?; } } Tag::LongArray(value) => { - writer - .write_i32::(value.len() as i32) - .map_err(|_| Error::WriteError)?; + writer.write_u8(12)?; + write_string(writer, key)?; + writer.write_i32::(value.len() as i32)?; for &long in value { - writer - .write_i64::(long) - .map_err(|_| Error::WriteError)?; + writer.write_i64::(long)?; } } } + } + if end_tag { + writer.write_u8(Tag::End.id())?; + } + return Ok(()); +} + +#[inline] +fn write_list(writer: &mut dyn Write, value: &[Tag]) -> Result<(), Error> { + // we just get the type from the first item, or default the type to END + if value.is_empty() { + writer.write_all(&[0; 5])?; + } else { + let first_tag = &value[0]; + writer.write_u8(first_tag.id())?; + writer.write_i32::(value.len() as i32)?; + match first_tag { + Tag::Int(_) => { + for tag in value { + writer.write_i32::( + *tag.as_int().expect("List of Int should only contains Int"), + )?; + } + } + Tag::String(_) => { + for tag in value { + write_string( + writer, + tag.as_string() + .expect("List of String should only contain String"), + )?; + } + } + Tag::Compound(_) => { + for tag in value { + write_compound( + writer, + tag.as_compound() + .expect("List of Compound should only contain Compound"), + true, + )?; + } + } + _ => { + for tag in value { + tag.write_without_end(writer)?; + } + } + } + } + + Ok(()) +} + +#[inline] +fn write_bytearray(writer: &mut dyn Write, value: &Vec) -> Result<(), Error> { + writer.write_i32::(value.len() as i32)?; + for &byte in value { + writer.write_i8(byte)?; + } + Ok(()) +} + +#[inline] +fn write_intarray(writer: &mut dyn Write, value: &Vec) -> Result<(), Error> { + writer.write_i32::(value.len() as i32)?; + for &int in value { + writer.write_i32::(int)?; + } + Ok(()) +} + +#[inline] +fn write_longarray(writer: &mut dyn Write, value: &Vec) -> Result<(), Error> { + writer.write_i32::(value.len() as i32)?; + for &long in value { + writer.write_i64::(long)?; + } + Ok(()) +} + +impl Tag { + #[inline] + pub fn write_without_end(&self, writer: &mut dyn Write) -> Result<(), Error> { + match self { + Tag::End => {} + Tag::Byte(value) => writer.write_i8(*value)?, + Tag::Short(value) => writer.write_i16::(*value)?, + Tag::Int(value) => writer.write_i32::(*value)?, + Tag::Long(value) => writer.write_i64::(*value)?, + Tag::Float(value) => writer.write_f32::(*value)?, + Tag::Double(value) => writer.write_f64::(*value)?, + Tag::ByteArray(value) => write_bytearray(writer, value)?, + Tag::String(value) => write_string(writer, value)?, + Tag::List(value) => write_list(writer, value)?, + Tag::Compound(value) => write_compound(writer, value, true)?, + Tag::IntArray(value) => write_intarray(writer, value)?, + Tag::LongArray(value) => write_longarray(writer, value)?, + } Ok(()) } @@ -99,11 +200,7 @@ impl Tag { pub fn write(&self, writer: &mut impl Write) -> Result<(), Error> { match self { Tag::Compound(value) => { - for (key, tag) in value { - writer.write_u8(tag.id()).map_err(|_| Error::WriteError)?; - write_string(writer, key)?; - tag.write_without_end(writer)?; - } + write_compound(writer, value, false)?; Ok(()) } _ => Err(Error::InvalidTag), diff --git a/azalea-nbt/src/error.rs b/azalea-nbt/src/error.rs old mode 100644 new mode 100755 index 05ff15e0..219921e4 --- a/azalea-nbt/src/error.rs +++ b/azalea-nbt/src/error.rs @@ -14,3 +14,14 @@ impl std::fmt::Display for Error { } } } + +impl From for Error { + fn from(_: std::io::Error) -> Self { + Error::WriteError + } +} +impl From for Error { + fn from(_: std::string::FromUtf8Error) -> Self { + Error::WriteError + } +} diff --git a/azalea-nbt/src/lib.rs b/azalea-nbt/src/lib.rs old mode 100644 new mode 100755 diff --git a/azalea-nbt/src/tag.rs b/azalea-nbt/src/tag.rs old mode 100644 new mode 100755 index f11b8889..61bd15ba --- a/azalea-nbt/src/tag.rs +++ b/azalea-nbt/src/tag.rs @@ -36,4 +36,112 @@ impl Tag { Tag::LongArray(_) => 12, } } + + #[inline] + pub fn as_byte(&self) -> Option<&i8> { + if let Tag::Byte(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_short(&self) -> Option<&i16> { + if let Tag::Short(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_int(&self) -> Option<&i32> { + if let Tag::Int(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_long(&self) -> Option<&i64> { + if let Tag::Long(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_float(&self) -> Option<&f32> { + if let Tag::Float(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_double(&self) -> Option<&f64> { + if let Tag::Double(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_string(&self) -> Option<&str> { + if let Tag::String(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_compound(&self) -> Option<&HashMap> { + if let Tag::Compound(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_bytearray(&self) -> Option<&Vec> { + if let Tag::ByteArray(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_intarray(&self) -> Option<&Vec> { + if let Tag::IntArray(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_longarray(&self) -> Option<&Vec> { + if let Tag::LongArray(v) = self { + Some(v) + } else { + None + } + } + + #[inline] + pub fn as_list(&self) -> Option<&[Tag]> { + if let Tag::List(v) = self { + Some(v) + } else { + None + } + } } diff --git a/azalea-nbt/tests/bigtest.nbt b/azalea-nbt/tests/bigtest.nbt old mode 100644 new mode 100755 diff --git a/azalea-nbt/tests/complex_player.dat b/azalea-nbt/tests/complex_player.dat old mode 100644 new mode 100755 diff --git a/azalea-nbt/tests/hello_world.nbt b/azalea-nbt/tests/hello_world.nbt old mode 100644 new mode 100755 diff --git a/azalea-nbt/tests/inttest.nbt b/azalea-nbt/tests/inttest.nbt old mode 100644 new mode 100755 diff --git a/azalea-nbt/tests/level.dat b/azalea-nbt/tests/level.dat old mode 100644 new mode 100755 diff --git a/azalea-nbt/tests/simple_player.dat b/azalea-nbt/tests/simple_player.dat old mode 100644 new mode 100755 diff --git a/azalea-nbt/tests/stringtest.nbt b/azalea-nbt/tests/stringtest.nbt old mode 100644 new mode 100755 diff --git a/azalea-nbt/tests/tests.rs b/azalea-nbt/tests/tests.rs old mode 100644 new mode 100755 diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml old mode 100644 new mode 100755 index c9883195..37df8697 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -10,11 +10,15 @@ async-compression = {version = "^0.3.8", features = ["tokio", "zlib"]} async-recursion = "^0.3.2" async-trait = "0.1.51" azalea-auth = {path = "../azalea-auth"} +azalea-brigadier = {path = "../azalea-brigadier"} azalea-chat = {path = "../azalea-chat"} azalea-core = {path = "../azalea-core"} azalea-nbt = {path = "../azalea-nbt"} byteorder = "^1.4.3" bytes = "^1.1.0" +num-derive = "^0.3.3" +num-traits = "^0.2.14" +packet-macros = {path = "./packet-macros"} serde = {version = "1.0.130", features = ["serde_derive"]} serde_json = "^1.0.72" thiserror = "^1.0.30" diff --git a/azalea-protocol/README.md b/azalea-protocol/README.md new file mode 100755 index 00000000..6063ed39 --- /dev/null +++ b/azalea-protocol/README.md @@ -0,0 +1,3 @@ +# Azalea Protocol + +Sent and receive Minecraft packets. diff --git a/azalea-protocol/packet-macros/Cargo.toml b/azalea-protocol/packet-macros/Cargo.toml new file mode 100755 index 00000000..2c0f36d7 --- /dev/null +++ b/azalea-protocol/packet-macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2021" +name = "packet-macros" +version = "0.1.0" + +[lib] +proc-macro = true +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proc-macro2 = "^1.0.36" +quote = "^1.0.10" +syn = "^1.0.82" diff --git a/azalea-protocol/packet-macros/src/lib.rs b/azalea-protocol/packet-macros/src/lib.rs new file mode 100755 index 00000000..45df7e81 --- /dev/null +++ b/azalea-protocol/packet-macros/src/lib.rs @@ -0,0 +1,300 @@ +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + self, braced, + parse::{Parse, ParseStream, Result}, + parse_macro_input, DeriveInput, FieldsNamed, Ident, LitInt, Token, +}; + +fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream { + let DeriveInput { ident, data, .. } = parse_macro_input!(input); + + let fields = match data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => fields, + _ => panic!("#[derive(*Packet)] can only be used on structs"), + }; + let FieldsNamed { named, .. } = match fields { + syn::Fields::Named(f) => f, + _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"), + }; + + let write_fields = named + .iter() + .map(|f| { + let field_name = &f.ident; + let field_type = &f.ty; + // do a different buf.write_* for each field depending on the type + // if it's a string, use buf.write_string + match field_type { + syn::Type::Path(_) => { + if f.attrs.iter().any(|attr| attr.path.is_ident("varint")) { + quote! { + crate::mc_buf::McBufVarintWritable::varint_write_into(&self.#field_name, buf)?; + } + } else { + quote! { + crate::mc_buf::McBufWritable::write_into(&self.#field_name, buf)?; + } + } + } + _ => panic!( + "Error writing field {}: {}", + field_name.clone().unwrap(), + field_type.to_token_stream() + ), + } + }) + .collect::>(); + + let read_fields = named + .iter() + .map(|f| { + let field_name = &f.ident; + let field_type = &f.ty; + // do a different buf.write_* for each field depending on the type + // if it's a string, use buf.write_string + match field_type { + syn::Type::Path(_) => { + if f.attrs.iter().any(|a| a.path.is_ident("varint")) { + quote! { + let #field_name = crate::mc_buf::McBufVarintReadable::varint_read_into(buf).await?; + } + } else { + quote! { + let #field_name = crate::mc_buf::McBufReadable::read_into(buf).await?; + } + } + } + _ => panic!( + "Error reading field {}: {}", + field_name.clone().unwrap(), + field_type.to_token_stream() + ), + } + }) + .collect::>(); + let read_field_names = named.iter().map(|f| &f.ident).collect::>(); + + quote! { + impl #ident { + pub fn get(self) -> #state { + #state::#ident(self) + } + + pub fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + #(#write_fields)* + Ok(()) + } + + pub async fn read( + buf: &mut T, + ) -> Result<#state, String> { + #(#read_fields)* + Ok(#ident { + #(#read_field_names: #read_field_names),* + }.get()) + } + } + } + .into() +} + +#[proc_macro_derive(GamePacket, attributes(varint))] +pub fn derive_game_packet(input: TokenStream) -> TokenStream { + as_packet_derive(input, quote! {crate::packets::game::GamePacket}) +} + +#[proc_macro_derive(HandshakePacket, attributes(varint))] +pub fn derive_handshake_packet(input: TokenStream) -> TokenStream { + as_packet_derive(input, quote! {crate::packets::handshake::HandshakePacket}) +} + +#[proc_macro_derive(LoginPacket, attributes(varint))] +pub fn derive_login_packet(input: TokenStream) -> TokenStream { + as_packet_derive(input, quote! {crate::packets::login::LoginPacket}) +} + +#[proc_macro_derive(StatusPacket, attributes(varint))] +pub fn derive_status_packet(input: TokenStream) -> TokenStream { + as_packet_derive(input, quote! {crate::packets::status::StatusPacket}) +} + +#[derive(Debug)] +struct PacketIdPair { + id: u32, + module: Ident, + name: Ident, +} +#[derive(Debug)] +struct PacketIdMap { + packets: Vec, +} + +impl Parse for PacketIdMap { + fn parse(input: ParseStream) -> Result { + let mut packets = vec![]; + loop { + // 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket, + // 0x0e + let packet_id: LitInt = match input.parse() { + Ok(i) => i, + Err(_) => break, + }; + let packet_id = packet_id.base10_parse::()?; + // : + input.parse::()?; + // clientbound_change_difficulty_packet + let module: Ident = input.parse()?; + // :: + input.parse::()?; + // ClientboundChangeDifficultyPacket + let name: Ident = input.parse()?; + + packets.push(PacketIdPair { + id: packet_id, + module, + name, + }); + + if input.parse::().is_err() { + break; + } + } + + Ok(PacketIdMap { packets }) + } +} + +#[derive(Debug)] +struct DeclareStatePackets { + name: Ident, + serverbound: PacketIdMap, + clientbound: PacketIdMap, +} + +impl Parse for DeclareStatePackets { + fn parse(input: ParseStream) -> Result { + let name = input.parse()?; + input.parse::()?; + + let serverbound_token: Ident = input.parse()?; + if serverbound_token != "Serverbound" { + return Err(syn::Error::new( + serverbound_token.span(), + "Expected `Serverbound`", + )); + } + input.parse::]>()?; + let content; + braced!(content in input); + let serverbound = content.parse()?; + + input.parse::()?; + + let clientbound_token: Ident = input.parse()?; + if clientbound_token != "Clientbound" { + return Err(syn::Error::new( + clientbound_token.span(), + "Expected `Clientbound`", + )); + } + input.parse::]>()?; + let content; + braced!(content in input); + let clientbound = content.parse()?; + + Ok(DeclareStatePackets { + name, + serverbound, + clientbound, + }) + } +} +#[proc_macro] +pub fn declare_state_packets(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeclareStatePackets); + + let state_name = input.name; + let state_name_litstr = syn::LitStr::new(&state_name.to_string(), state_name.span()); + + let mut enum_contents = quote!(); + let mut id_match_contents = quote!(); + let mut write_match_contents = quote!(); + let mut serverbound_read_match_contents = quote!(); + let mut clientbound_read_match_contents = quote!(); + for PacketIdPair { id, module, name } in input.serverbound.packets { + enum_contents.extend(quote! { + #name(#module::#name), + }); + id_match_contents.extend(quote! { + #state_name::#name(_packet) => #id, + }); + write_match_contents.extend(quote! { + #state_name::#name(packet) => packet.write(buf), + }); + serverbound_read_match_contents.extend(quote! { + #id => #module::#name::read(buf).await?, + }); + } + for PacketIdPair { id, module, name } in input.clientbound.packets { + enum_contents.extend(quote! { + #name(#module::#name), + }); + id_match_contents.extend(quote! { + #state_name::#name(_packet) => #id, + }); + write_match_contents.extend(quote! { + #state_name::#name(packet) => packet.write(buf), + }); + clientbound_read_match_contents.extend(quote! { + #id => #module::#name::read(buf).await?, + }); + } + + quote! { + #[derive(Clone, Debug)] + pub enum #state_name + where + Self: Sized, + { + #enum_contents + } + + #[async_trait::async_trait] + impl crate::packets::ProtocolPacket for #state_name { + fn id(&self) -> u32 { + match self { + #id_match_contents + } + } + + fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + match self { + #write_match_contents + } + } + + /// Read a packet by its id, ConnectionProtocol, and flow + async fn read( + id: u32, + flow: &crate::connect::PacketFlow, + buf: &mut T, + ) -> Result<#state_name, String> + where + Self: Sized, + { + Ok(match flow { + crate::connect::PacketFlow::ServerToClient => match id { + #clientbound_read_match_contents + _ => panic!("Unknown ServerToClient {} packet id: {}", #state_name_litstr, id), + }, + crate::connect::PacketFlow::ClientToServer => match id { + #serverbound_read_match_contents + _ => return Err(format!("Unknown ClientToServer {} packet id: {}", #state_name_litstr, id)), + }, + }) + } + } + } + .into() +} diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs old mode 100644 new mode 100755 diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs old mode 100644 new mode 100755 diff --git a/azalea-protocol/src/mc_buf.rs b/azalea-protocol/src/mc_buf.rs deleted file mode 100644 index 538fc212..00000000 --- a/azalea-protocol/src/mc_buf.rs +++ /dev/null @@ -1,478 +0,0 @@ -//! Utilities for reading and writing for the Minecraft protocol - -use std::io::Write; - -use async_trait::async_trait; -use azalea_core::resource_location::ResourceLocation; -use byteorder::{BigEndian, WriteBytesExt}; -use tokio::io::{AsyncRead, AsyncReadExt}; - -// const DEFAULT_NBT_QUOTA: u32 = 2097152; -const MAX_STRING_LENGTH: u16 = 32767; -// const MAX_COMPONENT_STRING_LENGTH: u32 = 262144; - -#[async_trait] -pub trait Writable { - fn write_list(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> - where - F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, - T: Sized, - Self: Sized; - fn write_int_id_list(&mut self, list: Vec) -> Result<(), std::io::Error>; - fn write_map( - &mut self, - map: Vec<(KT, VT)>, - key_writer: KF, - value_writer: VF, - ) -> Result<(), std::io::Error> - where - KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy, - VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy, - Self: Sized; - - fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error>; - fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>; - fn write_varint(&mut self, value: i32) -> Result<(), std::io::Error>; - fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error>; - fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error>; - fn write_short(&mut self, n: u16) -> Result<(), std::io::Error>; - fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>; - fn write_int(&mut self, n: i32) -> Result<(), std::io::Error>; - fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error>; - fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error>; - fn write_long(&mut self, n: i64) -> Result<(), std::io::Error>; - fn write_resource_location( - &mut self, - location: &ResourceLocation, - ) -> Result<(), std::io::Error>; -} - -#[async_trait] -impl Writable for Vec { - fn write_list(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> - where - F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, - Self: Sized, - { - self.write_varint(list.len() as i32)?; - for item in list { - writer(self, item)?; - } - Ok(()) - } - - fn write_int_id_list(&mut self, list: Vec) -> Result<(), std::io::Error> { - self.write_list(&list, |buf, n| buf.write_varint(*n)) - } - - fn write_map( - &mut self, - map: Vec<(KT, VT)>, - key_writer: KF, - value_writer: VF, - ) -> Result<(), std::io::Error> - where - KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy, - VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy, - Self: Sized, - { - self.write_varint(map.len() as i32)?; - for (key, value) in map { - key_writer(self, key)?; - value_writer(self, value)?; - } - Ok(()) - } - - fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error> { - WriteBytesExt::write_u8(self, n) - } - - fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> { - self.extend_from_slice(bytes); - Ok(()) - } - - fn write_varint(&mut self, mut value: i32) -> Result<(), std::io::Error> { - let mut buffer = [0]; - if value == 0 { - self.write_all(&buffer).unwrap(); - } - while value != 0 { - buffer[0] = (value & 0b0111_1111) as u8; - value = (value >> 7) & (i32::max_value() >> 6); - if value != 0 { - buffer[0] |= 0b1000_0000; - } - self.write_all(&buffer)?; - } - Ok(()) - } - - fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error> { - if string.len() > len { - panic!( - "String too big (was {} bytes encoded, max {})", - string.len(), - len - ); - } - self.write_varint(string.len() as i32)?; - self.write_bytes(string.as_bytes()) - } - - fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error> { - self.write_utf_with_len(string, MAX_STRING_LENGTH.into()) - } - - fn write_short(&mut self, n: u16) -> Result<(), std::io::Error> { - WriteBytesExt::write_u16::(self, n) - } - - fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> { - self.write_varint(bytes.len() as i32)?; - self.write_bytes(bytes) - } - - fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> { - WriteBytesExt::write_i32::(self, n) - } - - fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error> { - self.write_byte(if b { 1 } else { 0 }) - } - - fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error> { - nbt.write(self) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) - } - - fn write_long(&mut self, n: i64) -> Result<(), std::io::Error> { - WriteBytesExt::write_i64::(self, n) - } - - fn write_resource_location( - &mut self, - location: &ResourceLocation, - ) -> Result<(), std::io::Error> { - self.write_utf(&location.to_string()) - } -} - -#[async_trait] -pub trait Readable { - async fn read_int_id_list(&mut self) -> Result, String>; - async fn read_varint(&mut self) -> Result; - fn get_varint_size(&mut self, value: i32) -> u8; - fn get_varlong_size(&mut self, value: i32) -> u8; - async fn read_byte_array(&mut self) -> Result, String>; - async fn read_bytes(&mut self, n: usize) -> Result, String>; - async fn read_utf(&mut self) -> Result; - async fn read_utf_with_len(&mut self, max_length: u32) -> Result; - async fn read_byte(&mut self) -> Result; - async fn read_int(&mut self) -> Result; - async fn read_boolean(&mut self) -> Result; - async fn read_nbt(&mut self) -> Result; - async fn read_long(&mut self) -> Result; - async fn read_resource_location(&mut self) -> Result; -} - -#[async_trait] -impl Readable for R -where - R: AsyncRead + std::marker::Unpin + std::marker::Send, -{ - async fn read_int_id_list(&mut self) -> Result, String> { - let len = self.read_varint().await?; - let mut list = Vec::with_capacity(len as usize); - for _ in 0..len { - list.push(self.read_varint().await?); - } - Ok(list) - } - - // fast varints stolen from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L67 - /// Read a single varint from the reader and return the value, along with the number of bytes read - async fn read_varint(&mut self) -> Result { - let mut buffer = [0]; - let mut ans = 0; - for i in 0..4 { - self.read_exact(&mut buffer) - .await - .map_err(|_| "Invalid VarInt".to_string())?; - ans |= ((buffer[0] & 0b0111_1111) as i32) << (7 * i); - if buffer[0] & 0b1000_0000 == 0 { - return Ok(ans); - } - } - Ok(ans) - } - - fn get_varint_size(&mut self, value: i32) -> u8 { - for i in 1..5 { - if (value & -1 << (i * 7)) != 0 { - continue; - } - return i; - } - 5 - } - - fn get_varlong_size(&mut self, value: i32) -> u8 { - for i in 1..10 { - if (value & -1 << (i * 7)) != 0 { - continue; - } - return i; - } - 10 - } - - async fn read_byte_array(&mut self) -> Result, String> { - let length = self.read_varint().await? as usize; - Ok(self.read_bytes(length).await?) - } - - async fn read_bytes(&mut self, n: usize) -> Result, String> { - let mut bytes = vec![0; n]; - match AsyncReadExt::read_exact(self, &mut bytes).await { - Ok(_) => Ok(bytes), - Err(_) => Err("Error reading bytes".to_string()), - } - } - - async fn read_utf(&mut self) -> Result { - self.read_utf_with_len(MAX_STRING_LENGTH.into()).await - } - - async fn read_utf_with_len(&mut self, max_length: u32) -> Result { - let length = self.read_varint().await?; - // i don't know why it's multiplied by 4 but it's like that in mojang's code so - if length < 0 { - return Err( - "The received encoded string buffer length is less than zero! Weird string!" - .to_string(), - ); - } - if length as u32 > max_length * 4 { - return Err(format!( - "The received encoded string buffer length is longer than maximum allowed ({} > {})", - length, - max_length * 4 - )); - } - - // this is probably quite inefficient, idk how to do it better - let mut string = String::new(); - let mut buffer = vec![0; length as usize]; - self.read_exact(&mut buffer) - .await - .map_err(|_| "Invalid UTF-8".to_string())?; - - string.push_str(std::str::from_utf8(&buffer).unwrap()); - if string.len() > length as usize { - return Err(format!( - "The received string length is longer than maximum allowed ({} > {})", - length, max_length - )); - } - - Ok(string) - } - - /// Read a single byte from the reader - async fn read_byte(&mut self) -> Result { - match AsyncReadExt::read_u8(self).await { - Ok(r) => Ok(r), - Err(_) => Err("Error reading byte".to_string()), - } - } - - async fn read_int(&mut self) -> Result { - match AsyncReadExt::read_i32(self).await { - Ok(r) => Ok(r), - Err(_) => Err("Error reading int".to_string()), - } - } - - async fn read_boolean(&mut self) -> Result { - match self.read_byte().await { - Ok(0) => Ok(false), - Ok(1) => Ok(true), - _ => Err("Error reading boolean".to_string()), - } - } - - async fn read_nbt(&mut self) -> Result { - match azalea_nbt::Tag::read(self).await { - Ok(r) => Ok(r), - // Err(e) => Err(e.to_string()), - Err(e) => Err(e.to_string()).unwrap(), - } - } - - async fn read_long(&mut self) -> Result { - match AsyncReadExt::read_i64(self).await { - Ok(r) => Ok(r), - Err(_) => Err("Error reading long".to_string()), - } - } - - async fn read_resource_location(&mut self) -> Result { - // get the resource location from the string - let location_string = self.read_utf().await?; - let location = ResourceLocation::new(&location_string)?; - Ok(location) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::{collections::HashMap, io::Cursor}; - use tokio::io::BufReader; - - #[test] - fn test_write_varint() { - let mut buf = Vec::new(); - buf.write_varint(123456).unwrap(); - assert_eq!(buf, vec![192, 196, 7]); - - let mut buf = Vec::new(); - buf.write_varint(0).unwrap(); - assert_eq!(buf, vec![0]); - } - - #[tokio::test] - async fn test_read_varint() { - let mut buf = BufReader::new(Cursor::new(vec![192, 196, 7])); - assert_eq!(buf.read_varint().await.unwrap(), 123456); - assert_eq!(buf.get_varint_size(123456), 3); - - let mut buf = BufReader::new(Cursor::new(vec![0])); - assert_eq!(buf.read_varint().await.unwrap(), 0); - assert_eq!(buf.get_varint_size(0), 1); - - let mut buf = BufReader::new(Cursor::new(vec![1])); - assert_eq!(buf.read_varint().await.unwrap(), 1); - assert_eq!(buf.get_varint_size(1), 1); - } - - #[tokio::test] - async fn test_read_varint_longer() { - let mut buf = BufReader::new(Cursor::new(vec![138, 56, 0, 135, 56, 123])); - assert_eq!(buf.read_varint().await.unwrap(), 7178); - } - - #[tokio::test] - async fn test_list() { - let mut buf = Vec::new(); - buf.write_list(&vec!["a", "bc", "def"], |buf, s| buf.write_utf(s)) - .unwrap(); - - // there's no read_list because idk how to do it in rust - let mut buf = BufReader::new(Cursor::new(buf)); - - let mut result = Vec::new(); - let length = buf.read_varint().await.unwrap(); - for _ in 0..length { - result.push(buf.read_utf().await.unwrap()); - } - - assert_eq!(result, vec!["a", "bc", "def"]); - } - - #[tokio::test] - async fn test_int_id_list() { - let mut buf = Vec::new(); - buf.write_list(&vec![1, 2, 3], |buf, i| buf.write_varint(*i)) - .unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - let result = buf.read_int_id_list().await.unwrap(); - assert_eq!(result, vec![1, 2, 3]); - } - - #[tokio::test] - async fn test_map() { - let mut buf = Vec::new(); - buf.write_map( - vec![("a", 1), ("bc", 23), ("def", 456)], - Vec::write_utf, - Vec::write_varint, - ) - .unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - let mut result = Vec::new(); - let length = buf.read_varint().await.unwrap(); - for _ in 0..length { - result.push(( - buf.read_utf().await.unwrap(), - buf.read_varint().await.unwrap(), - )); - } - - assert_eq!( - result, - vec![ - ("a".to_string(), 1), - ("bc".to_string(), 23), - ("def".to_string(), 456) - ] - ); - } - - #[tokio::test] - async fn test_nbt() { - let mut buf = Vec::new(); - buf.write_nbt(&azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( - "hello world".to_string(), - azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( - "name".to_string(), - azalea_nbt::Tag::String("Bananrama".to_string()), - )])), - )]))) - .unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - let result = buf.read_nbt().await.unwrap(); - assert_eq!( - result, - azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( - "hello world".to_string(), - azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( - "name".to_string(), - azalea_nbt::Tag::String("Bananrama".to_string()), - )])), - )])) - ); - } - - #[tokio::test] - async fn test_long() { - let mut buf = Vec::new(); - buf.write_long(123456).unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - assert_eq!(buf.read_long().await.unwrap(), 123456); - } - - #[tokio::test] - async fn test_resource_location() { - let mut buf = Vec::new(); - buf.write_resource_location(&ResourceLocation::new("minecraft:dirt").unwrap()) - .unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - assert_eq!( - buf.read_resource_location().await.unwrap(), - ResourceLocation::new("minecraft:dirt").unwrap() - ); - } -} diff --git a/azalea-protocol/src/mc_buf/mod.rs b/azalea-protocol/src/mc_buf/mod.rs new file mode 100755 index 00000000..4ecb65d1 --- /dev/null +++ b/azalea-protocol/src/mc_buf/mod.rs @@ -0,0 +1,231 @@ +//! Utilities for reading and writing for the Minecraft protocol + +mod read; +mod write; + +pub use read::{McBufReadable, McBufVarintReadable, Readable}; +pub use write::{McBufVarintWritable, McBufWritable, Writable}; + +// const DEFAULT_NBT_QUOTA: u32 = 2097152; +const MAX_STRING_LENGTH: u16 = 32767; +// const MAX_COMPONENT_STRING_LENGTH: u32 = 262144; + +#[cfg(test)] +mod tests { + use super::*; + use azalea_core::resource_location::ResourceLocation; + use std::{collections::HashMap, io::Cursor}; + use tokio::io::BufReader; + + #[test] + fn test_write_varint() { + let mut buf = Vec::new(); + buf.write_varint(0).unwrap(); + assert_eq!(buf, vec![0]); + + let mut buf = Vec::new(); + buf.write_varint(1).unwrap(); + assert_eq!(buf, vec![1]); + + let mut buf = Vec::new(); + buf.write_varint(2).unwrap(); + assert_eq!(buf, vec![2]); + + let mut buf = Vec::new(); + buf.write_varint(127).unwrap(); + assert_eq!(buf, vec![127]); + + let mut buf = Vec::new(); + buf.write_varint(128).unwrap(); + assert_eq!(buf, vec![128, 1]); + + let mut buf = Vec::new(); + buf.write_varint(255).unwrap(); + assert_eq!(buf, vec![255, 1]); + + let mut buf = Vec::new(); + buf.write_varint(25565).unwrap(); + assert_eq!(buf, vec![221, 199, 1]); + + let mut buf = Vec::new(); + buf.write_varint(2097151).unwrap(); + assert_eq!(buf, vec![255, 255, 127]); + + let mut buf = Vec::new(); + buf.write_varint(2147483647).unwrap(); + assert_eq!(buf, vec![255, 255, 255, 255, 7]); + + let mut buf = Vec::new(); + buf.write_varint(-1).unwrap(); + assert_eq!(buf, vec![255, 255, 255, 255, 15]); + + let mut buf = Vec::new(); + buf.write_varint(-2147483648).unwrap(); + assert_eq!(buf, vec![128, 128, 128, 128, 8]); + } + + #[tokio::test] + async fn test_read_varint() { + let mut buf = BufReader::new(Cursor::new(vec![0])); + assert_eq!(buf.read_varint().await.unwrap(), 0); + assert_eq!(buf.get_varint_size(0), 1); + + let mut buf = BufReader::new(Cursor::new(vec![1])); + assert_eq!(buf.read_varint().await.unwrap(), 1); + assert_eq!(buf.get_varint_size(1), 1); + + let mut buf = BufReader::new(Cursor::new(vec![2])); + assert_eq!(buf.read_varint().await.unwrap(), 2); + assert_eq!(buf.get_varint_size(2), 1); + + let mut buf = BufReader::new(Cursor::new(vec![127])); + assert_eq!(buf.read_varint().await.unwrap(), 127); + assert_eq!(buf.get_varint_size(127), 1); + + let mut buf = BufReader::new(Cursor::new(vec![128, 1])); + assert_eq!(buf.read_varint().await.unwrap(), 128); + assert_eq!(buf.get_varint_size(128), 2); + + let mut buf = BufReader::new(Cursor::new(vec![255, 1])); + assert_eq!(buf.read_varint().await.unwrap(), 255); + assert_eq!(buf.get_varint_size(255), 2); + + let mut buf = BufReader::new(Cursor::new(vec![221, 199, 1])); + assert_eq!(buf.read_varint().await.unwrap(), 25565); + assert_eq!(buf.get_varint_size(25565), 3); + + let mut buf = BufReader::new(Cursor::new(vec![255, 255, 127])); + assert_eq!(buf.read_varint().await.unwrap(), 2097151); + assert_eq!(buf.get_varint_size(2097151), 3); + + let mut buf = BufReader::new(Cursor::new(vec![255, 255, 255, 255, 7])); + assert_eq!(buf.read_varint().await.unwrap(), 2147483647); + assert_eq!(buf.get_varint_size(2147483647), 5); + + let mut buf = BufReader::new(Cursor::new(vec![255, 255, 255, 255, 15])); + assert_eq!(buf.read_varint().await.unwrap(), -1); + assert_eq!(buf.get_varint_size(-1), 5); + + let mut buf = BufReader::new(Cursor::new(vec![128, 128, 128, 128, 8])); + assert_eq!(buf.read_varint().await.unwrap(), -2147483648); + assert_eq!(buf.get_varint_size(-2147483648), 5); + } + + #[tokio::test] + async fn test_read_varint_longer() { + let mut buf = BufReader::new(Cursor::new(vec![138, 56, 0, 135, 56, 123])); + assert_eq!(buf.read_varint().await.unwrap(), 7178); + } + + #[tokio::test] + async fn test_list() { + let mut buf = Vec::new(); + buf.write_list(&vec!["a", "bc", "def"], |buf, s| buf.write_utf(s)) + .unwrap(); + + // there's no read_list because idk how to do it in rust + let mut buf = BufReader::new(Cursor::new(buf)); + + let mut result = Vec::new(); + let length = buf.read_varint().await.unwrap(); + for _ in 0..length { + result.push(buf.read_utf().await.unwrap()); + } + + assert_eq!(result, vec!["a", "bc", "def"]); + } + + #[tokio::test] + async fn test_int_id_list() { + let mut buf = Vec::new(); + buf.write_list(&vec![1, 2, 3], |buf, i| buf.write_varint(*i)) + .unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + let result = buf.read_int_id_list().await.unwrap(); + assert_eq!(result, vec![1, 2, 3]); + } + + #[tokio::test] + async fn test_map() { + let mut buf = Vec::new(); + buf.write_map( + vec![("a", 1), ("bc", 23), ("def", 456)], + Vec::write_utf, + Vec::write_varint, + ) + .unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + let mut result = Vec::new(); + let length = buf.read_varint().await.unwrap(); + for _ in 0..length { + result.push(( + buf.read_utf().await.unwrap(), + buf.read_varint().await.unwrap(), + )); + } + + assert_eq!( + result, + vec![ + ("a".to_string(), 1), + ("bc".to_string(), 23), + ("def".to_string(), 456) + ] + ); + } + + #[tokio::test] + async fn test_nbt() { + let mut buf = Vec::new(); + buf.write_nbt(&azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( + "hello world".to_string(), + azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( + "name".to_string(), + azalea_nbt::Tag::String("Bananrama".to_string()), + )])), + )]))) + .unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + let result = buf.read_nbt().await.unwrap(); + assert_eq!( + result, + azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( + "hello world".to_string(), + azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( + "name".to_string(), + azalea_nbt::Tag::String("Bananrama".to_string()), + )])), + )])) + ); + } + + #[tokio::test] + async fn test_long() { + let mut buf = Vec::new(); + buf.write_long(123456).unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + assert_eq!(buf.read_long().await.unwrap(), 123456); + } + + #[tokio::test] + async fn test_resource_location() { + let mut buf = Vec::new(); + buf.write_resource_location(&ResourceLocation::new("minecraft:dirt").unwrap()) + .unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + assert_eq!( + buf.read_resource_location().await.unwrap(), + ResourceLocation::new("minecraft:dirt").unwrap() + ); + } +} diff --git a/azalea-protocol/src/mc_buf/read.rs b/azalea-protocol/src/mc_buf/read.rs new file mode 100755 index 00000000..1e031916 --- /dev/null +++ b/azalea-protocol/src/mc_buf/read.rs @@ -0,0 +1,459 @@ +use async_trait::async_trait; +use azalea_chat::component::Component; +use azalea_core::{ + difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation, +}; +use serde::Deserialize; +use tokio::io::{AsyncRead, AsyncReadExt}; + +use super::MAX_STRING_LENGTH; + +#[async_trait] +pub trait Readable { + async fn read_int_id_list(&mut self) -> Result, String>; + async fn read_varint(&mut self) -> Result; + fn get_varint_size(&mut self, value: i32) -> u8; + fn get_varlong_size(&mut self, value: i32) -> u8; + async fn read_byte_array(&mut self) -> Result, String>; + async fn read_bytes_with_len(&mut self, n: usize) -> Result, String>; + async fn read_bytes(&mut self) -> Result, String>; + async fn read_utf(&mut self) -> Result; + async fn read_utf_with_len(&mut self, max_length: u32) -> Result; + async fn read_byte(&mut self) -> Result; + async fn read_int(&mut self) -> Result; + async fn read_boolean(&mut self) -> Result; + async fn read_nbt(&mut self) -> Result; + async fn read_long(&mut self) -> Result; + async fn read_resource_location(&mut self) -> Result; + async fn read_short(&mut self) -> Result; + async fn read_float(&mut self) -> Result; +} + +#[async_trait] +impl Readable for R +where + R: AsyncRead + std::marker::Unpin + std::marker::Send, +{ + async fn read_int_id_list(&mut self) -> Result, String> { + let len = self.read_varint().await?; + let mut list = Vec::with_capacity(len as usize); + for _ in 0..len { + list.push(self.read_varint().await?); + } + Ok(list) + } + + // fast varints modified from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L67 + /// Read a single varint from the reader and return the value, along with the number of bytes read + async fn read_varint(&mut self) -> Result { + let mut buffer = [0]; + let mut ans = 0; + for i in 0..5 { + self.read_exact(&mut buffer) + .await + .map_err(|_| "Invalid VarInt".to_string())?; + ans |= ((buffer[0] & 0b0111_1111) as i32) << (7 * i); + if buffer[0] & 0b1000_0000 == 0 { + return Ok(ans); + } + } + Ok(ans) + } + + fn get_varint_size(&mut self, value: i32) -> u8 { + for i in 1..5 { + if (value & -1 << (i * 7)) != 0 { + continue; + } + return i; + } + 5 + } + + fn get_varlong_size(&mut self, value: i32) -> u8 { + for i in 1..10 { + if (value & -1 << (i * 7)) != 0 { + continue; + } + return i; + } + 10 + } + + async fn read_byte_array(&mut self) -> Result, String> { + let length = self.read_varint().await? as usize; + Ok(self.read_bytes_with_len(length).await?) + } + + async fn read_bytes_with_len(&mut self, n: usize) -> Result, String> { + let mut bytes = vec![0; n]; + match AsyncReadExt::read_exact(self, &mut bytes).await { + Ok(_) => Ok(bytes), + Err(_) => Err("Error reading bytes".to_string()), + } + } + + async fn read_bytes(&mut self) -> Result, String> { + // read to end of the buffer + let mut bytes = vec![]; + AsyncReadExt::read_to_end(self, &mut bytes) + .await + .map_err(|_| "Error reading bytes".to_string())?; + Ok(bytes) + } + + async fn read_utf(&mut self) -> Result { + self.read_utf_with_len(MAX_STRING_LENGTH.into()).await + } + + async fn read_utf_with_len(&mut self, max_length: u32) -> Result { + let length = self.read_varint().await?; + // i don't know why it's multiplied by 4 but it's like that in mojang's code so + if length < 0 { + return Err( + "The received encoded string buffer length is less than zero! Weird string!" + .to_string(), + ); + } + if length as u32 > max_length * 4 { + return Err(format!( + "The received encoded string buffer length is longer than maximum allowed ({} > {})", + length, + max_length * 4 + )); + } + + // this is probably quite inefficient, idk how to do it better + let mut string = String::new(); + let mut buffer = vec![0; length as usize]; + self.read_exact(&mut buffer) + .await + .map_err(|_| "Invalid UTF-8".to_string())?; + + string.push_str(std::str::from_utf8(&buffer).unwrap()); + if string.len() > length as usize { + return Err(format!( + "The received string length is longer than maximum allowed ({} > {})", + length, max_length + )); + } + + Ok(string) + } + + /// Read a single byte from the reader + async fn read_byte(&mut self) -> Result { + match AsyncReadExt::read_u8(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading byte".to_string()), + } + } + + async fn read_int(&mut self) -> Result { + match AsyncReadExt::read_i32(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading int".to_string()), + } + } + + async fn read_boolean(&mut self) -> Result { + match self.read_byte().await { + Ok(0) => Ok(false), + Ok(1) => Ok(true), + _ => Err("Error reading boolean".to_string()), + } + } + + async fn read_nbt(&mut self) -> Result { + match azalea_nbt::Tag::read(self).await { + Ok(r) => Ok(r), + // Err(e) => Err(e.to_string()), + Err(e) => Err(e.to_string()).unwrap(), + } + } + + async fn read_long(&mut self) -> Result { + match AsyncReadExt::read_i64(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading long".to_string()), + } + } + + async fn read_resource_location(&mut self) -> Result { + // get the resource location from the string + let location_string = self.read_utf().await?; + let location = ResourceLocation::new(&location_string)?; + Ok(location) + } + + async fn read_short(&mut self) -> Result { + match AsyncReadExt::read_i16(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading short".to_string()), + } + } + + async fn read_float(&mut self) -> Result { + match AsyncReadExt::read_f32(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading float".to_string()), + } + } +} + +#[async_trait] +pub trait McBufReadable +where + Self: Sized, +{ + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send; +} + +#[async_trait] +pub trait McBufVarintReadable +where + Self: Sized, +{ + async fn varint_read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send; +} + +#[async_trait] +impl McBufReadable for i32 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_int().await + } +} + +#[async_trait] +impl McBufVarintReadable for i32 { + async fn varint_read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_varint().await + } +} + +#[async_trait] +impl McBufReadable for Vec { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_bytes().await + } +} + +// string +#[async_trait] +impl McBufReadable for String { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_utf().await + } +} + +// ResourceLocation +#[async_trait] +impl McBufReadable for ResourceLocation { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_resource_location().await + } +} + +// u32 +#[async_trait] +impl McBufReadable for u32 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_int().await.map(|i| i as u32) + } +} + +// u32 varint +#[async_trait] +impl McBufVarintReadable for u32 { + async fn varint_read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_varint().await.map(|i| i as u32) + } +} + +// u16 +#[async_trait] +impl McBufReadable for u16 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_short().await.map(|i| i as u16) + } +} + +// u16 varint +#[async_trait] +impl McBufVarintReadable for u16 { + async fn varint_read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_varint().await.map(|i| i as u16) + } +} + +// i64 +#[async_trait] +impl McBufReadable for i64 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_long().await + } +} + +// u64 +#[async_trait] +impl McBufReadable for u64 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + i64::read_into(buf).await.map(|i| i as u64) + } +} + +// bool +#[async_trait] +impl McBufReadable for bool { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_boolean().await + } +} + +// u8 +#[async_trait] +impl McBufReadable for u8 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_byte().await + } +} + +// i8 +#[async_trait] +impl McBufReadable for i8 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_byte().await.map(|i| i as i8) + } +} + +// f32 +#[async_trait] +impl McBufReadable for f32 { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_float().await + } +} + +// GameType +#[async_trait] +impl McBufReadable for GameType { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + GameType::from_id(buf.read_byte().await?) + } +} + +// Option +#[async_trait] +impl McBufReadable for Option { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + GameType::from_optional_id(buf.read_byte().await? as i8) + } +} + +// Vec +#[async_trait] +impl McBufReadable for Vec { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let mut vec = Vec::new(); + let length = buf.read_varint().await?; + for _ in 0..length { + vec.push(buf.read_resource_location().await?); + } + Ok(vec) + } +} + +// azalea_nbt::Tag +#[async_trait] +impl McBufReadable for azalea_nbt::Tag { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_nbt().await + } +} + +// Difficulty +#[async_trait] +impl McBufReadable for Difficulty { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + Ok(Difficulty::by_id(u8::read_into(buf).await?)) + } +} + +// Component +#[async_trait] +impl McBufReadable for Component { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let string = buf.read_utf().await?; + let json: serde_json::Value = serde_json::from_str(string.as_str()) + .map_err(|e| "Component isn't valid JSON".to_string())?; + let component = Component::deserialize(json).map_err(|e| e.to_string())?; + Ok(component) + } +} diff --git a/azalea-protocol/src/mc_buf/write.rs b/azalea-protocol/src/mc_buf/write.rs new file mode 100755 index 00000000..05f613d8 --- /dev/null +++ b/azalea-protocol/src/mc_buf/write.rs @@ -0,0 +1,341 @@ +use async_trait::async_trait; +use azalea_chat::component::Component; +use azalea_core::{ + difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation, +}; +use byteorder::{BigEndian, WriteBytesExt}; +use std::io::Write; + +use super::MAX_STRING_LENGTH; + +#[async_trait] +pub trait Writable { + fn write_list(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> + where + F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, + T: Sized, + Self: Sized; + fn write_int_id_list(&mut self, list: &Vec) -> Result<(), std::io::Error>; + fn write_map( + &mut self, + map: Vec<(KT, VT)>, + key_writer: KF, + value_writer: VF, + ) -> Result<(), std::io::Error> + where + KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy, + VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy, + Self: Sized; + + fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error>; + fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>; + fn write_varint(&mut self, value: i32) -> Result<(), std::io::Error>; + fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error>; + fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error>; + fn write_short(&mut self, n: i16) -> Result<(), std::io::Error>; + fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>; + fn write_int(&mut self, n: i32) -> Result<(), std::io::Error>; + fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error>; + fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error>; + fn write_long(&mut self, n: i64) -> Result<(), std::io::Error>; + fn write_resource_location( + &mut self, + location: &ResourceLocation, + ) -> Result<(), std::io::Error>; + fn write_float(&mut self, n: f32) -> Result<(), std::io::Error>; +} + +#[async_trait] +impl Writable for Vec { + fn write_list(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> + where + F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, + Self: Sized, + { + self.write_varint(list.len() as i32)?; + for item in list { + writer(self, item)?; + } + Ok(()) + } + + fn write_int_id_list(&mut self, list: &Vec) -> Result<(), std::io::Error> { + self.write_list(&list, |buf, n| buf.write_varint(*n)) + } + + fn write_map( + &mut self, + map: Vec<(KT, VT)>, + key_writer: KF, + value_writer: VF, + ) -> Result<(), std::io::Error> + where + KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy, + VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy, + Self: Sized, + { + self.write_varint(map.len() as i32)?; + for (key, value) in map { + key_writer(self, key)?; + value_writer(self, value)?; + } + Ok(()) + } + + fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error> { + WriteBytesExt::write_u8(self, n) + } + + fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> { + self.extend_from_slice(bytes); + Ok(()) + } + + fn write_varint(&mut self, mut value: i32) -> Result<(), std::io::Error> { + let mut buffer = [0]; + if value == 0 { + self.write_all(&buffer).unwrap(); + } + while value != 0 { + buffer[0] = (value & 0b0111_1111) as u8; + value = (value >> 7) & (i32::max_value() >> 6); + if value != 0 { + buffer[0] |= 0b1000_0000; + } + self.write_all(&buffer)?; + } + Ok(()) + } + + fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error> { + if string.len() > len { + panic!( + "String too big (was {} bytes encoded, max {})", + string.len(), + len + ); + } + self.write_varint(string.len() as i32)?; + self.write_bytes(string.as_bytes()) + } + + fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error> { + self.write_utf_with_len(string, MAX_STRING_LENGTH.into()) + } + + fn write_short(&mut self, n: i16) -> Result<(), std::io::Error> { + WriteBytesExt::write_i16::(self, n) + } + + fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> { + self.write_varint(bytes.len() as i32)?; + self.write_bytes(bytes) + } + + fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> { + WriteBytesExt::write_i32::(self, n) + } + + fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error> { + self.write_byte(if b { 1 } else { 0 }) + } + + fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error> { + nbt.write(self) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) + } + + fn write_long(&mut self, n: i64) -> Result<(), std::io::Error> { + WriteBytesExt::write_i64::(self, n) + } + + fn write_float(&mut self, n: f32) -> Result<(), std::io::Error> { + WriteBytesExt::write_f32::(self, n) + } + + fn write_resource_location( + &mut self, + location: &ResourceLocation, + ) -> Result<(), std::io::Error> { + self.write_utf(&location.to_string()) + } +} + +pub trait McBufWritable +where + Self: Sized, +{ + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error>; +} + +pub trait McBufVarintWritable +where + Self: Sized, +{ + fn varint_write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error>; +} + +impl McBufWritable for i32 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + Writable::write_int(buf, *self) + } +} + +impl McBufVarintWritable for i32 { + fn varint_write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_varint(*self) + } +} + +impl McBufWritable for Vec { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_bytes(self) + } +} + +// string +impl McBufWritable for String { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_utf(self) + } +} + +// ResourceLocation +impl McBufWritable for ResourceLocation { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_resource_location(self) + } +} + +// u32 +impl McBufWritable for u32 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + i16::write_into(&(*self as i16), buf) + } +} + +// u32 varint +impl McBufVarintWritable for u32 { + fn varint_write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + i32::varint_write_into(&(*self as i32), buf) + } +} + +// u16 +impl McBufWritable for u16 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + i16::write_into(&(*self as i16), buf) + } +} + +// u16 varint +impl McBufVarintWritable for u16 { + fn varint_write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + i32::varint_write_into(&(*self as i32), buf) + } +} + +// u8 +impl McBufWritable for u8 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_byte(*self) + } +} + +// i16 +impl McBufWritable for i16 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + Writable::write_short(buf, *self) + } +} + +// i64 +impl McBufWritable for i64 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + Writable::write_long(buf, *self) + } +} + +// u64 +impl McBufWritable for u64 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + i64::write_into(&(*self as i64), buf) + } +} + +// bool +impl McBufWritable for bool { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_boolean(*self) + } +} + +// i8 +impl McBufWritable for i8 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_byte(*self as u8) + } +} + +// f32 +impl McBufWritable for f32 { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_float(*self) + } +} + +// GameType +impl McBufWritable for GameType { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + u8::write_into(&self.to_id(), buf) + } +} + +// Option +impl McBufWritable for Option { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_byte(GameType::to_optional_id(self) as u8) + } +} + +// Vec +impl McBufWritable for Vec { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_list(self, |buf, resource_location| { + buf.write_resource_location(resource_location) + }) + } +} + +// azalea_nbt::Tag +impl McBufWritable for azalea_nbt::Tag { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_nbt(self) + } +} + +// Difficulty +impl McBufWritable for Difficulty { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + u8::write_into(&self.id(), buf) + } +} + +// Component +#[async_trait] +impl McBufWritable for Component { + // async fn read_into(buf: &mut R) -> Result + // where + // R: AsyncRead + std::marker::Unpin + std::marker::Send, + // { + // let string = buf.read_utf().await?; + // let json: serde_json::Value = serde_json::from_str(string.as_str()) + // .map_err(|e| "Component isn't valid JSON".to_string())?; + // let component = Component::deserialize(json).map_err(|e| e.to_string())?; + // Ok(component) + // } + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + // component doesn't have serialize implemented yet + todo!() + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs b/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs new file mode 100755 index 00000000..e12cfff3 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs @@ -0,0 +1,8 @@ +use azalea_core::difficulty::Difficulty; +use packet_macros::GamePacket; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundChangeDifficultyPacket { + pub difficulty: Difficulty, + pub locked: bool, +} diff --git a/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs b/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs new file mode 100755 index 00000000..134a3109 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs @@ -0,0 +1,8 @@ +use azalea_core::resource_location::ResourceLocation; +use packet_macros::GamePacket; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundCustomPayloadPacket { + pub identifier: ResourceLocation, + pub data: Vec, +} diff --git a/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs b/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs new file mode 100755 index 00000000..1403630d --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs @@ -0,0 +1,96 @@ +use std::hash::Hash; + +use async_trait::async_trait; +use tokio::io::AsyncRead; + +use crate::mc_buf::{McBufReadable, Readable}; + +use super::GamePacket; + +#[derive(Hash, Clone, Debug)] +pub struct ClientboundDeclareCommandsPacket { + pub entries: Vec, + pub root_index: i32, +} + +impl ClientboundDeclareCommandsPacket { + pub fn get(self) -> GamePacket { + GamePacket::ClientboundDeclareCommandsPacket(self) + } + + pub fn write(&self, _buf: &mut Vec) -> Result<(), std::io::Error> { + panic!("ClientboundDeclareCommandsPacket::write not implemented") + } + + pub async fn read( + buf: &mut T, + ) -> Result { + let node_count = buf.read_varint().await?; + println!("node_count: {}", node_count); + let mut nodes = Vec::with_capacity(node_count as usize); + for _ in 0..node_count { + let node = BrigadierNodeStub::read_into(buf).await?; + nodes.push(node); + } + let root_index = buf.read_varint().await?; + Ok(GamePacket::ClientboundDeclareCommandsPacket( + ClientboundDeclareCommandsPacket { + entries: nodes, + root_index, + }, + )) + } +} + +#[derive(Hash, Debug, Clone)] +pub struct BrigadierNodeStub {} + +// azalea_brigadier::tree::CommandNode +#[async_trait] +impl McBufReadable for BrigadierNodeStub { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let flags = u8::read_into(buf).await?; + + let node_type = flags & 0x03; + let is_executable = flags & 0x04 != 0; + let has_redirect = flags & 0x08 != 0; + let has_suggestions_type = flags & 0x10 != 0; + println!("flags: {}, node_type: {}, is_executable: {}, has_redirect: {}, has_suggestions_type: {}", flags, node_type, is_executable, has_redirect, has_suggestions_type); + + let children = buf.read_int_id_list().await?; + println!("children: {:?}", children); + let redirect_node = if has_redirect { + buf.read_varint().await? + } else { + 0 + }; + println!("redirect_node: {}", redirect_node); + + if node_type == 2 { + let name = buf.read_utf().await?; + println!("name: {}", name); + + let resource_location = if has_suggestions_type { + Some(buf.read_resource_location().await?) + } else { + None + }; + println!( + "node_type=2, flags={}, name={}, resource_location={:?}", + flags, name, resource_location + ); + return Ok(BrigadierNodeStub {}); + } + if node_type == 1 { + let name = buf.read_utf().await?; + println!("node_type=1, flags={}, name={}", flags, name); + return Ok(BrigadierNodeStub {}); + } + println!("node_type={}, flags={}", node_type, flags); + Ok(BrigadierNodeStub {}) + // return Err("Unknown node type".to_string()); + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs new file mode 100644 index 00000000..74f5f72e --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs @@ -0,0 +1,9 @@ +use azalea_chat::component::Component; +use azalea_core::resource_location::ResourceLocation; +use packet_macros::GamePacket; +use serde::Deserialize; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundDisconnectPacket { + pub reason: Component, +} diff --git a/azalea-protocol/src/packets/game/clientbound_login_packet.rs b/azalea-protocol/src/packets/game/clientbound_login_packet.rs old mode 100644 new mode 100755 index 9043fa1a..57869202 --- a/azalea-protocol/src/packets/game/clientbound_login_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_login_packet.rs @@ -1,26 +1,8 @@ -use super::GamePacket; -use crate::mc_buf::{Readable, Writable}; use azalea_core::{game_type::GameType, resource_location::ResourceLocation}; +use packet_macros::GamePacket; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, GamePacket)] pub struct ClientboundLoginPacket { - // private final int playerId; - // private final boolean hardcore; - // private final GameType gameType; - // @Nullable - // private final GameType previousGameType; - // private final Set> levels; - // private final RegistryAccess.RegistryHolder registryHolder; - // private final DimensionType dimensionType; - // private final ResourceKey dimension; - // private final long seed; - // private final int maxPlayers; - // private final int chunkRadius; - // private final int simulationDistance; - // private final boolean reducedDebugInfo; - // private final boolean showDeathScreen; - // private final boolean isDebug; - // private final boolean isFlat; pub player_id: i32, pub hardcore: bool, pub game_type: GameType, @@ -30,93 +12,14 @@ pub struct ClientboundLoginPacket { pub dimension_type: azalea_nbt::Tag, pub dimension: ResourceLocation, pub seed: i64, + #[varint] pub max_players: i32, + #[varint] pub chunk_radius: i32, + #[varint] pub simulation_distance: i32, pub reduced_debug_info: bool, pub show_death_screen: bool, pub is_debug: bool, pub is_flat: bool, } - -impl ClientboundLoginPacket { - pub fn get(self) -> GamePacket { - GamePacket::ClientboundLoginPacket(self) - } - - pub fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error> { - buf.write_int(self.player_id)?; - buf.write_boolean(self.hardcore)?; - buf.write_byte(self.game_type.to_id())?; - buf.write_byte(GameType::to_optional_id(&self.previous_game_type) as u8)?; - buf.write_list(&self.levels, |buf, resource_location| { - buf.write_resource_location(resource_location) - })?; - buf.write_nbt(&self.registry_holder)?; - buf.write_nbt(&self.dimension_type)?; - buf.write_resource_location(&self.dimension)?; - buf.write_long(self.seed)?; - buf.write_varint(self.max_players)?; - buf.write_varint(self.chunk_radius)?; - buf.write_varint(self.simulation_distance)?; - buf.write_boolean(self.reduced_debug_info)?; - buf.write_boolean(self.show_death_screen)?; - buf.write_boolean(self.is_debug)?; - buf.write_boolean(self.is_flat)?; - Ok(()) - } - - pub async fn read( - buf: &mut T, - ) -> Result { - let player_id = buf.read_int().await?; - let hardcore = buf.read_boolean().await?; - let game_type = GameType::from_id(buf.read_byte().await?)?; - let previous_game_type = GameType::from_optional_id(buf.read_byte().await? as i8)?; - - let mut levels = Vec::new(); - let length = buf.read_varint().await?; - for _ in 0..length { - levels.push(buf.read_resource_location().await?); - } - - // println!("about to read nbt"); - // // read all the bytes into a buffer, print it, and panic - // let mut registry_holder_buf = Vec::new(); - // buf.read_to_end(&mut registry_holder_buf).await.unwrap(); - // println!("{:?}", String::from_utf8_lossy(®istry_holder_buf)); - // panic!(""); - - let registry_holder = buf.read_nbt().await?; - let dimension_type = buf.read_nbt().await?; - let dimension = buf.read_resource_location().await?; - let seed = buf.read_long().await?; - let max_players = buf.read_varint().await?; - let chunk_radius = buf.read_varint().await?; - let simulation_distance = buf.read_varint().await?; - let reduced_debug_info = buf.read_boolean().await?; - let show_death_screen = buf.read_boolean().await?; - let is_debug = buf.read_boolean().await?; - let is_flat = buf.read_boolean().await?; - - Ok(ClientboundLoginPacket { - player_id, - hardcore, - game_type, - previous_game_type, - levels, - registry_holder, - dimension_type, - dimension, - seed, - max_players, - chunk_radius, - simulation_distance, - reduced_debug_info, - show_death_screen, - is_debug, - is_flat, - } - .get()) - } -} diff --git a/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs new file mode 100755 index 00000000..f4f528cf --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs @@ -0,0 +1,57 @@ +// i don't know the actual name of this packet, i couldn't find it in the source code + +use crate::mc_buf::{McBufReadable, McBufWritable, Readable}; +use async_trait::async_trait; +use packet_macros::GamePacket; +use tokio::io::AsyncRead; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundPlayerAbilitiesPacket { + pub flags: PlayerAbilitiesFlags, + pub flying_speed: f32, + /// Used for the fov + pub walking_speed: f32, +} + +#[derive(Clone, Debug)] +pub struct PlayerAbilitiesFlags { + pub invulnerable: bool, + pub flying: bool, + pub can_fly: bool, + pub instant_break: bool, +} + +#[async_trait] +impl McBufReadable for PlayerAbilitiesFlags { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let byte = buf.read_byte().await?; + Ok(PlayerAbilitiesFlags { + invulnerable: byte & 1 != 0, + flying: byte & 2 != 0, + can_fly: byte & 4 != 0, + instant_break: byte & 8 != 0, + }) + } +} + +impl McBufWritable for PlayerAbilitiesFlags { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + let mut byte = 0; + if self.invulnerable { + byte = byte | 1; + } + if self.flying { + byte = byte | 2; + } + if self.can_fly { + byte = byte | 4; + } + if self.instant_break { + byte = byte | 8; + } + u8::write_into(&byte, buf) + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs new file mode 100755 index 00000000..4f0bf575 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs @@ -0,0 +1,7 @@ +use packet_macros::GamePacket; + +/// Sent to change the player's slot selection. +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundSetCarriedItemPacket { + pub slot: u8, +} diff --git a/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs new file mode 100755 index 00000000..a2f17370 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs @@ -0,0 +1,105 @@ +use std::collections::HashMap; + +use async_trait::async_trait; +use azalea_core::resource_location::ResourceLocation; +use packet_macros::GamePacket; +use tokio::io::AsyncRead; + +use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable}; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundUpdateTagsPacket { + pub tags: HashMap>, +} + +#[derive(Clone, Debug)] +pub struct Tags { + pub name: ResourceLocation, + pub elements: Vec, +} + +#[async_trait] +impl McBufReadable for HashMap> { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + println!("reading tags!"); + let length = buf.read_varint().await? as usize; + println!("length: {}", length); + let mut data = HashMap::with_capacity(length); + for _ in 0..length { + let tag_type = buf.read_resource_location().await?; + println!("read tag type {}", tag_type); + let tags_count = buf.read_varint().await? as usize; + let mut tags_vec = Vec::with_capacity(tags_count); + for _ in 0..tags_count { + let tags = Tags::read_into(buf).await?; + tags_vec.push(tags); + } + println!("tags: {} {:?}", tag_type, tags_vec); + data.insert(tag_type, tags_vec); + } + Ok(data) + } +} + +impl McBufWritable for HashMap> { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_varint(self.len() as i32)?; + for (k, v) in self { + k.write_into(buf)?; + v.write_into(buf)?; + } + Ok(()) + } +} + +#[async_trait] +impl McBufReadable for Vec { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let tags_count = buf.read_varint().await? as usize; + let mut tags_vec = Vec::with_capacity(tags_count); + for _ in 0..tags_count { + let tags = Tags::read_into(buf).await?; + tags_vec.push(tags); + } + Ok(tags_vec) + } +} + +impl McBufWritable for Vec { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_varint(self.len() as i32)?; + for tag in self { + tag.write_into(buf)?; + } + Ok(()) + } +} + +#[async_trait] +impl McBufReadable for Tags { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + println!("reading tags."); + let name = buf.read_resource_location().await?; + println!("tags name: {}", name); + let elements = buf.read_int_id_list().await?; + println!("elements: {:?}", elements); + Ok(Tags { name, elements }) + } +} + +impl McBufWritable for Tags { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + self.name.write_into(buf)?; + buf.write_int_id_list(&self.elements)?; + Ok(()) + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs new file mode 100755 index 00000000..8301c089 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs @@ -0,0 +1,9 @@ +// i don't know the actual name of this packet, i couldn't find it in the source code + +use packet_macros::GamePacket; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundUpdateViewDistancePacket { + #[varint] + pub view_distance: i32, +} diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs old mode 100644 new mode 100755 index 00fa1d75..dde3f753 --- a/azalea-protocol/src/packets/game/mod.rs +++ b/azalea-protocol/src/packets/game/mod.rs @@ -1,46 +1,27 @@ +pub mod clientbound_change_difficulty_packet; +pub mod clientbound_custom_payload_packet; +pub mod clientbound_declare_commands_packet; +pub mod clientbound_disconnect_packet; pub mod clientbound_login_packet; +pub mod clientbound_player_abilities_packet; +pub mod clientbound_set_carried_item_packet; +pub mod clientbound_update_tags_packet; +pub mod clientbound_update_view_distance_packet; -use super::ProtocolPacket; -use crate::connect::PacketFlow; -use async_trait::async_trait; +use packet_macros::declare_state_packets; -#[derive(Clone, Debug)] -pub enum GamePacket -where - Self: Sized, -{ - ClientboundLoginPacket(clientbound_login_packet::ClientboundLoginPacket), -} - -#[async_trait] -impl ProtocolPacket for GamePacket { - fn id(&self) -> u32 { - match self { - GamePacket::ClientboundLoginPacket(_packet) => 0x00, - } +declare_state_packets!( + GamePacket, + Serverbound => {}, + Clientbound => { + 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket, + 0x12: clientbound_declare_commands_packet::ClientboundDeclareCommandsPacket, + 0x1a: clientbound_disconnect_packet::ClientboundDisconnectPacket, + 0x18: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket, + 0x26: clientbound_login_packet::ClientboundLoginPacket, + 0x32: clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket, + 0x48: clientbound_set_carried_item_packet::ClientboundSetCarriedItemPacket, + 0x4a: clientbound_update_view_distance_packet::ClientboundUpdateViewDistancePacket, + 0x67: clientbound_update_tags_packet::ClientboundUpdateTagsPacket } - - fn write(&self, _buf: &mut Vec) {} - - /// Read a packet by its id, ConnectionProtocol, and flow - async fn read( - id: u32, - flow: &PacketFlow, - buf: &mut T, - ) -> Result - where - Self: Sized, - { - Ok(match flow { - PacketFlow::ServerToClient => match id { - 0x26 => clientbound_login_packet::ClientboundLoginPacket::read(buf).await?, - - _ => return Err(format!("Unknown ServerToClient game packet id: {}", id)), - }, - PacketFlow::ClientToServer => match id { - // 0x00 => serverbound_hello_packet::ServerboundHelloPacket::read(buf).await?, - _ => return Err(format!("Unknown ClientToServer game packet id: {}", id)), - }, - }) - } -} +); diff --git a/azalea-protocol/src/packets/handshake/client_intention_packet.rs b/azalea-protocol/src/packets/handshake/client_intention_packet.rs old mode 100644 new mode 100755 index 939a695e..a92d65f6 --- a/azalea-protocol/src/packets/handshake/client_intention_packet.rs +++ b/azalea-protocol/src/packets/handshake/client_intention_packet.rs @@ -1,34 +1,34 @@ -use std::hash::Hash; - use crate::{mc_buf::Writable, packets::ConnectionProtocol}; +use packet_macros::HandshakePacket; +use std::hash::Hash; use super::HandshakePacket; -#[derive(Hash, Clone, Debug)] +#[derive(Hash, Clone, Debug, HandshakePacket)] pub struct ClientIntentionPacket { + #[varint] pub protocol_version: u32, pub hostname: String, pub port: u16, - /// 1 for status, 2 for login pub intention: ConnectionProtocol, } -impl ClientIntentionPacket { - pub fn get(self) -> HandshakePacket { - HandshakePacket::ClientIntentionPacket(self) - } +// impl ClientIntentionPacket { +// pub fn get(self) -> HandshakePacket { +// HandshakePacket::ClientIntentionPacket(self) +// } - pub fn write(&self, buf: &mut Vec) { - buf.write_varint(self.protocol_version as i32).unwrap(); - buf.write_utf(&self.hostname).unwrap(); - buf.write_short(self.port).unwrap(); - buf.write_varint(self.intention.clone() as i32).unwrap(); - } +// pub fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error> { +// buf.write_varint(self.protocol_version as i32)?; +// buf.write_utf(&self.hostname)?; +// buf.write_short(self.port as i16)?; +// buf.write_varint(self.intention as i32)?; +// Ok(()) +// } - pub async fn read( - _buf: &mut T, - ) -> Result { - Err("ClientIntentionPacket::parse not implemented".to_string()) - // Ok(ClientIntentionPacket {}.get()) - } -} +// pub async fn read( +// buf: &mut T, +// ) -> Result { +// todo!() +// } +// } diff --git a/azalea-protocol/src/packets/handshake/mod.rs b/azalea-protocol/src/packets/handshake/mod.rs old mode 100644 new mode 100755 index 1d753645..88d9939b --- a/azalea-protocol/src/packets/handshake/mod.rs +++ b/azalea-protocol/src/packets/handshake/mod.rs @@ -1,48 +1,11 @@ pub mod client_intention_packet; -use async_trait::async_trait; +use packet_macros::declare_state_packets; -use crate::connect::PacketFlow; - -use super::ProtocolPacket; - -#[derive(Clone, Debug)] -pub enum HandshakePacket -where - Self: Sized, -{ - ClientIntentionPacket(client_intention_packet::ClientIntentionPacket), -} - -#[async_trait] -impl ProtocolPacket for HandshakePacket { - fn id(&self) -> u32 { - match self { - HandshakePacket::ClientIntentionPacket(_packet) => 0x00, - } +declare_state_packets!( + HandshakePacket, + Serverbound => {}, + Clientbound => { + 0x00: client_intention_packet::ClientIntentionPacket, } - - fn write(&self, buf: &mut Vec) { - match self { - HandshakePacket::ClientIntentionPacket(packet) => packet.write(buf), - } - } - - /// Read a packet by its id, ConnectionProtocol, and flow - async fn read( - id: u32, - flow: &PacketFlow, - buf: &mut T, - ) -> Result - where - Self: Sized, - { - match flow { - PacketFlow::ServerToClient => Err("HandshakePacket::read not implemented".to_string()), - PacketFlow::ClientToServer => match id { - 0x00 => Ok(client_intention_packet::ClientIntentionPacket::read(buf).await?), - _ => Err(format!("Unknown ClientToServer status packet id: {}", id)), - }, - } - } -} +); diff --git a/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs b/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs old mode 100644 new mode 100755 index 2bc1fc1e..3138106e --- a/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs @@ -1,37 +1,11 @@ -use super::LoginPacket; -use crate::mc_buf::{Readable, Writable}; use azalea_core::resource_location::ResourceLocation; +use packet_macros::LoginPacket; use std::hash::Hash; -#[derive(Hash, Clone, Debug)] +#[derive(Hash, Clone, Debug, LoginPacket)] pub struct ClientboundCustomQueryPacket { + #[varint] pub transaction_id: u32, pub identifier: ResourceLocation, pub data: Vec, } - -impl ClientboundCustomQueryPacket { - pub fn get(self) -> LoginPacket { - LoginPacket::ClientboundCustomQueryPacket(self) - } - - pub fn write(&self, buf: &mut Vec) { - buf.write_varint(self.transaction_id as i32).unwrap(); - buf.write_utf(self.identifier.to_string().as_str()).unwrap(); - buf.write_bytes(&self.data).unwrap(); - } - - pub async fn read( - buf: &mut T, - ) -> Result { - let transaction_id = buf.read_varint().await? as u32; - let identifier = ResourceLocation::new(&buf.read_utf().await?)?; - let data = buf.read_bytes(1048576).await?; - Ok(ClientboundCustomQueryPacket { - transaction_id, - identifier, - data, - } - .get()) - } -} diff --git a/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs b/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs old mode 100644 new mode 100755 index 1a752c1a..c54aa819 --- a/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs @@ -14,11 +14,12 @@ impl ClientboundGameProfilePacket { LoginPacket::ClientboundGameProfilePacket(self) } - pub fn write(&self, buf: &mut Vec) { + pub fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error> { for n in self.game_profile.uuid.to_int_array() { buf.write_int(n as i32).unwrap(); } buf.write_utf(self.game_profile.name.as_str()).unwrap(); + Ok(()) } pub async fn read( diff --git a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs old mode 100644 new mode 100755 index e0b865be..9d0cec39 --- a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs @@ -16,7 +16,7 @@ impl ClientboundHelloPacket { LoginPacket::ClientboundHelloPacket(self) } - pub fn write(&self, _buf: &mut Vec) { + pub fn write(&self, _buf: &mut Vec) -> Result<(), std::io::Error> { panic!("ClientboundHelloPacket::write not implemented") } diff --git a/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs old mode 100644 new mode 100755 index af355192..a88c6cbf --- a/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs @@ -14,8 +14,9 @@ impl ClientboundLoginCompressionPacket { LoginPacket::ClientboundLoginCompressionPacket(self) } - pub fn write(&self, buf: &mut Vec) { + pub fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error> { buf.write_varint(self.compression_threshold).unwrap(); + Ok(()) } pub async fn read( diff --git a/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs new file mode 100644 index 00000000..28d91c79 --- /dev/null +++ b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs @@ -0,0 +1,7 @@ +use azalea_chat::component::Component; +use packet_macros::LoginPacket; + +#[derive(Clone, Debug, LoginPacket)] +pub struct ClientboundLoginDisconnectPacket { + pub reason: Component, +} diff --git a/azalea-protocol/src/packets/login/mod.rs b/azalea-protocol/src/packets/login/mod.rs old mode 100644 new mode 100755 index 4d490d08..ab68518c --- a/azalea-protocol/src/packets/login/mod.rs +++ b/azalea-protocol/src/packets/login/mod.rs @@ -2,78 +2,22 @@ pub mod clientbound_custom_query_packet; pub mod clientbound_game_profile_packet; pub mod clientbound_hello_packet; pub mod clientbound_login_compression_packet; +pub mod clientbound_login_disconnect_packet; pub mod serverbound_hello_packet; -use super::ProtocolPacket; -use crate::connect::PacketFlow; -use async_trait::async_trait; +use packet_macros::declare_state_packets; -#[derive(Clone, Debug)] -pub enum LoginPacket -where - Self: Sized, -{ - ClientboundCustomQueryPacket(clientbound_custom_query_packet::ClientboundCustomQueryPacket), - ClientboundGameProfilePacket(clientbound_game_profile_packet::ClientboundGameProfilePacket), - ClientboundHelloPacket(clientbound_hello_packet::ClientboundHelloPacket), - ClientboundLoginCompressionPacket( - clientbound_login_compression_packet::ClientboundLoginCompressionPacket, - ), - ServerboundHelloPacket(serverbound_hello_packet::ServerboundHelloPacket), -} - -#[async_trait] -impl ProtocolPacket for LoginPacket { - fn id(&self) -> u32 { - match self { - LoginPacket::ClientboundCustomQueryPacket(_packet) => 0x04, - LoginPacket::ClientboundGameProfilePacket(_packet) => 0x02, - LoginPacket::ClientboundHelloPacket(_packet) => 0x01, - LoginPacket::ClientboundLoginCompressionPacket(_packet) => 0x03, - LoginPacket::ServerboundHelloPacket(_packet) => 0x00, - } +declare_state_packets!( + LoginPacket, + Serverbound => { + 0x00: serverbound_hello_packet::ServerboundHelloPacket, + }, + Clientbound => { + // 0x00: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, + 26: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, + 0x01: clientbound_hello_packet::ClientboundHelloPacket, + 0x02: clientbound_game_profile_packet::ClientboundGameProfilePacket, + 0x03: clientbound_login_compression_packet::ClientboundLoginCompressionPacket, + 0x04: clientbound_custom_query_packet::ClientboundCustomQueryPacket, } - - fn write(&self, buf: &mut Vec) { - match self { - LoginPacket::ClientboundCustomQueryPacket(packet) => packet.write(buf), - LoginPacket::ClientboundGameProfilePacket(packet) => packet.write(buf), - LoginPacket::ClientboundHelloPacket(packet) => packet.write(buf), - LoginPacket::ClientboundLoginCompressionPacket(packet) => packet.write(buf), - LoginPacket::ServerboundHelloPacket(packet) => packet.write(buf), - } - } - - /// Read a packet by its id, ConnectionProtocol, and flow - async fn read( - id: u32, - flow: &PacketFlow, - buf: &mut T, - ) -> Result - where - Self: Sized, - { - Ok(match flow { - PacketFlow::ServerToClient => match id { - 0x01 => clientbound_hello_packet::ClientboundHelloPacket::read(buf).await?, - 0x02 => { - clientbound_game_profile_packet::ClientboundGameProfilePacket::read(buf).await? - } - 0x04 => { - clientbound_custom_query_packet::ClientboundCustomQueryPacket::read(buf).await? - } - 0x03 => { - clientbound_login_compression_packet::ClientboundLoginCompressionPacket::read( - buf, - ) - .await? - } - _ => return Err(format!("Unknown ServerToClient login packet id: {}", id)), - }, - PacketFlow::ClientToServer => match id { - 0x00 => serverbound_hello_packet::ServerboundHelloPacket::read(buf).await?, - _ => return Err(format!("Unknown ClientToServer login packet id: {}", id)), - }, - }) - } -} +); diff --git a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs old mode 100644 new mode 100755 index 0039cbce..a72480f2 --- a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs +++ b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs @@ -14,8 +14,9 @@ impl ServerboundHelloPacket { LoginPacket::ServerboundHelloPacket(self) } - pub fn write(&self, buf: &mut Vec) { + pub fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error> { buf.write_utf(&self.username).unwrap(); + Ok(()) } pub async fn read( diff --git a/azalea-protocol/src/packets/mod.rs b/azalea-protocol/src/packets/mod.rs old mode 100644 new mode 100755 index e065b65c..98741a75 --- a/azalea-protocol/src/packets/mod.rs +++ b/azalea-protocol/src/packets/mod.rs @@ -3,13 +3,18 @@ pub mod handshake; pub mod login; pub mod status; +use crate::{ + connect::PacketFlow, + mc_buf::{McBufReadable, McBufWritable, Readable, Writable}, +}; use async_trait::async_trait; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use tokio::io::AsyncRead; -use crate::connect::PacketFlow; +pub const PROTOCOL_VERSION: u32 = 758; -pub const PROTOCOL_VERSION: u32 = 757; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive)] pub enum ConnectionProtocol { Handshake = -1, Game = 0, @@ -42,5 +47,22 @@ where where Self: Sized; - fn write(&self, buf: &mut Vec); + fn write(&self, buf: &mut Vec) -> Result<(), std::io::Error>; +} + +#[async_trait] +impl McBufReadable for ConnectionProtocol { + async fn read_into(buf: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + ConnectionProtocol::from_i32(buf.read_varint().await?) + .ok_or_else(|| "Invalid intention".to_string()) + } +} + +impl McBufWritable for ConnectionProtocol { + fn write_into(&self, buf: &mut Vec) -> Result<(), std::io::Error> { + buf.write_varint(*self as i32) + } } diff --git a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs old mode 100644 new mode 100755 index 38270ad1..884cf408 --- a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs +++ b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs @@ -36,10 +36,12 @@ pub struct ClientboundStatusResponsePacket { impl ClientboundStatusResponsePacket { pub fn get(self) -> StatusPacket { - StatusPacket::ClientboundStatusResponsePacket(Box::new(self)) + StatusPacket::ClientboundStatusResponsePacket(self) } - pub fn write(&self, _buf: &mut Vec) {} + pub fn write(&self, _buf: &mut Vec) -> Result<(), std::io::Error> { + Ok(()) + } pub async fn read( buf: &mut T, diff --git a/azalea-protocol/src/packets/status/mod.rs b/azalea-protocol/src/packets/status/mod.rs old mode 100644 new mode 100755 index 6383bae8..56aa577e --- a/azalea-protocol/src/packets/status/mod.rs +++ b/azalea-protocol/src/packets/status/mod.rs @@ -1,65 +1,14 @@ pub mod clientbound_status_response_packet; pub mod serverbound_status_request_packet; -use async_trait::async_trait; +use packet_macros::declare_state_packets; -use crate::connect::PacketFlow; - -use super::ProtocolPacket; - -#[derive(Clone, Debug)] -pub enum StatusPacket -where - Self: Sized, -{ - ServerboundStatusRequestPacket( - serverbound_status_request_packet::ServerboundStatusRequestPacket, - ), - ClientboundStatusResponsePacket( - Box, - ), -} - -#[async_trait] -impl ProtocolPacket for StatusPacket { - fn id(&self) -> u32 { - match self { - StatusPacket::ServerboundStatusRequestPacket(_packet) => 0x00, - StatusPacket::ClientboundStatusResponsePacket(_packet) => 0x00, - } +declare_state_packets!( + StatusPacket, + Serverbound => { + 0x00: serverbound_status_request_packet::ServerboundStatusRequestPacket, + }, + Clientbound => { + 0x00: clientbound_status_response_packet::ClientboundStatusResponsePacket, } - - fn write(&self, buf: &mut Vec) { - match self { - StatusPacket::ServerboundStatusRequestPacket(packet) => packet.write(buf), - StatusPacket::ClientboundStatusResponsePacket(packet) => packet.write(buf), - } - } - - /// Read a packet by its id, ConnectionProtocol, and flow - async fn read( - id: u32, - flow: &PacketFlow, - buf: &mut T, - ) -> Result - where - Self: Sized, - { - match flow { - PacketFlow::ServerToClient => match id { - 0x00 => Ok( - clientbound_status_response_packet::ClientboundStatusResponsePacket::read(buf) - .await?, - ), - _ => Err(format!("Unknown ServerToClient status packet id: {}", id)), - }, - PacketFlow::ClientToServer => match id { - 0x00 => Ok( - serverbound_status_request_packet::ServerboundStatusRequestPacket::read(buf) - .await?, - ), - _ => Err(format!("Unknown ClientToServer status packet id: {}", id)), - }, - } - } -} +); diff --git a/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs b/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs old mode 100644 new mode 100755 index 3a25ac42..e77687ec --- a/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs +++ b/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs @@ -1,22 +1,5 @@ +use packet_macros::StatusPacket; use std::hash::Hash; -use super::StatusPacket; - -#[derive(Hash, Clone, Debug)] +#[derive(Clone, Debug, StatusPacket)] pub struct ServerboundStatusRequestPacket {} - -impl ServerboundStatusRequestPacket { - pub fn get(self) -> StatusPacket { - StatusPacket::ServerboundStatusRequestPacket(self) - } - - pub fn write(&self, _buf: &mut Vec) { - panic!("ServerboundStatusRequestPacket::write not implemented") - } - - pub async fn read( - _buf: &mut T, - ) -> Result { - Err("ServerboundStatusRequestPacket::read not implemented".to_string()) - } -} diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs old mode 100644 new mode 100755 diff --git a/azalea-protocol/src/resolver.rs b/azalea-protocol/src/resolver.rs old mode 100644 new mode 100755 diff --git a/azalea-protocol/src/write.rs b/azalea-protocol/src/write.rs old mode 100644 new mode 100755 index 3898e74a..821ed6e8 --- a/azalea-protocol/src/write.rs +++ b/azalea-protocol/src/write.rs @@ -17,7 +17,7 @@ fn packet_encoder(packet: &P) -> Result MAXIMUM_UNCOMPRESSED_LENGTH as usize { return Err(format!( "Packet too big (is {} bytes, should be less than {}): {:?}", diff --git a/bot/Cargo.toml b/bot/Cargo.toml old mode 100644 new mode 100755 diff --git a/bot/src/main.rs b/bot/src/main.rs index 435803e2..7d129478 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,12 +1,15 @@ -use azalea_client::connect::join_server; - #[tokio::main] async fn main() { println!("Hello, world!"); // let address = "95.111.249.143:10000"; - let address = "localhost:58566"; - let _response = join_server(&address.try_into().unwrap()).await.unwrap(); + let address = "localhost:52400"; + // let response = azalea_client::ping::ping_server(&address.try_into().unwrap()) + // .await + // .unwrap(); // println!("{}", response.description.to_ansi(None)); + let _response = azalea_client::connect::join_server(&address.try_into().unwrap()) + .await + .unwrap(); println!("connected"); }