added Play state

This commit is contained in:
Snowiiii
2024-07-30 16:22:31 +02:00
parent 8a5e69f8e3
commit 4b8720efdc
17 changed files with 522 additions and 48 deletions

View File

@@ -1,10 +1,13 @@
use std::{rc::Rc, sync::Mutex};
use crate::protocol::{
client::{
config::CFinishConfig,
login::{CEncryptionRequest, CLoginSuccess},
status::{CPingResponse, CStatusResponse},
},
server::{
config::{SAcknowledgeFinishConfig, SClientInformation},
handshake::SHandShake,
login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart},
status::{SPingRequest, SStatusRequest},
@@ -12,7 +15,7 @@ use crate::protocol::{
ConnectionState,
};
use super::Client;
use super::{Client, PlayerConfig};
pub trait ClientPacketProcessor {
// Handshake
@@ -25,6 +28,9 @@ pub trait ClientPacketProcessor {
fn handle_encryption_response(&mut self, encryption_response: SEncryptionResponse);
fn handle_plugin_response(&mut self, plugin_response: SLoginPluginResponse);
fn handle_login_acknowledged(&mut self, login_acknowledged: SLoginAcknowledged);
// Config
fn handle_client_information(&mut self, client_information: SClientInformation);
fn handle_config_acknowledged(&mut self, config_acknowledged: SAcknowledgeFinishConfig);
}
impl ClientPacketProcessor for Client {
@@ -37,9 +43,15 @@ impl ClientPacketProcessor for Client {
fn handle_status_request(&mut self, _status_request: SStatusRequest) {
dbg!("sending status");
self.send_packet(CStatusResponse::new(
serde_json::to_string(&self.server.status_response).unwrap(),
))
dbg!("test first");
let guard = self.server.try_lock().unwrap();
dbg!("test");
let response = serde_json::to_string(&guard.status_response).unwrap();
drop(guard);
self.send_packet(CStatusResponse::new(response));
}
fn handle_ping_request(&mut self, ping_request: SPingRequest) {
@@ -53,11 +65,17 @@ impl ClientPacketProcessor for Client {
self.name = Some(login_start.name);
self.uuid = Some(login_start.uuid);
let verify_token: [u8; 4] = rand::random();
let public_key_der = &self.server.to_owned().public_key_der;
let public_key_der = self
.server
.to_owned()
.lock()
.unwrap()
.public_key_der
.clone(); // todo do not clone
let packet = CEncryptionRequest::new(
"".into(),
public_key_der.len() as i32,
public_key_der,
&public_key_der,
verify_token.len() as i32,
&verify_token,
false, // TODO
@@ -85,4 +103,25 @@ impl ClientPacketProcessor for Client {
self.connection_state = ConnectionState::Config;
dbg!("login achnowlaged");
}
fn handle_client_information(&mut self, client_information: SClientInformation) {
self.config = Some(PlayerConfig {
locale: client_information.locale,
view_distance: client_information.view_distance,
chat_mode: client_information.chat_mode,
chat_colors: client_information.chat_colors,
skin_parts: client_information.skin_parts,
main_hand: client_information.main_hand,
text_filtering: client_information.text_filtering,
server_listing: client_information.server_listing,
});
// We are done with configuring
self.send_packet(CFinishConfig::new());
}
fn handle_config_acknowledged(&mut self, config_acknowledged: SAcknowledgeFinishConfig) {
dbg!("config acknowledged");
self.connection_state = ConnectionState::Play;
// generate a player
self.server.lock().unwrap().spawn_player(&self.token);
}
}

View File

@@ -2,12 +2,15 @@ use std::{
collections::VecDeque,
io::{self, Write},
rc::Rc,
sync::{Arc, Mutex},
};
use crate::{
entity::player::{ChatMode, Hand},
protocol::{
client::login::CLoginDisconnect,
client::{config::CConfigDisconnect, login::CLoginDisconnect},
server::{
config::{SAcknowledgeFinishConfig, SClientInformation},
handshake::SHandShake,
login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart},
status::{SPingRequest, SStatusRequest},
@@ -19,14 +22,13 @@ use crate::{
use crate::protocol::ConnectionState;
use anyhow::Context;
use mio::{event::Event, net::TcpStream, Registry};
use mio::{event::Event, net::TcpStream, Registry, Token};
use packet_decoder::PacketDecoder;
use packet_encoder::PacketEncoder;
use rsa::Pkcs1v15Encrypt;
use std::io::Read;
mod client_packet;
pub mod player;
mod packet_decoder;
mod packet_encoder;
@@ -35,13 +37,27 @@ use client_packet::ClientPacketProcessor;
pub const MAX_PACKET_SIZE: i32 = 2097152;
pub struct PlayerConfig {
locale: String, // 16
view_distance: i8,
chat_mode: ChatMode,
chat_colors: bool,
skin_parts: u8,
main_hand: Hand,
text_filtering: bool,
server_listing: bool,
}
pub struct Client {
pub name: Option<String>,
pub uuid: Option<uuid::Uuid>,
pub server: Rc<Server>,
pub config: Option<PlayerConfig>,
pub server: Arc<Mutex<Server>>,
pub connection_state: ConnectionState,
pub encrytion: bool,
pub closed: bool,
pub token: Rc<Token>,
pub connection: TcpStream,
enc: PacketEncoder,
dec: PacketDecoder,
@@ -49,11 +65,13 @@ pub struct Client {
}
impl Client {
pub fn new(server: Rc<Server>, connection: TcpStream) -> Self {
pub fn new(server: Arc<Mutex<Server>>, token: Rc<Token>, connection: TcpStream) -> Self {
Self {
name: None,
uuid: None,
config: None,
server,
token,
connection_state: ConnectionState::HandShake,
connection,
enc: PacketEncoder::default(),
@@ -72,6 +90,8 @@ impl Client {
self.encrytion = true;
let shared_secret = self
.server
.lock()
.unwrap()
.private_key
.decrypt(Pkcs1v15Encrypt, &shared_secret)
.context("failed to decrypt shared secret")?;
@@ -136,6 +156,18 @@ impl Client {
packet.id
),
},
crate::protocol::ConnectionState::Config => match packet.id {
SClientInformation::PACKET_ID => {
self.handle_client_information(SClientInformation::read(bytebuf))
}
SAcknowledgeFinishConfig::PACKET_ID => {
self.handle_config_acknowledged(SAcknowledgeFinishConfig::read(bytebuf))
}
_ => log::error!(
"Failed to handle packet id {} while in Config state",
packet.id
),
},
_ => log::error!("Invalid Connection state {:?}", self.connection_state),
}
}
@@ -184,8 +216,16 @@ impl Client {
pub fn kick(&mut self, reason: String) {
// Todo
if self.connection_state == ConnectionState::Login {
self.send_packet(CLoginDisconnect::new(reason));
match self.connection_state {
ConnectionState::Login => {
self.send_packet(CLoginDisconnect::new(reason));
}
ConnectionState::Config => {
self.send_packet(CConfigDisconnect::new(reason));
}
_ => {
log::warn!("Cant't kick in {:?} State", self.connection_state)
}
}
self.close()
}

View File

@@ -1,4 +1,3 @@
use aes::cipher::{generic_array::GenericArray, BlockDecryptMut, BlockSizeUser, KeyIvInit};
use anyhow::{bail, ensure, Context};
use bytes::{Buf, BytesMut};

View File

@@ -37,7 +37,7 @@ impl PacketEncoder {
let data_len = self.buf.len() - start_len;
if false { // compression
}
}
let packet_len = data_len;
ensure!(

View File

@@ -1,12 +0,0 @@
use crate::protocol::RawPacket;
use super::Client;
pub struct Player {
// All networking stuff
pub client: Client,
}
impl Player {
pub fn handle_packet(&mut self, _packet: &mut RawPacket) {}
}

13
pumpkin/src/entity/mod.rs Normal file
View File

@@ -0,0 +1,13 @@
pub mod player;
pub type EntityId = i32;
pub struct Entity {
pub entity_id: EntityId,
}
impl Entity {
pub fn new(entity_id: EntityId) -> Self {
Self { entity_id }
}
}

View File

@@ -0,0 +1,103 @@
use std::rc::Rc;
use crate::{
client::Client,
protocol::{ClientPacket, RawPacket, VarInt},
};
use super::{Entity, EntityId};
pub struct Player {
pub entity: Entity,
// All networking stuff
pub client: Client,
}
impl Player {
pub fn new(entity: Entity, client: Client) -> Self {
Self { entity, client }
}
pub fn entity_id(&self) -> EntityId {
self.entity.entity_id
}
pub fn handle_packet(&mut self, _packet: &mut RawPacket) {}
pub fn send_packet<P: ClientPacket>(&mut self, packet: P) {
self.client.send_packet(packet);
}
}
pub enum Hand {
Main,
Off,
}
impl Hand {
pub fn from_varint(varint: VarInt) -> Self {
match varint {
0 => Self::Off,
1 => Self::Main,
_ => {
log::info!("Unexpected Hand {}", varint);
Self::Main
}
}
}
}
pub enum ChatMode {
Enabled,
CommandsOnly,
Hidden,
}
impl ChatMode {
pub fn from_varint(varint: VarInt) -> Self {
match varint {
0 => Self::Enabled,
1 => Self::CommandsOnly,
2 => Self::Hidden,
_ => {
log::info!("Unexpected ChatMode {}", varint);
Self::Enabled
}
}
}
}
#[derive(Clone, Copy)]
pub enum GameMode {
Undefined,
Survival,
Creative,
Adventure,
Spectator,
}
impl GameMode {
pub fn from_byte(byte: i8) -> Self {
match byte {
-1 => GameMode::Undefined,
0 => GameMode::Survival,
1 => GameMode::Creative,
2 => GameMode::Adventure,
3 => GameMode::Spectator,
_ => {
log::info!("Unexpected GameMode {}", byte);
Self::Survival
}
}
}
pub fn to_byte(self) -> i8 {
match self {
Self::Undefined => -1,
Self::Survival => 0,
Self::Creative => 1,
Self::Adventure => 2,
Self::Spectator => 3,
}
}
}

View File

@@ -1,5 +1,3 @@
#![feature(read_buf)]
use mio::net::TcpListener;
use mio::{Events, Interest, Poll, Token};
use std::collections::HashMap;
@@ -9,13 +7,17 @@ use std::io::{self};
const SERVER: Token = Token(0);
pub mod client;
pub mod entity;
pub mod protocol;
pub mod server;
pub mod util;
#[cfg(not(target_os = "wasi"))]
fn main() -> io::Result<()> {
use std::rc::Rc;
use std::{
rc::Rc,
sync::{Arc, Mutex},
};
use client::{interrupted, Client};
use server::Server;
@@ -35,14 +37,12 @@ fn main() -> io::Result<()> {
poll.registry()
.register(&mut listener, SERVER, Interest::READABLE)?;
// Map of `Token` -> `TcpStream`.
let mut connections = HashMap::new();
// Unique token for each incoming connection.
let mut unique_token = Token(SERVER.0 + 1);
log::info!("You now can connect to the server");
let server = Rc::new(Server::new());
let mut server = Arc::new(Mutex::new(Server::new()));
loop {
if let Err(err) = poll.poll(&mut events, None) {
@@ -82,11 +82,14 @@ fn main() -> io::Result<()> {
Interest::READABLE.add(Interest::WRITABLE),
)?;
connections.insert(token, Client::new(Rc::clone(&server), connection));
let rc_server = Arc::clone(&server);
let mut guard = server.try_lock().unwrap();
guard.new_client(rc_server, connection, token);
},
token => {
// Maybe received an event for a TCP connection.
let done = if let Some(client) = connections.get_mut(&token) {
let done = if let Some(client) = server.try_lock().unwrap().connections.get_mut(&token) {
client.poll(poll.registry(), event).unwrap();
client.closed
} else {
@@ -94,7 +97,7 @@ fn main() -> io::Result<()> {
false
};
if done {
if let Some(mut client) = connections.remove(&token) {
if let Some(mut client) = server.try_lock().unwrap().connections.remove(&token) {
poll.registry().deregister(&mut client.connection)?;
}
}

View File

@@ -423,6 +423,12 @@ impl ByteBuffer {
self.write_bytes_len(val.as_bytes(), max_len);
}
pub fn write_string_array(&mut self, array: &[String]) {
for string in array {
self.write_string(string)
}
}
// Read operations
/// Read a defined amount of raw bytes, or return an IO error if not enough bytes are

View File

@@ -0,0 +1,43 @@
use crate::protocol::ClientPacket;
pub struct CCookieRequest {
// TODO
}
impl ClientPacket for CCookieRequest {
const PACKET_ID: crate::protocol::VarInt = 0;
fn write(&self, bytebuf: &mut crate::protocol::bytebuf::buffer::ByteBuffer) {}
}
pub struct CConfigDisconnect {
reason: String,
}
impl CConfigDisconnect {
pub fn new(reason: String) -> Self {
Self { reason }
}
}
impl ClientPacket for CConfigDisconnect {
const PACKET_ID: crate::protocol::VarInt = 2;
fn write(&self, bytebuf: &mut crate::protocol::bytebuf::buffer::ByteBuffer) {
bytebuf.write_string(&self.reason);
}
}
pub struct CFinishConfig {}
impl CFinishConfig {
pub fn new() -> Self {
Self {}
}
}
impl ClientPacket for CFinishConfig {
const PACKET_ID: crate::protocol::VarInt = 3;
fn write(&self, _bytebuf: &mut crate::protocol::bytebuf::buffer::ByteBuffer) {}
}

View File

@@ -1,5 +1,7 @@
// Clientbound Packets
// Server -> Client
//
pub mod config;
pub mod login;
pub mod play;
pub mod status;

View File

@@ -0,0 +1,110 @@
use crate::{
entity::player::GameMode,
protocol::{ClientPacket, VarInt},
};
pub struct CLogin {
entity_id: i32,
is_hardcore: bool,
dimension_count: VarInt,
dimension_names: Vec<String>,
max_players: VarInt,
view_distance: VarInt,
simulated_distance: VarInt,
reduced_debug_info: bool,
enabled_respawn_screen: bool,
limited_crafting: bool,
dimension_type: VarInt,
dimension_name: String,
hashed_seed: i64,
game_mode: GameMode,
previous_gamemode: GameMode,
debug: bool,
is_flat: bool,
has_death_loc: bool,
death_dimension_name: Option<String>,
death_loc: Option<String>, // POSITION NOT STRING
portal_cooldown: VarInt,
enforce_secure_chat: bool,
}
impl CLogin {
pub fn new(
entity_id: i32,
is_hardcore: bool,
dimension_count: VarInt,
dimension_names: Vec<String>,
max_players: VarInt,
view_distance: VarInt,
simulated_distance: VarInt,
reduced_debug_info: bool,
enabled_respawn_screen: bool,
limited_crafting: bool,
dimension_type: VarInt,
dimension_name: String,
hashed_seed: i64,
game_mode: GameMode,
previous_gamemode: GameMode,
debug: bool,
is_flat: bool,
has_death_loc: bool,
death_dimension_name: Option<String>,
death_loc: Option<String>,
portal_cooldown: VarInt,
enforce_secure_chat: bool,
) -> Self {
Self {
entity_id,
is_hardcore,
dimension_count,
dimension_names,
max_players,
view_distance,
simulated_distance,
reduced_debug_info,
enabled_respawn_screen,
limited_crafting,
dimension_type,
dimension_name,
hashed_seed,
game_mode,
previous_gamemode,
debug,
is_flat,
has_death_loc,
death_dimension_name,
death_loc,
portal_cooldown,
enforce_secure_chat,
}
}
}
impl ClientPacket for CLogin {
const PACKET_ID: VarInt = 0x2B;
fn write(&self, bytebuf: &mut crate::protocol::bytebuf::buffer::ByteBuffer) {
bytebuf.write_i32(self.entity_id);
bytebuf.write_bool(self.is_hardcore);
bytebuf.write_var_int(self.dimension_count);
bytebuf.write_string_array(self.dimension_names.as_slice());
bytebuf.write_var_int(self.max_players);
bytebuf.write_var_int(self.view_distance);
bytebuf.write_var_int(self.simulated_distance);
bytebuf.write_bool(self.reduced_debug_info);
bytebuf.write_bool(self.enabled_respawn_screen);
bytebuf.write_bool(self.limited_crafting);
bytebuf.write_var_int(self.dimension_type);
bytebuf.write_string(&self.dimension_name);
bytebuf.write_i64(self.hashed_seed);
bytebuf.write_u8(self.game_mode.to_byte() as u8);
bytebuf.write_i8(self.previous_gamemode.to_byte());
bytebuf.write_bool(self.debug);
bytebuf.write_bool(self.is_flat);
bytebuf.write_bool(self.has_death_loc);
bytebuf.write_option(&self.death_dimension_name, |buf, v| buf.write_string(v));
bytebuf.write_option(&self.death_loc, |buf, v| buf.write_string(v));
bytebuf.write_var_int(self.portal_cooldown);
bytebuf.write_bool(self.enforce_secure_chat);
}
}

View File

@@ -91,9 +91,10 @@ pub type VarLong = i64;
pub enum ConnectionState {
HandShake,
Status,
Login,
Login,
Transfer,
Config,
Play,
}
impl ConnectionState {
@@ -102,7 +103,10 @@ impl ConnectionState {
1 => Self::Status,
2 => Self::Login,
3 => Self::Transfer,
_ => panic!("Unexpected Status {}", var_int),
_ => {
log::info!("Unexpected Status {}", var_int);
Self::Status
}
}
}
}
@@ -138,7 +142,7 @@ pub struct Version {
pub struct Players {
pub max: u32,
pub online: u32,
pub sample: Sample,
pub sample: Vec<Sample>,
}
#[derive(Serialize, Deserialize)]

View File

@@ -0,0 +1,42 @@
use crate::{
entity::player::{ChatMode, Hand},
protocol::{bytebuf::buffer::ByteBuffer, VarInt},
};
pub struct SClientInformation {
pub locale: String, // 16
pub view_distance: i8,
pub chat_mode: ChatMode, // Varint
pub chat_colors: bool,
pub skin_parts: u8,
pub main_hand: Hand,
pub text_filtering: bool,
pub server_listing: bool,
}
impl SClientInformation {
pub const PACKET_ID: VarInt = 0;
pub fn read(bytebuf: &mut ByteBuffer) -> Self {
Self {
locale: bytebuf.read_string_len(16).unwrap(),
view_distance: bytebuf.read_i8().unwrap(),
chat_mode: ChatMode::from_varint(bytebuf.read_var_int().unwrap()),
chat_colors: bytebuf.read_bool().unwrap(),
skin_parts: bytebuf.read_u8().unwrap(),
main_hand: Hand::from_varint(bytebuf.read_var_int().unwrap()),
text_filtering: bytebuf.read_bool().unwrap(),
server_listing: bytebuf.read_bool().unwrap(),
}
}
}
pub struct SAcknowledgeFinishConfig {}
impl SAcknowledgeFinishConfig {
pub const PACKET_ID: VarInt = 3;
pub fn read(_bytebuf: &mut ByteBuffer) -> Self {
Self {}
}
}

View File

@@ -1,5 +1,6 @@
// Serverbound Packets
// Client -> Server
pub mod config;
pub mod handshake;
pub mod login;
pub mod status;

View File

View File

@@ -1,20 +1,44 @@
use std::io::Cursor;
use std::{
collections::HashMap,
io::Cursor,
rc::Rc,
sync::{
atomic::{AtomicI32, Ordering},
Arc, Mutex,
},
};
use base64::{engine::general_purpose, Engine};
use mio::{net::TcpStream, Token};
use rsa::{rand_core::OsRng, traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
use crate::protocol::{Players, Sample, StatusResponse, Version};
use crate::{
client::Client,
entity::{
player::{GameMode, Player},
Entity, EntityId,
},
protocol::{client::play::CLogin, Players, Sample, StatusResponse, VarInt, Version},
};
pub struct Server {
pub compression_threshold: Option<u8>,
pub online_mode: bool,
pub encriyption: bool, // encription is always required when online_mode is disabled
pub encryption: bool, // encryptiony is always required when online_mode is disabled
pub public_key: RsaPublicKey,
pub private_key: RsaPrivateKey,
pub public_key_der: Box<[u8]>,
pub max_players: u32,
pub status_response: StatusResponse,
pub connections: HashMap<Token, Client>,
// todo replace with HashMap <World, Player>
entity_id: AtomicI32, // todo: place this into every world
pub players: Vec<Player>,
pub difficulty: Difficulty,
}
impl Default for Server {
@@ -25,7 +49,8 @@ impl Default for Server {
impl Server {
pub fn new() -> Self {
let status_response = Self::default_response();
let max_players = 20;
let status_response = Self::default_response(max_players);
// todo, only create when needed
let (public_key, private_key) = Self::generate_keys();
@@ -37,17 +62,66 @@ impl Server {
.into_boxed_slice();
Self {
// 0 is invalid
entity_id: 2.into(),
online_mode: true,
encriyption: true,
encryption: true,
compression_threshold: None, // 256
public_key,
private_key,
max_players,
status_response,
public_key_der,
connections: HashMap::new(),
players: Vec::new(),
difficulty: Difficulty::Normal,
}
}
pub fn default_response() -> StatusResponse {
pub fn new_client(&mut self, rc: Arc<Mutex<Server>>, connection: TcpStream, token: Token) {
self.connections
.insert(token, Client::new(rc, Rc::new(token), connection));
}
pub fn spawn_player(&mut self, token: &Token) {
let mut player = Player {
entity: Entity::new(self.new_entity_id()),
client: self.connections.remove(token).unwrap(),
};
player.send_packet(CLogin::new(
player.entity_id(),
self.difficulty == Difficulty::Hard,
1,
vec!["minecraft:overworld".into()],
self.max_players as VarInt,
8, // view distance todo
8, // sim view dinstance todo
false,
false,
false,
1,
"minecraft:overworld".into(),
0, // seed
GameMode::Survival,
GameMode::Undefined,
false,
false,
false, // deth loc
None,
None,
0,
false,
));
self.players.push(player);
}
// move to world
pub fn new_entity_id(&self) -> EntityId {
self.entity_id.fetch_add(1, Ordering::SeqCst)
}
pub fn default_response(max_players: u32) -> StatusResponse {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png");
StatusResponse {
@@ -56,12 +130,12 @@ impl Server {
protocol: 767,
},
players: Players {
max: 20,
max: max_players,
online: 0,
sample: Sample {
sample: vec![Sample {
name: "".into(),
id: "".into(),
},
}],
},
description: "Pumpkin Server".into(),
favicon: Self::load_icon(path),
@@ -88,3 +162,10 @@ impl Server {
(pub_key, priv_key)
}
}
#[derive(PartialEq)]
pub enum Difficulty {
Easy,
Normal,
Hard,
}