mirror of
https://github.com/G2-Games/minecraft_alpha_server.git
synced 2025-04-19 07:12:58 -05:00
257 lines
7.6 KiB
Rust
257 lines
7.6 KiB
Rust
mod utils;
|
|
mod byte_ops;
|
|
mod chunk;
|
|
|
|
use std::{io::{self, Read, Write}, net::{TcpListener, TcpStream}};
|
|
|
|
use base16ct::lower::encode_string;
|
|
use chunk::{BlockArray, MapChunk, PreChunk};
|
|
use log::{info, warn};
|
|
use byteorder::{ReadBytesExt, WriteBytesExt, BE};
|
|
use num_derive::FromPrimitive;
|
|
use num_traits::FromPrimitive;
|
|
use byte_ops::ToBytes;
|
|
use utils::{MCString, ReadMCString, WriteMCString};
|
|
use rand::random;
|
|
|
|
|
|
fn main() {
|
|
colog::default_builder()
|
|
.filter_level(log::LevelFilter::Debug)
|
|
.init();
|
|
|
|
let listener = TcpListener::bind("0.0.0.0:25565").unwrap();
|
|
|
|
for mut connection in listener.incoming().filter_map(|c| c.ok()) {
|
|
info!("Connected to client @ {}", connection.peer_addr().unwrap());
|
|
while let Some(cmd) = connection.read_u8().ok() {
|
|
let command = Command::from_u8(cmd);
|
|
info!("COMMAND: {command:?} (0x{cmd:02X?})");
|
|
handle_command(&mut connection, command.unwrap()).unwrap();
|
|
}
|
|
warn!("Lost connection to client");
|
|
}
|
|
}
|
|
|
|
fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<(), io::Error> {
|
|
match command {
|
|
Command::KeepAlive => todo!(),
|
|
Command::Handshake => {
|
|
let username = connection.read_mcstring()?;
|
|
let random_number = random::<u128>();
|
|
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_mcstring(&MCString::try_from(random_hash).unwrap())?;
|
|
},
|
|
Command::Login => {
|
|
info!("Initiating login");
|
|
let protocol_version = connection.read_u32::<BE>()?;
|
|
let username = connection.read_mcstring()?;
|
|
let password = connection.read_mcstring()?;
|
|
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 login_packet = ServerLoginPacket {
|
|
entity_id: 1200,
|
|
unknown1: MCString::default(),
|
|
unknown2: MCString::default(),
|
|
map_seed: 1715505462032542147,
|
|
dimension: 0,
|
|
};
|
|
connection.write_u8(Command::Login as u8).unwrap();
|
|
connection.write(&login_packet.to_bytes())?;
|
|
|
|
info!("Responded to auth request");
|
|
|
|
for i in 0..7 {
|
|
for o in 0..7 {
|
|
let x = (-4 + i) * 16;
|
|
let z = (-5 + o) * 16;
|
|
|
|
connection.write_u8(Command::PreChunk as u8).unwrap();
|
|
connection.write_all(&PreChunk::new_load(x, z).to_bytes()).unwrap();
|
|
|
|
connection.write_u8(Command::MapChunk as u8)?;
|
|
connection.write_all(&MapChunk::new(x, z, BlockArray::new_air()).to_bytes())?;
|
|
}
|
|
}
|
|
|
|
connection.write_u8(Command::SpawnPosition as u8)?;
|
|
connection.write_u32::<BE>(0)?;
|
|
connection.write_u32::<BE>(70)?;
|
|
connection.write_u32::<BE>(0)?;
|
|
|
|
let playerpos = PlayerPositionAndLook {
|
|
position_x: 0.0,
|
|
stance: 0.0,
|
|
position_y: 70.0,
|
|
position_z: 0.0,
|
|
yaw: 0.0,
|
|
pitch: 0.0,
|
|
on_ground: true,
|
|
};
|
|
connection.write_u8(Command::PlayerPositionAndLook as u8)?;
|
|
connection.write_all(&playerpos.to_bytes())?;
|
|
},
|
|
Command::PlayerPositionAndLook => {
|
|
let _poslook = PlayerPositionAndLook::from_bytes(&mut connection);
|
|
}
|
|
Command::PlayerPosition => {
|
|
let _pos = PlayerPosition::from_bytes(&mut connection);
|
|
}
|
|
c => unimplemented!("This command ({c:?}) is probably `Server -> Client` only")
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[derive(FromPrimitive)]
|
|
enum Command {
|
|
KeepAlive = 0x00,
|
|
Login = 0x01,
|
|
Handshake = 0x02,
|
|
ChatMessage = 0x03,
|
|
TimeUpdate = 0x04,
|
|
PlayerInventory = 0x05,
|
|
SpawnPosition = 0x06,
|
|
UpdateHealth = 0x08,
|
|
Respawn = 0x09,
|
|
PlayerPositionAndLook = 0x0D,
|
|
PlayerPosition = 0x0B,
|
|
PreChunk = 0x32,
|
|
MapChunk = 0x33,
|
|
Disconnect = 0xFF,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct CommandError {
|
|
_id: u8,
|
|
}
|
|
|
|
struct ServerLoginPacket {
|
|
entity_id: i32,
|
|
unknown1: MCString,
|
|
unknown2: MCString,
|
|
map_seed: i64,
|
|
dimension: i8,
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct PlayerPositionAndLook {
|
|
position_x: f64,
|
|
stance: f64,
|
|
position_y: f64,
|
|
position_z: f64,
|
|
yaw: f32,
|
|
pitch: f32,
|
|
on_ground: bool,
|
|
}
|
|
|
|
impl ToBytes for PlayerPositionAndLook {
|
|
type Bytes = Vec<u8>;
|
|
|
|
fn to_bytes(self) -> Self::Bytes {
|
|
let mut out_buf = Vec::new();
|
|
out_buf.write_f64::<BE>(self.position_x).unwrap();
|
|
out_buf.write_f64::<BE>(self.stance).unwrap();
|
|
out_buf.write_f64::<BE>(self.position_y).unwrap();
|
|
out_buf.write_f64::<BE>(self.position_z).unwrap();
|
|
out_buf.write_f32::<BE>(self.yaw).unwrap();
|
|
out_buf.write_f32::<BE>(self.pitch).unwrap();
|
|
out_buf.write_u8(self.on_ground as u8).unwrap();
|
|
|
|
out_buf
|
|
}
|
|
}
|
|
|
|
impl PlayerPositionAndLook {
|
|
fn from_bytes<R: Read>(stream: &mut R) -> Self {
|
|
let position_x = stream.read_f64::<BE>().unwrap();
|
|
let position_y = stream.read_f64::<BE>().unwrap();
|
|
let stance = stream.read_f64::<BE>().unwrap();
|
|
let position_z = stream.read_f64::<BE>().unwrap();
|
|
|
|
let yaw = stream.read_f32::<BE>().unwrap();
|
|
let pitch = stream.read_f32::<BE>().unwrap();
|
|
let on_ground = stream.read_u8().unwrap() != 0;
|
|
|
|
Self {
|
|
position_x,
|
|
stance,
|
|
position_y,
|
|
position_z,
|
|
yaw,
|
|
pitch,
|
|
on_ground
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct PlayerPosition {
|
|
position_x: f64,
|
|
position_y: f64,
|
|
stance: f64,
|
|
position_z: f64,
|
|
on_ground: bool,
|
|
}
|
|
|
|
impl ToBytes for PlayerPosition {
|
|
type Bytes = Vec<u8>;
|
|
|
|
fn to_bytes(self) -> Self::Bytes {
|
|
let mut out_buf = Vec::new();
|
|
out_buf.write_f64::<BE>(self.position_x).unwrap();
|
|
out_buf.write_f64::<BE>(self.position_y).unwrap();
|
|
out_buf.write_f64::<BE>(self.stance).unwrap();
|
|
out_buf.write_f64::<BE>(self.position_z).unwrap();
|
|
out_buf.write_u8(self.on_ground as u8).unwrap();
|
|
|
|
out_buf
|
|
}
|
|
}
|
|
|
|
impl PlayerPosition {
|
|
fn from_bytes<R: Read>(stream: &mut R) -> Self {
|
|
let position_x = stream.read_f64::<BE>().unwrap();
|
|
let position_y = stream.read_f64::<BE>().unwrap();
|
|
let stance = stream.read_f64::<BE>().unwrap();
|
|
let position_z = stream.read_f64::<BE>().unwrap();
|
|
|
|
let on_ground = stream.read_u8().unwrap() != 0;
|
|
|
|
Self {
|
|
position_x,
|
|
stance,
|
|
position_y,
|
|
position_z,
|
|
on_ground
|
|
}
|
|
}
|
|
}
|