Initial Commit
This commit is contained in:
commit
257c64b705
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,6 @@
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"minecraft-protocol",
|
||||||
|
"minecraft-protocol-derive",
|
||||||
|
"minecraft-bot-framework"
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "minecraft-bot-framework"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = "0.1"
|
||||||
|
base64 = "0.13.0"
|
||||||
|
hex = "0.4.3"
|
||||||
|
minecraft-protocol = { path = "../minecraft-protocol", features = [ "tokio-support" ] }
|
||||||
|
openssl = "0.10"
|
||||||
|
rand = "0.8.4"
|
||||||
|
reqwest = "0.11"
|
||||||
|
thiserror = "1.0"
|
||||||
|
tokio = { version = "1.16", features = ["full"] }
|
||||||
|
tracing = "0.1"
|
|
@ -0,0 +1,16 @@
|
||||||
|
use minecraft_bot_framework::{Auth, Bot};
|
||||||
|
|
||||||
|
struct EventListener;
|
||||||
|
|
||||||
|
impl minecraft_bot_framework::EventListener for EventListener {}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let mut bot = Bot::new(EventListener);
|
||||||
|
bot.connect("home.dyn.oupsman.fr", 25565, Auth{
|
||||||
|
username: "AymDroid",
|
||||||
|
uuid: "583204e9426447c79bbeebc42f4c0224",
|
||||||
|
token: "eyJhbGciOiJIUzI1NiJ9.eyJ4dWlkIjoiMjUzNTQ2NzA1NzEzNjgxMCIsImFnZyI6IkFkdWx0Iiwic3ViIjoiYzRjZTgwMDItMTI2Ny00MmQ2LTgyMjYtNGVkYjIwZmJhN2E2IiwibmJmIjoxNjQwMDgyNDQ4LCJhdXRoIjoiWEJPWCIsInJvbGVzIjpbXSwiaXNzIjoiYXV0aGVudGljYXRpb24iLCJleHAiOjE2NDAxNjg4NDgsImlhdCI6MTY0MDA4MjQ0OCwicGxhdGZvcm0iOiJQQ19MQVVOQ0hFUiIsInl1aWQiOiIyZjkwNjI0ZGUwZjY0ZDQ5OTExYTU3ODYyNThmZmYyYyJ9.ZetvhYW7gEmk28jTM1P7813RP7qdwjeuMXey5gS1vIM"
|
||||||
|
}).await.unwrap();
|
||||||
|
bot.run().await.unwrap();
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
use std::{fmt::Debug, io::Cursor};
|
||||||
|
|
||||||
|
use minecraft_protocol::{
|
||||||
|
types::VarInt,
|
||||||
|
v1_18::{clientbound::EncryptionRequest, serverbound::Handshake, PROTOCOL_VERSION},
|
||||||
|
AsyncPacketContent, PacketContent,
|
||||||
|
};
|
||||||
|
use openssl::sha::Sha1;
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||||
|
net::TcpStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use minecraft_protocol as protocol;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("IO Error")]
|
||||||
|
Disconnect(#[from] std::io::Error),
|
||||||
|
#[error("Protocol Error")]
|
||||||
|
ProtocolError(#[from] minecraft_protocol::Error),
|
||||||
|
#[error("Request Error")]
|
||||||
|
RequestError(#[from] reqwest::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
pub struct Auth<'s> {
|
||||||
|
pub username: &'s str,
|
||||||
|
pub uuid: &'s str,
|
||||||
|
pub token: &'s str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Bot<E>
|
||||||
|
where
|
||||||
|
E: EventListener,
|
||||||
|
{
|
||||||
|
event_listener: E,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> Bot<E>
|
||||||
|
where
|
||||||
|
E: EventListener,
|
||||||
|
{
|
||||||
|
pub fn new(event_listener: E) -> Self {
|
||||||
|
Self {
|
||||||
|
event_listener: event_listener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect(&mut self, address: &str, port: u16, auth: Auth<'_>) -> Result<()> {
|
||||||
|
let mut stream = TcpStream::connect(format!("{}:{}", address, port)).await?;
|
||||||
|
let packet = Packet {
|
||||||
|
packet_id: VarInt(0x00),
|
||||||
|
content: Handshake {
|
||||||
|
protocol_version: VarInt(PROTOCOL_VERSION),
|
||||||
|
server_address: String::from(address),
|
||||||
|
server_port: port,
|
||||||
|
next_state: VarInt(0x02),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
packet.write_to(&mut stream).await?;
|
||||||
|
|
||||||
|
let packet = Packet {
|
||||||
|
packet_id: VarInt(0x00),
|
||||||
|
content: auth.username.to_string(),
|
||||||
|
};
|
||||||
|
packet.write_to(&mut stream).await?;
|
||||||
|
|
||||||
|
let res: Packet<EncryptionRequest> = Packet::read_from(&mut stream).await?;
|
||||||
|
|
||||||
|
self.setup_encryption(res.content, &auth).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup_encryption(
|
||||||
|
&mut self,
|
||||||
|
request: EncryptionRequest,
|
||||||
|
auth: &Auth<'_>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let secret: [u8; 16] = rand::random();
|
||||||
|
let server_hash = calc_hash(&secret, &request);
|
||||||
|
println!("{}", server_hash);
|
||||||
|
println!(
|
||||||
|
r#"{{"accessToken": "{}","selectedProfile": {{"id": "{}", "name": "{}"}},"serverId": "{}"}}"#,
|
||||||
|
auth.token,
|
||||||
|
auth.uuid.replace('-', ""),
|
||||||
|
auth.username,
|
||||||
|
server_hash
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = reqwest::Client::new()
|
||||||
|
.post("https://sessionserver.mojang.com/session/minecraft/join")
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.header("user-agent", "minecraft-bot")
|
||||||
|
.body(format!(
|
||||||
|
r#"{{"accessToken": "{}","selectedProfile": {{"id": "{}", "name": "{}"}},"serverId": "{}"}}"#,
|
||||||
|
auth.token,
|
||||||
|
auth.uuid.replace('-', ""),
|
||||||
|
auth.username,
|
||||||
|
server_hash
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("{}", res);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_hash(secret: &[u8], request: &EncryptionRequest) -> String {
|
||||||
|
let mut sha1 = Sha1::new();
|
||||||
|
sha1.update(request.server_id.as_bytes());
|
||||||
|
sha1.update(secret);
|
||||||
|
sha1.update(&request.public_key);
|
||||||
|
let mut hex = sha1.finish();
|
||||||
|
|
||||||
|
let negative = (hex[0] & 0x80) == 0x80;
|
||||||
|
|
||||||
|
if negative {
|
||||||
|
two_complement(&mut hex);
|
||||||
|
|
||||||
|
format!("-{}", hex::encode(hex).trim_start_matches('0'))
|
||||||
|
} else {
|
||||||
|
hex::encode(hex).trim_start_matches('0').to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn two_complement(bytes: &mut [u8]) {
|
||||||
|
let mut carry = true;
|
||||||
|
for i in (0..bytes.len()).rev() {
|
||||||
|
bytes[i] = !bytes[i] & 0xff;
|
||||||
|
if carry {
|
||||||
|
carry = bytes[i] == 0xff;
|
||||||
|
bytes[i] = bytes[i] + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait EventListener: Send + Sync {}
|
||||||
|
|
||||||
|
struct Packet<T>
|
||||||
|
where
|
||||||
|
T: PacketContent,
|
||||||
|
{
|
||||||
|
packet_id: VarInt,
|
||||||
|
content: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Debug for Packet<T>
|
||||||
|
where
|
||||||
|
T: PacketContent + Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Packet")
|
||||||
|
.field("packet_id", &self.packet_id)
|
||||||
|
.field("content", &self.content)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Packet<T>
|
||||||
|
where
|
||||||
|
T: PacketContent,
|
||||||
|
{
|
||||||
|
pub async fn read_from<R>(reader: &mut R) -> Result<Self>
|
||||||
|
where
|
||||||
|
R: AsyncRead + std::marker::Unpin,
|
||||||
|
{
|
||||||
|
let mut packet = Vec::new();
|
||||||
|
let mut buffer = [0u8; 8092];
|
||||||
|
|
||||||
|
let size = reader.read(&mut buffer).await.unwrap();
|
||||||
|
|
||||||
|
let packet_size = {
|
||||||
|
let mut cursor = Cursor::new(&buffer);
|
||||||
|
let packet_size = VarInt::read_from(&mut cursor).unwrap();
|
||||||
|
packet.extend_from_slice(&buffer[cursor.position() as usize..size]);
|
||||||
|
packet_size
|
||||||
|
};
|
||||||
|
|
||||||
|
while packet.len() < packet_size.0 as usize {
|
||||||
|
let size = reader.read(&mut buffer).await.unwrap();
|
||||||
|
packet.extend_from_slice(&buffer[0..size]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = Cursor::new(&packet);
|
||||||
|
let packet_id = VarInt::read_from(&mut cursor).unwrap();
|
||||||
|
|
||||||
|
let content = T::read_from(&mut cursor)?;
|
||||||
|
|
||||||
|
Ok(Packet { packet_id, content })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_to<W>(&self, writer: &mut W) -> Result<()>
|
||||||
|
where
|
||||||
|
W: AsyncWrite + std::marker::Unpin + std::marker::Send,
|
||||||
|
{
|
||||||
|
let mut write_buffer = Cursor::new(Vec::new());
|
||||||
|
self.packet_id.write_to(&mut write_buffer)?;
|
||||||
|
self.content.write_to(&mut write_buffer)?;
|
||||||
|
|
||||||
|
let write_buffer = write_buffer.get_ref();
|
||||||
|
|
||||||
|
VarInt(write_buffer.len() as i32)
|
||||||
|
.async_write_to(writer)
|
||||||
|
.await?;
|
||||||
|
writer.write_all(write_buffer).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "minecraft-protocol-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1.0.10"
|
||||||
|
syn = { version = "1.0.82", features = ["extra-traits"] }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
|
@ -0,0 +1,55 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput};
|
||||||
|
|
||||||
|
#[proc_macro_derive(PacketContent)]
|
||||||
|
pub fn derive_packet_content(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
|
let fields = match input.data {
|
||||||
|
Data::Struct(st) => st.fields,
|
||||||
|
_ => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let parse_all = fields.iter().map(|f| {
|
||||||
|
let name = &f.ident;
|
||||||
|
let ty = &f.ty;
|
||||||
|
quote_spanned! { f.span() =>
|
||||||
|
#name : <#ty as minecraft_protocol::PacketContent>::read_from(reader)?,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let write_all = fields.iter().map(|f| {
|
||||||
|
let name = &f.ident;
|
||||||
|
let ty = &f.ty;
|
||||||
|
quote_spanned! { f.span() =>
|
||||||
|
<#ty as minecraft_protocol::PacketContent>::write_to(&self.#name, writer)?;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let name = &input.ident;
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl minecraft_protocol::PacketContent for #name {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, minecraft_protocol::Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read {
|
||||||
|
Ok(Self {
|
||||||
|
#(#parse_all)*
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), minecraft_protocol::Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write{
|
||||||
|
#(#write_all)*
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "minecraft-protocol"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
tokio-support = ["tokio", "async-trait"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = "1.0"
|
||||||
|
byteorder = "1"
|
||||||
|
minecraft-protocol-derive = { path = "../minecraft-protocol-derive" }
|
||||||
|
tokio = { version = "1.16", optional = true }
|
||||||
|
async-trait = { version = "0.1", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.9.0"
|
||||||
|
serde_json = "1.0.73"
|
|
@ -0,0 +1,169 @@
|
||||||
|
use std::{env, io::Cursor, net::TcpStream};
|
||||||
|
|
||||||
|
use minecraft_protocol::{types::VarInt, v1_18::{serverbound::Handshake, PROTOCOL_VERSION}, PacketContent};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
struct Packet<T>
|
||||||
|
where
|
||||||
|
T: PacketContent,
|
||||||
|
{
|
||||||
|
packet_id: VarInt,
|
||||||
|
content: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Packet<T>
|
||||||
|
where
|
||||||
|
T: PacketContent,
|
||||||
|
{
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, minecraft_protocol::Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
let mut packet = Vec::new();
|
||||||
|
let mut buffer = [0u8; 8092];
|
||||||
|
|
||||||
|
let size = reader.read(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
let packet_size = {
|
||||||
|
let mut cursor = Cursor::new(&buffer);
|
||||||
|
let packet_size = VarInt::read_from(&mut cursor).unwrap();
|
||||||
|
packet.extend_from_slice(&buffer[cursor.position() as usize..size]);
|
||||||
|
packet_size
|
||||||
|
};
|
||||||
|
|
||||||
|
while packet.len() < packet_size.0 as usize {
|
||||||
|
let size = reader.read(&mut buffer).unwrap();
|
||||||
|
packet.extend_from_slice(&buffer[0..size]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = Cursor::new(&packet);
|
||||||
|
let packet_id = VarInt::read_from(&mut cursor).unwrap();
|
||||||
|
|
||||||
|
let content = T::read_from(&mut cursor)?;
|
||||||
|
|
||||||
|
Ok(Packet { packet_id, content })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), minecraft_protocol::Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
let mut write_buffer = Cursor::new(Vec::new());
|
||||||
|
self.packet_id.write_to(&mut write_buffer)?;
|
||||||
|
self.content.write_to(&mut write_buffer)?;
|
||||||
|
|
||||||
|
let write_buffer = write_buffer.get_ref();
|
||||||
|
|
||||||
|
VarInt(write_buffer.len() as i32).write_to(writer)?;
|
||||||
|
writer.write_all(write_buffer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||||
|
let mut args = env::args().skip(1);
|
||||||
|
|
||||||
|
let address = if let Some(ad) = args.next() {
|
||||||
|
ad
|
||||||
|
} else {
|
||||||
|
panic!("missing server address")
|
||||||
|
};
|
||||||
|
|
||||||
|
let port = if let Some(port) = args.next() {
|
||||||
|
port.parse::<u16>().expect("failed to get port")
|
||||||
|
} else {
|
||||||
|
25565
|
||||||
|
};
|
||||||
|
|
||||||
|
ping(&address, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ping(address: &str, port: u16) {
|
||||||
|
let mut stream = TcpStream::connect(format!("{}:{}", address, port)).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
debug!("writting ...");
|
||||||
|
|
||||||
|
let packet = Packet {
|
||||||
|
packet_id: VarInt(0x00),
|
||||||
|
content: Handshake {
|
||||||
|
protocol_version: VarInt(PROTOCOL_VERSION),
|
||||||
|
server_address: String::from(address),
|
||||||
|
server_port: port,
|
||||||
|
next_state: VarInt(1),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
packet.write_to(&mut stream).unwrap();
|
||||||
|
|
||||||
|
let packet = Packet {
|
||||||
|
packet_id: VarInt(0x00),
|
||||||
|
content: (),
|
||||||
|
};
|
||||||
|
packet.write_to(&mut stream).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let ping_value: serde_json::Value = {
|
||||||
|
debug!("reading ...");
|
||||||
|
|
||||||
|
let p: Packet<String> = Packet::read_from(&mut stream).unwrap();
|
||||||
|
log::trace!("{}", p.content);
|
||||||
|
|
||||||
|
serde_json::from_str(&p.content).expect("failed to decode as json")
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("{:#?}", ping_value);
|
||||||
|
|
||||||
|
println!("result for {}:{}", address, port);
|
||||||
|
let ping_value = ping_value.as_object().unwrap();
|
||||||
|
if let Some(version) = ping_value.get("version").and_then(|s| s.as_object()) {
|
||||||
|
println!(
|
||||||
|
"\tversion: {} ({})",
|
||||||
|
version
|
||||||
|
.get("name")
|
||||||
|
.and_then(|m| m.as_str())
|
||||||
|
.expect("failed to get version name"),
|
||||||
|
version
|
||||||
|
.get("protocol")
|
||||||
|
.and_then(|m| m.as_i64())
|
||||||
|
.expect("failed to get protocol")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(description) = ping_value.get("description") {
|
||||||
|
println!(
|
||||||
|
"\tdescription : {}",
|
||||||
|
description.get("text").unwrap().as_str().unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(players) = ping_value.get("players").and_then(|o| o.as_object()) {
|
||||||
|
println!("\tplayers");
|
||||||
|
println!(
|
||||||
|
"\t\tnumbers : {}/{}",
|
||||||
|
players
|
||||||
|
.get("online")
|
||||||
|
.and_then(|m| m.as_i64())
|
||||||
|
.expect("failed to get online players count"),
|
||||||
|
players
|
||||||
|
.get("max")
|
||||||
|
.and_then(|m| m.as_i64())
|
||||||
|
.expect("failed to get max player")
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(sample) = players.get("sample").and_then(|s| s.as_array()) {
|
||||||
|
println!("\t\tsample:");
|
||||||
|
for player in sample {
|
||||||
|
println!(
|
||||||
|
"\t\t\t- {}",
|
||||||
|
player
|
||||||
|
.get("name")
|
||||||
|
.and_then(|m| m.as_str())
|
||||||
|
.expect("failed to get name of player")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("`{0}`")]
|
||||||
|
IOError(#[from] std::io::Error),
|
||||||
|
#[error("failed to get `{0}`")]
|
||||||
|
MissingValue(&'static str),
|
||||||
|
#[error("data must be {0} bytes long less")]
|
||||||
|
DataTooLong(u8),
|
||||||
|
#[error("Failed to decode string")]
|
||||||
|
FromUtf8Error(#[from] std::string::FromUtf8Error),
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
mod error;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub use error::Error;
|
||||||
|
pub mod derive {
|
||||||
|
pub use minecraft_protocol_derive::PacketContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod v1_18;
|
||||||
|
|
||||||
|
pub trait PacketContent: Sized {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read;
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait AsyncPacketContent: Sized {
|
||||||
|
async fn async_read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send;
|
||||||
|
|
||||||
|
async fn async_write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: tokio::io::AsyncWrite + std::marker::Unpin + std::marker::Send;
|
||||||
|
}
|
|
@ -0,0 +1,477 @@
|
||||||
|
//! Contain definitions for all minecraft protocol primitive data types.\
|
||||||
|
//! See [https://wiki.vg/Protocol#Data_types](wiki.vg)
|
||||||
|
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
|
use crate::{AsyncPacketContent, Error, PacketContent};
|
||||||
|
|
||||||
|
/// Either false or true
|
||||||
|
pub type Boolean = bool;
|
||||||
|
|
||||||
|
impl PacketContent for Boolean {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
match reader.read_u8()? {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
value => {
|
||||||
|
panic!("unkown value : {}", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_i8(if *self { 1 } else { 0 })?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An integer between -128 and 127
|
||||||
|
pub type Byte = i8;
|
||||||
|
|
||||||
|
impl PacketContent for Byte {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(reader.read_i8()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_i8(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An integer between 0 and 255
|
||||||
|
pub type UnsignedByte = u8;
|
||||||
|
|
||||||
|
impl PacketContent for UnsignedByte {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(reader.read_u8()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_u8(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An integer between -32768 and 32767
|
||||||
|
pub type Short = i16;
|
||||||
|
|
||||||
|
impl PacketContent for Short {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(reader.read_i16::<BigEndian>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_i16::<BigEndian>(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An integer between 0 and 65535
|
||||||
|
pub type UnsignedShort = u16;
|
||||||
|
|
||||||
|
impl PacketContent for UnsignedShort {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(reader.read_u16::<BigEndian>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_u16::<BigEndian>(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An integer between -2147483648 and 2147483647
|
||||||
|
pub type Int = i32;
|
||||||
|
|
||||||
|
impl PacketContent for Int {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(reader.read_i32::<BigEndian>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_i32::<BigEndian>(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An integer between -9223372036854775808 and 9223372036854775807
|
||||||
|
pub type Long = i64;
|
||||||
|
|
||||||
|
impl PacketContent for Long {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(reader.read_i64::<BigEndian>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_i64::<BigEndian>(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single-precision [32-bit IEEE 754 floating point number](https://en.wikipedia.org/wiki/Single-precision_floating-point_format)
|
||||||
|
pub type Float = f32;
|
||||||
|
|
||||||
|
impl PacketContent for Float {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(reader.read_f32::<BigEndian>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_f32::<BigEndian>(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A double-precision [64-bit IEEE 754 floating point number](https://en.wikipedia.org/wiki/Double-precision_floating-point_format)
|
||||||
|
pub type Double = f64;
|
||||||
|
|
||||||
|
impl PacketContent for Double {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(reader.read_f64::<BigEndian>()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
writer.write_f64::<BigEndian>(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketContent for () {
|
||||||
|
fn read_from<R>(_: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, _: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VarInt(pub i32);
|
||||||
|
|
||||||
|
impl PacketContent for VarInt {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
let mut value = 0;
|
||||||
|
let mut length = 0;
|
||||||
|
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
reader.read_exact(&mut buf)?;
|
||||||
|
|
||||||
|
value |= ((buf[0] as i32) & 0x7F) << (length * 7);
|
||||||
|
|
||||||
|
length += 1;
|
||||||
|
|
||||||
|
if length > 5 {
|
||||||
|
return Err(Error::DataTooLong(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf[0] & 0x80) != 0x80 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(VarInt(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
let mut value = self.0;
|
||||||
|
loop {
|
||||||
|
if (value & !0x7F) == 0 {
|
||||||
|
writer.write_u8(value as u8)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u8(((value & 0x7F) | 0x80) as u8)?;
|
||||||
|
|
||||||
|
// TODO BETTER WAY ?
|
||||||
|
value = unsafe { std::mem::transmute(std::mem::transmute::<i32, u32>(value) >> 7) };
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tokio-support")]
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl AsyncPacketContent for VarInt {
|
||||||
|
async fn async_read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send,
|
||||||
|
{
|
||||||
|
let mut value = 0;
|
||||||
|
let mut length = 0;
|
||||||
|
|
||||||
|
let mut buf = [0u8; 1];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
reader.read_exact(&mut buf).await.unwrap(); // TODO
|
||||||
|
|
||||||
|
value |= ((buf[0] as i32) & 0x7F) << (length * 7);
|
||||||
|
|
||||||
|
length += 1;
|
||||||
|
|
||||||
|
if length > 5 {
|
||||||
|
return Err(Error::DataTooLong(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf[0] & 0x80) != 0x80 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(VarInt(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn async_write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: tokio::io::AsyncWrite + std::marker::Unpin + std::marker::Send,
|
||||||
|
{
|
||||||
|
let mut value = self.0;
|
||||||
|
loop {
|
||||||
|
if (value & !0x7F) == 0 {
|
||||||
|
writer.write_u8(value as u8).await?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_u8(((value & 0x7F) | 0x80) as u8).await?;
|
||||||
|
|
||||||
|
// TODO BETTER WAY ?
|
||||||
|
value = unsafe { std::mem::transmute(std::mem::transmute::<i32, u32>(value) >> 7) };
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VarLong(pub i64);
|
||||||
|
|
||||||
|
impl PacketContent for VarLong {
|
||||||
|
fn read_from<R>(_reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, _writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sequence of [Unicode scalar values](https://unicode.org/glossary/#unicode_scalar_value)
|
||||||
|
pub type String = std::string::String;
|
||||||
|
|
||||||
|
impl PacketContent for String {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
let size = VarInt::read_from(reader)?;
|
||||||
|
|
||||||
|
let mut string_reader = reader.take(size.0 as u64);
|
||||||
|
|
||||||
|
let mut buf = Vec::with_capacity(size.0 as usize);
|
||||||
|
|
||||||
|
string_reader.read_to_end(&mut buf)?;
|
||||||
|
|
||||||
|
Ok(String::from_utf8(buf)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
let buf = self.as_bytes();
|
||||||
|
|
||||||
|
VarInt(buf.len() as i32).write_to(writer)?;
|
||||||
|
|
||||||
|
writer.write_all(buf)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
pub type ByteArray = Vec<u8>;
|
||||||
|
|
||||||
|
impl PacketContent for ByteArray {
|
||||||
|
fn read_from<R>(reader: &mut R) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
R: std::io::Read,
|
||||||
|
{
|
||||||
|
let size = VarInt::read_from(reader)?;
|
||||||
|
|
||||||
|
let mut string_reader = reader.take(size.0 as u64);
|
||||||
|
|
||||||
|
let mut buf = Vec::with_capacity(size.0 as usize);
|
||||||
|
|
||||||
|
string_reader.read_to_end(&mut buf)?;
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: std::io::Write,
|
||||||
|
{
|
||||||
|
|
||||||
|
VarInt(self.len() as i32).write_to(writer)?;
|
||||||
|
|
||||||
|
writer.write_all(self)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use crate::{types::VarInt, PacketContent};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_varint() {
|
||||||
|
assert_eq!(VarInt::read_from(&mut Cursor::new([0x00])).unwrap().0, 0);
|
||||||
|
assert_eq!(VarInt::read_from(&mut Cursor::new([0x01])).unwrap().0, 1);
|
||||||
|
assert_eq!(VarInt::read_from(&mut Cursor::new([0x02])).unwrap().0, 2);
|
||||||
|
assert_eq!(VarInt::read_from(&mut Cursor::new([0x7f])).unwrap().0, 127);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
VarInt::read_from(&mut Cursor::new([0x80, 0x01])).unwrap().0,
|
||||||
|
128
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
VarInt::read_from(&mut Cursor::new([0xff, 0x01])).unwrap().0,
|
||||||
|
255
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
VarInt::read_from(&mut Cursor::new([0xdd, 0xc7, 0x01]))
|
||||||
|
.unwrap()
|
||||||
|
.0,
|
||||||
|
25565
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
VarInt::read_from(&mut Cursor::new([0xff, 0xff, 0x7f]))
|
||||||
|
.unwrap()
|
||||||
|
.0,
|
||||||
|
2097151
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
VarInt::read_from(&mut Cursor::new([255, 255, 255, 255, 7]))
|
||||||
|
.unwrap()
|
||||||
|
.0,
|
||||||
|
2147483647
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
VarInt::read_from(&mut Cursor::new([0xff, 0xff, 0xff, 0xff, 0x0f]))
|
||||||
|
.unwrap()
|
||||||
|
.0,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
VarInt::read_from(&mut Cursor::new([128, 128, 128, 128, 8]))
|
||||||
|
.unwrap()
|
||||||
|
.0,
|
||||||
|
-2147483648
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_varint() {
|
||||||
|
let mut buffer = Cursor::new(vec![0u8; 3]);
|
||||||
|
VarInt(25565).write_to(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(buffer.get_ref(), &[0xdd, 0xc7, 0x01]);
|
||||||
|
|
||||||
|
let mut buffer = Cursor::new(vec![0u8; 5]);
|
||||||
|
VarInt(-2147483648).write_to(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(buffer.get_ref(), &[128, 128, 128, 128, 8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_varlong() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_varlong() {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
use crate as minecraft_protocol;
|
||||||
|
use crate::types::ByteArray;
|
||||||
|
use crate::{
|
||||||
|
derive::PacketContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// See [https://wiki.vg/Protocol#Encryption_Request](https://wiki.vg/Protocol#Encryption_Request)
|
||||||
|
#[derive(PacketContent, Debug)]
|
||||||
|
pub struct EncryptionRequest {
|
||||||
|
pub server_id: String,
|
||||||
|
pub public_key: ByteArray,
|
||||||
|
pub verify_token: ByteArray,
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod clientbound;
|
||||||
|
pub mod serverbound;
|
||||||
|
|
||||||
|
pub const PROTOCOL_VERSION : i32 = 757;
|
|
@ -0,0 +1,14 @@
|
||||||
|
use crate as minecraft_protocol;
|
||||||
|
use crate::{
|
||||||
|
derive::PacketContent,
|
||||||
|
types::{UnsignedShort, VarInt},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// See [https://wiki.vg/Protocol#Handshake](https://wiki.vg/Protocol#Handshake)
|
||||||
|
#[derive(PacketContent)]
|
||||||
|
pub struct Handshake {
|
||||||
|
pub protocol_version: VarInt,
|
||||||
|
pub server_address: String,
|
||||||
|
pub server_port: UnsignedShort,
|
||||||
|
pub next_state: VarInt,
|
||||||
|
}
|
Loading…
Reference in New Issue