Some refactors

This commit is contained in:
Alexander Medvedev 2024-12-24 00:56:19 +01:00
parent fd667901cd
commit 3dae300f81
72 changed files with 333 additions and 284 deletions

View File

@ -3,6 +3,13 @@ name = "pumpkin-protocol"
version.workspace = true
edition.workspace = true
[features]
default = ["packets", "query"]
packets = ["serverbound", "clientbound"]
serverbound = []
clientbound = []
query = []
[dependencies]
pumpkin-nbt = { path = "../pumpkin-nbt" }
pumpkin-config = { path = "../pumpkin-config" }

View File

@ -1,4 +1,4 @@
use crate::slot::Slot;
use crate::codec::slot::Slot;
use crate::VarInt;
use pumpkin_macros::client_packet;

View File

@ -1,4 +1,4 @@
use crate::slot::Slot;
use crate::codec::slot::Slot;
use crate::VarInt;
use pumpkin_macros::client_packet;

View File

@ -5,6 +5,7 @@ use thiserror::Error;
pub mod bit_set;
pub mod identifier;
pub mod slot;
pub mod var_int;
pub mod var_long;

View File

@ -7,13 +7,15 @@ use pumpkin_core::text::{style::Style, TextComponent};
use serde::{Deserialize, Serialize};
pub mod bytebuf;
#[cfg(feature = "clientbound")]
pub mod client;
pub mod codec;
pub mod packet_decoder;
pub mod packet_encoder;
#[cfg(feature = "query")]
pub mod query;
#[cfg(feature = "serverbound")]
pub mod server;
pub mod slot;
/// To current Minecraft protocol
/// Don't forget to change this when porting

View File

@ -16,8 +16,8 @@ type Cipher = cfb8::Decryptor<aes::Aes128>;
pub struct PacketDecoder {
buf: BytesMut,
decompress_buf: BytesMut,
compression: bool,
cipher: Option<Cipher>,
compression: bool,
decompressor: Decompressor,
}
@ -28,8 +28,8 @@ impl Default for PacketDecoder {
Self {
buf: BytesMut::new(),
decompress_buf: BytesMut::new(),
compression: false,
cipher: None,
compression: false,
decompressor: Decompressor::new(),
}
}

View File

@ -15,8 +15,8 @@ type Cipher = cfb8::Encryptor<aes::Aes128>;
pub struct PacketEncoder {
buf: BytesMut,
compress_buf: Vec<u8>,
compression_threshold: Option<u32>,
cipher: Option<Cipher>,
compression_threshold: Option<u32>,
compressor: Compressor, // Reuse the compressor for all packets
}
@ -27,8 +27,8 @@ impl Default for PacketEncoder {
Self {
buf: BytesMut::with_capacity(1024),
compress_buf: Vec::with_capacity(1024),
compression_threshold: None,
cipher: None,
compression_threshold: None,
compressor: Compressor::new(CompressionLvl::fastest()), // init compressor with fastest compression level
}
}

View File

@ -11,7 +11,7 @@ use crate::{
#[server_packet("config:cookie_response")]
/// Response to a Cookie Request (configuration) from the server.
/// The Notchian (vanilla) server only accepts responses of up to 5 kiB in size.
pub struct SCookieResponse {
pub struct SConfigCookieResponse {
pub key: Identifier,
pub has_payload: bool,
pub payload_length: Option<VarInt>,
@ -20,7 +20,7 @@ pub struct SCookieResponse {
const MAX_PAYLOAD_SIZE: i32 = 5120;
impl ServerPacket for SCookieResponse {
impl ServerPacket for SConfigCookieResponse {
fn read(bytebuf: &mut impl Buf) -> Result<Self, ReadingError> {
let key = bytebuf.try_get_identifer()?;
let has_payload = bytebuf.try_get_bool()?;

View File

@ -10,7 +10,7 @@ use serde::de;
#[server_packet("login:cookie_response")]
/// Response to a Cookie Request (login) from the server.
/// The Notchian server only accepts responses of up to 5 kiB in size.
pub struct SCookieResponse {
pub struct SLoginCookieResponse {
pub key: Identifier,
pub has_payload: bool,
pub payload_length: Option<VarInt>,
@ -19,7 +19,7 @@ pub struct SCookieResponse {
const MAX_PAYLOAD_SIZE: i32 = 5120;
impl ServerPacket for SCookieResponse {
impl ServerPacket for SLoginCookieResponse {
fn read(bytebuf: &mut impl Buf) -> Result<Self, ReadingError> {
let key = bytebuf.try_get_identifer()?;
let has_payload = bytebuf.try_get_bool()?;

View File

@ -1,4 +1,4 @@
use crate::slot::Slot;
use crate::codec::slot::Slot;
use crate::VarInt;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;

View File

@ -1,6 +1,6 @@
use pumpkin_macros::server_packet;
use crate::slot::Slot;
use crate::codec::slot::Slot;
#[derive(serde::Deserialize, Debug)]
#[server_packet("play:set_creative_mode_slot")]

View File

@ -661,7 +661,7 @@ mod test {
use crate::{
block::BlockState,
world_gen::{
generation::{
chunk_noise::{
BlockStateSampler, ChunkNoiseDensityFunctions, ChunkNoiseGenerator,
ChunkNoiseState, LAVA_BLOCK, WATER_BLOCK,

View File

@ -6,11 +6,11 @@ use pumpkin_macros::block_state;
use crate::{
block::BlockState,
match_ref_implementations,
world_gen::{
generation::{
noise::{density::basic::WrapperType, lerp3},
section_coords,
},
match_ref_implementations,
};
use super::{
@ -1379,7 +1379,7 @@ impl ChunkNoiseGenerator {
mod test {
use pumpkin_core::math::vector2::Vector2;
use crate::world_gen::{
use crate::generation::{
aquifer_sampler::{FluidLevel, FluidLevelSampler},
generation_shapes::GenerationShape,
noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER},

View File

@ -6,7 +6,7 @@ use crate::biome::Biome;
use crate::block::block_state::BlockState;
use crate::chunk::{ChunkBlocks, ChunkData};
use crate::coordinates::{BlockCoordinates, ChunkRelativeBlockCoordinates, XZBlockCoordinates};
use crate::world_gen::Seed;
use crate::generation::Seed;
pub trait GeneratorInit {
fn new(seed: Seed) -> Self;

View File

@ -7,7 +7,7 @@ use crate::{
biome::Biome,
chunk::ChunkBlocks,
coordinates::{BlockCoordinates, ChunkRelativeBlockCoordinates, XZBlockCoordinates},
world_gen::{
generation::{
generator::{BiomeGenerator, GeneratorInit, PerlinTerrainGenerator},
generic_generator::GenericGenerator,
Seed,

View File

@ -4,7 +4,7 @@ use crate::{
biome::Biome,
block::block_state::BlockState,
coordinates::XZBlockCoordinates,
world_gen::{
generation::{
generator::{BiomeGenerator, GeneratorInit, TerrainGenerator},
generic_generator::GenericGenerator,
Seed,

View File

@ -14,7 +14,7 @@ use crate::{
coordinates::{
ChunkRelativeBlockCoordinates, ChunkRelativeXZBlockCoordinates, XZBlockCoordinates,
},
world_gen::{
generation::{
generator::{BiomeGenerator, GeneratorInit, TerrainGenerator},
proto_chunk::ProtoChunk,
Seed, WorldGenerator,

View File

@ -151,8 +151,7 @@ mod test {
};
use crate::{
read_data_from_file,
world_gen::noise::{
generation::noise::{
config::NoiseConfig,
density::{
built_in_density_function::{
@ -165,6 +164,7 @@ mod test {
},
router::OVERWORLD_NOISE_ROUTER,
},
read_data_from_file,
};
use super::LegacyChunkNoiseVisitor;

View File

@ -1,6 +1,6 @@
use std::{hash::Hash, marker::PhantomData};
use crate::{match_ref_implementations, world_gen::noise::clamped_map};
use crate::{generation::noise::clamped_map, match_ref_implementations};
use super::{
component_functions::{
@ -419,10 +419,10 @@ mod test {
use std::{fs, path::Path};
use crate::{
read_data_from_file,
world_gen::noise::density::{
generation::noise::density::{
component_functions::ImmutableComponentFunctionImpl, NoisePos, UnblendedNoisePos,
},
read_data_from_file,
};
use super::YClampedFunction;

View File

@ -1,6 +1,6 @@
use std::marker::PhantomData;
use crate::world_gen::blender::BlenderImpl;
use crate::generation::blender::BlenderImpl;
use super::{
component_functions::{

View File

@ -953,7 +953,7 @@ mod test {
use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl};
use crate::world_gen::noise::{
use crate::generation::noise::{
built_in_noise_params,
density::{
noise::{InternalNoise, NoiseFunction},

View File

@ -1,6 +1,6 @@
use pumpkin_core::random::{legacy_rand::LegacyRand, RandomImpl};
use crate::world_gen::noise::simplex::SimplexNoiseSampler;
use crate::generation::noise::simplex::SimplexNoiseSampler;
use super::{
component_functions::{

View File

@ -10,7 +10,7 @@ use component_functions::{
use enum_dispatch::enum_dispatch;
use noise::{InternalNoise, NoiseFunction};
use crate::world_gen::{blender::Blender, chunk_noise::ChunkNoisePos};
use crate::generation::{blender::Blender, chunk_noise::ChunkNoisePos};
use super::perlin::DoublePerlinNoiseParameters;
@ -76,8 +76,8 @@ pub trait NoisePosImpl {
pub mod built_in_density_function {
use std::sync::{Arc, LazyLock};
use crate::world_gen::noise::built_in_noise_params::{self};
use crate::world_gen::positions::{MAX_COLUMN_HEIGHT, MIN_HEIGHT};
use crate::generation::noise::built_in_noise_params::{self};
use crate::generation::positions::{MAX_COLUMN_HEIGHT, MIN_HEIGHT};
use pumpkin_core::math::floor_div;
@ -879,7 +879,7 @@ mod test {
legacy_rand::LegacyRand, RandomDeriver, RandomDeriverImpl, RandomImpl,
};
use crate::world_gen::noise::{
use crate::generation::noise::{
built_in_noise_params,
density::{built_in_density_function::*, NoisePos, UnblendedNoisePos},
perlin::DoublePerlinNoiseSampler,

View File

@ -3,11 +3,11 @@ use std::{marker::PhantomData, sync::Arc};
use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomGenerator, RandomImpl};
use crate::{
match_ref_implementations,
world_gen::noise::{
generation::noise::{
clamped_lerp,
perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler, OctavePerlinNoiseSampler},
},
match_ref_implementations,
};
use super::{

View File

@ -2,7 +2,7 @@ use std::{marker::PhantomData, sync::Arc};
use enum_dispatch::enum_dispatch;
use crate::world_gen::noise::lerp;
use crate::generation::noise::lerp;
use super::{
component_functions::{
@ -744,7 +744,7 @@ mod test {
use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl};
use crate::world_gen::noise::density::{
use crate::generation::noise::density::{
built_in_density_function::CONTINENTS_OVERWORLD,
component_functions::{ComponentReference, NoEnvironment, SharedComponentReference},
test::{FakeEnvironment, OwnedConverter, TestConverter},

View File

@ -1,7 +1,7 @@
// From da java
use crate::world_gen::noise::density::peaks_valleys_noise;
use crate::world_gen::noise::lerp;
use crate::generation::noise::density::peaks_valleys_noise;
use crate::generation::noise::lerp;
use super::component_functions::SharedComponentReference;
use super::spline::{FloatAmplifier, ImmutableSpline, ImmutableSplineRef, SplineBuilder};
@ -523,7 +523,7 @@ pub fn create_jaggedness_spline(
mod test {
use pumpkin_core::random::{legacy_rand::LegacyRand, RandomDeriver, RandomImpl};
use crate::world_gen::noise::density::{
use crate::generation::noise::density::{
built_in_density_function::{
CONTINENTS_OVERWORLD, EROSION_OVERWORLD, RIDGES_FOLDED_OVERWORLD, RIDGES_OVERWORLD,
},

View File

@ -381,7 +381,7 @@ mod double_perlin_noise_sampler_test {
legacy_rand::LegacyRand, xoroshiro128::Xoroshiro, RandomGenerator, RandomImpl,
};
use crate::world_gen::noise::perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler};
use crate::generation::noise::perlin::{DoublePerlinNoiseParameters, DoublePerlinNoiseSampler};
#[test]
fn sample_legacy() {
@ -760,7 +760,7 @@ mod perlin_noise_sampler_test {
random::{xoroshiro128::Xoroshiro, RandomDeriverImpl, RandomGenerator, RandomImpl},
};
use crate::{read_data_from_file, world_gen::noise::perlin::PerlinNoiseSampler};
use crate::{generation::noise::perlin::PerlinNoiseSampler, read_data_from_file};
use super::OctavePerlinNoiseSampler;

View File

@ -1,6 +1,6 @@
use std::sync::{Arc, LazyLock};
use crate::world_gen::{
use crate::generation::{
noise::density::{
apply_blend_density,
basic::RangeFunction,
@ -476,8 +476,7 @@ mod test {
};
use crate::{
read_data_from_file,
world_gen::noise::{
generation::noise::{
config::LegacyChunkNoiseVisitor,
density::{
built_in_density_function::{EROSION_OVERWORLD, SLOPED_CHEESE_OVERWORLD},
@ -491,6 +490,7 @@ mod test {
perlin::DoublePerlinNoiseSampler,
router::OVERWORLD_NOISE_ROUTER,
},
read_data_from_file,
};
use super::{apply_surface_slides, create_caves};

View File

@ -276,7 +276,7 @@ impl OctaveSimplexNoiseSampler {
mod octave_simplex_noise_sampler_test {
use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl};
use crate::world_gen::noise::simplex::OctaveSimplexNoiseSampler;
use crate::generation::noise::simplex::OctaveSimplexNoiseSampler;
#[test]
fn test_new() {
@ -413,7 +413,7 @@ mod simplex_noise_sampler_test {
use pumpkin_core::random::{xoroshiro128::Xoroshiro, RandomImpl};
use crate::world_gen::noise::simplex::SimplexNoiseSampler;
use crate::generation::noise::simplex::SimplexNoiseSampler;
#[test]
fn test_create() {

View File

@ -2,7 +2,7 @@ use pumpkin_core::math::{vector2::Vector2, vector3::Vector3};
use crate::{
block::BlockState,
world_gen::{
generation::{
chunk_noise::CHUNK_DIM,
generation_shapes::GenerationShape,
noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER},

View File

@ -14,7 +14,7 @@ use crate::{
chunk::{
anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError,
},
world_gen::{get_world_gen, Seed, WorldGenerator},
generation::{get_world_gen, Seed, WorldGenerator},
};
/// The `Level` module provides functionality for working with chunks within or outside a Minecraft world.

View File

@ -1,11 +1,11 @@
use pumpkin_core::math::vector2::Vector2;
use world_gen::{
use generation::{
aquifer_sampler::{FluidLevel, FluidLevelSampler},
chunk_noise::{ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK},
generation_shapes::GenerationShape,
noise::{config::NoiseConfig, router::OVERWORLD_NOISE_ROUTER},
proto_chunk::{ProtoChunk, StandardChunkFluidLevelSampler},
};
use pumpkin_core::math::vector2::Vector2;
pub mod biome;
pub mod block;
@ -13,9 +13,9 @@ pub mod chunk;
pub mod coordinates;
pub mod cylindrical_chunk_iterator;
pub mod dimension;
mod generation;
pub mod item;
pub mod level;
mod world_gen;
pub const WORLD_HEIGHT: usize = 384;
pub const WORLD_LOWEST_Y: i16 = -64;

View File

@ -57,10 +57,9 @@ use pumpkin_world::{
use tokio::sync::{Mutex, Notify};
use super::Entity;
use crate::error::PumpkinError;
use crate::{error::PumpkinError, net::GameProfile};
use crate::{
client::{
authentication::GameProfile,
net::{
combat::{self, player_attack_sound, AttackType},
Client, PlayerConfig,
},

View File

@ -35,7 +35,7 @@ compile_error!("Compiling for WASI targets is not supported!");
use log::LevelFilter;
use client::Client;
use net::{lan_broadcast, query, rcon::RCONServer, Client};
use server::{ticker::Ticker, Server};
use std::io::{self};
use tokio::io::{AsyncBufReadExt, BufReader};
@ -50,19 +50,14 @@ use crate::server::CURRENT_MC_VERSION;
use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
use pumpkin_core::text::{color::NamedColor, TextComponent};
use pumpkin_protocol::CURRENT_MC_PROTOCOL;
use rcon::RCONServer;
use std::time::Instant;
// Setup some tokens to allow us to identify which event is for which socket.
pub mod block;
pub mod client;
pub mod command;
pub mod entity;
pub mod error;
pub mod lan_broadcast;
pub mod proxy;
pub mod query;
pub mod rcon;
pub mod net;
pub mod server;
pub mod world;

View File

@ -2,15 +2,14 @@ use std::{collections::HashMap, net::IpAddr};
use base64::{engine::general_purpose, Engine};
use pumpkin_config::{auth::TextureConfig, ADVANCED_CONFIG};
use pumpkin_core::ProfileAction;
use pumpkin_protocol::Property;
use reqwest::{StatusCode, Url};
use serde::Deserialize;
use sha1::Digest;
use sha2::Sha256;
use thiserror::Error;
use uuid::Uuid;
use super::GameProfile;
#[derive(Deserialize, Clone, Debug)]
#[expect(dead_code)]
#[serde(rename_all = "camelCase")]
@ -29,14 +28,8 @@ pub struct Texture {
metadata: Option<HashMap<String, String>>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct GameProfile {
pub id: Uuid,
pub name: String,
pub properties: Vec<Property>,
#[serde(rename = "profileActions")]
pub profile_actions: Option<Vec<ProfileAction>>,
}
const MOJANG_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}";
const MOJANG_PREVENT_PROXY_AUTHENTICATION_URL: &str = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}";
/// Sends a GET request to Mojang's authentication servers to verify a client's Minecraft account.
///
@ -50,20 +43,19 @@ pub struct GameProfile {
/// 2. Mojang's servers verify the client's credentials and add the player to the their Servers
/// 3. Now our server will send a Request to the Session servers and check if the Player has joined the Session Server .
///
/// See <https://snowiiii.github.io/Pumpkin/developer/authentication.html>
/// See <https://snowiiii.github.io/Pumpkin-Website/developer/authentication.html>
pub async fn authenticate(
username: &str,
server_hash: &str,
ip: &IpAddr,
auth_client: &reqwest::Client,
) -> Result<GameProfile, AuthError> {
assert!(ADVANCED_CONFIG.authentication.enabled);
let address = if ADVANCED_CONFIG.authentication.prevent_proxy_connections {
let auth_url = ADVANCED_CONFIG
.authentication
.prevent_proxy_connection_auth_url
.as_deref()
.unwrap_or("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}");
.unwrap_or(MOJANG_PREVENT_PROXY_AUTHENTICATION_URL);
auth_url
.replace("{username}", username)
@ -74,7 +66,7 @@ pub async fn authenticate(
.authentication
.auth_url
.as_deref()
.unwrap_or("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}");
.unwrap_or(MOJANG_AUTHENTICATION_URL);
auth_url
.replace("{username}", username)
@ -129,10 +121,6 @@ pub fn is_texture_url_valid(url: &Url, config: &TextureConfig) -> Result<(), Tex
Ok(())
}
pub fn offline_uuid(username: &str) -> Result<Uuid, uuid::Error> {
Uuid::from_slice(&Sha256::digest(username)[..16])
}
#[derive(Error, Debug)]
pub enum AuthError {
#[error("Missing auth client")]

View File

@ -12,9 +12,9 @@ use pumpkin_inventory::{Container, WindowType};
use pumpkin_protocol::client::play::{
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
};
use pumpkin_protocol::codec::slot::Slot;
use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::server::play::SClickContainer;
use pumpkin_protocol::slot::Slot;
use pumpkin_world::item::item_registry::Item;
use pumpkin_world::item::ItemStack;
use std::sync::Arc;

View File

@ -13,34 +13,57 @@ use crate::{
server::Server,
};
use authentication::GameProfile;
use crossbeam::atomic::AtomicCell;
use pumpkin_config::compression::CompressionInfo;
use pumpkin_core::text::TextComponent;
use pumpkin_core::{text::TextComponent, ProfileAction};
use pumpkin_protocol::{
bytebuf::{packet_id::Packet, ReadingError},
client::{config::CConfigDisconnect, login::CLoginDisconnect, play::CPlayDisconnect},
packet_decoder::PacketDecoder,
packet_encoder::{PacketEncodeError, PacketEncoder},
server::{
config::{SAcknowledgeFinishConfig, SClientInformationConfig, SKnownPacks, SPluginMessage},
config::{
SAcknowledgeFinishConfig, SClientInformationConfig, SConfigCookieResponse, SKnownPacks,
SPluginMessage,
},
handshake::SHandShake,
login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart},
login::{
SEncryptionResponse, SLoginAcknowledged, SLoginCookieResponse, SLoginPluginResponse,
SLoginStart,
},
status::{SStatusPingRequest, SStatusRequest},
},
ClientPacket, ConnectionState, RawPacket, ServerPacket,
ClientPacket, ConnectionState, Property, RawPacket, ServerPacket,
};
use serde::Deserialize;
use sha1::Digest;
use sha2::Sha256;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::Mutex;
use pumpkin_protocol::server::config::SCookieResponse as SCCookieResponse;
use pumpkin_protocol::server::login::SCookieResponse as SLCookieResponse;
use thiserror::Error;
pub mod authentication;
mod client_packet;
use uuid::Uuid;
mod authentication;
pub mod combat;
mod container;
pub mod player_packet;
pub mod lan_broadcast;
mod packet;
mod proxy;
pub mod query;
pub mod rcon;
#[derive(Deserialize, Clone, Debug)]
pub struct GameProfile {
pub id: Uuid,
pub name: String,
pub properties: Vec<Property>,
#[serde(rename = "profileActions")]
pub profile_actions: Option<Vec<ProfileAction>>,
}
pub fn offline_uuid(username: &str) -> Result<Uuid, uuid::Error> {
Uuid::from_slice(&Sha256::digest(username)[..16])
}
/// Represents a player's configuration settings.
///
@ -102,8 +125,6 @@ pub struct Client {
pub server_address: Mutex<String>,
/// The current connection state of the client (e.g., Handshaking, Status, Play).
pub connection_state: AtomicCell<ConnectionState>,
/// Whether encryption is enabled for the connection.
pub encryption: AtomicBool,
/// Indicates if the client connection is closed.
pub closed: AtomicBool,
/// The underlying TCP connection to the client.
@ -138,7 +159,6 @@ impl Client {
connection_writer: Arc::new(Mutex::new(connection_writer)),
enc: Arc::new(Mutex::new(PacketEncoder::default())),
dec: Arc::new(Mutex::new(PacketDecoder::default())),
encryption: AtomicBool::new(false),
closed: AtomicBool::new(false),
client_packets_queue: Arc::new(Mutex::new(VecDeque::new())),
make_player: AtomicBool::new(false),
@ -182,8 +202,6 @@ impl Client {
shared_secret: Option<&[u8]>, // decrypted
) -> Result<(), EncryptionError> {
if let Some(shared_secret) = shared_secret {
self.encryption
.store(true, std::sync::atomic::Ordering::Relaxed);
let crypt_key: [u8; 16] = shared_secret
.try_into()
.map_err(|_| EncryptionError::SharedWrongLength)?;
@ -433,8 +451,8 @@ impl Client {
SLoginAcknowledged::PACKET_ID => {
self.handle_login_acknowledged(server).await;
}
SLCookieResponse::PACKET_ID => {
self.handle_login_cookie_response(SLCookieResponse::read(bytebuf)?);
SLoginCookieResponse::PACKET_ID => {
self.handle_login_cookie_response(SLoginCookieResponse::read(bytebuf)?);
}
_ => {
log::error!(
@ -469,8 +487,8 @@ impl Client {
self.handle_known_packs(server, SKnownPacks::read(bytebuf)?)
.await;
}
SCCookieResponse::PACKET_ID => {
self.handle_config_cookie_response(SCCookieResponse::read(bytebuf)?);
SConfigCookieResponse::PACKET_ID => {
self.handle_config_cookie_response(SConfigCookieResponse::read(bytebuf)?);
}
_ => {
log::error!(

View File

@ -0,0 +1,98 @@
use std::num::NonZeroU8;
use crate::{
entity::player::{ChatMode, Hand},
net::{Client, PlayerConfig},
server::Server,
};
use core::str;
use num_traits::FromPrimitive;
use pumpkin_protocol::{
client::config::{CFinishConfig, CRegistryData},
codec::var_int::VarInt,
server::config::{
SClientInformationConfig, SConfigCookieResponse, SKnownPacks, SPluginMessage,
},
ConnectionState,
};
impl Client {
pub async fn handle_client_information_config(
&self,
client_information: SClientInformationConfig,
) {
log::debug!("Handling client settings");
if client_information.view_distance <= 0 {
self.kick("Cannot have zero or negative view distance!")
.await;
return;
}
if let (Some(main_hand), Some(chat_mode)) = (
Hand::from_i32(client_information.main_hand.into()),
ChatMode::from_i32(client_information.chat_mode.into()),
) {
*self.config.lock().await = Some(PlayerConfig {
locale: client_information.locale,
view_distance: unsafe {
NonZeroU8::new_unchecked(client_information.view_distance as u8)
},
chat_mode,
chat_colors: client_information.chat_colors,
skin_parts: client_information.skin_parts,
main_hand,
text_filtering: client_information.text_filtering,
server_listing: client_information.server_listing,
});
} else {
self.kick("Invalid hand or chat type").await;
}
}
pub async fn handle_plugin_message(&self, plugin_message: SPluginMessage) {
log::debug!("Handling plugin message");
if plugin_message
.channel
.to_string()
.starts_with("minecraft:brand")
{
log::debug!("got a client brand");
match str::from_utf8(&plugin_message.data) {
Ok(brand) => *self.brand.lock().await = Some(brand.to_string()),
Err(e) => self.kick(&e.to_string()).await,
}
}
}
pub fn handle_config_cookie_response(&self, packet: SConfigCookieResponse) {
// TODO: allow plugins to access this
log::debug!(
"Received cookie_response[config]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"",
packet.key.to_string(),
packet.has_payload,
packet.payload_length.unwrap_or(VarInt::from(0)).0
);
}
pub async fn handle_known_packs(&self, server: &Server, _config_acknowledged: SKnownPacks) {
log::debug!("Handling known packs");
for registry in &server.cached_registry {
self.send_packet(&CRegistryData::new(
&registry.registry_id,
&registry.registry_entries,
))
.await;
}
// We are done with configuring
log::debug!("finished config");
self.send_packet(&CFinishConfig::new()).await;
}
pub fn handle_config_acknowledged(&self) {
log::debug!("Handling config acknowledge");
self.connection_state.store(ConnectionState::Play);
self.make_player
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}

View File

@ -0,0 +1,29 @@
use std::num::NonZeroI32;
use pumpkin_protocol::{server::handshake::SHandShake, ConnectionState, CURRENT_MC_PROTOCOL};
use crate::{net::Client, server::CURRENT_MC_VERSION};
impl Client {
pub async fn handle_handshake(&self, handshake: SHandShake) {
let version = handshake.protocol_version.0;
self.protocol_version
.store(version, std::sync::atomic::Ordering::Relaxed);
*self.server_address.lock().await = handshake.server_address;
log::debug!("Handshake: next state {:?}", &handshake.next_state);
self.connection_state.store(handshake.next_state);
if self.connection_state.load() != ConnectionState::Status {
let protocol = version;
match protocol.cmp(&NonZeroI32::from(CURRENT_MC_PROTOCOL).get()) {
std::cmp::Ordering::Less => {
self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await;
}
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await;
}
}
}
}
}

View File

@ -1,43 +1,30 @@
use super::{authentication::AuthError, Client, PlayerConfig};
use crate::{
client::authentication::{self, offline_uuid, validate_textures, GameProfile},
entity::player::{ChatMode, Hand},
proxy::{
bungeecord,
velocity::{self, velocity_login},
},
server::{Server, CURRENT_MC_VERSION},
};
use core::str;
use num_traits::FromPrimitive;
use std::sync::LazyLock;
use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
use pumpkin_core::text::TextComponent;
use pumpkin_protocol::server::config::SCookieResponse as SCCookieResponse;
use pumpkin_protocol::server::login::SCookieResponse as SLCookieResponse;
use pumpkin_protocol::{
client::config::{CServerLinks, Label, Link, LinkType},
codec::var_int::VarInt,
};
use pumpkin_protocol::{
client::{
config::{CConfigAddResourcePack, CFinishConfig, CKnownPacks, CRegistryData},
config::{CConfigAddResourcePack, CKnownPacks},
login::{CLoginSuccess, CSetCompression},
status::CPingResponse,
play::{CServerLinks, Label, Link, LinkType},
},
server::{
config::{SClientInformationConfig, SKnownPacks, SPluginMessage},
handshake::SHandShake,
login::{SEncryptionResponse, SLoginPluginResponse, SLoginStart},
status::SStatusPingRequest,
},
ConnectionState, KnownPack, CURRENT_MC_PROTOCOL,
};
use std::{
num::{NonZeroI32, NonZeroU8},
sync::LazyLock,
codec::var_int::VarInt,
server::login::{SEncryptionResponse, SLoginCookieResponse, SLoginPluginResponse, SLoginStart},
ConnectionState, KnownPack,
};
use uuid::Uuid;
use crate::{
net::{
authentication::{self, AuthError},
offline_uuid,
packet::is_valid_player_name,
proxy::{bungeecord, velocity},
Client, GameProfile,
},
server::Server,
};
static LINKS: LazyLock<Vec<Link>> = LazyLock::new(|| {
let mut links: Vec<Link> = Vec::new();
@ -98,49 +85,7 @@ static LINKS: LazyLock<Vec<Link>> = LazyLock::new(|| {
links
});
/// Processes incoming Packets from the Client to the Server
/// Implements the `Client` Packets
/// NEVER TRUST THE CLIENT. HANDLE EVERY ERROR, UNWRAP/EXPECT
impl Client {
pub async fn handle_handshake(&self, handshake: SHandShake) {
let version = handshake.protocol_version.0;
self.protocol_version
.store(version, std::sync::atomic::Ordering::Relaxed);
*self.server_address.lock().await = handshake.server_address;
log::debug!("Handshake: next state {:?}", &handshake.next_state);
self.connection_state.store(handshake.next_state);
if self.connection_state.load() != ConnectionState::Status {
let protocol = version;
match protocol.cmp(&NonZeroI32::from(CURRENT_MC_PROTOCOL).get()) {
std::cmp::Ordering::Less => {
self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await;
}
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await;
}
}
}
}
pub async fn handle_status_request(&self, server: &Server) {
log::debug!("Handling status request");
let status = server.get_status();
self.send_packet(&status.lock().await.get_status()).await;
}
pub async fn handle_ping_request(&self, ping_request: SStatusPingRequest) {
log::debug!("Handling ping request");
self.send_packet(&CPingResponse::new(ping_request.payload))
.await;
self.close();
}
fn is_valid_player_name(name: &str) -> bool {
name.len() <= 16 && name.chars().all(|c| c > 32u8 as char && c < 127u8 as char)
}
pub async fn handle_login_start(&self, server: &Server, login_start: SLoginStart) {
log::debug!("login start");
@ -154,7 +99,7 @@ impl Client {
return;
}
if !Self::is_valid_player_name(&login_start.name) {
if !is_valid_player_name(&login_start.name) {
self.kick("Invalid characters in username").await;
return;
}
@ -164,9 +109,15 @@ impl Client {
let proxy = &ADVANCED_CONFIG.proxy;
if proxy.enabled {
if proxy.velocity.enabled {
velocity_login(self).await;
velocity::velocity_login(self).await;
} else if proxy.bungeecord.enabled {
match bungeecord::bungeecord_login(self, login_start.name).await {
match bungeecord::bungeecord_login(
&self.address,
&self.server_address.lock().await,
login_start.name,
)
.await
{
Ok((_ip, profile)) => {
// self.address.lock() = ip;
self.finish_login(&profile).await;
@ -309,7 +260,10 @@ impl Client {
}
// validate textures
for property in &profile.properties {
validate_textures(property, &ADVANCED_CONFIG.authentication.textures)
authentication::validate_textures(
property,
&ADVANCED_CONFIG.authentication.textures,
)
.map_err(AuthError::TextureError)?;
}
return Ok(profile);
@ -317,7 +271,7 @@ impl Client {
Err(AuthError::MissingAuthClient)
}
pub fn handle_login_cookie_response(&self, packet: SLCookieResponse) {
pub fn handle_login_cookie_response(&self, packet: SLoginCookieResponse) {
// TODO: allow plugins to access this
log::debug!(
"Received cookie_response[login]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"",
@ -326,7 +280,6 @@ impl Client {
packet.payload_length.unwrap_or(VarInt::from(0)).0
);
}
pub async fn handle_plugin_response(&self, plugin_response: SLoginPluginResponse) {
log::debug!("Handling plugin");
let velocity_config = &ADVANCED_CONFIG.proxy.velocity;
@ -386,82 +339,4 @@ impl Client {
.await;
log::debug!("login acknowledged");
}
pub async fn handle_client_information_config(
&self,
client_information: SClientInformationConfig,
) {
log::debug!("Handling client settings");
if client_information.view_distance <= 0 {
self.kick("Cannot have zero or negative view distance!")
.await;
return;
}
if let (Some(main_hand), Some(chat_mode)) = (
Hand::from_i32(client_information.main_hand.into()),
ChatMode::from_i32(client_information.chat_mode.into()),
) {
*self.config.lock().await = Some(PlayerConfig {
locale: client_information.locale,
view_distance: unsafe {
NonZeroU8::new_unchecked(client_information.view_distance as u8)
},
chat_mode,
chat_colors: client_information.chat_colors,
skin_parts: client_information.skin_parts,
main_hand,
text_filtering: client_information.text_filtering,
server_listing: client_information.server_listing,
});
} else {
self.kick("Invalid hand or chat type").await;
}
}
pub async fn handle_plugin_message(&self, plugin_message: SPluginMessage) {
log::debug!("Handling plugin message");
if plugin_message
.channel
.to_string()
.starts_with("minecraft:brand")
{
log::debug!("got a client brand");
match str::from_utf8(&plugin_message.data) {
Ok(brand) => *self.brand.lock().await = Some(brand.to_string()),
Err(e) => self.kick(&e.to_string()).await,
}
}
}
pub fn handle_config_cookie_response(&self, packet: SCCookieResponse) {
// TODO: allow plugins to access this
log::debug!(
"Received cookie_response[config]: key: \"{}\", has_payload: \"{}\", payload_length: \"{}\"",
packet.key.to_string(),
packet.has_payload,
packet.payload_length.unwrap_or(VarInt::from(0)).0
);
}
pub async fn handle_known_packs(&self, server: &Server, _config_acknowledged: SKnownPacks) {
log::debug!("Handling known packs");
for registry in &server.cached_registry {
self.send_packet(&CRegistryData::new(
&registry.registry_id,
&registry.registry_entries,
))
.await;
}
// We are done with configuring
log::debug!("finished config");
self.send_packet(&CFinishConfig::new()).await;
}
pub fn handle_config_acknowledged(&self) {
log::debug!("Handling config acknowledge");
self.connection_state.store(ConnectionState::Play);
self.make_player
.store(true, std::sync::atomic::Ordering::Relaxed);
}
}

View File

@ -0,0 +1,9 @@
mod config;
mod handshake;
mod login;
mod play;
mod status;
fn is_valid_player_name(name: &str) -> bool {
name.len() <= 16 && name.chars().all(|c| c > 32u8 as char && c < 127u8 as char)
}

View File

@ -1,8 +1,8 @@
use std::num::NonZeroU8;
use std::sync::Arc;
use super::PlayerConfig;
use crate::block::block_manager::BlockActionResult;
use crate::net::PlayerConfig;
use crate::{
command::CommandSender,
entity::player::{ChatMode, Hand, Player},

View File

@ -0,0 +1,18 @@
use pumpkin_protocol::{client::status::CPingResponse, server::status::SStatusPingRequest};
use crate::{net::Client, server::Server};
impl Client {
pub async fn handle_status_request(&self, server: &Server) {
log::debug!("Handling status request");
let status = server.get_status();
self.send_packet(&status.lock().await.get_status()).await;
}
pub async fn handle_ping_request(&self, ping_request: SStatusPingRequest) {
log::debug!("Handling ping request");
self.send_packet(&CPingResponse::new(ping_request.payload))
.await;
self.close();
}
}

View File

@ -1,12 +1,10 @@
use std::net::IpAddr;
use std::{net::IpAddr, net::SocketAddr};
use pumpkin_protocol::Property;
use thiserror::Error;
use tokio::sync::Mutex;
use crate::{
client::authentication::{offline_uuid, GameProfile},
Client,
};
use crate::net::{offline_uuid, GameProfile};
#[derive(Error, Debug)]
pub enum BungeeCordError {
@ -20,11 +18,23 @@ pub enum BungeeCordError {
FailedMakeOfflineUUID,
}
/// Attempts to login a player via `BungeeCord`.
///
/// This function should be called when receiving the `SLoginStart` packet.
/// It utilizes the `server_address` received in the `SHandShake` packet,
/// which may contain optional data about the client:
///
/// 1. IP address (if `ip_forward` is enabled on the `BungeeCord` server)
/// 2. UUID (if `ip_forward` is enabled on the `BungeeCord` server)
/// 3. Game profile properties (if `ip_forward` and `online_mode` are enabled on the `BungeeCord` server)
///
/// If any of the optional data is missing, the function will attempt to
/// determine the player's information locally.
pub async fn bungeecord_login(
client: &Client,
username: String,
client_address: &Mutex<SocketAddr>,
server_address: &str,
name: String,
) -> Result<(IpAddr, GameProfile), BungeeCordError> {
let server_address = client.server_address.lock().await;
let data = server_address.split('\0').take(4).collect::<Vec<_>>();
// Ip of player, only given if ip_forward on bungee is true
@ -32,15 +42,13 @@ pub async fn bungeecord_login(
Some(ip) => ip
.parse()
.map_err(|_| BungeeCordError::FailedParseAddress)?,
None => client.address.lock().await.ip(),
None => client_address.lock().await.ip(),
};
// Uuid of player, only given if ip_forward on bungee is true
let id = match data.get(2) {
Some(uuid) => uuid.parse().map_err(|_| BungeeCordError::FailedParseUUID)?,
None => {
offline_uuid(username.as_str()).map_err(|_| BungeeCordError::FailedMakeOfflineUUID)?
}
None => offline_uuid(name.as_str()).map_err(|_| BungeeCordError::FailedMakeOfflineUUID)?,
};
// Read properties and get textures
@ -57,7 +65,7 @@ pub async fn bungeecord_login(
ip,
GameProfile {
id,
name: username,
name,
properties,
profile_actions: None,
},

View File

@ -14,7 +14,7 @@ use rand::Rng;
use sha2::Sha256;
use thiserror::Error;
use crate::client::{authentication::GameProfile, Client};
use crate::net::{Client, GameProfile};
type HmacSha256 = Hmac<Sha256>;

View File

@ -13,7 +13,6 @@ pub struct RCONServer;
impl RCONServer {
pub async fn new(config: &RCONConfig, server: Arc<Server>) -> Result<Self, std::io::Error> {
assert!(config.enabled, "RCON is not enabled");
let listener = tokio::net::TcpListener::bind(config.address).await.unwrap();
let password = Arc::new(config.password.clone());

View File

@ -26,6 +26,7 @@ fn load_icon_from_file<P: AsRef<Path>>(path: P) -> Result<String, Box<dyn error:
}
fn load_icon_from_bytes(png_data: &[u8]) -> Result<String, Box<dyn error::Error>> {
assert!(!png_data.is_empty(), "PNG data is empty");
let icon = png::Decoder::new(Cursor::new(&png_data));
let reader = icon.read_info()?;
let info = reader.info();
@ -70,6 +71,7 @@ impl CachedBranding {
}
impl CachedStatus {
#[must_use]
pub fn new() -> Self {
let status_response = Self::build_response(&BASIC_CONFIG);
let status_response_json = serde_json::to_string(&status_response)

View File

@ -5,7 +5,7 @@ use rsa::{traits::PublicKeyParts as _, Pkcs1v15Encrypt, RsaPrivateKey};
use sha1::Sha1;
use sha2::Digest;
use crate::client::EncryptionError;
use crate::net::EncryptionError;
pub struct KeyStore {
pub private_key: RsaPrivateKey,
@ -13,6 +13,7 @@ pub struct KeyStore {
}
impl KeyStore {
#[must_use]
pub fn new() -> Self {
log::debug!("Creating encryption keys...");
let private_key = Self::generate_private_key();

View File

@ -23,12 +23,12 @@ use tokio::sync::{Mutex, RwLock};
use crate::block::block_manager::BlockManager;
use crate::block::default_block_manager;
use crate::client::EncryptionError;
use crate::net::EncryptionError;
use crate::world::custom_bossbar::CustomBossbars;
use crate::{
client::Client,
command::{default_dispatcher, dispatcher::CommandDispatcher},
entity::player::Player,
net::Client,
world::World,
};

View File

@ -3,7 +3,7 @@ use pumpkin_protocol::client::play::{
CSetBorderWarningDelay, CSetBorderWarningDistance,
};
use crate::client::Client;
use crate::net::Client;
use super::World;