1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

start implementing joining servers

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

View file

@ -1,11 +1,11 @@
use minecraft_client::ping;
use minecraft_protocol::ServerAddress;
use minecraft_client::{connect::join_server, ping::ping_server};
use tokio::runtime::Runtime;
async fn bot() {
let address = ServerAddress::parse(&"mc.hypixel.net".to_string()).unwrap();
let response = ping::ping_server(&address).await.unwrap();
println!("{}", response.description.to_ansi(None));
let address = "localhost:63425";
let response = join_server(&address.try_into().unwrap()).await.unwrap();
// println!("{}", response.description.to_ansi(None));
println!("connected");
}
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 {
let formatting_code = legacy_color_code.chars().nth(i + 1).unwrap();
if let Ok(formatter) = ChatFormatting::from_code(formatting_code) {
if components.is_empty() {
components.push(TextComponent::new("".to_string()));
} else if !components.last().unwrap().text.is_empty() {
if components.is_empty() || !components.last().unwrap().text.is_empty() {
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;
#[cfg(test)]

View file

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

View file

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

View file

@ -3,7 +3,7 @@
use std::net::IpAddr;
use std::str::FromStr;
pub mod connection;
pub mod connect;
pub mod mc_buf;
pub mod packets;
pub mod resolver;
@ -20,9 +20,12 @@ pub struct ServerIpAddress {
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
pub fn parse(string: &str) -> Result<ServerAddress, String> {
fn try_from(string: &str) -> Result<Self, Self::Error> {
if string.is_empty() {
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();
}
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]) {
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) {
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;
#[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 tokio::io::{AsyncRead, BufReader};
use crate::connection::PacketFlow;
use crate::connect::PacketFlow;
pub const PROTOCOL_VERSION: u32 = 757;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConnectionProtocol {
@ -18,90 +20,123 @@ pub enum ConnectionProtocol {
#[derive(Clone, Debug)]
pub enum Packet {
// game
// handshake
ClientIntentionPacket(handshake::client_intention_packet::ClientIntentionPacket),
// login
// status
ServerboundStatusRequestPacket(
status::serverbound_status_request_packet::ServerboundStatusRequestPacket,
),
ClientboundStatusResponsePacket(
status::clientbound_status_response_packet::ClientboundStatusResponsePacket,
),
Game(game::GamePacket),
Handshake(handshake::HandshakePacket),
Login(login::LoginPacket),
Status(status::StatusPacket),
}
// TODO: do all this with macros so it's less repetitive
impl Packet {
fn get_inner_packet(&self) -> &dyn PacketTrait {
match self {
Packet::ClientIntentionPacket(packet) => packet,
Packet::ServerboundStatusRequestPacket(packet) => packet,
Packet::ClientboundStatusResponsePacket(packet) => packet,
}
}
#[async_trait]
pub trait ProtocolPacket {
fn get_inner<P: PacketTrait>(&self) -> &P;
pub fn id(&self) -> u32 {
match self {
Packet::ClientIntentionPacket(_packet) => 0x00,
Packet::ServerboundStatusRequestPacket(_packet) => 0x00,
Packet::ClientboundStatusResponsePacket(_packet) => 0x00,
}
}
fn id(&self) -> u32;
/// 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,
protocol: &ConnectionProtocol,
flow: &PacketFlow,
buf: &mut BufReader<T>,
) -> Result<Packet, String> {
match protocol {
ConnectionProtocol::Handshake => match id {
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()),
}
}
) -> Result<P, String>
where
Self: Sized;
pub fn write(&self, buf: &mut Vec<u8>) {
self.get_inner_packet().write(buf);
}
fn write(&self, buf: &mut Vec<u8>);
}
// 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]
pub trait PacketTrait {
/// 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>);
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>,
) -> Result<Packet, String>
) -> Result<P, String>
where
Self: Sized;
}

View file

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

View file

@ -1,2 +1,80 @@
pub mod clientbound_status_response_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 tokio::io::BufReader;
use crate::{
packets::{Packet, PacketTrait},
};
use crate::packets::{Packet, PacketTrait, ProtocolPacket};
use super::StatusPacket;
#[derive(Hash, Clone, Debug)]
pub struct ServerboundStatusRequestPacket {}
#[async_trait]
impl PacketTrait for ServerboundStatusRequestPacket {
fn get(self) -> Packet {
Packet::ServerboundStatusRequestPacket(self)
fn get(self) -> StatusPacket {
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>(
_buf: &mut BufReader<T>,