Compare commits

..

4 commits

Author SHA1 Message Date
G2
e898dfa859
Update README.md 2024-10-11 02:04:08 -05:00
G2
ceafebac70
Update README.md 2024-10-11 02:00:45 -05:00
G2
d059b9f160
Create README.md 2024-10-10 20:17:03 -05:00
33a0b37564 Cleanup and work on state 2024-10-10 20:14:55 -05:00
6 changed files with 203 additions and 98 deletions

4
README.md Normal file
View file

@ -0,0 +1,4 @@
# Alpha Server
A server written in rust or something idk
Targeting Minecraft Alpha `1.2.6`, server version `0.2.8`.

View file

@ -1,6 +1,7 @@
use byteorder::{WriteBytesExt, BE}; use byteorder::{WriteBytesExt, BE};
use flate2::write::ZlibEncoder; use flate2::write::ZlibEncoder;
use flate2::Compression; use flate2::Compression;
use num_derive::FromPrimitive;
use std::io::prelude::*; use std::io::prelude::*;
use crate::byte_ops::ToBytes; use crate::byte_ops::ToBytes;
@ -32,79 +33,71 @@ impl MapChunk {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BlockArray { pub struct BlockArray {
compressed_size: i32, blocks: Vec<u8>,
compressed_data: Vec<u8>, metadata: Vec<u8>,
block_light: Vec<u8>,
sky_light: Vec<u8>,
} }
const CHUNK_WIDTH_X: usize = 16;
const CHUNK_WIDTH_Z: usize = 16;
const CHUNK_HEIGHT_Y: usize = 128;
const CHUNK_TOTAL_BLOCKS: usize = CHUNK_WIDTH_X * CHUNK_WIDTH_Z * CHUNK_HEIGHT_Y;
impl BlockArray { impl BlockArray {
pub fn new_air() -> Self { fn compress(self) -> Vec<u8> {
let mut block_vec = Vec::new();
for _ in 0..(16 * 127 * 15) {
block_vec.push(0);
}
for _ in 0..(16 * 127 * 15) / 2 {
block_vec.push(0);
}
for _ in 0..(16 * 127 * 15) / 2 {
block_vec.push(0);
}
for _ in 0..(16 * 127 * 15) / 2 {
block_vec.push(0);
}
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write(&block_vec).unwrap(); encoder.write(&self.blocks).unwrap();
encoder.write(&self.metadata).unwrap();
encoder.write(&self.block_light).unwrap();
encoder.write(&self.sky_light).unwrap();
let output_buf = encoder.finish().unwrap(); let output_buf = encoder.finish().unwrap();
output_buf
}
pub fn new_air() -> Self {
Self { Self {
compressed_size: output_buf.len() as i32, blocks: vec![0; CHUNK_TOTAL_BLOCKS],
compressed_data: output_buf, metadata: vec![0; CHUNK_TOTAL_BLOCKS / 2],
block_light: vec![0; CHUNK_TOTAL_BLOCKS / 2],
sky_light: vec![0xFF; CHUNK_TOTAL_BLOCKS / 2],
} }
} }
pub fn new_superflat() -> Self { pub fn new_superflat() -> Self {
let mut block_vec = vec![0; 16 * 16 * 128]; let mut blocks = vec![0; CHUNK_TOTAL_BLOCKS];
for y in 0..CHUNK_HEIGHT_Y {
for x in 0..16 { for x in 0..CHUNK_WIDTH_X {
for y in 0..128 { for z in 0..CHUNK_WIDTH_Z {
for z in 0..16 { let pos = y + (z * (CHUNK_HEIGHT_Y)) + (x * (CHUNK_HEIGHT_Y) * (CHUNK_WIDTH_X));
let pos = y + (z * (128)) + (x * (128) * (16));
if y == 7 { if y == 7 {
block_vec[pos] = BlockType::Grass as u8; blocks[pos] = BlockType::Grass as u8;
} else if y > 0 && y < 7 { } else if y > 0 && y < 7 {
block_vec[pos] = BlockType::Dirt as u8; blocks[pos] = BlockType::Dirt as u8;
} else if y == 0 { } else if y == 0 {
block_vec[pos] = BlockType::Bedrock as u8; blocks[pos] = BlockType::Bedrock as u8;
} else { } else {
block_vec[pos] = 0; blocks[pos] = BlockType::Air as u8;
} }
} }
} }
} }
for _ in 0..(16 * 128 * 16) / 2 {
block_vec.push(0);
}
for _ in 0..(16 * 128 * 16) / 2 {
block_vec.push(0);
}
for _ in 0..(16 * 128 * 16) / 2 {
block_vec.push(0xFF);
}
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write(&block_vec).unwrap();
let output_buf = encoder.finish().unwrap();
Self { Self {
compressed_size: output_buf.len() as i32, blocks,
compressed_data: output_buf, metadata: vec![0xFF; CHUNK_TOTAL_BLOCKS / 2],
block_light: vec![0; CHUNK_TOTAL_BLOCKS / 2],
sky_light: vec![0xFF; CHUNK_TOTAL_BLOCKS / 2],
} }
} }
} }
#[repr(u8)] #[repr(i16)]
enum BlockType { #[derive(Debug, Clone, Copy)]
#[derive(FromPrimitive)]
pub enum BlockType {
None = -1,
Air, Air,
Stone, Stone,
Grass, Grass,
@ -127,8 +120,9 @@ impl ToBytes for MapChunk {
buffer.write_u8(self.size_y).unwrap(); buffer.write_u8(self.size_y).unwrap();
buffer.write_u8(self.size_z).unwrap(); buffer.write_u8(self.size_z).unwrap();
buffer.write_i32::<BE>(self.compressed_data.compressed_size).unwrap(); let block_buf = self.compressed_data.compress();
buffer.write_all(&self.compressed_data.compressed_data).unwrap(); buffer.write_i32::<BE>(block_buf.len() as i32).unwrap();
buffer.write_all(&block_buf).unwrap();
buffer buffer
} }

View file

@ -2,8 +2,10 @@ mod utils;
mod byte_ops; mod byte_ops;
mod chunk; mod chunk;
mod position; mod position;
mod state;
mod player;
use std::{cell::LazyCell, io::{self, Write}, net::{TcpListener, TcpStream}}; use std::{io::{self, Write}, net::{TcpListener, TcpStream}, sync::RwLock};
use base16ct::lower::encode_string; use base16ct::lower::encode_string;
use chunk::{BlockArray, MapChunk, PreChunk}; use chunk::{BlockArray, MapChunk, PreChunk};
@ -12,23 +14,24 @@ use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use byte_ops::ToBytes; use byte_ops::ToBytes;
use player::{DiggingStatus, PlayerBlockPlacement};
use position::{PlayerLook, PlayerPosition, PlayerPositionAndLook}; use position::{PlayerLook, PlayerPosition, PlayerPositionAndLook};
use state::PlayerState;
use utils::{MCString, ReadMCString, WriteMCString}; use utils::{MCString, ReadMCString, WriteMCString};
use rand::random; use rand::random;
const CHUNKS: LazyCell<Vec<MapChunk>> = LazyCell::new(|| { /// List of players.
let mut mapchunk = Vec::new(); const PLAYER_LIST: RwLock<Vec<PlayerState>> = RwLock::new(Vec::new());
for i in -10..10 {
for o in -10..10 {
let x = i * 16;
let z = o * 16;
mapchunk.push(MapChunk::new(x, z, BlockArray::new_superflat())); /// The current Entity ID. Incremented by one every time there is a new entity.
} const ENTITY_ID: RwLock<i32> = RwLock::new(0);
}
mapchunk fn get_eid() -> i32 {
}); let eid = ENTITY_ID.read().unwrap().clone();
*ENTITY_ID.write().unwrap() += 1;
eid
}
fn main() { fn main() {
colog::default_builder() colog::default_builder()
@ -36,13 +39,15 @@ fn main() {
.init(); .init();
let listener = TcpListener::bind("0.0.0.0:25565").unwrap(); let listener = TcpListener::bind("0.0.0.0:25565").unwrap();
info!("Server started and listening on {}", listener.local_addr().unwrap());
for mut connection in listener.incoming().filter_map(|c| c.ok()) { for mut connection in listener.incoming().filter_map(|c| c.ok()) {
info!("Connected to client @ {}", connection.peer_addr().unwrap()); info!("Player joined from {}", connection.peer_addr().unwrap());
while let Some(cmd) = connection.read_u8().ok() { while let Some(cmd) = connection.read_u8().ok() {
let command = Command::from_u8(cmd); let command = Command::from_u8(cmd);
if command.is_none() { if command.is_none() {
info!("COMMAND: {command:?} (0x{cmd:02X?})"); info!("COMMAND: {command:?} (0x{cmd:02X?})");
panic!("This command isn't implemented yet");
} }
handle_command(&mut connection, command.unwrap()).unwrap(); handle_command(&mut connection, command.unwrap()).unwrap();
} }
@ -57,27 +62,22 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<()
let random_number = random::<u128>(); let random_number = random::<u128>();
let random_hash = encode_string(md5::compute(random_number.to_le_bytes()).as_slice()); let random_hash = encode_string(md5::compute(random_number.to_le_bytes()).as_slice());
info!("Handshake with {username} successful. Providing hash: {random_hash:?}");
connection.write_u8(0x02)?; connection.write_u8(0x02)?;
connection.write_mcstring(&MCString::try_from(random_hash).unwrap())?; connection.write_mcstring(&MCString::try_from(random_hash).unwrap())?;
info!("Handshake with {username} successful");
}, },
Command::Login => { Command::Login => {
info!("Initiating login");
let protocol_version = connection.read_u32::<BE>()?; let protocol_version = connection.read_u32::<BE>()?;
let username = connection.read_mcstring()?; let username = connection.read_mcstring()?;
let password = connection.read_mcstring()?; // These are mostly useless
let map_seed = connection.read_i64::<BE>()?; let _password = connection.read_mcstring()?;
let dimension = connection.read_i8()?; let _map_seed = connection.read_i64::<BE>()?;
let _dimension = connection.read_i8()?;
info!("Protocol Version: {protocol_version}");
info!("Username: {username}");
info!("Password: {password}");
info!("Map Seed: {map_seed}");
info!("Dimension: {dimension}");
let eid = get_eid();
let login_packet = ServerLoginPacket { let login_packet = ServerLoginPacket {
entity_id: 1, entity_id: eid,
unknown1: MCString::default(), unknown1: MCString::default(),
unknown2: MCString::default(), unknown2: MCString::default(),
map_seed: 0, map_seed: 0,
@ -86,7 +86,9 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<()
connection.write_u8(Command::Login as u8).unwrap(); connection.write_u8(Command::Login as u8).unwrap();
connection.write(&login_packet.to_bytes())?; connection.write(&login_packet.to_bytes())?;
info!("Responded to auth request"); PLAYER_LIST.write().unwrap().push(PlayerState::new(username.to_string(), eid));
info!("{username} logged in. Protocol version {protocol_version}");
for i in -10..10 { for i in -10..10 {
for o in -10..10 { for o in -10..10 {
@ -137,24 +139,28 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<()
let _poslook = PlayerPositionAndLook::from_bytes(&mut connection); let _poslook = PlayerPositionAndLook::from_bytes(&mut connection);
} }
Command::PlayerDigging => { Command::PlayerDigging => {
let status = DiggingStatus::from_u8(connection.read_u8()?).unwrap(); let _status = DiggingStatus::from_u8(connection.read_u8()?).unwrap();
let pos_x = connection.read_i32::<BE>()?; let _pos_x = connection.read_i32::<BE>()?;
let pos_y = connection.read_u8()?; let _pos_y = connection.read_u8()?;
let pos_z = connection.read_i32::<BE>()?; let _pos_z = connection.read_i32::<BE>()?;
let face = connection.read_u8()?; let _face = connection.read_u8()?;
}
Command::PlayerBlockPlacement => {
let _status = PlayerBlockPlacement::from_bytes(&mut connection);
dbg!(_status);
} }
Command::ArmAnimation => { Command::ArmAnimation => {
let eid = connection.read_i32::<BE>()?; let _eid = connection.read_i32::<BE>()?;
let animate = connection.read_u8()? != 0; let _animate = connection.read_u8()? != 0;
dbg!(_animate);
} }
Command::Disconnect => { Command::Disconnect => {
let disconnect_string = connection.read_mcstring()?; let disconnect_string = connection.read_mcstring()?;
info!("Disconnecting client: {disconnect_string}"); info!("Disconnecting client. Reason: {disconnect_string}");
connection.shutdown(std::net::Shutdown::Both)?; connection.shutdown(std::net::Shutdown::Both)?;
} }
Command::KeepAlive => { Command::KeepAlive => {
let _ = connection.write_u8(0x00); let _ = connection.write_u8(0x00);
// TODO: Feed keepalive watchdog for client
} }
Command::UpdateHealth => { Command::UpdateHealth => {
connection.read_u8()?; connection.read_u8()?;
@ -165,16 +171,6 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<()
Ok(()) Ok(())
} }
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[derive(FromPrimitive)]
enum DiggingStatus {
StartedDigging = 0,
Digging = 1,
StoppedDigging = 2,
BlockBroken = 3,
}
#[repr(u8)] #[repr(u8)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[derive(FromPrimitive)] #[derive(FromPrimitive)]
@ -193,6 +189,7 @@ enum Command {
PlayerLook = 0x0C, PlayerLook = 0x0C,
PlayerPositionAndLook = 0x0D, PlayerPositionAndLook = 0x0D,
PlayerDigging = 0x0E, PlayerDigging = 0x0E,
PlayerBlockPlacement = 0x0F,
ArmAnimation = 0x12, ArmAnimation = 0x12,
PreChunk = 0x32, PreChunk = 0x32,
MapChunk = 0x33, MapChunk = 0x33,
@ -207,6 +204,18 @@ struct ServerLoginPacket {
dimension: i8, dimension: i8,
} }
impl ServerLoginPacket {
pub fn new(entity_id: i32, map_seed: i64, dimension: i8) -> Self {
Self {
entity_id,
unknown1: MCString::default(),
unknown2: MCString::default(),
map_seed,
dimension,
}
}
}
impl ToBytes for ServerLoginPacket { impl ToBytes for ServerLoginPacket {
type Bytes = Vec<u8>; type Bytes = Vec<u8>;

68
src/player.rs Normal file
View file

@ -0,0 +1,68 @@
use std::io::Read;
use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use crate::chunk::BlockType;
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[derive(FromPrimitive)]
pub enum DiggingStatus {
StartedDigging = 0,
Digging = 1,
StoppedDigging = 2,
BlockBroken = 3,
}
/// The face of a block, a direction.
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
#[derive(FromPrimitive)]
pub enum Direction {
NegY = 0,
PosY = 1,
NegZ = 2,
PosZ = 3,
NegX = 4,
PosX = 5,
}
#[derive(Debug, Clone, Copy)]
pub struct PlayerDigging {
status: DiggingStatus,
position_x: i32,
position_y: u8,
position_z: i32,
face: Direction,
}
#[derive(Debug, Clone, Copy)]
pub struct PlayerBlockPlacement {
block_id: BlockType,
position_x: i32,
position_y: u8,
position_z: i32,
direction: Direction,
//amount: u8,
//health: u8,
}
impl PlayerBlockPlacement {
pub fn from_bytes<R: Read>(stream: &mut R) -> Self {
let block_id = BlockType::from_i16(stream.read_i16::<BE>().unwrap()).unwrap();
let position_x = stream.read_i32::<BE>().unwrap();
let position_y = stream.read_u8().unwrap();
let position_z = stream.read_i32::<BE>().unwrap();
let direction = Direction::from_u8(stream.read_u8().unwrap()).unwrap();
Self {
block_id,
position_x,
position_y,
position_z,
direction,
}
}
}

View file

@ -54,7 +54,7 @@ impl PlayerPositionAndLook {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct PlayerPosition { pub struct PlayerPosition {
pub position_x: f64, pub position_x: f64,
pub position_y: f64, pub position_y: f64,
@ -95,7 +95,7 @@ impl PlayerPosition {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct PlayerLook { pub struct PlayerLook {
pub yaw: f32, pub yaw: f32,
pub pitch: f32, pub pitch: f32,

30
src/state.rs Normal file
View file

@ -0,0 +1,30 @@
use crate::{
chunk::MapChunk, position::{PlayerLook, PlayerPosition},
};
pub struct GameState {
}
pub struct PlayerState {
eid: i32,
username: String,
position: PlayerPosition,
look: PlayerLook,
}
impl PlayerState {
/// Create a new player when they join
pub fn new(username: String, eid: i32,) -> Self {
Self {
eid,
username,
position: PlayerPosition::default(),
look: PlayerLook::default(),
}
}
}
pub struct WorldState {
chunks: Vec<MapChunk>,
}