Merge remote-tracking branch 'upstream/master'

This commit is contained in:
lukas0008 2024-08-07 17:14:32 +02:00
commit 2e31fda6de
23 changed files with 446 additions and 114 deletions

View File

@ -14,8 +14,10 @@ Pumpkin is currently under heavy development.
## What Pumpkin aims to be: ## What Pumpkin aims to be:
- A fast, efficient, and scalable Minecraft server - A fast, efficient, and scalable Minecraft server
- Use Multi-Threading
- Compatible with the latest Minecraft server version - Compatible with the latest Minecraft server version
- Adherent to Vanilla game mechanics - Adherent to Vanilla game mechanics
- Be Secure and prevent all Exploits found
- Highly configurable with the ability to disable unnecessary features - Highly configurable with the ability to disable unnecessary features
- A platform for plugin development - A platform for plugin development
@ -51,7 +53,7 @@ Pumpkin is currently under heavy development.
- [x] Player Client brand - [x] Player Client brand
- [x] Player Teleport - [x] Player Teleport
- [x] Player Movement - [x] Player Movement
- [ ] Player Animation - [x] Player Animation
- [ ] Player Inventory - [ ] Player Inventory
- [ ] Player Attack - [ ] Player Attack
- Server - Server

View File

@ -299,6 +299,17 @@ impl ByteBuffer {
self.buffer.put_slice(src) self.buffer.put_slice(src)
} }
pub fn put<T: Buf>(&mut self, src: T)
where
Self: Sized,
{
self.buffer.put(src)
}
pub fn reserve(&mut self, additional: usize) {
self.buffer.reserve(additional)
}
pub fn get_slice(&mut self) -> BytesMut { pub fn get_slice(&mut self) -> BytesMut {
self.buffer.split() self.buffer.split()
} }

View File

@ -3,6 +3,8 @@ use std::fmt::Display;
use serde::{ser, Serialize}; use serde::{ser, Serialize};
use thiserror::Error; use thiserror::Error;
use crate::VarInt;
use super::ByteBuffer; use super::ByteBuffer;
pub struct Serializer { pub struct Serializer {
@ -121,16 +123,22 @@ impl<'a> ser::Serializer for &'a mut Serializer {
unimplemented!() unimplemented!()
} }
fn serialize_none(self) -> Result<Self::Ok, Self::Error> { fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
unimplemented!() self.output.put_bool(false);
Ok(())
} }
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> { fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
unimplemented!() if let Some(len) = len {
self.output.put_var_int(&VarInt(len as i32));
} }
fn serialize_some<T>(self, _value: &T) -> Result<Self::Ok, Self::Error> Ok(self)
}
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
where where
T: ?Sized + Serialize, T: ?Sized + Serialize,
{ {
unimplemented!() self.output.put_bool(true);
dbg!("aa");
value.serialize(self)
} }
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> { fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
self.output.put_string(v); self.output.put_string(v);
@ -213,16 +221,16 @@ impl<'a> ser::SerializeSeq for &'a mut Serializer {
type Error = SerializerError; type Error = SerializerError;
// Serialize a single element of the sequence. // Serialize a single element of the sequence.
fn serialize_element<T>(&mut self, _value: &T) -> Result<(), Self::Error> fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
where where
T: ?Sized + Serialize, T: ?Sized + Serialize,
{ {
todo!() value.serialize(&mut **self)
} }
// Close the sequence. // Close the sequence.
fn end(self) -> Result<(), Self::Error> { fn end(self) -> Result<(), Self::Error> {
todo!() Ok(())
} }
} }

View File

@ -1,11 +1,13 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use crate::{bytebuf::ByteBuffer, ClientPacket, VarIntType}; use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt};
#[packet(0x01)] #[packet(0x01)]
pub struct CEncryptionRequest<'a> { pub struct CEncryptionRequest<'a> {
server_id: &'a str, // 20 server_id: &'a str, // 20
public_key_length: VarInt,
public_key: &'a [u8], public_key: &'a [u8],
verify_token_length: VarInt,
verify_token: &'a [u8], verify_token: &'a [u8],
should_authenticate: bool, should_authenticate: bool,
} }
@ -19,7 +21,9 @@ impl<'a> CEncryptionRequest<'a> {
) -> Self { ) -> Self {
Self { Self {
server_id, server_id,
public_key_length: public_key.len().into(),
public_key, public_key,
verify_token_length: verify_token.len().into(),
verify_token, verify_token,
should_authenticate, should_authenticate,
} }
@ -29,9 +33,9 @@ impl<'a> CEncryptionRequest<'a> {
impl<'a> ClientPacket for CEncryptionRequest<'a> { impl<'a> ClientPacket for CEncryptionRequest<'a> {
fn write(&self, bytebuf: &mut ByteBuffer) { fn write(&self, bytebuf: &mut ByteBuffer) {
bytebuf.put_string(self.server_id); bytebuf.put_string(self.server_id);
bytebuf.put_var_int(&(self.public_key.len() as VarIntType).into()); bytebuf.put_var_int(&self.public_key_length);
bytebuf.put_slice(self.public_key); bytebuf.put_slice(self.public_key);
bytebuf.put_var_int(&(self.verify_token.len() as VarIntType).into()); bytebuf.put_var_int(&self.verify_token_length);
bytebuf.put_slice(self.verify_token); bytebuf.put_slice(self.verify_token);
bytebuf.put_bool(self.should_authenticate); bytebuf.put_bool(self.should_authenticate);
} }

View File

@ -33,10 +33,7 @@ impl<'a> ClientPacket for CLoginSuccess<'a> {
bytebuf.put_list::<Property>(self.properties, |p, v| { bytebuf.put_list::<Property>(self.properties, |p, v| {
p.put_string(&v.name); p.put_string(&v.name);
p.put_string(&v.value); p.put_string(&v.value);
// has signature ? p.put_option(&v.signature, |p, v| p.put_string(v));
// todo: for some reason we get "got too many bytes error when using a signature"
p.put_bool(false);
// p.put_option(&v.signature, |p,v| p.put_string(v));
}); });
bytebuf.put_bool(self.strict_error_handling); bytebuf.put_bool(self.strict_error_handling);
} }

View File

@ -0,0 +1,31 @@
use num_derive::ToPrimitive;
use pumpkin_macros::packet;
use serde::Serialize;
use crate::VarInt;
#[derive(Serialize, Clone)]
#[packet(0x03)]
pub struct CEntityAnimation {
entity_id: VarInt,
/// See `Animation`
animation: u8,
}
impl CEntityAnimation {
pub fn new(entity_id: VarInt, animation: u8) -> Self {
Self {
entity_id,
animation,
}
}
}
#[derive(ToPrimitive)]
pub enum Animation {
SwingMainArm,
LeaveBed,
SwingOffhand,
CriticalEffect,
MagicCriticaleffect,
}

View File

@ -0,0 +1,33 @@
use pumpkin_macros::packet;
use serde::Serialize;
use crate::VarInt;
#[derive(Serialize, Clone)]
#[packet(0x58)]
pub struct CSetEntityMetadata {
entity_id: VarInt,
metadata: Vec<Metadata>,
}
impl CSetEntityMetadata {
pub fn new(entity_id: VarInt, metadata: Vec<Metadata>) -> Self {
Self {
entity_id,
metadata,
}
}
}
#[derive(Serialize, Clone)]
pub struct Metadata {
index: u8,
typ: VarInt,
value: u8,
}
impl Metadata {
pub fn new(index: u8, typ: VarInt, value: u8) -> Self {
Self { index, typ, value }
}
}

View File

@ -1,7 +1,9 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use serde::Serialize;
use crate::{bytebuf::ByteBuffer, ClientPacket, VarInt}; use crate::VarInt;
#[derive(Serialize)]
#[packet(0x2B)] #[packet(0x2B)]
pub struct CLogin { pub struct CLogin {
entity_id: i32, entity_id: i32,
@ -20,9 +22,7 @@ pub struct CLogin {
previous_gamemode: i8, previous_gamemode: i8,
debug: bool, debug: bool,
is_flat: bool, is_flat: bool,
has_death_loc: bool, death_dimension_name: Option<(String, i64)>, // POSITION NOT STRING
death_dimension_name: Option<String>,
death_loc: Option<i64>, // POSITION NOT STRING
portal_cooldown: VarInt, portal_cooldown: VarInt,
enforce_secure_chat: bool, enforce_secure_chat: bool,
} }
@ -46,9 +46,7 @@ impl CLogin {
previous_gamemode: i8, previous_gamemode: i8,
debug: bool, debug: bool,
is_flat: bool, is_flat: bool,
has_death_loc: bool, death_dimension_name: Option<(String, i64)>,
death_dimension_name: Option<String>,
death_loc: Option<i64>, // todo add block pos
portal_cooldown: VarInt, portal_cooldown: VarInt,
enforce_secure_chat: bool, enforce_secure_chat: bool,
) -> Self { ) -> Self {
@ -69,39 +67,9 @@ impl CLogin {
previous_gamemode, previous_gamemode,
debug, debug,
is_flat, is_flat,
has_death_loc,
death_dimension_name, death_dimension_name,
death_loc,
portal_cooldown, portal_cooldown,
enforce_secure_chat, enforce_secure_chat,
} }
} }
} }
impl ClientPacket for CLogin {
fn write(&self, bytebuf: &mut ByteBuffer) {
bytebuf.put_i32(self.entity_id);
bytebuf.put_bool(self.is_hardcore);
bytebuf.put_list(&self.dimension_names, |buf, v| buf.put_string(v));
bytebuf.put_var_int(&self.max_players);
bytebuf.put_var_int(&self.view_distance);
bytebuf.put_var_int(&self.simulated_distance);
bytebuf.put_bool(self.reduced_debug_info);
bytebuf.put_bool(self.enabled_respawn_screen);
bytebuf.put_bool(self.limited_crafting);
bytebuf.put_var_int(&self.dimension_type);
bytebuf.put_string(&self.dimension_name);
bytebuf.put_i64(self.hashed_seed);
bytebuf.put_u8(self.game_mode);
bytebuf.put_i8(self.previous_gamemode);
bytebuf.put_bool(self.debug);
bytebuf.put_bool(self.is_flat);
bytebuf.put_bool(self.has_death_loc);
if self.has_death_loc {
bytebuf.put_string(self.death_dimension_name.as_ref().unwrap());
bytebuf.put_i64(self.death_loc.unwrap());
}
bytebuf.put_var_int(&self.portal_cooldown);
bytebuf.put_bool(self.enforce_secure_chat);
}
}

View File

@ -1,7 +1,7 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use serde::Serialize;
use crate::{bytebuf::ByteBuffer, ClientPacket}; #[derive(Serialize)]
#[packet(0x38)] #[packet(0x38)]
pub struct CPlayerAbilities { pub struct CPlayerAbilities {
flags: i8, flags: i8,
@ -18,11 +18,3 @@ impl CPlayerAbilities {
} }
} }
} }
impl ClientPacket for CPlayerAbilities {
fn write(&self, bytebuf: &mut ByteBuffer) {
bytebuf.put_i8(self.flags);
bytebuf.put_f32(self.flying_speed);
bytebuf.put_f32(self.field_of_view);
}
}

View File

@ -0,0 +1,70 @@
use num_derive::FromPrimitive;
use serde::Serialize;
use crate::{text::TextComponent, VarInt};
#[derive(Serialize)]
pub struct PlayerChatMessage<'a> {
sender: uuid::Uuid,
index: VarInt,
message_signature: Option<&'a [u8]>,
message: String,
timestamp: i64,
salt: i64,
previous_messages: &'a [PreviousMessage<'a>], // max 20
unsigned_content: Option<TextComponent>,
/// See `FilterType`
filter_type: VarInt,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
}
impl<'a> PlayerChatMessage<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
sender: uuid::Uuid,
index: VarInt,
message_signature: Option<&'a [u8]>,
message: String,
timestamp: i64,
salt: i64,
previous_messages: &'a [PreviousMessage<'a>],
unsigned_content: Option<TextComponent>,
filter_type: VarInt,
chat_type: VarInt,
sender_name: TextComponent,
target_name: Option<TextComponent>,
) -> Self {
Self {
sender,
index,
message_signature,
message,
timestamp,
salt,
previous_messages,
unsigned_content,
filter_type,
chat_type,
sender_name,
target_name,
}
}
}
#[derive(Serialize)]
pub struct PreviousMessage<'a> {
message_id: VarInt,
signature: Option<&'a [u8]>,
}
#[derive(FromPrimitive)]
pub enum FilterType {
/// Message is not filtered at all
PassThrough,
/// Message is fully filtered
FullyFiltered,
/// Only some characters in the message are filtered
PartiallyFiltered,
}

View File

@ -34,10 +34,7 @@ impl<'a> ClientPacket for CPlayerInfoUpdate<'a> {
p.put_list::<Property>(properties, |p, v| { p.put_list::<Property>(properties, |p, v| {
p.put_string(&v.name); p.put_string(&v.name);
p.put_string(&v.value); p.put_string(&v.value);
// has signature ? p.put_option(&v.signature, |p, v| p.put_string(v));
// todo: for some reason we get "got too many bytes error when using a signature"
p.put_bool(false);
// todo signature
}); });
} }
PlayerAction::InitializeChat(_) => todo!(), PlayerAction::InitializeChat(_) => todo!(),

View File

@ -1,10 +1,13 @@
mod c_change_difficulty; mod c_change_difficulty;
mod c_chunk_data_update_light; mod c_chunk_data_update_light;
mod c_entity_animation;
mod c_entity_metadata;
mod c_game_event; mod c_game_event;
mod c_head_rot; mod c_head_rot;
mod c_login; mod c_login;
mod c_play_disconnect; mod c_play_disconnect;
mod c_player_abilities; mod c_player_abilities;
mod c_player_chat_message;
mod c_player_info_update; mod c_player_info_update;
mod c_set_held_item; mod c_set_held_item;
mod c_spawn_player; mod c_spawn_player;
@ -17,11 +20,14 @@ mod player_action;
pub use c_change_difficulty::*; pub use c_change_difficulty::*;
pub use c_chunk_data_update_light::*; pub use c_chunk_data_update_light::*;
pub use c_entity_animation::*;
pub use c_entity_metadata::*;
pub use c_game_event::*; pub use c_game_event::*;
pub use c_head_rot::*; pub use c_head_rot::*;
pub use c_login::*; pub use c_login::*;
pub use c_play_disconnect::*; pub use c_play_disconnect::*;
pub use c_player_abilities::*; pub use c_player_abilities::*;
pub use c_player_chat_message::*;
pub use c_player_info_update::*; pub use c_player_info_update::*;
pub use c_set_held_item::*; pub use c_set_held_item::*;
pub use c_spawn_player::*; pub use c_spawn_player::*;

View File

@ -1,6 +1,7 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use serde::Serialize;
#[derive(serde::Serialize)] #[derive(Serialize)]
#[packet(0x01)] #[packet(0x01)]
pub struct CPingResponse { pub struct CPingResponse {
payload: i64, // must responde with the same as in `SPingRequest` payload: i64, // must responde with the same as in `SPingRequest`

View File

@ -1,6 +1,7 @@
use pumpkin_macros::packet; use pumpkin_macros::packet;
use serde::Serialize;
#[derive(serde::Serialize)] #[derive(Serialize)]
#[packet(0x00)] #[packet(0x00)]
pub struct CStatusResponse<'a> { pub struct CStatusResponse<'a> {
json_response: &'a str, // 32767 json_response: &'a str, // 32767

View File

@ -213,7 +213,7 @@ pub struct Sample {
} }
// basicly game profile // basicly game profile
#[derive(Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Property { pub struct Property {
pub name: String, pub name: String,
// base 64 // base 64

View File

@ -1,19 +1,49 @@
use std::net::IpAddr; use std::{collections::HashMap, net::IpAddr};
use base64::{engine::general_purpose, Engine};
use num_bigint::BigInt; use num_bigint::BigInt;
use pumpkin_protocol::Property; use pumpkin_protocol::Property;
use reqwest::StatusCode; use reqwest::{StatusCode, Url};
use serde::Deserialize; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use uuid::Uuid; use uuid::Uuid;
use crate::server::Server; use crate::{config::auth_config::TextureConfig, server::Server};
#[derive(Deserialize, Clone, Debug)]
#[allow(non_snake_case)]
#[allow(dead_code)]
pub struct ProfileTextures {
timestamp: i64,
profileId: Uuid,
profileName: String,
signatureRequired: bool,
textures: HashMap<String, Texture>,
}
#[derive(Deserialize, Clone, Debug)]
#[allow(non_snake_case)]
#[allow(dead_code)]
pub struct Texture {
url: String,
metadata: Option<HashMap<String, String>>,
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
pub enum ProfileAction {
#[serde(rename = "FORCED_NAME_CHANGE")]
ForcedNameChange,
#[serde(rename = "USING_BANNED_SKIN")]
UsingBannedSkin,
}
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
pub struct GameProfile { pub struct GameProfile {
pub id: Uuid, pub id: Uuid,
pub name: String, pub name: String,
pub properties: Vec<Property>, pub properties: Vec<Property>,
#[serde(rename = "profileActions")]
pub profile_actions: Option<Vec<ProfileAction>>,
} }
pub fn authenticate( pub fn authenticate(
@ -22,8 +52,13 @@ pub fn authenticate(
ip: &IpAddr, ip: &IpAddr,
server: &mut Server, server: &mut Server,
) -> Result<GameProfile, AuthError> { ) -> Result<GameProfile, AuthError> {
assert!(server.advanced_config.authentication.use_authentication);
assert!(server.auth_client.is_some()); assert!(server.auth_client.is_some());
let address = if server.base_config.prevent_proxy_connections { let address = if server
.advanced_config
.authentication
.prevent_proxy_connections
{
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}") format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}")
} else { } else {
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}") format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}")
@ -44,10 +79,32 @@ pub fn authenticate(
Ok(profile) Ok(profile)
} }
pub fn unpack_textures(property: Property, config: &TextureConfig) {
// todo: no unwrap
let from64 = general_purpose::STANDARD.decode(property.value).unwrap();
let textures: ProfileTextures = serde_json::from_slice(&from64).unwrap();
dbg!(&textures);
for texture in textures.textures {
is_texture_url_valid(Url::parse(&texture.1.url).unwrap(), config);
}
}
pub fn auth_digest(bytes: &[u8]) -> String { pub fn auth_digest(bytes: &[u8]) -> String {
BigInt::from_signed_bytes_be(bytes).to_str_radix(16) BigInt::from_signed_bytes_be(bytes).to_str_radix(16)
} }
pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> bool {
let scheme = url.scheme();
if !config.allowed_url_schemes.contains(&scheme.to_string()) {
return false;
}
let domain = url.domain().unwrap_or("");
if !config.allowed_url_domains.contains(&domain.to_string()) {
return false;
}
true
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AuthError { pub enum AuthError {
#[error("Authentication servers are down")] #[error("Authentication servers are down")]

View File

@ -23,7 +23,10 @@ use crate::{
server::Server, server::Server,
}; };
use super::{authentication::auth_digest, Client, EncryptionError, PlayerConfig}; use super::{
authentication::{auth_digest, unpack_textures},
Client, EncryptionError, PlayerConfig,
};
/// Processes incoming Packets from the Client to the Server /// Processes incoming Packets from the Client to the Server
/// Implements the `Client` Packets /// Implements the `Client` Packets
@ -45,12 +48,15 @@ impl Client {
} }
pub fn handle_login_start(&mut self, server: &mut Server, login_start: SLoginStart) { pub fn handle_login_start(&mut self, server: &mut Server, login_start: SLoginStart) {
// todo: do basic name validation
dbg!("login start"); dbg!("login start");
// default game profile, when no online mode // default game profile, when no online mode
// todo: make offline uuid
self.gameprofile = Some(GameProfile { self.gameprofile = Some(GameProfile {
id: login_start.uuid, id: login_start.uuid,
name: login_start.name, name: login_start.name,
properties: vec![], properties: vec![],
profile_actions: None,
}); });
// todo: check config for encryption // todo: check config for encryption
@ -91,10 +97,40 @@ impl Client {
&ip, &ip,
server, server,
) { ) {
Ok(p) => self.gameprofile = Some(p), Ok(p) => {
// Check if player should join
if let Some(p) = &p.profile_actions {
if !server
.advanced_config
.authentication
.player_profile
.allow_banned_players
{
if !p.is_empty() {
self.kick("Your account can't join");
}
} else {
for allowed in server
.advanced_config
.authentication
.player_profile
.allowed_actions
.clone()
{
if !p.contains(&allowed) {
self.kick("Your account can't join");
}
}
}
}
self.gameprofile = Some(p);
}
Err(e) => self.kick(&e.to_string()), Err(e) => self.kick(&e.to_string()),
} }
} }
for ele in self.gameprofile.as_ref().unwrap().properties.clone() {
unpack_textures(ele, &server.advanced_config.authentication.textures);
}
if let Some(profile) = self.gameprofile.as_ref().cloned() { if let Some(profile) = self.gameprofile.as_ref().cloned() {
let packet = CLoginSuccess::new(profile.id, profile.name, &profile.properties, false); let packet = CLoginSuccess::new(profile.id, profile.name, &profile.properties, false);

View File

@ -28,7 +28,7 @@ use pumpkin_protocol::{
login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart}, login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart},
play::{ play::{
SChatCommand, SConfirmTeleport, SPlayerCommand, SPlayerPosition, SChatCommand, SConfirmTeleport, SPlayerCommand, SPlayerPosition,
SPlayerPositionRotation, SPlayerRotation, SPlayerPositionRotation, SPlayerRotation, SSwingArm,
}, },
status::{SPingRequest, SStatusRequest}, status::{SPingRequest, SStatusRequest},
}, },
@ -39,7 +39,7 @@ use pumpkin_protocol::{
use std::io::Read; use std::io::Read;
use thiserror::Error; use thiserror::Error;
mod authentication; pub mod authentication;
mod client_packet; mod client_packet;
pub mod player_packet; pub mod player_packet;
@ -258,6 +258,7 @@ impl Client {
SPlayerCommand::PACKET_ID => { SPlayerCommand::PACKET_ID => {
self.handle_player_command(server, SPlayerCommand::read(bytebuf).unwrap()) self.handle_player_command(server, SPlayerCommand::read(bytebuf).unwrap())
} }
SSwingArm::PACKET_ID => self.handle_swing_arm(server, SSwingArm::read(bytebuf)),
_ => log::error!("Failed to handle player packet id {}", packet.id.0), _ => log::error!("Failed to handle player packet id {}", packet.id.0),
} }
} }

View File

@ -1,13 +1,18 @@
use num_traits::FromPrimitive;
use pumpkin_protocol::{ use pumpkin_protocol::{
client::play::{CHeadRot, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot}, client::play::{
Animation, CEntityAnimation, CHeadRot, CUpdateEntityPos, CUpdateEntityPosRot,
CUpdateEntityRot,
},
server::play::{ server::play::{
SChatCommand, SConfirmTeleport, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, SChatCommand, SConfirmTeleport, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation,
SPlayerRotation, SPlayerRotation, SSwingArm,
}, },
}; };
use crate::{ use crate::{
commands::{handle_command, CommandSender}, commands::{handle_command, CommandSender},
entity::player::Hand,
server::Server, server::Server,
}; };
@ -156,4 +161,14 @@ impl Client {
pumpkin_protocol::server::play::Action::StartFlyingElytra => todo!(), pumpkin_protocol::server::play::Action::StartFlyingElytra => todo!(),
} }
} }
pub fn handle_swing_arm(&mut self, server: &mut Server, swing_arm: SSwingArm) {
let animation = match Hand::from_i32(swing_arm.hand.0).unwrap() {
Hand::Main => Animation::SwingMainArm,
Hand::Off => Animation::SwingOffhand,
};
let player = self.player.as_mut().unwrap();
let id = player.entity_id();
server.broadcast_packet_expect(self, CEntityAnimation::new(id.into(), animation as u8))
}
} }

View File

@ -0,0 +1,93 @@
use serde::{Deserialize, Serialize};
use crate::client::authentication::ProfileAction;
#[derive(Deserialize, Serialize)]
pub struct Authentication {
/// Whether to use Mojang authentication.
pub use_authentication: bool,
/// Prevent proxy connections.
pub prevent_proxy_connections: bool,
/// Player profile handling.
pub player_profile: PlayerProfileConfig,
/// Texture handling.
pub textures: TextureConfig,
}
#[derive(Deserialize, Serialize)]
pub struct PlayerProfileConfig {
/// Allow players flagged by Mojang (banned, forced name change).
pub allow_banned_players: bool,
/// Depends on the value above
pub allowed_actions: Vec<ProfileAction>,
}
impl Default for PlayerProfileConfig {
fn default() -> Self {
Self {
allow_banned_players: false,
allowed_actions: vec![
ProfileAction::ForcedNameChange,
ProfileAction::UsingBannedSkin,
],
}
}
}
#[derive(Deserialize, Serialize)]
pub struct TextureConfig {
/// Whether to use player textures.
pub enabled: bool,
pub allowed_url_schemes: Vec<String>,
pub allowed_url_domains: Vec<String>,
/// Specific texture types.
pub types: TextureTypes,
}
impl Default for TextureConfig {
fn default() -> Self {
Self {
enabled: true,
allowed_url_schemes: vec!["http".into(), "https".into()],
allowed_url_domains: vec![".minecraft.net".into(), ".mojang.com".into()],
types: Default::default(),
}
}
}
#[derive(Deserialize, Serialize)]
pub struct TextureTypes {
/// Use player skins.
pub skin: bool,
/// Use player capes.
pub cape: bool,
/// Use player elytras.
/// (i didn't know myself that there are custom elytras)
pub elytra: bool,
}
impl Default for TextureTypes {
fn default() -> Self {
Self {
skin: true,
cape: true,
elytra: true,
}
}
}
impl Default for Authentication {
fn default() -> Self {
Self {
use_authentication: true,
prevent_proxy_connections: true,
player_profile: Default::default(),
textures: Default::default(),
}
}
}

View File

@ -1,9 +1,12 @@
use std::path::Path; use std::path::Path;
use auth_config::Authentication;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{entity::player::GameMode, server::Difficulty}; use crate::{entity::player::GameMode, server::Difficulty};
pub mod auth_config;
/// Current Config version of the Base Config /// Current Config version of the Base Config
const CURRENT_BASE_VERSION: &str = "1.0.0"; const CURRENT_BASE_VERSION: &str = "1.0.0";
@ -12,19 +15,15 @@ const CURRENT_BASE_VERSION: &str = "1.0.0";
/// This also allows you get some Performance or Resource boosts. /// This also allows you get some Performance or Resource boosts.
/// Important: The Configuration should match Vanilla by default /// Important: The Configuration should match Vanilla by default
pub struct AdvancedConfiguration { pub struct AdvancedConfiguration {
/// Requires Online mode
/// Should player have skins
pub use_skins: bool,
/// Should chat be enabled
pub enable_chat: bool,
pub commands: Commands, pub commands: Commands,
pub authentication: Authentication,
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Commands { pub struct Commands {
// Are commands from the Console accepted ? /// Are commands from the Console accepted ?
pub use_console: bool, pub use_console: bool,
// todo commands...
} }
impl Default for Commands { impl Default for Commands {
@ -37,8 +36,7 @@ impl Default for Commands {
impl Default for AdvancedConfiguration { impl Default for AdvancedConfiguration {
fn default() -> Self { fn default() -> Self {
Self { Self {
use_skins: true, authentication: Authentication::default(),
enable_chat: true,
commands: Commands::default(), commands: Commands::default(),
} }
} }
@ -74,8 +72,6 @@ pub struct BasicConfiguration {
pub online_mode: bool, pub online_mode: bool,
/// Whether packet encryption is enabled. Required when online mode is enabled. /// Whether packet encryption is enabled. Required when online mode is enabled.
pub encryption: bool, pub encryption: bool,
/// Whether to prevent proxy connections.
pub prevent_proxy_connections: bool,
/// The server's description displayed on the status screen. /// The server's description displayed on the status screen.
pub motd: String, pub motd: String,
/// The default game mode for players. /// The default game mode for players.
@ -99,7 +95,6 @@ impl Default for BasicConfiguration {
hardcore: false, hardcore: false,
online_mode: true, online_mode: true,
encryption: true, encryption: true,
prevent_proxy_connections: true,
motd: "A Blazing fast Pumpkin Server!".to_string(), motd: "A Blazing fast Pumpkin Server!".to_string(),
default_gamemode: GameMode::Survival, default_gamemode: GameMode::Survival,
} }

View File

@ -4,12 +4,12 @@ use std::io::{self};
use client::Client; use client::Client;
use commands::handle_command; use commands::handle_command;
use configuration::AdvancedConfiguration; use config::AdvancedConfiguration;
use std::{collections::HashMap, rc::Rc, thread}; use std::{collections::HashMap, rc::Rc, thread};
use client::interrupted; use client::interrupted;
use configuration::BasicConfiguration; use config::BasicConfiguration;
use server::Server; use server::Server;
// Setup some tokens to allow us to identify which event is for which socket. // Setup some tokens to allow us to identify which event is for which socket.
@ -17,7 +17,7 @@ const SERVER: Token = Token(0);
pub mod client; pub mod client;
pub mod commands; pub mod commands;
pub mod configuration; pub mod config;
pub mod entity; pub mod entity;
pub mod server; pub mod server;
pub mod util; pub mod util;
@ -104,6 +104,9 @@ fn main() -> io::Result<()> {
return Err(e); return Err(e);
} }
}; };
if let Err(e) = connection.set_nodelay(true) {
log::warn!("failed to set TCP_NODELAY {e}");
}
log::info!("Accepted connection from: {}", address); log::info!("Accepted connection from: {}", address);

View File

@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
client::Client, client::Client,
configuration::{AdvancedConfiguration, BasicConfiguration}, config::{AdvancedConfiguration, BasicConfiguration},
entity::player::{GameMode, Player}, entity::player::{GameMode, Player},
}; };
@ -144,8 +144,6 @@ impl Server {
self.base_config.default_gamemode.to_i8().unwrap(), self.base_config.default_gamemode.to_i8().unwrap(),
false, false,
false, false,
false, // deth loc
None,
None, None,
0.into(), 0.into(),
false, false,
@ -160,19 +158,7 @@ impl Server {
client.teleport(x, y, z, 10.0, 10.0); client.teleport(x, y, z, 10.0, 10.0);
let gameprofile = client.gameprofile.as_ref().unwrap(); let gameprofile = client.gameprofile.as_ref().unwrap();
// first send info update to our new player, So he can see his Skin // first send info update to our new player, So he can see his Skin
// TODO: send more actions, (chat. list, ping) // also send his info to everyone else
client.send_packet(CPlayerInfoUpdate::new(
0x01,
&[pumpkin_protocol::client::play::Player {
uuid: gameprofile.id,
actions: vec![PlayerAction::AddPlayer {
name: gameprofile.name.clone(),
properties: gameprofile.properties.clone(),
}],
}],
));
let gameprofile = client.gameprofile.as_ref().unwrap();
// send his info to everyone else
self.broadcast_packet( self.broadcast_packet(
client, client,
CPlayerInfoUpdate::new( CPlayerInfoUpdate::new(
@ -210,7 +196,7 @@ impl Server {
let gameprofile = client.gameprofile.as_ref().unwrap(); let gameprofile = client.gameprofile.as_ref().unwrap();
// spawn player for every client // spawn player for every client
self.broadcast_packet( self.broadcast_packet_expect(
client, client,
CSpawnEntity::new( CSpawnEntity::new(
entity_id.into(), entity_id.into(),
@ -252,12 +238,37 @@ impl Server {
)) ))
} }
} }
// entity meta data
/* if let Some(config) = &client.config {
self.broadcast_packet(
client,
CSetEntityMetadata::new(entity_id.into(), vec![Metadata::new(18, VarInt(0), 0)]),
)
}
*/
// Server::spawn_test_chunk(client); // Server::spawn_test_chunk(client);
} }
/// Sends a Packet to all Players /// Sends a Packet to all Players
pub fn broadcast_packet<P>(&mut self, from: &Client, packet: P) pub fn broadcast_packet<P>(&mut self, from: &mut Client, packet: P)
where
P: ClientPacket,
P: Clone,
{
// we can't borrow twice at same time
from.send_packet(packet.clone());
for (_, client) in self.current_clients.iter().filter(|c| c.0 != &from.token) {
// Check if client is a player
let mut client = client.borrow_mut();
if client.is_player() {
// we need to clone, Because we send a new packet to every client
client.send_packet(packet.clone());
}
}
}
pub fn broadcast_packet_expect<P>(&mut self, from: &mut Client, packet: P)
where where
P: ClientPacket, P: ClientPacket,
P: Clone, P: Clone,