mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
193 lines
5.5 KiB
Rust
193 lines
5.5 KiB
Rust
use std::{
|
|
io::{self, Cursor},
|
|
sync::atomic::{AtomicI32, Ordering},
|
|
};
|
|
|
|
use base64::{engine::general_purpose, Engine};
|
|
use mio::{event::Event, Poll};
|
|
use rsa::{rand_core::OsRng, traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
client::{Client, PacketError},
|
|
configuration::{AdvancedConfiguration, BasicConfiguration},
|
|
entity::{
|
|
player::{GameMode, Player},
|
|
Entity, EntityId,
|
|
},
|
|
protocol::{
|
|
client::{config::CPluginMessage, play::CLogin},
|
|
Players, Sample, StatusResponse, VarInt, VarInt32, Version,
|
|
},
|
|
world::World,
|
|
};
|
|
|
|
pub struct Server {
|
|
pub compression_threshold: Option<u8>,
|
|
|
|
pub online_mode: bool,
|
|
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 world: World,
|
|
|
|
pub status_response: StatusResponse,
|
|
|
|
// todo replace with HashMap <World, Player>
|
|
entity_id: AtomicI32, // todo: place this into every world
|
|
pub difficulty: Difficulty,
|
|
}
|
|
|
|
impl Server {
|
|
pub fn new(config: (BasicConfiguration, AdvancedConfiguration)) -> Self {
|
|
let max_players = 20;
|
|
let config_clone = &config;
|
|
let status_response = Self::default_response(config_clone);
|
|
|
|
// todo, only create when needed
|
|
let (public_key, private_key) = Self::generate_keys();
|
|
|
|
let public_key_der = rsa_der::public_key_to_der(
|
|
&private_key.n().to_bytes_be(),
|
|
&private_key.e().to_bytes_be(),
|
|
)
|
|
.into_boxed_slice();
|
|
|
|
Self {
|
|
// 0 is invalid
|
|
entity_id: 2.into(),
|
|
world: World::new(),
|
|
online_mode: config.0.online_mode,
|
|
encryption: config.1.encryption,
|
|
compression_threshold: None, // 256
|
|
public_key,
|
|
private_key,
|
|
max_players,
|
|
status_response,
|
|
public_key_der,
|
|
difficulty: config.0.default_difficulty,
|
|
}
|
|
}
|
|
|
|
// Returns Tokens to remove
|
|
pub fn poll(
|
|
&mut self,
|
|
client: &mut Client,
|
|
_poll: &Poll,
|
|
event: &Event,
|
|
) -> Result<bool, io::Error> {
|
|
// todo, Poll players in every world
|
|
client.poll(self, event)
|
|
}
|
|
|
|
pub fn spawn_player(&mut self, client: &mut Client) {
|
|
let player = Player {
|
|
entity: Entity {
|
|
entity_id: self.new_entity_id(),
|
|
},
|
|
};
|
|
|
|
client
|
|
.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,
|
|
))
|
|
.unwrap_or_else(|e| client.kick(&e.to_string()));
|
|
|
|
dbg!("spawning player");
|
|
client.player = Some(player);
|
|
}
|
|
|
|
// move to world
|
|
pub fn new_entity_id(&self) -> EntityId {
|
|
self.entity_id.fetch_add(1, Ordering::SeqCst)
|
|
}
|
|
|
|
pub fn send_brand(client: &mut Client) -> Result<(), PacketError> {
|
|
// send server brand
|
|
let brand = "pumpkin";
|
|
let mut buf = vec![];
|
|
let _ = VarInt32(brand.len() as i32).encode(&mut buf);
|
|
buf.extend_from_slice(brand.as_bytes());
|
|
client.send_packet(CPluginMessage::new(
|
|
"minecraft:brand".to_string(),
|
|
buf.as_slice(),
|
|
))
|
|
}
|
|
|
|
pub fn default_response(
|
|
config: &(BasicConfiguration, AdvancedConfiguration),
|
|
) -> StatusResponse {
|
|
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png");
|
|
|
|
StatusResponse {
|
|
version: Version {
|
|
name: "1.21".into(),
|
|
protocol: 767,
|
|
},
|
|
players: Players {
|
|
max: config.0.max_plyers,
|
|
online: 0,
|
|
sample: vec![Sample {
|
|
name: "".into(),
|
|
id: "".into(),
|
|
}],
|
|
},
|
|
description: config.0.motd.clone(),
|
|
favicon: Self::load_icon(path),
|
|
}
|
|
}
|
|
|
|
pub fn load_icon(path: &str) -> String {
|
|
let mut icon = match image::open(path).map_err(|e| panic!("error loading icon: {}", e)) {
|
|
Ok(icon) => icon,
|
|
Err(_) => return "".into(),
|
|
};
|
|
icon = icon.resize_exact(64, 64, image::imageops::FilterType::Triangle);
|
|
let mut image = Vec::new();
|
|
icon.write_to(&mut Cursor::new(&mut image), image::ImageFormat::Png)
|
|
.unwrap();
|
|
let mut result = "data:image/png;base64,".to_owned();
|
|
general_purpose::STANDARD.encode_string(image, &mut result);
|
|
result
|
|
}
|
|
|
|
pub fn generate_keys() -> (RsaPublicKey, RsaPrivateKey) {
|
|
let priv_key = RsaPrivateKey::new(&mut OsRng, 1024).expect("failed to generate a key");
|
|
let pub_key = RsaPublicKey::from(&priv_key);
|
|
(pub_key, priv_key)
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Serialize, Deserialize)]
|
|
pub enum Difficulty {
|
|
Peaceful,
|
|
Easy,
|
|
Normal,
|
|
Hard,
|
|
}
|