Compare commits

..

No commits in common. "a9677205ddfaf538af2e02857d5bc78993dd38da" and "81dc4ec3d9208af06b25c9649e43cd714dfc376e" have entirely different histories.

13 changed files with 246 additions and 385 deletions

View file

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

View file

@ -1,54 +1,6 @@
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::fmt::Debug;
/// A trait to unify [`Block`]s and [`Item`]s.
pub trait BlockItemID: Debug + Clone + PartialEq {
/// The ID of the Block/Item
fn id(&self) -> i16;
/// The Block/Item corresponding to an ID
fn from_id(id: i16) -> Self;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockItem {
Unknown,
Block(Block),
Item(Item),
}
impl BlockItemID for BlockItem {
fn id(&self) -> i16 {
match self {
Self::Unknown => -1,
BlockItem::Block(b) => *b as i16,
BlockItem::Item(i) => *i as i16 + 255,
}
}
fn from_id(id: i16) -> Self {
if id <= 255 {
if let Some(b) = Block::from_i16(id) {
Self::Block(b)
} else {
Self::Unknown
}
} else {
if let Some(b) = Item::from_i16(id - 255) {
Self::Item(b)
} else {
Self::Unknown
}
}
}
}
#[repr(i16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(FromPrimitive)]
pub enum Block {
Air = 0,
Unknown = -1,
Stone = 1,
Grass = 2,
Dirt = 3,
@ -127,126 +79,110 @@ pub enum Block {
PumpkinLantern = 91,
}
#[repr(i16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(FromPrimitive)]
pub enum Item {
ShovelSteel = 0,
PickaxeSteel = 1,
AxeSteel = 2,
FlintAndSteel = 3,
AppleRed = 4,
Bow = 5,
Arrow = 6,
Coal = 7,
Diamond = 8,
IngotIron = 9,
IngotGold = 10,
SwordSteel = 11,
SwordWood = 12,
ShovelWood = 13,
PickaxeWood = 14,
AxeWood = 15,
SwordStone = 16,
ShovelStone = 17,
PickaxeStone = 18,
AxeStone = 19,
SwordDiamond = 20,
ShovelDiamond = 21,
PickaxeDiamond = 22,
AxeDiamond = 23,
Stick = 24,
BowlEmpty = 25,
BowlSoup = 26,
SwordGold = 27,
ShovelGold = 28,
PickaxeGold = 29,
AxeGold = 30,
Silk = 31,
Feather = 32,
Gunpowder = 33,
HoeWood = 34,
HoeStone = 35,
HoeSteel = 36,
HoeDiamond = 37,
HoeGold = 38,
Seeds = 39,
Wheat = 40,
Bread = 41,
HelmetLeather = 42,
PlateLeather = 43,
LegsLeather = 44,
BootsLeather = 45,
HelmetChain = 46,
PlateChain = 47,
LegsChain = 48,
BootsChain = 49,
HelmetSteel = 50,
PlateSteel = 51,
LegsSteel = 52,
BootsSteel = 53,
HelmetDiamond = 54,
PlateDiamond = 55,
LegsDiamond = 56,
BootsDiamond = 57,
HelmetGold = 58,
PlateGold = 59,
LegsGold = 60,
BootsGold = 61,
Flint = 62,
PorkRaw = 63,
PorkCooked = 64,
Painting = 65,
AppleGold = 66,
Sign = 67,
DoorWood = 68,
BucketEmpty = 69,
BucketWater = 70,
BucketLava = 71,
MinecartEmpty = 72,
Saddle = 73,
DoorSteel = 74,
Redstone = 75,
Snowball = 76,
Boat = 77,
Leather = 78,
BucketMilk = 79,
Brick = 80,
Clay = 81,
Reed = 82,
Paper = 83,
Book = 84,
SlimeBall = 85,
MinecartCrate = 86,
MinecartPowered = 87,
Egg = 88,
Compass = 89,
FishingRod = 90,
PocketSundial = 91,
LightStoneDust = 92,
FishRaw = 93,
FishCooked = 94,
Unknown = -1,
ShovelSteel = 256,
PickaxeSteel = 257,
AxeSteel = 258,
FlintAndSteel = 259,
AppleRed = 260,
Bow = 261,
Arrow = 262,
Coal = 263,
Diamond = 264,
IngotIron = 265,
IngotGold = 266,
SwordSteel = 267,
SwordWood = 268,
ShovelWood = 269,
PickaxeWood = 270,
AxeWood = 271,
SwordStone = 272,
ShovelStone = 273,
PickaxeStone = 274,
AxeStone = 275,
SwordDiamond = 276,
ShovelDiamond = 277,
PickaxeDiamond = 278,
AxeDiamond = 279,
Stick = 280,
BowlEmpty = 281,
BowlSoup = 282,
SwordGold = 283,
ShovelGold = 284,
PickaxeGold = 285,
AxeGold = 286,
Silk = 287,
Feather = 288,
Gunpowder = 289,
HoeWood = 290,
HoeStone = 291,
HoeSteel = 292,
HoeDiamond = 293,
HoeGold = 294,
Seeds = 295,
Wheat = 296,
Bread = 297,
HelmetLeather = 298,
PlateLeather = 299,
LegsLeather = 300,
BootsLeather = 301,
HelmetChain = 302,
PlateChain = 303,
LegsChain = 304,
BootsChain = 305,
HelmetSteel = 306,
PlateSteel = 307,
LegsSteel = 308,
BootsSteel = 309,
HelmetDiamond = 310,
PlateDiamond = 311,
LegsDiamond = 312,
BootsDiamond = 313,
HelmetGold = 314,
PlateGold = 315,
LegsGold = 316,
BootsGold = 317,
Flint = 318,
PorkRaw = 319,
PorkCooked = 320,
Painting = 321,
AppleGold = 322,
Sign = 323,
DoorWood = 324,
BucketEmpty = 325,
BucketWater = 326,
BucketLava = 327,
MinecartEmpty = 328,
Saddle = 329,
DoorSteel = 330,
Redstone = 331,
Snowball = 332,
Boat = 333,
Leather = 334,
BucketMilk = 335,
Brick = 336,
Clay = 337,
Reed = 338,
Paper = 339,
Book = 340,
SlimeBall = 341,
MinecartCrate = 342,
MinecartPowered = 343,
Egg = 344,
Compass = 345,
FishingRod = 346,
PocketSundial = 347,
LightStoneDust = 348,
FishRaw = 349,
FishCooked = 350,
Record13 = 2000,
RecordCat = 2001,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ItemStack {
pub stack_size: i32,
pub animations_to_go: i32,
pub item_id: BlockItem,
pub item_damage: i32,
}
impl ItemStack {
pub fn new(item_id: i32, stack_size: i32, item_damage: i32) -> Self {
Self {
stack_size,
item_id: BlockItem::from_id(item_id as i16),
item_damage,
animations_to_go: -1,
}
impl Item {
fn index() -> i16 {
256
}
}

View file

@ -1,4 +1,4 @@
use std::{io::Read, ops};
use std::ops;
pub trait ToBytes: Sized {
/// A byte array which can store a packed representation of this type.
@ -7,12 +7,6 @@ pub trait ToBytes: Sized {
fn to_bytes(self) -> Self::Bytes;
}
pub trait FromBytes: Sized {
type Bytes: ByteArray;
fn from_bytes<R: Read>(stream: &mut R) -> Self;
}
mod private {
pub trait ByteArray {}

View file

@ -1,8 +1,9 @@
use byteorder::{WriteBytesExt, BE};
use flate2::{Compression, write::ZlibEncoder};
use flate2::write::ZlibEncoder;
use flate2::Compression;
use num_derive::FromPrimitive;
use std::io::prelude::*;
use crate::blocks_items::Block;
use crate::byte_ops::ToBytes;
#[derive(Debug, Clone)]
@ -70,13 +71,13 @@ impl BlockArray {
for z in 0..CHUNK_WIDTH_Z {
let pos = y + (z * (CHUNK_HEIGHT_Y)) + (x * (CHUNK_HEIGHT_Y) * (CHUNK_WIDTH_X));
if y == 7 {
blocks[pos] = Block::Grass as u8;
blocks[pos] = BlockType::Grass as u8;
} else if y > 0 && y < 7 {
blocks[pos] = Block::Dirt as u8;
blocks[pos] = BlockType::Dirt as u8;
} else if y == 0 {
blocks[pos] = Block::Bedrock as u8;
blocks[pos] = BlockType::Bedrock as u8;
} else {
blocks[pos] = Block::Air as u8;
blocks[pos] = BlockType::Air as u8;
}
}
}
@ -91,6 +92,21 @@ impl BlockArray {
}
}
#[repr(i16)]
#[derive(Debug, Clone, Copy)]
#[derive(FromPrimitive)]
pub enum BlockType {
None = -1,
Air,
Stone,
Grass,
Dirt,
Cobblestone,
Planks,
Sapling,
Bedrock,
}
impl ToBytes for MapChunk {
type Bytes = Vec<u8>;

View file

@ -1,28 +0,0 @@
use std::sync::atomic::{self, AtomicI32};
/// The current Entity ID. Incremented by one every time there is a new entity.
///
/// This value should rarely be accessed directly, and definitely never updated.
pub static ENTITY_ID: EntityID = EntityID::new(0);
pub struct EntityID {
id: AtomicI32
}
impl EntityID {
/// Create a new entity ID
const fn new(id: i32) -> Self {
Self {
id: AtomicI32::new(id)
}
}
/// Get a new Entity ID and increment the global value by 1.
#[inline]
pub fn get(&self) -> i32 {
let eid = self.id.load(atomic::Ordering::Relaxed);
self.id.store(eid + 1, atomic::Ordering::Relaxed);
eid
}
}

View file

@ -1,45 +1,49 @@
mod mcstring;
mod utils;
mod byte_ops;
mod chunk;
mod position;
mod state;
mod player;
mod blocks_items;
mod entity_id;
mod packets;
use std::{io::{self, Write}, net::{TcpListener, TcpStream}, process::exit, sync::{Arc, RwLock}, thread};
use std::{io::{self, Write}, net::{TcpListener, TcpStream}, sync::{atomic::{self, AtomicI32}, Arc, RwLock}, thread};
use base16ct::lower::encode_string;
use chunk::{BlockArray, MapChunk, PreChunk};
use entity_id::ENTITY_ID;
use log::{debug, error, info};
use log::{error, info, warn};
use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use byte_ops::ToBytes;
use packets::{packet15_place::Packet15Place, packet1_login, Packet};
use player::DiggingStatus;
use player::{DiggingStatus, PlayerBlockPlacement};
use position::{PlayerLook, PlayerPosition, PlayerPositionLook};
use state::{GameState, PlayerState};
use mcstring::{MCString, ReadMCString, WriteMCString};
use utils::{MCString, ReadMCString, WriteMCString};
use rand::random;
/// The current Entity ID. Incremented by one every time there is a new entity.
///
/// This value should rarely be accessed directly, and definitely never updated.
static ENTITY_ID: AtomicI32 = AtomicI32::new(0);
/// Get an Entity ID and increment the global value by 1.
#[inline]
fn get_eid() -> i32 {
let eid = ENTITY_ID.load(atomic::Ordering::Relaxed);
ENTITY_ID.store(eid + 1, atomic::Ordering::Relaxed);
eid
}
fn main() {
colog::default_builder()
.filter_level(log::LevelFilter::Debug)
.init();
info!("Starting Minecraft server version Beta 1.1_02");
info!("Setting up game state");
let game_state: Arc<RwLock<GameState>> = Arc::new(RwLock::new(GameState::new()));
let listener = match TcpListener::bind("0.0.0.0:25565") {
Ok(l) => l,
Err(e) => {
error!("Starting server failed: {e}");
exit(1)
},
};
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()) {
@ -78,17 +82,17 @@ fn player_loop(
break;
}
if player_state.is_valid()
&& (game_state.read().unwrap().player_list().get(player_state.username()).is_some_and(|p| *p != player_state)
|| game_state.read().unwrap().player_list().get(player_state.username()).is_none())
{
game_state.write()
.unwrap()
.player_list_mut()
.insert(player_state.username().clone(), player_state.clone());
if player_state.is_valid() {
if game_state.read().unwrap().player_list().get(player_state.username()).is_some_and(|p| *p != player_state)
|| game_state.read().unwrap().player_list().get(player_state.username()).is_none()
{
game_state.write()
.unwrap()
.player_list_mut()
.insert(player_state.username().clone(), player_state.clone());
}
}
}
Ok(())
@ -111,19 +115,22 @@ fn handle_command(
info!("Handshake with {username} successful");
},
Command::Login => {
let login_info = packet1_login::Packet1Login::read_from(&mut connection)?;
let protocol_version = connection.read_u32::<BE>()?;
let username = connection.read_mcstring()?;
// These are mostly useless
let _password = connection.read_mcstring()?;
let _map_seed = connection.read_i64::<BE>()?;
let _dimension = connection.read_i8()?;
// Return a successful login packet to the client
let eid = ENTITY_ID.get();
let login_packet = packet1_login::Packet1Login::new(eid, 0, 0);
connection.write_u8(Command::Login as u8)?;
login_packet.write_into(&mut connection)?;
let eid = get_eid();
let login_packet = ServerLoginPacket::new(eid, 0, 0);
connection.write_u8(Command::Login as u8).unwrap();
connection.write_all(&login_packet.to_bytes())?;
info!("{} [{}] logged in with entity id {}", login_info.username, connection.peer_addr().unwrap(), eid);
info!("{username} logged in. Protocol version {protocol_version}");
*player_state = PlayerState::new(username.to_string(), eid);
*player_state = PlayerState::new(login_info.username.to_string(), eid);
// Send "chunks" to the player. This simulates a flat-world of 20 by 20 chunks.
for i in -10..10 {
for o in -10..10 {
let x = i * 16;
@ -179,7 +186,7 @@ fn handle_command(
}
Command::HoldingChange => {
let _unused = connection.read_i32::<BE>()?;
let _block_id = connection.read_i16::<BE>()?;
let block_id = connection.read_i16::<BE>()?;
}
Command::PlayerDigging => {
let _status = DiggingStatus::from_u8(connection.read_u8()?).unwrap();
@ -189,8 +196,7 @@ fn handle_command(
let _face = connection.read_u8()?;
}
Command::PlayerBlockPlacement => {
let status = Packet15Place::read_from(&mut connection)?;
dbg!(status);
let _status = PlayerBlockPlacement::from_bytes(&mut connection);
}
Command::Animation => {
let _eid = connection.read_i32::<BE>()?;
@ -203,7 +209,6 @@ fn handle_command(
}
Command::KeepAlive => {
connection.write_u8(Command::KeepAlive as u8)?;
info!("Keepalive!");
}
Command::UpdateHealth => {
connection.read_u8()?;
@ -211,8 +216,6 @@ fn handle_command(
c => unimplemented!("This command ({c:?}) is probably `Server -> Client` only; thus it is unimplemented for the other way around!")
}
connection.write_u8(Command::KeepAlive as u8)?;
Ok(())
}
@ -257,3 +260,38 @@ enum Command {
ComplexEntities = 0x3B,
Disconnect = 0xFF,
}
struct ServerLoginPacket {
entity_id: i32,
unknown1: MCString,
unknown2: MCString,
map_seed: i64,
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 {
type Bytes = Vec<u8>;
fn to_bytes(self) -> Self::Bytes {
let mut out_buf = Vec::new();
out_buf.write_i32::<BE>(self.entity_id).unwrap();
out_buf.write_mcstring(&self.unknown1).unwrap();
out_buf.write_mcstring(&self.unknown2).unwrap();
out_buf.write_i64::<BE>(self.map_seed).unwrap();
out_buf.write_i8(self.dimension).unwrap();
out_buf
}
}

View file

@ -1,19 +0,0 @@
use std::io::{self, Read, Write};
pub mod packet1_login;
pub mod packet15_place;
pub mod packet101;
/// A packet for communicating across the network.
pub trait Packet
where Self: Sized
{
/// Read the packet in from a stream
fn read_from<R: Read>(input: &mut R) -> Result<Self, io::Error>;
/// Write the packet out to a stream
fn write_into<W: Write>(&self, output: &mut W) -> Result<(), io::Error>;
/// The size of the packet in bytes
fn size(&self) -> usize;
}

View file

@ -1,8 +0,0 @@
use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use num_traits::FromPrimitive;
use super::Packet;
pub struct Packet101 {
}

View file

@ -1,40 +0,0 @@
use crate::{blocks_items::{BlockItem, BlockItemID, ItemStack}, player::Direction};
use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use num_traits::FromPrimitive;
use super::Packet;
#[derive(Debug, Clone, Copy)]
pub struct Packet15Place {
id: BlockItem,
x_position: i32,
y_position: u8,
z_position: i32,
direction: u8,
amount: Option<u8>,
health: Option<i16>,
}
impl Packet for Packet15Place {
fn read_from<R: std::io::Read>(input: &mut R) -> Result<Self, std::io::Error> {
let id = BlockItem::from_id(input.read_i16::<BE>()?);
Ok(Self {
id,
x_position: input.read_i32::<BE>()?,
y_position: input.read_u8()?,
z_position: input.read_i32::<BE>()?,
direction: input.read_u8()?,
amount: if id.id() <= 0 { None } else { Some(input.read_u8()?) },
health: if id.id() <= 0 { None } else { Some(input.read_i16::<BE>()?) }
})
}
fn write_into<W: std::io::Write>(&self, output: &mut W) -> Result<(), std::io::Error> {
unimplemented!()
}
fn size(&self) -> usize {
todo!()
}
}

View file

@ -1,58 +0,0 @@
use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use crate::mcstring::{MCString, WriteMCString, ReadMCString};
use super::Packet;
#[derive(Debug, Clone)]
pub struct Packet1Login {
pub username: MCString,
pub password: MCString,
pub protocol_version: i32,
pub world_seed: i64,
pub dimension: i8,
}
impl Packet1Login {
pub fn new(protocol_version: i32, world_seed: i64, dimension: i8) -> Self {
Self {
username: MCString::default(),
password: MCString::default(),
protocol_version,
world_seed,
dimension,
}
}
}
impl Packet for Packet1Login {
fn read_from<R: std::io::Read>(input: &mut R) -> Result<Self, std::io::Error> {
let protocol_version = input.read_i32::<BE>()?;
let username = input.read_mcstring()?.into();
let password = input.read_mcstring()?.into();
let world_seed = input.read_i64::<BE>()?;
let dimension = input.read_i8()?;
Ok(Packet1Login {
username,
password,
protocol_version,
world_seed,
dimension,
})
}
fn write_into<W: std::io::Write>(&self, output: &mut W) -> Result<(), std::io::Error> {
output.write_i32::<BE>(self.protocol_version).unwrap();
output.write_mcstring(&self.username).unwrap();
output.write_mcstring(&self.password).unwrap();
output.write_i64::<BE>(self.world_seed).unwrap();
output.write_i8(self.dimension).unwrap();
Ok(())
}
fn size(&self) -> usize {
4 + self.username.len() + self.password.len() + 8 + 1
}
}

View file

@ -1,4 +1,10 @@
use std::io::Read;
use byteorder::{ReadBytesExt, BE};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use crate::chunk::BlockType;
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
@ -31,3 +37,32 @@ pub struct PlayerDigging {
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

@ -1,7 +1,8 @@
use std::collections::BTreeMap;
use crate::{
blocks_items::BlockItem, chunk::MapChunk, position::{PlayerLook, PlayerPosition, PlayerPositionLook}
chunk::{BlockType, MapChunk},
position::{PlayerLook, PlayerPosition, PlayerPositionLook},
};
pub struct GameState {
@ -28,7 +29,7 @@ impl GameState {
pub struct PlayerState {
eid: i32,
username: String,
holding: BlockItem,
holding: i16,
position_look: PlayerPositionLook,
}
@ -38,7 +39,7 @@ impl PlayerState {
Self {
eid,
username,
holding: BlockItem::Unknown,
holding: -1,
position_look: PlayerPositionLook::default(),
}
}
@ -47,7 +48,7 @@ impl PlayerState {
Self {
eid: -1,
username: String::new(),
holding: BlockItem::Unknown,
holding: -1,
position_look: PlayerPositionLook::default(),
}
}
@ -76,7 +77,7 @@ impl PlayerState {
&self.position_look
}
pub fn holding(&self) -> &BlockItem {
pub fn holding(&self) -> &BlockType {
&self.holding
}
@ -88,7 +89,7 @@ impl PlayerState {
self.position_look.look = look
}
pub fn set_holding(&mut self, holding: BlockItem) {
pub fn set_holding(&mut self, holding: i16) {
self.holding = holding
}
}

View file

@ -2,18 +2,12 @@ use std::{fmt::Display, io::{self, Read, Write}};
use byteorder::{ReadBytesExt, WriteBytesExt, BE};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[derive(Debug, Default, Clone)]
pub struct MCString {
len: u16,
chars: Vec<u8>,
}
impl MCString {
pub fn len(&self) -> usize {
self.len as usize
}
}
impl TryFrom<&str> for MCString {
type Error = ();