try to implement game packet

This commit is contained in:
Alexander Medvedev
2025-07-01 19:44:21 +02:00
parent 8d934e31c7
commit e2dec9b5e2
16 changed files with 369 additions and 62 deletions

View File

@@ -1 +1,2 @@
pub mod network_settings;
pub mod raknet;

View File

@@ -0,0 +1,30 @@
use pumpkin_macros::packet;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[packet(0x8F)]
pub struct CNetworkSettings {
compression_threshold: u16,
compression_method: u16,
client_throttle_enabled: bool,
client_throttle_threshold: i8,
client_throttle_scalar: f32,
}
impl CNetworkSettings {
pub fn new(
compression_threshold: u16,
compression_method: u16,
client_throttle_enabled: bool,
client_throttle_threshold: i8,
client_throttle_scalar: f32,
) -> Self {
Self {
compression_threshold,
compression_method,
client_throttle_enabled,
client_throttle_threshold,
client_throttle_scalar,
}
}
}

View File

@@ -1,9 +1,9 @@
use pumpkin_macros::packet;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use crate::codec::socket_address::SocketAddress;
#[derive(Serialize)]
#[derive(Serialize, Deserialize)]
#[packet(0x10)]
pub struct CConnectionRequestAccepted {
client_address: SocketAddress,

View File

@@ -14,6 +14,9 @@ pub const RAKNET_MAGIC: [u8; 16] = [
pub const RAKNET_VALID: u8 = 0x80;
pub const RAKNET_ACK: u8 = 0xC0;
pub const RAKNET_NACK: u8 = 0xA0;
pub const RAKNET_GAME_PACKET: i32 = 0xfe;
pub const RAKNET_SPLIT: u8 = 0x10;
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]

View File

@@ -4,7 +4,12 @@ use async_compression::tokio::bufread::ZlibDecoder;
use bytes::Bytes;
use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
use crate::{Aes128Cfb8Dec, CompressionThreshold, PacketDecodeError, StreamDecryptor};
use crate::{
Aes128Cfb8Dec, CompressionThreshold, MAX_PACKET_SIZE, PacketDecodeError, RawPacket,
StreamDecryptor,
codec::var_int::VarInt,
ser::{NetworkReadExt, ReadingError},
};
// decrypt -> decompress -> raw
pub enum DecompressionReader<R: AsyncRead + Unpin> {
@@ -109,4 +114,58 @@ impl UDPNetworkDecoder {
Ok(payload.into())
}
pub async fn get_game_packet(
&mut self,
mut reader: Cursor<Vec<u8>>,
) -> Result<RawPacket, PacketDecodeError> {
let compression = reader.get_u8_be()?;
dbg!(compression);
// TODO: compression & encryption
let packet_len = VarInt::decode_async(&mut reader)
.await
.map_err(|err| match err {
ReadingError::CleanEOF(_) => PacketDecodeError::ConnectionClosed,
err => PacketDecodeError::MalformedLength(err.to_string()),
})?;
let packet_len = packet_len.0 as u64;
dbg!(packet_len);
if !(0..=MAX_PACKET_SIZE).contains(&packet_len) {
Err(PacketDecodeError::OutOfBounds)?
}
let header = VarInt::decode_async(&mut reader).await?;
let header_value = header.0;
// Extract components from GamePacket Header (14 bits)
// Gamepacket ID (10 bits)
// SubClient Sender ID (2 bits)
// SubClient Target ID (2 bits)
// The header is 14 bits. Ensure we only consider these bits.
// A varint u32 could be larger, so we mask to the relevant bits.
let fourteen_bit_header = header_value & 0x3FFF; // Mask to get the lower 14 bits (2^14 - 1)
// SubClient Target ID: Lowest 2 bits
let _sub_client_target_id = (fourteen_bit_header & 0b11) as u8;
// SubClient Sender ID: Next 2 bits (bits 2 and 3)
let _sub_client_sender_id = ((fourteen_bit_header >> 2) & 0b11) as u8;
// Gamepacket ID: Remaining 10 bits (bits 4 to 13)
let gamepacket_id = ((fourteen_bit_header >> 4) & 0x3FF) as u16; // 0x3FF is 10 bits set to 1
let payload = reader
.read_boxed_slice(packet_len as usize)
.map_err(|err| PacketDecodeError::FailedDecompression(err.to_string()))?;
Ok(RawPacket {
id: gamepacket_id as i32,
payload: payload.into(),
})
}
}

View File

@@ -1,4 +1,4 @@
use std::net::SocketAddr;
use std::{io::Write, net::SocketAddr};
use bytes::Bytes;
use thiserror::Error;
@@ -6,6 +6,7 @@ use tokio::{io::AsyncWrite, net::UdpSocket};
use crate::{
Aes128Cfb8Enc, CompressionLevel, CompressionThreshold, PacketEncodeError, StreamEncryptor,
codec::var_int::VarInt, ser::NetworkWriteExt,
};
// raw -> compress -> encrypt
@@ -107,6 +108,57 @@ impl UDPNetworkEncoder {
// take_mut::take(&mut self.writer, |encoder| encoder.upgrade(cipher));
}
pub async fn write_game_packet(
&mut self,
packet_id: i32,
sub_client_sender_id: i32,
sub_client_target_id: i32,
packet_payload: Bytes,
mut writer: impl Write,
) -> Result<(), PacketEncodeError> {
// Game Packet ID
writer.write_u8(0xfe).unwrap();
// TODO: compression & encryption
// Gamepacket ID (10 bits) << 4 (offset by 2 bits for target + 2 bits for sender)
// SubClient Sender ID (2 bits) << 2 (offset by 2 bits for target)
// SubClient Target ID (2 bits)
let header_value: u32 = ((packet_id as u32) << 4)
| ((sub_client_sender_id as u32) << 2)
| (sub_client_target_id as u32);
// Ensure the combined header doesn't exceed 14 bits (just a sanity check, should be handled by above shifts)
let fourteen_bit_header = header_value & 0x3FFF; // Mask to ensure it fits in 14 bits
// 2. Calculate total packet_len
// This is where `VarInt::encoded_len` is crucial.
// We need to know the byte length of the header's VarInt *before* we write the packet_len.
let header_byte_len = VarInt(fourteen_bit_header as i32).written_size();
let packet_payload_len = packet_payload.len() as u32;
// total_content_length is the length of the header VarInt bytes + payload bytes.
let total_content_length = header_byte_len as u32 + packet_payload_len;
// 3. Write packet_len as VarInt
// Note: Your `VarInt` struct takes `i32`, but lengths are typically `u32`.
// Ensure consistency in your actual `VarInt` definition.
// For this example, I'll cast `total_content_length` to `i32`.
writer
.write_var_int(&VarInt(total_content_length as i32))
.unwrap();
// 4. Write the combined 14-bit header_value as VarInt
writer
.write_var_int(&VarInt(fourteen_bit_header as i32))
.unwrap();
// 5. Write the Packet ID + payload
writer.write_u8(packet_id as u8).unwrap();
writer.write_all(&packet_payload).unwrap();
Ok(())
}
pub async fn write_packet(
&mut self,
packet_data: Bytes,
@@ -114,7 +166,6 @@ impl UDPNetworkEncoder {
socket: &UdpSocket,
) -> Result<(), PacketEncodeError> {
socket.send_to(&packet_data, addr).await.unwrap();
Ok(())
}
}

View File

@@ -1 +1,2 @@
pub mod raknet;
pub mod request_network_settings;

View File

@@ -0,0 +1,8 @@
use pumpkin_macros::packet;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[packet(0xC1)]
pub struct SRequestNetworkSettings {
pub protocol_version: i32,
}

View File

@@ -363,7 +363,6 @@ async fn move_piston(
let mut moved_blocks_map: HashMap<BlockPos, BlockState> = HashMap::new();
let moved_blocks: Vec<BlockPos> = handler.moved_blocks;
dbg!(&moved_blocks);
let mut moved_block_states: Vec<BlockState> = Vec::new();

View File

@@ -4,9 +4,11 @@ use std::{
};
use pumpkin_protocol::{
ConnectionState,
bedrock::{
RakReliability, client::raknet::connection::CConnectionRequestAccepted,
server::raknet::connection::SConnectionRequest,
RakReliability,
client::raknet::connection::CConnectionRequestAccepted,
server::raknet::connection::{SConnectionRequest, SNewIncomingConnection},
},
codec::socket_address::SocketAddress,
};
@@ -37,4 +39,9 @@ impl Client {
)
.await;
}
pub fn handle_new_incoming_connection(&self, packet: &SNewIncomingConnection) {
dbg!(packet.pong_time);
self.connection_state.store(ConnectionState::Login);
}
}

View File

@@ -0,0 +1,27 @@
use pumpkin_protocol::bedrock::{
RakReliability, client::network_settings::CNetworkSettings,
server::request_network_settings::SRequestNetworkSettings,
};
use crate::net::{Client, bedrock::BedrockClientPlatform};
impl Client {
pub async fn handle_request_network_settings(
&self,
bedrock: &BedrockClientPlatform,
packet: SRequestNetworkSettings,
) {
dbg!("requested network settings");
self.protocol_version.store(
packet.protocol_version,
std::sync::atomic::Ordering::Relaxed,
);
bedrock
.send_game_packet(
self,
&CNetworkSettings::new(0, 0xFF, false, 0, 0.0),
RakReliability::Unreliable,
)
.await;
}
}

View File

@@ -8,17 +8,20 @@ use std::{
use bytes::Bytes;
use pumpkin_protocol::{
ClientPacket, PacketDecodeError, PacketEncodeError, ServerPacket,
ClientPacket, PacketDecodeError, PacketEncodeError, RawPacket, ServerPacket,
bedrock::{
RAKNET_ACK, RAKNET_NACK, RAKNET_VALID, RakReliability,
RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RAKNET_VALID, RakReliability,
ack::Ack,
frame_set::{Frame, FrameSet},
packet_decoder::UDPNetworkDecoder,
packet_encoder::UDPNetworkEncoder,
server::raknet::{
connection::{SConnectionRequest, SDisconnect},
open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
unconnected_ping::SUnconnectedPing,
server::{
raknet::{
connection::{SConnectionRequest, SDisconnect, SNewIncomingConnection},
open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
unconnected_ping::SUnconnectedPing,
},
request_network_settings::SRequestNetworkSettings,
},
},
codec::u24::U24,
@@ -29,6 +32,7 @@ use std::net::SocketAddr;
use tokio::{net::UdpSocket, sync::Mutex};
pub mod connection;
pub mod login;
pub mod open_connection;
pub mod unconnected;
@@ -77,7 +81,7 @@ impl BedrockClientPlatform {
}
}
pub fn write_packet<P: ClientPacket>(
pub fn write_raw_packet<P: ClientPacket>(
packet: &P,
mut write: impl Write,
) -> Result<(), WritingError> {
@@ -85,6 +89,24 @@ impl BedrockClientPlatform {
packet.write_packet_data(write)
}
pub async fn write_game_packet<P: ClientPacket>(
&self,
packet: &P,
write: impl Write,
) -> Result<(), WritingError> {
let mut packet_payload = Vec::new();
packet.write_packet_data(&mut packet_payload)?;
// TODO
self.network_writer
.lock()
.await
.write_game_packet(P::PACKET_ID, 0, 0, packet_payload.into(), write)
.await
.unwrap();
Ok(())
}
pub async fn write_packet_data(&self, packet_data: Bytes) -> Result<(), PacketEncodeError> {
self.network_writer
.lock()
@@ -93,20 +115,49 @@ impl BedrockClientPlatform {
.await
}
pub async fn send_raknet_packet_now<P: ClientPacket>(&self, client: &Client, packet: &P) {
let mut packet_buf = Vec::new();
let writer = &mut packet_buf;
Self::write_raw_packet(packet, writer).unwrap();
self.send_packet_now(client, packet_buf).await;
}
pub async fn send_game_packet<P: ClientPacket>(
&self,
client: &Client,
packet: &P,
reliability: RakReliability,
) {
let mut packet_buf = Vec::new();
self.write_game_packet(packet, &mut packet_buf)
.await
.unwrap();
self.send_framed_packet_data(client, packet_buf, reliability)
.await;
}
pub async fn send_framed_packet<P: ClientPacket>(
&self,
client: &Client,
packet: &P,
reliability: RakReliability,
) {
let mut packet_buf = Vec::new();
Self::write_raw_packet(packet, &mut packet_buf).unwrap();
self.send_framed_packet_data(client, packet_buf, reliability)
.await;
}
pub async fn send_framed_packet_data(
&self,
client: &Client,
packet_buf: Vec<u8>,
reliability: RakReliability,
) {
let mut frame_set = FrameSet {
sequence: U24(self.output_sequence_number.fetch_add(1, Ordering::Relaxed)),
frames: Vec::with_capacity(1),
};
// Todo! Calculate required capacity
let mut packet_buf = Vec::new();
Self::write_packet(packet, &mut packet_buf).unwrap();
let mut frame = Frame {
payload: packet_buf.into(),
reliability,
@@ -186,7 +237,9 @@ impl BedrockClientPlatform {
let is_valid = id & RAKNET_VALID == RAKNET_VALID;
if !is_valid {
// Offline packets just have Packet ID + Payload
return Self::handle_offline_packet(client, server, i32::from(id), payload).await;
return self
.handle_offline_packet(client, server, i32::from(id), payload)
.await;
}
self.use_frame_sets.store(true, Ordering::Relaxed);
let header = id;
@@ -203,7 +256,7 @@ impl BedrockClientPlatform {
.await;
}
_ => {
log::warn!("Received unknown online packet {header}");
log::warn!("Bedrock: Received unknown packet header {header}");
}
}
Ok(())
@@ -238,14 +291,34 @@ impl BedrockClientPlatform {
let mut payload = &frame.payload[..];
let id = payload.get_u8_be()?;
self.handle_packet(client, server, i32::from(id), payload)
self.handle_raknet_packet(client, server, i32::from(id), payload)
.await
}
async fn handle_packet(
async fn handle_game_packet(
&self,
client: &Client,
_server: &Server,
packet: RawPacket,
) -> Result<(), ReadingError> {
let payload = &packet.payload[..];
match packet.id {
SRequestNetworkSettings::PACKET_ID => {
client
.handle_request_network_settings(self, SRequestNetworkSettings::read(payload)?)
.await;
}
_ => {
log::warn!("Bedrock: Received Unknown Game packet: {}", packet.id);
}
}
Ok(())
}
async fn handle_raknet_packet(
&self,
client: &Client,
server: &Server,
packet_id: i32,
payload: &[u8],
) -> Result<(), ReadingError> {
@@ -255,18 +328,36 @@ impl BedrockClientPlatform {
.handle_connection_request(self, SConnectionRequest::read(payload)?)
.await;
}
SNewIncomingConnection::PACKET_ID => {
client.handle_new_incoming_connection(&SNewIncomingConnection::read(payload)?);
}
SDisconnect::PACKET_ID => {
dbg!("Bedrock client disconnected");
client.close();
}
RAKNET_GAME_PACKET => {
dbg!("game packet");
dbg!(payload.len());
let game_packet = self
.network_reader
.lock()
.await
.get_game_packet(Cursor::new(payload.to_vec()))
.await
.unwrap();
self.handle_game_packet(client, server, game_packet).await?;
}
_ => {
log::warn!("Received Online online packet {packet_id}");
log::warn!("Bedrock: Received Unknown RakNet Online packet: {packet_id}");
}
}
Ok(())
}
async fn handle_offline_packet(
&self,
client: &Client,
server: &Server,
packet_id: i32,
@@ -275,21 +366,21 @@ impl BedrockClientPlatform {
match packet_id {
SUnconnectedPing::PACKET_ID => {
client
.handle_unconnected_ping(server, SUnconnectedPing::read(payload)?)
.handle_unconnected_ping(self, server, SUnconnectedPing::read(payload)?)
.await;
}
SOpenConnectionRequest1::PACKET_ID => {
client
.handle_open_connection_1(server, SOpenConnectionRequest1::read(payload)?)
.handle_open_connection_1(self, server, SOpenConnectionRequest1::read(payload)?)
.await;
}
SOpenConnectionRequest2::PACKET_ID => {
client
.handle_open_connection_2(server, SOpenConnectionRequest2::read(payload)?)
.handle_open_connection_2(self, server, SOpenConnectionRequest2::read(payload)?)
.await;
}
_ => {
log::error!("Failed to handle bedrock client packet id {packet_id}");
log::error!("Bedrock: Received Unknown RakNet Offline packet: {packet_id}");
}
}
Ok(())

View File

@@ -7,25 +7,46 @@ use pumpkin_protocol::{
codec::socket_address::SocketAddress,
};
use crate::{net::Client, server::Server};
use crate::{
net::{Client, bedrock::BedrockClientPlatform},
server::Server,
};
impl Client {
pub async fn handle_open_connection_1(&self, server: &Server, packet: SOpenConnectionRequest1) {
self.send_packet_now(&COpenConnectionReply1::new(
server.server_guid,
false,
0,
packet.mtu + UDP_HEADER_SIZE,
))
.await;
pub async fn handle_open_connection_1(
&self,
bedrock: &BedrockClientPlatform,
server: &Server,
packet: SOpenConnectionRequest1,
) {
bedrock
.send_raknet_packet_now(
self,
&COpenConnectionReply1::new(
server.server_guid,
false,
0,
packet.mtu + UDP_HEADER_SIZE,
),
)
.await;
}
pub async fn handle_open_connection_2(&self, server: &Server, packet: SOpenConnectionRequest2) {
self.send_packet_now(&COpenConnectionReply2::new(
server.server_guid,
SocketAddress(*self.address.lock().await),
packet.mtu,
false,
))
.await;
pub async fn handle_open_connection_2(
&self,
bedrock: &BedrockClientPlatform,
server: &Server,
packet: SOpenConnectionRequest2,
) {
bedrock
.send_raknet_packet_now(
self,
&COpenConnectionReply2::new(
server.server_guid,
SocketAddress(*self.address.lock().await),
packet.mtu,
false,
),
)
.await;
}
}

View File

@@ -7,10 +7,18 @@ use pumpkin_protocol::{
codec::ascii_string::AsciiString,
};
use crate::{net::Client, server::Server};
use crate::{
net::{Client, bedrock::BedrockClientPlatform},
server::Server,
};
impl Client {
pub async fn handle_unconnected_ping(&self, server: &Server, packet: SUnconnectedPing) {
pub async fn handle_unconnected_ping(
&self,
bedrock: &BedrockClientPlatform,
server: &Server,
packet: SUnconnectedPing,
) {
let motd_string = ServerInfo {
edition: "MCPE",
motd_line_1: &BASIC_CONFIG.motd,
@@ -25,12 +33,16 @@ impl Client {
port_ipv4: 19132,
port_ipv6: 19133,
};
self.send_packet_now(&CUnconnectedPong::new(
packet.time,
server.server_guid,
packet.magic,
AsciiString(format!("{motd_string}")),
))
.await;
bedrock
.send_raknet_packet_now(
self,
&CUnconnectedPong::new(
packet.time,
server.server_guid,
packet.magic,
AsciiString(format!("{motd_string}")),
),
)
.await;
}
}

View File

@@ -163,10 +163,7 @@ impl JavaClientPlatform {
}
// This way players get kicked when players using client functions (e.g. poll, send_packet)
ConnectionState::Play => client.send_packet_now(&CPlayDisconnect::new(&reason)).await,
_ => {
log::warn!("Can't kick in {:?} State", client.connection_state);
return;
}
_ => {}
}
log::debug!("Closing connection for {}", client.id);
client.close();

View File

@@ -158,14 +158,14 @@ impl ClientPlatform {
}
}
pub fn write_packet<P: ClientPacket>(
pub async fn write_packet<P: ClientPacket>(
&self,
packet: &P,
write: impl Write,
) -> Result<(), WritingError> {
match self {
Self::Java(_) => JavaClientPlatform::write_packet(packet, write),
Self::Bedrock(_) => BedrockClientPlatform::write_packet(packet, write),
Self::Bedrock(bedrock) => bedrock.write_game_packet(packet, write).await,
}
}
@@ -362,7 +362,7 @@ impl Client {
{
let mut buf = Vec::new();
let writer = &mut buf;
self.platform.write_packet(packet, writer).unwrap();
self.platform.write_packet(packet, writer).await.unwrap();
self.enqueue_packet_data(buf.into()).await;
}
@@ -397,7 +397,7 @@ impl Client {
pub async fn send_packet_now<P: ClientPacket>(&self, packet: &P) {
let mut packet_buf = Vec::new();
let writer = &mut packet_buf;
self.platform.write_packet(packet, writer).unwrap();
self.platform.write_packet(packet, writer).await.unwrap();
self.platform.send_packet_now(self, packet_buf).await;
}