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:
parent
f64750afdd
commit
be762fc5d3
11 changed files with 164 additions and 65 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue