commit 5ef70901e69029326b51bf3493661482e5801332 Author: Avery Date: Fri Mar 28 19:59:21 2025 +0100 recieve text messages from 1.7.10 vanilla server diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..0dfe103 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,102 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "client" +version = "0.1.0" +dependencies = [ + "serde_json", + "varint-rs", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "varint-rs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..15378af --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "client" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde_json = "1.0.140" +varint-rs = "2.2.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..61bec41 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,83 @@ +mod readers; +mod text; + +use std::{ + io::{Read, Write}, + net::TcpStream, +}; + +use readers::{read_packet, read_string}; +use text::Message; +use varint_rs::{VarintReader, VarintWriter}; + +fn main() { + let mut stream = TcpStream::connect("localhost:25565").unwrap(); + + // Handshake + let mut buf: Vec = Vec::new(); + buf.write_u8_varint(0).unwrap(); + buf.write_u8_varint(5).unwrap(); + let addr = "localhost"; + buf.write_usize_varint(addr.len()).unwrap(); + buf.write_all(addr.as_bytes()).unwrap(); + buf.write_all(&25565u16.to_le_bytes()).unwrap(); + buf.write_u8_varint(2).unwrap(); + + stream.write_usize_varint(buf.len()).unwrap(); + stream.write_all(&buf).unwrap(); + + // Login start + let mut buf: Vec = Vec::new(); + buf.write_u8_varint(0).unwrap(); + let addr = "CursedChat"; + buf.write_usize_varint(addr.len()).unwrap(); + buf.write_all(addr.as_bytes()).unwrap(); + + stream.write_usize_varint(buf.len()).unwrap(); + stream.write_all(&buf).unwrap(); + + let mut buf = [0u8; 128]; + dbg!(Read::read(&mut stream, &mut buf).unwrap()); + println!("{:X?}", buf); + + let mut iter = buf.into_iter(); + let _len = iter.next().unwrap(); + let id = iter.next().unwrap(); + assert!(id == 2); + let uuid = read_string(&mut iter); + let username = read_string(&mut iter); + dbg!(uuid, username); + + loop { + let packet = read_packet(&mut stream); + let packetid = (&packet[..]).read_usize_varint().unwrap(); + match packetid { + 0x00 => handle_keepalive(&mut stream, &packet), + 0x02 => handle_chat(&packet), + 0x40 => break, + _ => {} + } + } +} + +fn handle_keepalive(stream: &mut TcpStream, mut packet: &[u8]) { + let _packetid = packet.read_usize_varint().unwrap(); + let mut id = [0u8; 4]; + packet.read_exact(&mut id).unwrap(); + + let mut buf: Vec = Vec::new(); + buf.write_u8_varint(0).unwrap(); + buf.write_all(&id).unwrap(); + + stream.write_usize_varint(buf.len()).unwrap(); + stream.write_all(&buf).unwrap(); +} + +fn handle_chat(mut packet: &[u8]) { + let _packetid = packet.read_usize_varint().unwrap(); + + let mut iter = packet.iter().cloned(); + let msg = read_string(&mut iter); + let msg = Message::from_string(msg.unwrap()); + dbg!(msg); +} diff --git a/src/readers.rs b/src/readers.rs new file mode 100644 index 0000000..07cc420 --- /dev/null +++ b/src/readers.rs @@ -0,0 +1,19 @@ +use std::{io::Read as _, net::TcpStream}; + +use varint_rs::VarintReader as _; + +pub fn read_string>(iter: &mut I) -> Option { + let len = iter.next()?.into(); + let bytes = iter.take(len).collect::>(); + (len == bytes.len()).then_some(())?; + String::from_utf8(bytes).ok() +} + +pub fn read_packet(stream: &mut TcpStream) -> Vec { + let len = stream.read_usize_varint().unwrap(); + + let mut buf = vec![0u8; len]; + stream.read_exact(&mut buf).unwrap(); + + buf +} diff --git a/src/text.rs b/src/text.rs new file mode 100644 index 0000000..0a8850e --- /dev/null +++ b/src/text.rs @@ -0,0 +1,159 @@ +use serde_json::Value; + +#[derive(Debug)] +pub struct Message { + content: MessageContent, +} + +#[derive(Debug)] +pub enum MessageContent { + Text(String), + Translate { + translate: String, + fallback: Option, + with: Option>, + }, + Score { + name: String, + objective: String, + }, + Selector { + selector: String, + seperator: Value, + }, + Keybind(String), + Nbt { + source: String, + nbt: String, + interpret: bool, + seperator: String, + }, +} + +impl Message { + pub fn from_string(string: String) -> Option { + let json: Value = serde_json::from_str(&string).ok()?; + Self::from_json(json) + } + + fn from_json(message: Value) -> Option { + let message = message.as_object()?; + + match message.get("type") { + Some(typ) => match typ.as_str()? { + "text" => Some(Message { + content: MessageContent::Text(message.get("text")?.as_str()?.to_string()), + }), + "translatable" => Some(Message { + content: MessageContent::Translate { + translate: message.get("translate")?.as_str()?.to_owned(), + fallback: message + .get("fallback") + .map(|v| v.as_str().map(str::to_string)) + .flatten(), + with: message.get("with").map(|a| a.as_array().cloned()).flatten(), + }, + }), + "score" => { + let score = message.get("score")?.as_object()?; + Some(Message { + content: MessageContent::Score { + name: score.get("name")?.as_str()?.to_string(), + objective: score.get("objective")?.as_str()?.to_string(), + }, + }) + } + "selector" => Some(Message { + content: MessageContent::Selector { + selector: message.get("selector")?.as_str()?.to_string(), + seperator: message.get("seperator")?.clone(), + }, + }), + "keybind" => Some(Message { + content: MessageContent::Keybind(message.get("keybind")?.as_str()?.to_string()), + }), + "nbt" => Some(Message { + content: MessageContent::Nbt { + source: message + .get(message.get("source")?.as_str()?)? + .as_str()? + .to_string(), + nbt: message.get("nbt")?.as_str()?.to_string(), + interpret: message + .get("interpret") + .map(|v| v.as_bool()) + .flatten() + .unwrap_or(false), + seperator: message + .get("seperator") + .map(|v| v.as_str()) + .flatten() + .unwrap_or(", ") + .to_string(), + }, + }), + _ => return None, + }, + None => { + if let Some(txt) = message.get("text") { + Some(Message { + content: MessageContent::Text(txt.as_str()?.to_string()), + }) + } else if let Some(translate) = message.get("translate") { + Some(Message { + content: MessageContent::Translate { + translate: translate.as_str()?.to_owned(), + fallback: message + .get("fallback") + .map(|v| v.as_str().map(str::to_string)) + .flatten(), + with: message.get("with").map(|a| a.as_array().cloned()).flatten(), + }, + }) + } else if let Some(score) = message.get("score") { + let score = score.as_object()?; + Some(Message { + content: MessageContent::Score { + name: score.get("name")?.as_str()?.to_string(), + objective: score.get("objective")?.as_str()?.to_string(), + }, + }) + } else if let Some(selector) = message.get("selector") { + Some(Message { + content: MessageContent::Selector { + selector: selector.as_str()?.to_string(), + seperator: message.get("seperator")?.clone(), + }, + }) + } else if let Some(keybind) = message.get("keybind") { + Some(Message { + content: MessageContent::Keybind(keybind.as_str()?.to_string()), + }) + } else if let Some(nbt) = message.get("nbt") { + Some(Message { + content: MessageContent::Nbt { + source: message + .get(message.get("source")?.as_str()?)? + .as_str()? + .to_string(), + nbt: nbt.as_str()?.to_string(), + interpret: message + .get("interpret") + .map(|v| v.as_bool()) + .flatten() + .unwrap_or(false), + seperator: message + .get("seperator") + .map(|v| v.as_str()) + .flatten() + .unwrap_or(", ") + .to_string(), + }, + }) + } else { + None + } + } + } + } +}