Valence Version
2e4bc6e
What You Did
- Establish TCP listener on default MC port.
- Accept connection from 1.21.4 vanilla client
- Read data from TCP stream into buffer
- Queue slice into
PacketDecoder
- Get next frame with
try_next_packet
- Try decode
LoginHelloC2s
Playground
use std::time::Instant;
use tokio::net::{TcpListener, TcpStream};
use tracing::{error, info, warn};
use tracing_subscriber;
use valence_protocol::PacketDecoder;
use valence_protocol::packets::login::LoginHelloC2s;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let listener = TcpListener::bind("0.0.0.0:25565").await?;
info!("Listening on port 25565");
loop {
match listener.accept().await {
Ok((socket, addr)) => {
info!("New connection from {}", addr);
tokio::spawn(handle_connection(socket, addr.to_string()));
}
Err(e) => {
error!("Failed to accept connection: {}", e);
}
}
}
}
async fn handle_connection(socket: TcpStream, addr: String) {
let start_time = Instant::now();
info!("Connection established with {} at {:?}", addr, start_time);
loop {
socket.readable().await.unwrap_or_else(|e| {
warn!("Error waiting for socket readability: {}", e);
});
let mut decoder = PacketDecoder::new();
let mut buf = [0; 1024];
match socket.try_read(&mut buf) {
Ok(0) => {
let duration = start_time.elapsed();
info!(
"Connection from {} closed after {:?} ({:.3} seconds)",
addr,
duration,
duration.as_secs_f64()
);
break;
}
Ok(n) => {
info!("Received {} bytes from {}", n, addr);
decoder.queue_slice(&buf[0..n]);
while let Ok(Some(frame)) = decoder.try_next_packet() {
match frame.decode::<LoginHelloC2s>() {
Ok(login_packet) => {
info!("Bingo! Received login from: {}", login_packet.username)
}
Err(err) => info!("Not LoginHelloC2s: {err}"),
}
}
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
continue;
}
Err(e) => {
let duration = start_time.elapsed();
warn!(
"Connection from {} error after {:?} ({:.3} seconds): {}",
addr,
duration,
duration.as_secs_f64(),
e
);
break;
}
}
}
}
What Went Wrong
Expected Result
2025-08-28T14:08:46.987550Z INFO mcprox: Listening on port 25565
2025-08-28T14:08:49.420192Z INFO mcprox: New connection from 127.0.0.1:51014
2025-08-28T14:08:49.420360Z INFO mcprox: Connection established with 127.0.0.1:51014 at Instant { tv_sec: 6612, tv_nsec: 740116261 }
2025-08-28T14:08:49.420896Z INFO mcprox: Received 77 bytes from 127.0.0.1:51014
2025-08-28T14:08:49.420933Z INFO mcprox: Not LoginHelloC2s: failed to decode field `username` in `LoginHelloC2s`
2025-08-28T14:08:49.421136Z INFO mcprox: Received 27 bytes from 127.0.0.1:51014
2025-08-28T14:08:49.421155Z INFO mcprox: Bingo! Received login from: rohedin4
2025-08-28T14:08:52.680779Z INFO mcprox: Connection from 127.0.0.1:51014 closed after 3.260432602s (3.260 seconds)
Actual Result
2025-08-28T14:08:23.785379Z INFO mcprox: Listening on port 25565
2025-08-28T14:08:26.259271Z INFO mcprox: New connection from 127.0.0.1:60720
2025-08-28T14:08:26.259399Z INFO mcprox: Connection established with 127.0.0.1:60720 at Instant { tv_sec: 6589, tv_nsec: 579175778 }
2025-08-28T14:08:26.259655Z INFO mcprox: Received 77 bytes from 127.0.0.1:60720
2025-08-28T14:08:26.259709Z INFO mcprox: Not LoginHelloC2s: failed to decode field `username` in `LoginHelloC2s`
2025-08-28T14:08:26.259732Z INFO mcprox: Received 27 bytes from 127.0.0.1:60720
2025-08-28T14:08:26.259747Z INFO mcprox: Not LoginHelloC2s: failed to decode field `profile_id` in `LoginHelloC2s`
2025-08-28T14:08:31.326701Z INFO mcprox: Connection from 127.0.0.1:60720 closed after 5.067285718s (5.067 seconds)
Additional Information
The Minecraft Wiki indicates that the profile ID is not optional. However, the LoginHelloC2s struct is implemented with an Option<Uuid>:
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(state = PacketState::Login)]
pub struct LoginHelloC2s<'a> {
pub username: Bounded<&'a str, 16>,
pub profile_id: Option<Uuid>,
}
Replacing Option<Uuid> with Uuid results in successfully decoding packets from the 1.20.4 client.
- pub profile_id: Option<Uuid>,
+ pub profile_id: Uuid,
The issue seems to be in the Decode implementation for Option<T>:
impl<'a, T: Decode<'a>> Decode<'a> for Option<T> {
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
Ok(if bool::decode(r)? {
Some(T::decode(r)?)
} else {
None
})
}
}
This essentially expects either [username]1[profile_id] or [username]0. However, the actual packet looks like this: [username][profile_id].
I'm not sure if there is a version of MC which doesn't send the profile_id, so I'm not sure how this should be fixed.
Valence Version
2e4bc6e
What You Did
PacketDecodertry_next_packetLoginHelloC2sPlayground
What Went Wrong
Expected Result
Actual Result
Additional Information
The Minecraft Wiki indicates that the profile ID is not optional. However, the
LoginHelloC2sstruct is implemented with anOption<Uuid>:Replacing
Option<Uuid>withUuidresults in successfully decoding packets from the1.20.4client.The issue seems to be in the
Decodeimplementation forOption<T>:This essentially expects either
[username]1[profile_id]or[username]0. However, the actual packet looks like this:[username][profile_id].I'm not sure if there is a version of MC which doesn't send the
profile_id, so I'm not sure how this should be fixed.