Handshake with forge server, error handling

main
Avery 2 weeks ago
parent 5ef70901e6
commit 188e2b7610
Signed by: Avery
GPG Key ID: 4E53F4CB69B2CC8D

14
Cargo.lock generated

@ -3,19 +3,19 @@
version = 4
[[package]]
name = "client"
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "mc_chat_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"

@ -1,5 +1,5 @@
[package]
name = "client"
name = "mc_chat_client"
version = "0.1.0"
edition = "2024"

@ -0,0 +1,246 @@
use std::{
collections::HashMap,
io::{Read, Write},
};
use varint_rs::{VarintReader, VarintWriter};
use crate::{
Connected, Server, Status,
error::ChatError,
error::Result,
readers::{read_packet, read_plugin_msg, read_string},
status::ServerType,
writers::{write_packet, write_plugin_msg, write_string},
};
impl Server<Status> {
pub fn connect(self) -> Result<Server<Connected>> {
let Self {
mut stream,
addr,
port,
state:
Status {
typ,
description,
mods,
},
} = self;
let (uuid, username) = match typ {
ServerType::Vanilla => {
// Handshake
write_packet(&mut stream, |buf| {
buf.write_u8_varint(0)?;
buf.write_u8_varint(5)?;
buf.write_usize_varint(addr.len())?;
buf.write_all(addr.as_bytes())?;
buf.write_all(&port.to_le_bytes())?;
buf.write_u8_varint(2)?;
Ok(())
})?;
// Login start
write_packet(&mut stream, |buf| {
buf.write_u8_varint(0)?;
let uname = "McChatClient";
write_string(buf, uname)?;
Ok(())
})?;
read_packet(&mut stream, |packet| {
let id = packet.read_usize_varint()?;
assert!(id == 2);
let uuid = read_string(packet)?;
let username = read_string(packet)?;
Ok((uuid, username))
})?
}
ServerType::Forge => {
// Handshake
write_packet(&mut stream, |buf| {
buf.write_u8_varint(0)?;
buf.write_u8_varint(5)?;
let addr = format!("{addr}\0FML\0");
buf.write_usize_varint(addr.len())?;
buf.write_all(addr.as_bytes())?;
buf.write_all(&port.to_le_bytes())?;
buf.write_u8_varint(2)?;
Ok(())
})?;
// Login start
write_packet(&mut stream, |buf| {
buf.write_u8_varint(0)?;
let uname = "McChatClient";
write_string(buf, uname)?;
Ok(())
})?;
// Login Success
let (uuid, username) = read_packet(&mut stream, |packet| {
let id = packet.read_usize_varint()?;
assert!(id == 2);
let uuid = read_string(packet)?;
let username = read_string(packet)?;
Ok((uuid, username))
})?;
// Server Register Channels
let (_channels, channels_buf) =
read_plugin_msg(&mut stream, "REGISTER", |packet| {
Ok((
packet
.split(|b| *b == 0)
.map(|b| String::from_utf8(b.to_vec()).map_err(ChatError::from))
.collect::<Result<Vec<_>>>()?,
packet.to_vec(),
))
})?;
// Server Hello
let _dim = read_plugin_msg(&mut stream, "FML|HS", |packet| {
let discriminant = VarintReader::read(packet)?;
assert_eq!(discriminant, 0);
let proto_ver = VarintReader::read(packet)?;
assert_eq!(proto_ver, 2);
let mut buf = [0u8; 4];
packet.read_exact(&mut buf)?;
let dim = i32::from_be_bytes(buf);
Ok(dim)
})?;
// Client Register Channels
write_plugin_msg(&mut stream, "REGISTER", |body| {
body.write_all(&channels_buf)?;
Ok(())
})?;
// Client Hello
write_plugin_msg(&mut stream, "FML|HS", |body| {
body.write_all(&[0x1])?;
body.write_all(&[0x2])?;
Ok(())
})?;
// Client Modlist
write_plugin_msg(&mut stream, "FML|HS", |body| {
body.write_all(&[0x2])?;
let mods = mods.as_ref().unwrap();
body.write_usize_varint(mods.len())?;
for (id, ver) in mods {
write_string(body, id)?;
write_string(body, ver)?;
}
Ok(())
})?;
// Server Modlist
let _mods = read_plugin_msg(&mut stream, "FML|HS", |packet| {
let discriminant = VarintReader::read(packet)?;
assert_eq!(discriminant, 2);
let num_mods = VarintReader::read(packet)?;
let mut mods = HashMap::new();
for _ in 0..num_mods {
let id = read_string(packet)?;
let ver = read_string(packet)?;
mods.insert(id, ver);
}
Ok(mods)
})?;
// HandshakeAck waitingseverdata
write_plugin_msg(&mut stream, "FML|HS", |body| {
body.write_all(&[0xFF, 0x02])?;
Ok(())
})?;
// Server RegistryData
read_plugin_msg(&mut stream, "FML|HS", |packet| {
let discriminant = VarintReader::read(packet)?;
assert_eq!(discriminant, 3);
let num_ids = packet.read_usize_varint()?;
for _ in 0..num_ids {
let _name = read_string(packet)?;
let _id = packet.read_usize_varint()?;
}
if packet.len() > 0 {
let len = packet.read_usize_varint()?;
for _ in 0..len {
read_string(packet)?;
}
let len = packet.read_usize_varint()?;
for _ in 0..len {
read_string(packet)?;
}
}
Ok(())
})?;
// Server HandshakeAck waitingack
read_plugin_msg(&mut stream, "FML|HS", |packet| {
let discriminant = VarintReader::read(packet)?;
assert_eq!(discriminant, 255);
let phrase = VarintReader::read(packet)?;
assert_eq!(phrase, 2);
Ok(())
})?;
// HandshakeAck waitingsevercomplete
write_plugin_msg(&mut stream, "FML|HS", |body| {
body.write_all(&[0xFF, 0x03])?;
Ok(())
})?;
// Serrver FluidIdmap
read_plugin_msg(&mut stream, "FORGE", |packet| {
let discriminant = VarintReader::read(packet)?;
assert_eq!(discriminant, 2);
Ok(())
})?;
// HandshakeAck pendingcomplete
write_plugin_msg(&mut stream, "FML|HS", |body| {
body.write_all(&[0xFF, 0x04])?;
Ok(())
})?;
// Server HandshakeAck complete
read_plugin_msg(&mut stream, "FML|HS", |packet| {
let discriminant = VarintReader::read(packet)?;
assert_eq!(discriminant, 255);
let phrase = VarintReader::read(packet)?;
assert_eq!(phrase, 3);
Ok(())
})?;
// HandshakeAck complete
write_plugin_msg(&mut stream, "FML|HS", |body| {
body.write_all(&[0xFF, 0x05])?;
Ok(())
})?;
(uuid, username)
}
};
Ok(Server {
stream,
addr,
port,
state: Connected {
typ,
description,
uuid,
username,
},
})
}
}

@ -0,0 +1,44 @@
use serde_json::Value;
use std::{io, result::Result as StdResult, string::FromUtf8Error};
#[derive(Debug)]
pub enum ChatError {
IoError(io::Error),
FromUtf8Error(FromUtf8Error),
JsonError(JsonError),
}
#[derive(Debug)]
pub enum JsonError {
SerdeError(serde_json::Error),
ExpectedObject(Value),
ExpectedArray,
ExpectedString,
ExpectedMember(String),
}
impl From<io::Error> for ChatError {
fn from(value: io::Error) -> Self {
Self::IoError(value)
}
}
impl From<FromUtf8Error> for ChatError {
fn from(value: FromUtf8Error) -> Self {
Self::FromUtf8Error(value)
}
}
impl From<JsonError> for ChatError {
fn from(value: JsonError) -> Self {
Self::JsonError(value)
}
}
impl From<serde_json::Error> for JsonError {
fn from(value: serde_json::Error) -> Self {
Self::SerdeError(value)
}
}
pub type Result<T> = StdResult<T, ChatError>;

@ -1,63 +1,75 @@
mod connect;
mod error;
mod readers;
mod status;
mod text;
mod writers;
use std::{
collections::HashMap,
fmt::Debug,
io::{Read, Write},
net::TcpStream,
};
use readers::{read_packet, read_string};
use error::Result;
use readers::read_string;
use status::ServerType;
use text::Message;
use varint_rs::{VarintReader, VarintWriter};
fn main() {
let mut stream = TcpStream::connect("localhost:25565").unwrap();
pub trait ServerState {}
// Handshake
let mut buf: Vec<u8> = 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();
#[derive(Debug)]
pub struct Disconnected;
impl ServerState for Disconnected {}
#[derive(Debug)]
pub struct Status {
typ: ServerType,
description: String,
mods: Option<HashMap<String, String>>,
}
impl ServerState for Status {}
#[derive(Debug)]
#[allow(unused)]
pub struct Connected {
typ: ServerType,
description: String,
uuid: String,
username: String,
}
impl ServerState for Connected {}
stream.write_usize_varint(buf.len()).unwrap();
stream.write_all(&buf).unwrap();
#[derive(Debug)]
pub struct Server<S: ServerState + Debug> {
stream: TcpStream,
addr: String,
port: u16,
state: S,
}
// Login start
let mut buf: Vec<u8> = 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();
impl Server<Disconnected> {
fn new(addr: String, port: u16) -> Result<Self> {
Ok(Self {
stream: TcpStream::connect(format!("{addr}:{port}"))?,
addr,
port,
state: Disconnected,
})
}
}
stream.write_usize_varint(buf.len()).unwrap();
stream.write_all(&buf).unwrap();
fn main() -> Result<()> {
let server = Server::new("localhost".to_string(), 25565)?;
dbg!(&server);
let mut buf = [0u8; 128];
dbg!(Read::read(&mut stream, &mut buf).unwrap());
println!("{:X?}", buf);
let server = server.status()?;
dbg!(&server);
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);
let server = server.connect()?;
dbg!(&server);
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,
_ => {}
}
}
Ok(())
}
fn handle_keepalive(stream: &mut TcpStream, mut packet: &[u8]) {
@ -76,8 +88,7 @@ fn handle_keepalive(stream: &mut TcpStream, mut packet: &[u8]) {
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 = read_string(&mut packet);
let msg = Message::from_string(msg.unwrap());
dbg!(msg);
}

@ -1,19 +1,47 @@
use std::{io::Read as _, net::TcpStream};
use std::{io::Read, net::TcpStream};
use crate::error::Result;
use varint_rs::VarintReader as _;
pub fn read_string<I: Iterator<Item = u8>>(iter: &mut I) -> Option<String> {
let len = iter.next()?.into();
let bytes = iter.take(len).collect::<Vec<_>>();
(len == bytes.len()).then_some(())?;
String::from_utf8(bytes).ok()
pub fn read_string(iter: &mut &[u8]) -> Result<String> {
let len = iter.read_usize_varint()?;
let mut buf = vec![0u8; len];
iter.read_exact(&mut buf)?;
String::from_utf8(buf).map_err(Into::into)
}
pub fn read_packet(stream: &mut TcpStream) -> Vec<u8> {
let len = stream.read_usize_varint().unwrap();
pub fn read_packet<T, F: Fn(&mut &[u8]) -> Result<T>>(
stream: &mut TcpStream,
read_body: F,
) -> Result<T> {
let len = stream.read_usize_varint()?;
let mut buf = vec![0u8; len];
stream.read_exact(&mut buf).unwrap();
stream.read_exact(&mut buf)?;
let mut packet = &buf[..];
read_body(&mut packet)
}
pub fn read_plugin_msg<T, F: Fn(&mut &[u8]) -> Result<T>>(
stream: &mut TcpStream,
channel: &str,
read_body: F,
) -> Result<T> {
read_packet(stream, |packet| {
let id = packet.read_usize_varint()?;
assert!(id == 63);
let channel_read = read_string(packet)?;
assert_eq!(channel_read, channel);
let mut buf = [0u8; 2];
packet.read_exact(&mut buf)?;
let len = u16::from_be_bytes(buf);
let mut buf = vec![0u8; len as usize];
packet.read_exact(&mut buf)?;
buf
let mut packet = &buf[..];
read_body(&mut packet)
})
}

@ -0,0 +1,118 @@
use std::{collections::HashMap, io::Write, net::TcpStream};
use serde_json::Value;
use varint_rs::{VarintReader, VarintWriter};
use crate::{
Disconnected, Server, Status,
error::ChatError,
error::JsonError,
error::Result,
readers::{read_packet, read_string},
writers::write_packet,
};
#[derive(Debug)]
pub enum ServerType {
Vanilla,
Forge,
}
impl Server<Disconnected> {
pub fn status(self) -> Result<Server<Status>> {
let Self {
mut stream,
addr,
port,
..
} = self;
// Handshake
write_packet(&mut stream, |buf| {
buf.write_u8_varint(0)?;
buf.write_u8_varint(5)?;
buf.write_usize_varint(addr.len())?;
buf.write_all(addr.as_bytes())?;
buf.write_all(&port.to_le_bytes())?;
buf.write_u8_varint(1)?;
Ok(())
})?;
write_packet(&mut stream, |buf| {
buf.write_all(&[0x0])?;
Ok(())
})?;
let json: Value = read_packet(&mut stream, |packet| {
let id = packet.read_usize_varint()?;
assert!(id == 0);
let json = read_string(packet)?;
serde_json::from_str(&json)
.map_err(<JsonError as From<serde_json::Error>>::from)
.map_err(ChatError::from)
})?;
let json = json
.as_object()
.ok_or(JsonError::ExpectedObject(json.clone()))?;
let description = json
.get("description")
.ok_or(JsonError::ExpectedMember("description".to_string()))?
.as_str()
.ok_or(JsonError::ExpectedString)?
.to_string();
let (typ, mods) = match json.get("modinfo") {
Some(info) => {
if info
.as_object()
.ok_or(JsonError::ExpectedObject(info.clone()))?
.get("type")
.ok_or(JsonError::ExpectedMember("type".to_string()))?
.as_str()
.ok_or(JsonError::ExpectedString)?
== "FML"
{
let mod_list = info
.get("modList")
.ok_or(JsonError::ExpectedMember("modList".to_string()))?;
let mod_list = mod_list.as_array().ok_or(JsonError::ExpectedArray)?;
let mut mods = HashMap::new();
for mod_ in mod_list {
let mod_ = mod_
.as_object()
.ok_or(JsonError::ExpectedObject(mod_.clone()))?;
let id = mod_
.get("modid")
.ok_or(JsonError::ExpectedMember("modid".to_string()))?;
let ver = mod_
.get("version")
.ok_or(JsonError::ExpectedMember("version".to_string()))?;
mods.insert(
id.as_str().ok_or(JsonError::ExpectedString)?.to_string(),
ver.as_str().ok_or(JsonError::ExpectedString)?.to_string(),
);
}
(ServerType::Forge, Some(mods))
} else {
panic!()
}
}
None => (ServerType::Vanilla, None),
};
stream.shutdown(std::net::Shutdown::Both)?;
Ok(Server {
stream: TcpStream::connect(format!("{addr}:{port}"))?,
addr,
port,
state: Status {
typ,
description,
mods,
},
})
}
}

@ -0,0 +1,41 @@
use std::{io::Write, net::TcpStream};
use varint_rs::VarintWriter;
use crate::error::Result;
pub fn write_packet<F: Fn(&mut Vec<u8>) -> Result<()>>(
stream: &mut TcpStream,
build_packet: F,
) -> Result<()> {
let mut buf: Vec<u8> = Vec::new();
build_packet(&mut buf)?;
stream.write_usize_varint(buf.len())?;
stream.write_all(&buf)?;
Ok(())
}
pub fn write_string<W: Write>(buf: &mut W, val: &str) -> Result<()> {
buf.write_usize_varint(val.len())?;
buf.write_all(val.as_bytes())?;
Ok(())
}
pub fn write_plugin_msg<F: Fn(&mut Vec<u8>) -> Result<()>>(
stream: &mut TcpStream,
channel: &str,
build_body: F,
) -> Result<()> {
let mut body: Vec<u8> = Vec::new();
build_body(&mut body)?;
write_packet(stream, |mut buf| {
buf.write_u8_varint(0x17)?;
write_string(&mut buf, channel)?;
buf.write_all(&(body.len() as u16).to_be_bytes())?;
buf.write_all(&body)?;
Ok(())
})
}
Loading…
Cancel
Save