1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 23:44:38 +00:00

start implementing joining servers

This commit is contained in:
mat 2021-12-13 00:07:21 -06:00
commit c96ae8fce4
19 changed files with 374 additions and 95 deletions

View file

@ -1,11 +1,11 @@
use minecraft_client::ping; use minecraft_client::{connect::join_server, ping::ping_server};
use minecraft_protocol::ServerAddress;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
async fn bot() { async fn bot() {
let address = ServerAddress::parse(&"mc.hypixel.net".to_string()).unwrap(); let address = "localhost:63425";
let response = ping::ping_server(&address).await.unwrap(); let response = join_server(&address.try_into().unwrap()).await.unwrap();
println!("{}", response.description.to_ansi(None)); // println!("{}", response.description.to_ansi(None));
println!("connected");
} }
fn main() { fn main() {

View file

@ -27,9 +27,7 @@ pub fn legacy_color_code_to_text_component(legacy_color_code: &str) -> TextCompo
if legacy_color_code.chars().nth(i).unwrap() == LEGACY_FORMATTING_CODE_SYMBOL { if legacy_color_code.chars().nth(i).unwrap() == LEGACY_FORMATTING_CODE_SYMBOL {
let formatting_code = legacy_color_code.chars().nth(i + 1).unwrap(); let formatting_code = legacy_color_code.chars().nth(i + 1).unwrap();
if let Ok(formatter) = ChatFormatting::from_code(formatting_code) { if let Ok(formatter) = ChatFormatting::from_code(formatting_code) {
if components.is_empty() { if components.is_empty() || !components.last().unwrap().text.is_empty() {
components.push(TextComponent::new("".to_string()));
} else if !components.last().unwrap().text.is_empty() {
components.push(TextComponent::new("".to_string())); components.push(TextComponent::new("".to_string()));
} }

View file

@ -0,0 +1,49 @@
///! Connect to Minecraft servers.
use minecraft_protocol::{
connect::Connection,
packets::{
handshake::client_intention_packet::ClientIntentionPacket,
login::serverbound_hello_packet::ServerboundHelloPacket,
status::clientbound_status_response_packet::ClientboundStatusResponsePacket,
ConnectionProtocol, Packet, PacketTrait, PROTOCOL_VERSION,
},
resolver, ServerAddress,
};
pub async fn join_server(address: &ServerAddress) -> Result<(), String> {
let username = "bot".to_string();
let resolved_address = resolver::resolve_address(address).await?;
let mut conn = Connection::new(&resolved_address).await?;
// handshake
conn.send_packet(
ClientIntentionPacket {
protocol_version: PROTOCOL_VERSION,
hostname: address.host.clone(),
port: address.port,
intention: ConnectionProtocol::Login,
}
.get(),
)
.await;
conn.switch_state(ConnectionProtocol::Login);
// login start
conn.send_packet(ServerboundHelloPacket { username }.get())
.await;
// encryption request
let packet = conn.read_packet().await.unwrap();
let encryption_request_packet = match packet {
Packet::ClientboundHelloPacket(p) => p,
_ => Err(format!("Invalid packet type: {:?}", packet))?,
};
// TODO: client auth
// TODO: encryption response
Ok(())
}

View file

@ -1,3 +1,6 @@
//! Significantly abstract minecraft-protocol so it's actually useable for bots.
pub mod connect;
pub mod ping; pub mod ping;
#[cfg(test)] #[cfg(test)]

View file

@ -0,0 +1,3 @@
trait PacketListener {
handle(Packet)
}

View file

@ -1,14 +1,13 @@
///! Ping Minecraft servers. ///! Ping Minecraft servers.
use minecraft_protocol::{ use minecraft_protocol::{
connection::Connection, connect::Connection,
packets::{ packets::{
handshake::client_intention_packet::ClientIntentionPacket, handshake::client_intention_packet::ClientIntentionPacket,
status::{ status::{
clientbound_status_response_packet::ClientboundStatusResponsePacket, clientbound_status_response_packet::ClientboundStatusResponsePacket,
serverbound_status_request_packet::ServerboundStatusRequestPacket, serverbound_status_request_packet::ServerboundStatusRequestPacket,
}, },
ConnectionProtocol, Packet, PacketTrait, ConnectionProtocol, Packet, PacketTrait, PROTOCOL_VERSION,
}, },
resolver, ServerAddress, resolver, ServerAddress,
}; };
@ -23,7 +22,7 @@ pub async fn ping_server(
// send the client intention packet and switch to the status state // send the client intention packet and switch to the status state
conn.send_packet( conn.send_packet(
ClientIntentionPacket { ClientIntentionPacket {
protocol_version: 757, protocol_version: PROTOCOL_VERSION,
hostname: address.host.clone(), hostname: address.host.clone(),
port: address.port, port: address.port,
intention: ConnectionProtocol::Status, intention: ConnectionProtocol::Status,

View file

@ -3,7 +3,7 @@
use std::net::IpAddr; use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
pub mod connection; pub mod connect;
pub mod mc_buf; pub mod mc_buf;
pub mod packets; pub mod packets;
pub mod resolver; pub mod resolver;
@ -20,9 +20,12 @@ pub struct ServerIpAddress {
pub port: u16, pub port: u16,
} }
impl ServerAddress { // impl try_from for ServerAddress
impl<'a> TryFrom<&'a str> for ServerAddress {
type Error = String;
/// Convert a Minecraft server address (host:port, the port is optional) to a ServerAddress /// Convert a Minecraft server address (host:port, the port is optional) to a ServerAddress
pub fn parse(string: &str) -> Result<ServerAddress, String> { fn try_from(string: &str) -> Result<Self, Self::Error> {
if string.is_empty() { if string.is_empty() {
return Err("Empty string".to_string()); return Err("Empty string".to_string());
} }

View file

@ -23,6 +23,17 @@ pub fn write_byte(buf: &mut Vec<u8>, n: u8) {
WriteBytesExt::write_u8(buf, n).unwrap(); WriteBytesExt::write_u8(buf, n).unwrap();
} }
pub async fn read_bytes<T: AsyncRead + std::marker::Unpin>(
buf: &mut BufReader<T>,
n: usize,
) -> Result<Vec<u8>, String> {
let mut bytes = vec![0; n];
match AsyncReadExt::read_exact(buf, &mut bytes).await {
Ok(_) => Ok(bytes),
Err(_) => Err("Error reading bytes".to_string()),
}
}
pub fn write_bytes(buf: &mut Vec<u8>, bytes: &[u8]) { pub fn write_bytes(buf: &mut Vec<u8>, bytes: &[u8]) {
buf.extend_from_slice(bytes); buf.extend_from_slice(bytes);
} }
@ -159,3 +170,15 @@ pub fn write_utf(buf: &mut Vec<u8>, string: &str) {
pub fn write_short(buf: &mut Vec<u8>, n: u16) { pub fn write_short(buf: &mut Vec<u8>, n: u16) {
WriteBytesExt::write_u16::<BigEndian>(buf, n).unwrap(); WriteBytesExt::write_u16::<BigEndian>(buf, n).unwrap();
} }
pub async fn read_byte_array<T: AsyncRead + std::marker::Unpin>(
buf: &mut BufReader<T>,
) -> Result<Vec<u8>, String> {
let length = read_varint(buf).await?.0 as usize;
Ok(read_bytes(buf, length).await?)
}
pub fn write_byte_array(buf: &mut Vec<u8>, bytes: &[u8]) {
write_varint(buf, bytes.len() as i32);
write_bytes(buf, bytes);
}

View file

@ -1 +1,2 @@
#[derive(Clone, Debug)]
pub enum GamePacket {}

View file

@ -1 +1,6 @@
pub mod client_intention_packet; pub mod client_intention_packet;
#[derive(Clone, Debug)]
pub enum HandshakePacket {
ClientIntentionPacket(client_intention_packet::ClientIntentionPacket),
}

View file

@ -0,0 +1,40 @@
use async_trait::async_trait;
use std::hash::Hash;
use tokio::io::BufReader;
use crate::{
mc_buf,
packets::{Packet, PacketTrait},
};
#[derive(Hash, Clone, Debug)]
pub struct ClientboundHelloPacket {
pub server_id: String,
pub public_key: Vec<u8>,
pub nonce: Vec<u8>,
}
#[async_trait]
impl PacketTrait for ClientboundHelloPacket {
fn get(self) -> Packet {
Packet::ClientboundHelloPacket(self)
}
fn write(&self, _buf: &mut Vec<u8>) {
panic!("ClientboundHelloPacket::write not implemented")
}
async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
buf: &mut BufReader<T>,
) -> Result<Packet, String> {
let server_id = mc_buf::read_utf_with_len(buf, 20).await?;
let public_key = mc_buf::read_byte_array(buf).await?;
let nonce = mc_buf::read_byte_array(buf).await?;
Ok(ClientboundHelloPacket {
server_id,
public_key,
nonce,
}
.get())
}
}

View file

@ -1 +1,8 @@
pub mod clientbound_hello_packet;
pub mod serverbound_hello_packet;
#[derive(Clone, Debug)]
pub enum LoginPacket {
ServerboundHelloPacket(serverbound_hello_packet::ServerboundHelloPacket),
ClientboundHelloPacket(clientbound_hello_packet::ClientboundHelloPacket),
}

View file

@ -0,0 +1,29 @@
use async_trait::async_trait;
use std::hash::Hash;
use tokio::io::BufReader;
use crate::{
mc_buf,
packets::{Packet, PacketTrait},
};
#[derive(Hash, Clone, Debug)]
pub struct ServerboundHelloPacket {
pub username: String,
}
#[async_trait]
impl PacketTrait for ServerboundHelloPacket {
fn get(self) -> Packet {
Packet::ServerboundHelloPacket(self)
}
fn write(&self, buf: &mut Vec<u8>) {
mc_buf::write_utf(buf, &self.username);
}
async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
_buf: &mut BufReader<T>,
) -> Result<Packet, String> {
Err("ServerboundHelloPacket::read not implemented".to_string())
}
}

View file

@ -6,7 +6,9 @@ pub mod status;
use async_trait::async_trait; use async_trait::async_trait;
use tokio::io::{AsyncRead, BufReader}; use tokio::io::{AsyncRead, BufReader};
use crate::connection::PacketFlow; use crate::connect::PacketFlow;
pub const PROTOCOL_VERSION: u32 = 757;
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConnectionProtocol { pub enum ConnectionProtocol {
@ -18,90 +20,123 @@ pub enum ConnectionProtocol {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Packet { pub enum Packet {
// game Game(game::GamePacket),
Handshake(handshake::HandshakePacket),
// handshake Login(login::LoginPacket),
ClientIntentionPacket(handshake::client_intention_packet::ClientIntentionPacket), Status(status::StatusPacket),
// login
// status
ServerboundStatusRequestPacket(
status::serverbound_status_request_packet::ServerboundStatusRequestPacket,
),
ClientboundStatusResponsePacket(
status::clientbound_status_response_packet::ClientboundStatusResponsePacket,
),
} }
// TODO: do all this with macros so it's less repetitive #[async_trait]
impl Packet { pub trait ProtocolPacket {
fn get_inner_packet(&self) -> &dyn PacketTrait { fn get_inner<P: PacketTrait>(&self) -> &P;
match self {
Packet::ClientIntentionPacket(packet) => packet,
Packet::ServerboundStatusRequestPacket(packet) => packet,
Packet::ClientboundStatusResponsePacket(packet) => packet,
}
}
pub fn id(&self) -> u32 { fn id(&self) -> u32;
match self {
Packet::ClientIntentionPacket(_packet) => 0x00,
Packet::ServerboundStatusRequestPacket(_packet) => 0x00,
Packet::ClientboundStatusResponsePacket(_packet) => 0x00,
}
}
/// Read a packet by its id, ConnectionProtocol, and flow /// Read a packet by its id, ConnectionProtocol, and flow
pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( async fn read<
T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send,
P: ProtocolPacket,
>(
id: u32, id: u32,
protocol: &ConnectionProtocol,
flow: &PacketFlow, flow: &PacketFlow,
buf: &mut BufReader<T>, buf: &mut BufReader<T>,
) -> Result<Packet, String> { ) -> Result<P, String>
match protocol { where
ConnectionProtocol::Handshake => match id { Self: Sized;
0x00 => Ok(
handshake::client_intention_packet::ClientIntentionPacket::read(buf).await?,
),
_ => Err(format!("Unknown packet id: {}", id)),
},
ConnectionProtocol::Game => Err("Game protocol not implemented yet".to_string()),
ConnectionProtocol::Status => match flow {
PacketFlow::ServerToClient => match id {
0x00 => Ok(
status::clientbound_status_response_packet::ClientboundStatusResponsePacket
::read(buf)
.await?,
),
_ => Err(format!("Unknown packet id: {}", id)),
},
PacketFlow::ClientToServer => match id {
0x00 => Ok(
status::serverbound_status_request_packet::ServerboundStatusRequestPacket
::read(buf)
.await?,
),
_ => Err(format!("Unknown packet id: {}", id)),
},
},
ConnectionProtocol::Login => Err("Login protocol not implemented yet".to_string()),
}
}
pub fn write(&self, buf: &mut Vec<u8>) { fn write(&self, buf: &mut Vec<u8>);
self.get_inner_packet().write(buf);
}
} }
// impl Packet {
// fn get_inner_packet(&self) -> &dyn PacketTrait {
// match self {
// Packet::ClientIntentionPacket(packet) => packet,
// Packet::ServerboundStatusRequestPacket(packet) => packet,
// Packet::ClientboundStatusResponsePacket(packet) => packet,
// Packet::ServerboundHelloPacket(packet) => packet,
// Packet::ClientboundHelloPacket(packet) => packet,
// }
// }
// pub fn id(&self) -> u32 {
// match self {
// Packet::ClientIntentionPacket(_packet) => 0x00,
// Packet::ServerboundStatusRequestPacket(_packet) => 0x00,
// Packet::ClientboundStatusResponsePacket(_packet) => 0x00,
// Packet::ServerboundHelloPacket(_packet) => 0x00,
// Packet::ClientboundHelloPacket(_packet) => 0x01,
// }
// }
// /// Read a packet by its id, ConnectionProtocol, and flow
// pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
// id: u32,
// protocol: &ConnectionProtocol,
// flow: &PacketFlow,
// buf: &mut BufReader<T>,
// ) -> Result<Packet, String> {
// match protocol {
// ConnectionProtocol::Handshake => match flow {
// PacketFlow::ClientToServer => match id {
// 0x00 => Ok(
// handshake::client_intention_packet::ClientIntentionPacket::read(buf).await?,
// ),
// _ => Err(format!("Unknown ClientToServer handshake packet id: {}", id)),
// }
// PacketFlow::ServerToClient => Err("ServerToClient handshake packets not implemented".to_string()),
// },
// ConnectionProtocol::Game => Err("Game protocol not implemented yet".to_string()),
// ConnectionProtocol::Status => match flow {
// PacketFlow::ServerToClient => match id {
// 0x00 => Ok(
// status::clientbound_status_response_packet::ClientboundStatusResponsePacket
// ::read(buf)
// .await?,
// ),
// _ => Err(format!("Unknown ServerToClient status packet id: {}", id)),
// },
// PacketFlow::ClientToServer => match id {
// 0x00 => Ok(
// status::serverbound_status_request_packet::ServerboundStatusRequestPacket
// ::read(buf)
// .await?,
// ),
// _ => Err(format!("Unknown ClientToServer status packet id: {}", id)),
// },
// },
// ConnectionProtocol::Login => match flow {
// PacketFlow::ServerToClient => match id {
// 0x01 => Ok(
// login::clientbound_hello_packet::ClientboundHelloPacket::read(buf).await?,
// ),
// _ => Err(format!("Unknown ServerToClient login packet id: {}", id)),
// },
// PacketFlow::ClientToServer => match id {
// 0x00 => Ok(
// login::serverbound_hello_packet::ServerboundHelloPacket::read(buf).await?,
// ),
// _ => Err(format!("Unknown ClientToServer login packet id: {}", id)),
// },
// },
// }
// }
// pub fn write(&self, buf: &mut Vec<u8>) {
// self.get_inner_packet().write(buf);
// }
// }
#[async_trait] #[async_trait]
pub trait PacketTrait { pub trait PacketTrait {
/// Return a version of the packet that you can actually use for stuff /// Return a version of the packet that you can actually use for stuff
fn get(self) -> Packet; fn get<P: ProtocolPacket>(self) -> P;
fn write(&self, buf: &mut Vec<u8>); fn write(&self, buf: &mut Vec<u8>);
async fn read<T: AsyncRead + std::marker::Unpin + std::marker::Send>( async fn read<T: AsyncRead + std::marker::Unpin + std::marker::Send, P: ProtocolPacket>(
buf: &mut BufReader<T>, buf: &mut BufReader<T>,
) -> Result<Packet, String> ) -> Result<P, String>
where where
Self: Sized; Self: Sized;
} }

View file

@ -9,9 +9,11 @@ use crate::{
packets::{Packet, PacketTrait}, packets::{Packet, PacketTrait},
}; };
use super::StatusPacket;
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Version { pub struct Version {
pub name: String, pub name: Component,
pub protocol: u32, pub protocol: u32,
} }
@ -31,14 +33,16 @@ pub struct Players {
// the entire packet is just json, which is why it has deserialize // the entire packet is just json, which is why it has deserialize
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct ClientboundStatusResponsePacket { pub struct ClientboundStatusResponsePacket {
pub version: Version,
pub description: Component, pub description: Component,
pub favicon: Option<String>,
pub players: Players,
pub version: Version,
} }
#[async_trait] #[async_trait]
impl PacketTrait for ClientboundStatusResponsePacket { impl PacketTrait for ClientboundStatusResponsePacket {
fn get(self) -> Packet { fn get(self) -> StatusPacket {
Packet::ClientboundStatusResponsePacket(self) StatusPacket::ClientboundStatusResponsePacket(self)
} }
fn write(&self, _buf: &mut Vec<u8>) {} fn write(&self, _buf: &mut Vec<u8>) {}

View file

@ -1,2 +1,80 @@
pub mod clientbound_status_response_packet; pub mod clientbound_status_response_packet;
pub mod serverbound_status_request_packet; pub mod serverbound_status_request_packet;
use async_trait::async_trait;
use tokio::io::BufReader;
use crate::connect::PacketFlow;
use super::{ConnectionProtocol, PacketTrait, ProtocolPacket};
#[derive(Clone, Debug)]
pub enum StatusPacket {
ServerboundStatusRequestPacket(
serverbound_status_request_packet::ServerboundStatusRequestPacket,
),
ClientboundStatusResponsePacket(
clientbound_status_response_packet::ClientboundStatusResponsePacket,
),
}
// #[async_trait]
// impl ProtocolPacket for StatusPacket {
impl StatusPacket {
fn get_inner(self) -> impl PacketTrait {
match self {
StatusPacket::ServerboundStatusRequestPacket(packet) => packet,
StatusPacket::ClientboundStatusResponsePacket(packet) => packet,
}
}
// fn get_inner(&self) -> StatusPacket {
// match self {
// StatusPacket::ServerboundStatusRequestPacket(packet) => packet,
// StatusPacket::ClientboundStatusResponsePacket(packet) => packet,
// }
// }
fn id(&self) -> u32 {
match self {
StatusPacket::ServerboundStatusRequestPacket(_packet) => 0x00,
StatusPacket::ClientboundStatusResponsePacket(_packet) => 0x00,
}
}
fn write(&self, buf: &mut Vec<u8>) {
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<
T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send,
P: ProtocolPacket,
>(
id: u32,
flow: &PacketFlow,
buf: &mut BufReader<T>,
) -> Result<P, String>
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)),
},
}
}
}

View file

@ -2,19 +2,21 @@ use async_trait::async_trait;
use std::hash::Hash; use std::hash::Hash;
use tokio::io::BufReader; use tokio::io::BufReader;
use crate::{ use crate::packets::{Packet, PacketTrait, ProtocolPacket};
packets::{Packet, PacketTrait},
}; use super::StatusPacket;
#[derive(Hash, Clone, Debug)] #[derive(Hash, Clone, Debug)]
pub struct ServerboundStatusRequestPacket {} pub struct ServerboundStatusRequestPacket {}
#[async_trait] #[async_trait]
impl PacketTrait for ServerboundStatusRequestPacket { impl PacketTrait for ServerboundStatusRequestPacket {
fn get(self) -> Packet { fn get(self) -> StatusPacket {
Packet::ServerboundStatusRequestPacket(self) StatusPacket::ServerboundStatusRequestPacket(self)
}
fn write(&self, _buf: &mut Vec<u8>) {
panic!("ServerboundStatusRequestPacket::write not implemented")
} }
fn write(&self, _buf: &mut Vec<u8>) {}
async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
_buf: &mut BufReader<T>, _buf: &mut BufReader<T>,