Skip to content

Commit

Permalink
Better doc
Browse files Browse the repository at this point in the history
  • Loading branch information
Snowiiii committed Sep 12, 2024
1 parent ada0d82 commit 0bebc6f
Show file tree
Hide file tree
Showing 17 changed files with 300 additions and 94 deletions.
7 changes: 7 additions & 0 deletions pumpkin-config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Pumpkin Configuration
Pumpkin offers a robust configuration system that allows users to customize various aspects of the server's behavior without relying on external plugins. This provides flexibility and control over the server's operation.

#### Key Features:
- Extensive Customization: Configure server settings, player behavior, world generation, and more.
- Performance Optimization: Optimize server performance through configuration tweaks.
- Plugin-Free Customization: Achieve desired changes without the need for additional plugins.
Empty file added pumpkin-entity/README.md
Empty file.
6 changes: 5 additions & 1 deletion pumpkin-protocol/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ Thats a Serverbound packet
pub struct CPlayDisconnect {
reason: TextComponent,
}
``
```

### Porting
You can compare difference in Protocol on wiki.vg https://wiki.vg/index.php?title=Protocol&action=history
Also change the `CURRENT_MC_PROTOCOL` in `src/lib.rs`
26 changes: 20 additions & 6 deletions pumpkin-protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub mod server;
pub mod slot;
pub mod uuid;

/// To current Minecraft protocol
/// Don't forget to change this when porting
pub const CURRENT_MC_PROTOCOL: u32 = 767;

pub const MAX_PACKET_SIZE: i32 = 2097152;
Expand Down Expand Up @@ -175,7 +177,6 @@ impl From<VarInt> for ConnectionState {
}
}
}

pub struct RawPacket {
pub id: VarInt,
pub bytebuf: ByteBuffer,
Expand All @@ -191,29 +192,42 @@ pub trait ServerPacket: Packet + Sized {

#[derive(Serialize)]
pub struct StatusResponse {
pub version: Version,
pub players: Players,
/// The version on which the Server is running. Optional
pub version: Option<Version>,
/// Informations about currently connected Players. Optional
pub players: Option<Players>,
/// The description displayed also called MOTD (Message of the day). Optional
pub description: String,
pub favicon: Option<String>, // data:image/png;base64,<data>
// Players, favicon ...
/// The icon displayed, Optional
pub favicon: Option<String>,
/// Players are forced to use Secure chat
pub enforece_secure_chat: bool,
}
#[derive(Serialize)]
pub struct Version {
/// The current name of the Version (e.g. 1.21.1)
pub name: String,
/// The current Protocol Version (e.g. 767)
pub protocol: u32,
}

#[derive(Serialize)]
pub struct Players {
/// The maximum Player count the server allows
pub max: u32,
/// The current online player count
pub online: u32,
/// Informations about currently connected players.
/// Note player can disable listing here.
pub sample: Vec<Sample>,
}

#[derive(Serialize)]
pub struct Sample {
/// Players Name
pub name: String,
pub id: String, // uuid
/// Players UUID
pub id: String,
}

// basicly game profile
Expand Down
3 changes: 3 additions & 0 deletions pumpkin-protocol/src/packet_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use crate::{
type Cipher = cfb8::Decryptor<aes::Aes128>;

// Decoder: Client -> Server
// Supports ZLib decoding/decompression
// Supports Aes128 Encyption
#[derive(Default)]
pub struct PacketDecoder {
buf: BytesMut,
Expand Down Expand Up @@ -105,6 +107,7 @@ impl PacketDecoder {
self.cipher = Some(cipher);
}

/// Enables ZLib Deompression
pub fn set_compression(&mut self, compression: Option<u32>) {
self.compression = compression;
}
Expand Down
3 changes: 3 additions & 0 deletions pumpkin-protocol/src/packet_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use crate::{bytebuf::ByteBuffer, ClientPacket, PacketError, VarInt, MAX_PACKET_S
type Cipher = cfb8::Encryptor<aes::Aes128>;

// Encoder: Server -> Client
// Supports ZLib endecoding/compression
// Supports Aes128 Encyption
#[derive(Default)]
pub struct PacketEncoder {
buf: BytesMut,
Expand Down Expand Up @@ -121,6 +123,7 @@ impl PacketEncoder {
self.cipher = Some(Cipher::new_from_slices(key, key).expect("invalid key"));
}

/// Enables ZLib Compression
pub fn set_compression(&mut self, compression: Option<(u32, u32)>) {
self.compression = compression;
}
Expand Down
2 changes: 2 additions & 0 deletions pumpkin-protocol/src/uuid.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use serde::Serialize;

#[derive(Clone)]
/// Wrapper around uuid::UUID, Please use this in every Packet containing a UUID
/// We use this to we can do own Serializing
pub struct UUID(pub uuid::Uuid);

impl Serialize for UUID {
Expand Down
17 changes: 17 additions & 0 deletions pumpkin-world/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### Pumpkin World
Contains everything World related for example

- Loading Chunks (Anvil Format)
- Generating Chunks
- Loading Blocks/Items

### Porting
When updating your Minecraft server to a newer version, you typically need to replace the files in the assets directory to ensure compatibility with the new version's resources.
Thankfully, vanilla Minecraft provides a way to extract these updated assets directly from the server JAR file itself.

1. Download the latest Minecraft server JAR file for the version you want to upgrade to.
2. Run `java -DbundlerMainClass=net.minecraft.data.Main -jar <minecraft_server>.jar --reports`.
3. This command will create a new folder named `reports` in the same directory as the server JAR. This folder contains the updated "assets" directory for the new version.
4. Copy the assets folder from the reports folder and replace the existing assets directory within your server directory.

For details see https://wiki.vg/Data_Generators
21 changes: 9 additions & 12 deletions pumpkin-world/src/level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ use crate::{
world_gen::{get_world_gen, Seed, WorldGenerator},
};

/// The Level represents a single Dimension.
/// The `Level` module provides functionality for working with chunks within or outside a Minecraft world.
///
/// Key features include:
///
/// - **Chunk Loading:** Efficiently loads chunks from disk (Anvil format).
/// - **Chunk Caching:** Stores accessed chunks in memory for faster access.
/// - **Chunk Generation:** Generates new chunks on-demand using a specified `WorldGenerator`.
///
/// For more details on world generation, refer to the `WorldGenerator` module.
pub struct Level {
save_file: Option<SaveFile>,
loaded_chunks: Arc<Mutex<HashMap<Vector2<i32>, Arc<ChunkData>>>>,
Expand Down Expand Up @@ -126,17 +134,6 @@ impl Level {
}
}

// /// Read one chunk in the world
// ///
// /// Do not use this function if reading many chunks is required, since in case those two chunks which are read separately using `.read_chunk` are in the same region file, it will need to be opened and closed separately for both of them, leading to a performance loss.
// pub async fn read_chunk(&self, chunk: (i32, i32)) -> Result<ChunkData, WorldError> {
// self.read_chunks(vec![chunk])
// .await
// .pop()
// .expect("Read chunks must return a chunk")
// .1
// }

/// Reads/Generates many chunks in a world
/// MUST be called from a tokio runtime thread
///
Expand Down
50 changes: 41 additions & 9 deletions pumpkin/src/client/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ pub struct GameProfile {
pub profile_actions: Option<Vec<ProfileAction>>,
}

/// Sends a GET request to Mojang's authentication servers to verify a client's Minecraft account.
///
/// **Purpose:**
///
/// This function is used to ensure that a client connecting to the server has a valid, premium Minecraft account. It's a crucial step in preventing unauthorized access and maintaining server security.
///
/// **How it Works:**
///
/// 1. A client with a premium account sends a login request to the Mojang session server.
/// 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 .
///
/// **Note:** This process helps prevent unauthorized access to the server and ensures that only legitimate Minecraft accounts can connect.
pub async fn authenticate(
username: &str,
server_hash: &str,
Expand Down Expand Up @@ -71,29 +84,34 @@ pub async fn authenticate(
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();
pub fn unpack_textures(property: Property, config: &TextureConfig) -> Result<(), TextureError> {
let from64 = general_purpose::STANDARD
.decode(property.value)
.map_err(|e| TextureError::DecodeError(e.to_string()))?;
let textures: ProfileTextures =
serde_json::from_slice(&from64).map_err(|e| TextureError::JSONError(e.to_string()))?;
for texture in textures.textures {
is_texture_url_valid(Url::parse(&texture.1.url).unwrap(), config);
let url =
Url::parse(&texture.1.url).map_err(|e| TextureError::InvalidURL(e.to_string()))?;
is_texture_url_valid(url, config)?
}
Ok(())
}

pub fn auth_digest(bytes: &[u8]) -> String {
BigInt::from_signed_bytes_be(bytes).to_str_radix(16)
}

pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> bool {
pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> Result<(), TextureError> {
let scheme = url.scheme();
if !config.allowed_url_schemes.contains(&scheme.to_string()) {
return false;
return Err(TextureError::DisallowedUrlScheme(scheme.to_string()));
}
let domain = url.domain().unwrap_or("");
if !config.allowed_url_domains.contains(&domain.to_string()) {
return false;
return Err(TextureError::DisallowedUrlDomain(domain.to_string()));
}
true
Ok(())
}

#[derive(Error, Debug)]
Expand All @@ -109,3 +127,17 @@ pub enum AuthError {
#[error("Unknown Status Code")]
UnknownStatusCode(String),
}

#[derive(Error, Debug)]
pub enum TextureError {
#[error("Invalid URL")]
InvalidURL(String),
#[error("Invalid URL scheme for player texture: {0}")]
DisallowedUrlScheme(String),
#[error("Invalid URL domain for player texture: {0}")]
DisallowedUrlDomain(String),
#[error("Failed to decode base64 player texture: {0}")]
DecodeError(String),
#[error("Failed to parse JSON from player texture: {0}")]
JSONError(String),
}
6 changes: 3 additions & 3 deletions pumpkin/src/client/client_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,9 @@ impl Client {
Err(e) => self.kick(&e.to_string()),
}
}
for ele in gameprofile.as_ref().unwrap().properties.clone() {
// todo, use this
unpack_textures(ele, &ADVANCED_CONFIG.authentication.textures);
for property in gameprofile.as_ref().unwrap().properties.clone() {
unpack_textures(property, &ADVANCED_CONFIG.authentication.textures)
.unwrap_or_else(|e| self.kick(&e.to_string()));
}

// enable compression
Expand Down
41 changes: 35 additions & 6 deletions pumpkin/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,30 @@ mod client_packet;
mod container;
pub mod player_packet;

/// Represents a player's configuration settings.
///
/// This struct contains various options that can be customized by the player, affecting their gameplay experience.
///
/// **Usage:**
///
/// This struct is typically used to store and manage a player's preferences. It can be sent to the server when a player joins or when they change their settings.
#[derive(Clone)]
pub struct PlayerConfig {
/// The player's preferred language.
pub locale: String, // 16
/// The maximum distance at which chunks are rendered.
pub view_distance: i8,
/// The player's chat mode settings
pub chat_mode: ChatMode,
/// Whether chat colors are enabled.
pub chat_colors: bool,
/// The player's skin configuration options.
pub skin_parts: u8,
/// The player's dominant hand (left or right).
pub main_hand: Hand,
/// Whether text filtering is enabled.
pub text_filtering: bool,
/// Whether the player wants to appear in the server list.
pub server_listing: bool,
}

Expand All @@ -64,23 +79,37 @@ impl Default for PlayerConfig {
}
}

/// Everything which makes a Conection with our Server is a `Client`.
/// Client will become Players when they reach the `Play` state
pub struct Client {
/// The client's game profile information.
pub gameprofile: Mutex<Option<GameProfile>>,

/// The client's configuration settings, Optional
pub config: Mutex<Option<PlayerConfig>>,
/// The client's brand or modpack information, Optional.
pub brand: Mutex<Option<String>>,

/// The minecraft protocol version used by the client.
pub protocol_version: AtomicI32,
/// The current connection state of the client (e.g., Handshaking, Status, Play).
pub connection_state: Mutex<ConnectionState>,
/// Whether encryption is enabled for the connection.
pub encryption: AtomicBool,
/// Indicates if the client connection is closed.
pub closed: AtomicBool,
/// A unique token identifying the client.
pub token: Token,
/// The underlying TCP connection to the client.
pub connection: Arc<Mutex<TcpStream>>,
/// The client's IP address.
pub address: Mutex<SocketAddr>,
/// The packet encoder for outgoing packets.
enc: Arc<Mutex<PacketEncoder>>,
/// The packet decoder for incoming packets.
dec: Arc<Mutex<PacketDecoder>>,
/// A queue of raw packets received from the client, waiting to be processed.
pub client_packets_queue: Arc<Mutex<Vec<RawPacket>>>,

/// Indicates whether the client should be converted into a player.
pub make_player: AtomicBool,
}

Expand All @@ -104,13 +133,13 @@ impl Client {
}
}

/// adds a Incoming packet to the queue
/// Adds a Incoming packet to the queue
pub fn add_packet(&self, packet: RawPacket) {
let mut client_packets_queue = self.client_packets_queue.lock().unwrap();
client_packets_queue.push(packet);
}

/// enables encryption
/// Enables encryption
pub fn enable_encryption(
&self,
shared_secret: &[u8], // decrypted
Expand All @@ -125,7 +154,7 @@ impl Client {
Ok(())
}

// Compression threshold, Compression level
/// Compression threshold, Compression level
pub fn set_compression(&self, compression: Option<(u32, u32)>) {
self.dec
.lock()
Expand Down Expand Up @@ -161,6 +190,7 @@ impl Client {
Ok(())
}

/// Processes all packets send by the client
pub async fn process_packets(&self, server: &Arc<Server>) {
while let Some(mut packet) = self.client_packets_queue.lock().unwrap().pop() {
match self.handle_packet(server, &mut packet).await {
Expand Down Expand Up @@ -330,7 +360,6 @@ impl Client {

/// Kicks the Client with a reason depending on the connection state
pub fn kick(&self, reason: &str) {
dbg!(reason);
match *self.connection_state.lock().unwrap() {
ConnectionState::Login => {
self.try_send_packet(&CLoginDisconnect::new(
Expand Down
Loading

0 comments on commit 0bebc6f

Please sign in to comment.