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

rust is driving me insane

This commit is contained in:
mat 2021-12-10 00:54:58 -06:00
parent f64750afdd
commit be762fc5d3
11 changed files with 164 additions and 65 deletions

1
Cargo.lock generated
View file

@ -288,6 +288,7 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
name = "minecraft-chat" name = "minecraft-chat"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"lazy_static",
"serde_json", "serde_json",
] ]

View file

@ -6,4 +6,5 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
lazy_static = "1.4.0"
serde_json = "^1.0.72" serde_json = "^1.0.72"

View file

@ -1,4 +1,8 @@
//! Things for working with Minecraft chat messages, inspired by the Minecraft source code and prismarine-chat. //! Things for working with Minecraft chat messages.
//! This was inspired by Minecraft and prismarine-chat.
#[macro_use]
extern crate lazy_static;
pub mod base_component; pub mod base_component;
pub mod component; pub mod component;

View file

@ -9,8 +9,26 @@ pub struct TextColor {
} }
impl TextColor { impl TextColor {
// hopefully rust/llvm optimizes this so it's just calculated once pub fn parse(value: String) -> Result<TextColor, String> {
fn calculate_legacy_format_to_color() -> HashMap<&'static ChatFormatting<'static>, TextColor> { if value.starts_with("#") {
let n = value.chars().skip(1).collect::<String>();
let n = u32::from_str_radix(&n, 16).unwrap();
return Ok(TextColor::from_rgb(n));
}
let color = NAMED_COLORS.get(&value.to_ascii_uppercase());
if color.is_some() {
return Ok(color.unwrap().clone());
}
Err(format!("Invalid color {}", value))
}
fn from_rgb(value: u32) -> TextColor {
TextColor { value, name: None }
}
}
lazy_static! {
static ref LEGACY_FORMAT_TO_COLOR: HashMap<&'static ChatFormatting<'static>, TextColor> = {
let mut legacy_format_to_color = HashMap::new(); let mut legacy_format_to_color = HashMap::new();
for formatter in &ChatFormatting::FORMATTERS { for formatter in &ChatFormatting::FORMATTERS {
if !formatter.is_format && *formatter != ChatFormatting::RESET { if !formatter.is_format && *formatter != ChatFormatting::RESET {
@ -24,34 +42,14 @@ impl TextColor {
} }
} }
legacy_format_to_color legacy_format_to_color
} };
static ref NAMED_COLORS: HashMap<String, TextColor> = {
fn calculate_named_colors() -> HashMap<String, TextColor> {
let legacy_format_to_color = Self::calculate_legacy_format_to_color();
let mut named_colors = HashMap::new(); let mut named_colors = HashMap::new();
for color in legacy_format_to_color.values() { for color in LEGACY_FORMAT_TO_COLOR.values() {
named_colors.insert(color.name.clone().unwrap(), color.clone()); named_colors.insert(color.name.clone().unwrap(), color.clone());
} }
named_colors named_colors
} };
pub fn parse(value: String) -> Result<TextColor, String> {
if value.starts_with("#") {
let n = value.chars().skip(1).collect::<String>();
let n = u32::from_str_radix(&n, 16).unwrap();
return Ok(TextColor::from_rgb(n));
}
let named_colors = Self::calculate_named_colors();
let color = named_colors.get(&value.to_ascii_uppercase());
if color.is_some() {
return Ok(color.unwrap().clone());
}
Err(format!("Invalid color {}", value))
}
fn from_rgb(value: u32) -> TextColor {
TextColor { value, name: None }
}
} }
/// The weird S character Minecraft used to use for chat formatting /// The weird S character Minecraft used to use for chat formatting

View file

@ -35,7 +35,7 @@ impl Connection {
.expect("Error enabling tcp_nodelay"); .expect("Error enabling tcp_nodelay");
Ok(Connection { Ok(Connection {
state: ConnectionProtocol::Handshaking, state: ConnectionProtocol::Handshake,
flow: PacketFlow::ClientToServer, flow: PacketFlow::ClientToServer,
stream, stream,
}) })
@ -80,7 +80,7 @@ impl Connection {
// write the packet id // write the packet id
let mut id_and_data_buf = vec![]; let mut id_and_data_buf = vec![];
mc_buf::write_varint(&mut id_and_data_buf, packet.get_id() as i32); mc_buf::write_varint(&mut id_and_data_buf, packet.id() as i32);
packet.write(&mut id_and_data_buf); packet.write(&mut id_and_data_buf);
// write the packet data // write the packet data

View file

@ -146,7 +146,9 @@ pub fn write_utf_with_len(buf: &mut Vec<u8>, string: &String, len: usize) {
write_bytes(buf, string.as_bytes()); write_bytes(buf, string.as_bytes());
} }
pub async fn read_utf<T: AsyncRead + std::marker::Unpin>(buf: &mut T) -> Result<String, String> { pub async fn read_utf<T: AsyncRead + std::marker::Unpin>(
buf: &mut BufReader<T>,
) -> Result<String, String> {
read_utf_with_len(buf, MAX_STRING_LENGTH.into()).await read_utf_with_len(buf, MAX_STRING_LENGTH.into()).await
} }

View file

@ -1,5 +1,8 @@
use std::hash::Hash; use std::hash::Hash;
use async_trait::async_trait;
use tokio::io::BufReader;
use crate::{ use crate::{
mc_buf, mc_buf,
packets::{ConnectionProtocol, Packet, PacketTrait}, packets::{ConnectionProtocol, Packet, PacketTrait},
@ -14,9 +17,10 @@ pub struct ClientIntentionPacket<'a> {
pub intention: ConnectionProtocol, pub intention: ConnectionProtocol,
} }
#[async_trait]
impl<'a> PacketTrait for ClientIntentionPacket<'a> { impl<'a> PacketTrait for ClientIntentionPacket<'a> {
fn get(&self) -> Packet { fn get(&self) -> Packet {
Packet::ClientIntentionPacket(self) Packet::ClientIntentionPacket(*self)
} }
fn write(&self, buf: &mut Vec<u8>) { fn write(&self, buf: &mut Vec<u8>) {
@ -26,5 +30,10 @@ impl<'a> PacketTrait for ClientIntentionPacket<'a> {
mc_buf::write_varint(buf, self.intention.clone() as i32); mc_buf::write_varint(buf, self.intention.clone() as i32);
} }
fn parse<T: tokio::io::AsyncRead + std::marker::Unpin>(&self, buf: T) -> () {} async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
buf: &mut BufReader<T>,
) -> Result<Packet<'_>, String> {
Err("ClientIntentionPacket::parse not implemented".to_string())
// Ok(ClientIntentionPacket {}.get())
}
} }

View file

@ -1,10 +1,12 @@
mod game; pub mod game;
mod handshake; pub mod handshake;
mod login; pub mod login;
mod status; pub mod status;
use async_trait::async_trait; use async_trait::async_trait;
use tokio::io::AsyncRead; use tokio::io::{AsyncRead, BufReader};
use crate::connection::PacketFlow;
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConnectionProtocol { pub enum ConnectionProtocol {
@ -16,24 +18,89 @@ pub enum ConnectionProtocol {
pub enum Packet<'a> { pub enum Packet<'a> {
// game // game
// handshake // handshake
ClientIntentionPacket(&'a handshake::client_intention_packet::ClientIntentionPacket<'a>), ClientIntentionPacket(handshake::client_intention_packet::ClientIntentionPacket<'a>),
// login // login
// status // status
ServerboundStatusRequestPacket( ServerboundStatusRequestPacket(
&'a status::serverbound_status_request_packet::ServerboundStatusRequestPacket, status::serverbound_status_request_packet::ServerboundStatusRequestPacket,
), ),
ClientboundStatusRequestPacket( ClientboundStatusResponsePacket(
&'a status::clientbound_status_response_packet::ClientboundStatusRequestPacket, status::clientbound_status_response_packet::ClientboundStatusResponsePacket,
), ),
} }
// 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,
}
}
pub 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
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 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()),
}
}
pub fn write(&self, buf: &mut Vec<u8>) {
self.get_inner_packet().write(buf);
}
}
#[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(&self) -> Packet;
fn write(&self, buf: &mut Vec<u8>) -> (); fn write(&self, buf: &mut Vec<u8>) -> ();
fn parse<T: AsyncRead + std::marker::Unpin>( async fn read<T: AsyncRead + std::marker::Unpin + std::marker::Send>(
buf: &mut T, buf: &mut BufReader<T>,
// is using a static lifetime here a good idea? idk ) -> Result<Packet<'_>, String>
) -> Result<Packet<'_>, String>; where
Self: Sized;
} }

View file

@ -1,28 +1,31 @@
use async_trait::async_trait; use async_trait::async_trait;
use std::hash::Hash; use std::hash::Hash;
use tokio::io::BufReader;
use crate::{ use crate::{
mc_buf, mc_buf,
packets::{Packet, PacketTrait}, packets::{Packet, PacketTrait},
}; };
#[derive(Hash)] #[derive(Hash, Clone)]
pub struct ClientboundStatusRequestPacket { pub struct ClientboundStatusResponsePacket {
status: String, status: String,
} }
impl PacketTrait for ClientboundStatusRequestPacket { #[async_trait]
impl PacketTrait for ClientboundStatusResponsePacket {
fn get(&self) -> Packet { fn get(&self) -> Packet {
Packet::ClientboundStatusRequestPacket(self) Packet::ClientboundStatusResponsePacket(self.clone())
} }
fn write(&self, _buf: &mut Vec<u8>) {} fn write(&self, _buf: &mut Vec<u8>) {}
fn parse<T: tokio::io::AsyncRead + std::marker::Unpin>( async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
buf: &mut T, buf: &mut BufReader<T>,
) -> Result<Packet<'_>, String> { ) -> Result<Packet<'_>, String> {
let status = mc_buf::read_utf(&mut buf).await?; let status = mc_buf::read_utf(buf).await?;
// this.status = GsonHelper.fromJson(GSON, friendlyByteBuf.readUtf(32767), ServerStatus.class); // this.status = GsonHelper.fromJson(GSON, friendlyByteBuf.readUtf(32767), ServerStatus.class);
Ok(ClientboundStatusRequestPacket { status }.get()) let packet = ClientboundStatusResponsePacket { status }.get();
Ok(packet)
} }
} }

View file

@ -1,4 +1,6 @@
use async_trait::async_trait;
use std::hash::Hash; use std::hash::Hash;
use tokio::io::BufReader;
use crate::{ use crate::{
mc_buf, mc_buf,
@ -8,11 +10,16 @@ use crate::{
#[derive(Hash)] #[derive(Hash)]
pub struct ServerboundStatusRequestPacket {} pub struct ServerboundStatusRequestPacket {}
// implement "Packet" for "ClientIntentionPacket" #[async_trait]
impl PacketTrait for ServerboundStatusRequestPacket { impl PacketTrait for ServerboundStatusRequestPacket {
fn get(&self) -> Packet { fn get(&self) -> Packet {
Packet::ServerboundStatusRequestPacket(self) Packet::ServerboundStatusRequestPacket(*self)
} }
fn write(&self, _buf: &mut Vec<u8>) {} fn write(&self, _buf: &mut Vec<u8>) {}
fn parse<T: tokio::io::AsyncRead + std::marker::Unpin>(&self, buf: T) -> () {}
async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
buf: &mut BufReader<T>,
) -> Result<Packet<'_>, String> {
Err("ServerboundStatusRequestPacket::read not implemented".to_string())
}
} }

View file

@ -1,7 +1,10 @@
use crate::{ use crate::{
connection::Connection, connection::Connection,
mc_buf, packets::{
packets::{ClientIntentionPacket, ConnectionProtocol, ServerboundStatusRequestPacket}, handshake::client_intention_packet::ClientIntentionPacket,
status::serverbound_status_request_packet::ServerboundStatusRequestPacket,
ConnectionProtocol, PacketTrait,
},
resolver, ServerAddress, resolver, ServerAddress,
}; };
@ -14,17 +17,21 @@ pub async fn ping_server(address: &ServerAddress) -> Result<(), String> {
println!("writing intention packet {}", address.host); println!("writing intention packet {}", address.host);
// 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(&ClientIntentionPacket { conn.send_packet(
protocol_version: 757, ClientIntentionPacket {
hostname: &address.host, protocol_version: 757,
port: address.port, hostname: &address.host,
intention: ConnectionProtocol::Status, port: address.port,
}) intention: ConnectionProtocol::Status,
}
.get(),
)
.await; .await;
conn.switch_state(ConnectionProtocol::Status); conn.switch_state(ConnectionProtocol::Status);
// send the empty status request packet // send the empty status request packet
conn.send_packet(&ServerboundStatusRequestPacket {}).await; conn.send_packet(ServerboundStatusRequestPacket {}.get())
.await;
conn.read_packet().await.unwrap(); conn.read_packet().await.unwrap();