544 lines
20 KiB
Rust
544 lines
20 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
fmt::Debug,
|
|
fs::read_to_string,
|
|
io::{Cursor, Read, Write},
|
|
net::{Shutdown, SocketAddr, TcpListener, TcpStream},
|
|
path::PathBuf,
|
|
sync::Arc,
|
|
thread,
|
|
};
|
|
|
|
use potato_protocol::{
|
|
datatypes::{identifier::Identifier, pack::Pack, var_int::VarInt},
|
|
packet::{
|
|
Packet,
|
|
clientbound::{
|
|
self, GameEventPacket, SetChunkCacheCenterPacket,
|
|
registry_data::{
|
|
Biome, BiomeEffects, DamageType, DimensionType, PaintingVariant, RegistryData,
|
|
RegistryDataEntry, WolfVariant,
|
|
},
|
|
status_response,
|
|
},
|
|
serverbound,
|
|
},
|
|
packet_encodable::{Json, Nbt, PacketDecodeError, PacketEncodable, PacketEncodeError},
|
|
};
|
|
use thiserror::Error;
|
|
|
|
fn main() {
|
|
println!("Looking for datapacks...");
|
|
let datapacks: Vec<PathBuf> = std::fs::read_dir("run/datapacks")
|
|
.expect("Failed to read datapacks directory")
|
|
.map(|f| f.unwrap().path())
|
|
.filter(|p| p.is_dir())
|
|
.collect();
|
|
println!("Found {} datapacks", datapacks.len());
|
|
println!("Loading datapacks...");
|
|
let mut damage_types = HashMap::new();
|
|
for datapack in datapacks {
|
|
let datapack_name = datapack.file_name().unwrap().to_str().unwrap();
|
|
println!("Loading datapack: {}", datapack_name);
|
|
let path = datapack.join("data/minecraft/damage_type");
|
|
if let Ok(files) = std::fs::read_dir(path) {
|
|
for file in files {
|
|
let file = file.unwrap().path();
|
|
let name = file.file_stem().unwrap().to_str().unwrap().to_owned();
|
|
let data: DamageType = serde_json::from_str(&read_to_string(file).unwrap())
|
|
.expect("Failed to parse damage type");
|
|
|
|
damage_types.insert(Identifier::minecraft(name), data);
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("Finished loading datapacks");
|
|
println!("Loaded {} damage types", damage_types.len());
|
|
|
|
let registries = Arc::new(Registries { damage_types });
|
|
|
|
let listener = TcpListener::bind("127.0.0.1:25565").unwrap();
|
|
println!("listening started, ready to accept");
|
|
for stream in listener.incoming() {
|
|
let registries = registries.clone();
|
|
match stream {
|
|
Err(e) => println!("Accept ERROR: {}", e),
|
|
// Explicit drop because of the diverging branches
|
|
Ok(stream) => drop(thread::spawn(|| handle_client(stream, registries))),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_client(stream: TcpStream, registries: Arc<Registries>) {
|
|
if let Ok(mut client) = ClientConnection::new(stream, registries) {
|
|
loop {
|
|
match client.read_packet() {
|
|
Ok(_) => (),
|
|
Err(e) => {
|
|
println!("[{}] Error while handling packets: {}", client.address, e);
|
|
// Make sure the connection is closed.
|
|
let _ = client.stream.shutdown(Shutdown::Both);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum ConnectionState {
|
|
Handshaking,
|
|
Status,
|
|
Login,
|
|
Configuration,
|
|
Play,
|
|
}
|
|
|
|
struct ClientConnection {
|
|
stream: TcpStream,
|
|
address: SocketAddr,
|
|
state: ConnectionState,
|
|
recv_buffer: Vec<u8>,
|
|
|
|
registries: Arc<Registries>,
|
|
}
|
|
|
|
struct Registries {
|
|
damage_types: HashMap<Identifier, DamageType>,
|
|
}
|
|
|
|
// Max packet size is 2MB in vanilla
|
|
const RECV_BUFFER_SIZE: usize = 1024 * 1024 * 2;
|
|
|
|
#[allow(clippy::enum_variant_names)]
|
|
#[derive(Error, Debug)]
|
|
enum ConnectionError {
|
|
#[error("IO error while reading packet: {0}")]
|
|
IoError(#[from] std::io::Error),
|
|
#[error("Error while decoding packet: {0}")]
|
|
DecodeError(#[from] PacketDecodeError),
|
|
#[error("Error while encoding packet: {0}")]
|
|
EncodeError(#[from] PacketEncodeError),
|
|
#[error("Client provided invalid next state")]
|
|
InvalidNextState,
|
|
#[error("Finished")]
|
|
Finished,
|
|
}
|
|
|
|
// TODO: Need to start sending keep alive packets once we enter configuration phase
|
|
impl ClientConnection {
|
|
fn new(
|
|
stream: TcpStream,
|
|
registries: Arc<Registries>,
|
|
) -> Result<ClientConnection, std::io::Error> {
|
|
let address = stream.peer_addr()?;
|
|
|
|
Ok(ClientConnection {
|
|
state: ConnectionState::Handshaking,
|
|
address,
|
|
stream,
|
|
recv_buffer: vec![0; RECV_BUFFER_SIZE],
|
|
registries,
|
|
})
|
|
}
|
|
|
|
fn read_packet(&mut self) -> Result<(), ConnectionError> {
|
|
let packet_size: usize = VarInt::read(&mut self.stream)?.into();
|
|
|
|
self.stream
|
|
.read_exact(&mut self.recv_buffer[..packet_size])?;
|
|
|
|
self.handle_packet(packet_size)
|
|
}
|
|
|
|
fn send_packet<T: Packet>(&mut self, packet: &T) -> Result<(), ConnectionError> {
|
|
println!("[{} ({:?})] -> {:?}", self.address, self.state, packet);
|
|
let buffer = &mut Vec::new();
|
|
// Encode ID
|
|
let packet_id: i32 = match self.state {
|
|
ConnectionState::Handshaking => T::HANDSHAKE_ID,
|
|
ConnectionState::Status => T::STATUS_ID,
|
|
ConnectionState::Login => T::LOGIN_ID,
|
|
ConnectionState::Configuration => T::CONFIGURATION_ID,
|
|
ConnectionState::Play => T::PLAY_ID,
|
|
};
|
|
if packet_id == -1 {
|
|
return Err(ConnectionError::EncodeError(
|
|
PacketEncodeError::InvalidPacketId(packet_id),
|
|
));
|
|
}
|
|
let packet_id: VarInt = packet_id.into();
|
|
|
|
packet_id.encode_packet(buffer)?;
|
|
// Encode packet
|
|
packet.encode_packet(buffer)?;
|
|
// Encode length
|
|
let length_buffer = &mut Vec::new();
|
|
let length: VarInt = buffer.len().into();
|
|
length.encode_packet(length_buffer)?;
|
|
|
|
// Print raw packet bytes
|
|
// println!(
|
|
// "{:X?} ({}) {:X?} ({})",
|
|
// length_buffer,
|
|
// length_buffer.len(),
|
|
// buffer,
|
|
// buffer.len()
|
|
// );
|
|
|
|
// Send packet
|
|
self.stream.write_all(length_buffer)?;
|
|
self.stream.write_all(buffer)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_packet(&mut self, length: usize) -> Result<(), ConnectionError> {
|
|
match self.state {
|
|
ConnectionState::Handshaking => {
|
|
self.handle_handshaking(length)?;
|
|
}
|
|
ConnectionState::Status => {
|
|
self.handle_status(length)?;
|
|
}
|
|
ConnectionState::Login => {
|
|
self.handle_login(length)?;
|
|
}
|
|
ConnectionState::Configuration => {
|
|
self.handle_configuration(length)?;
|
|
}
|
|
ConnectionState::Play => {
|
|
self.handle_play(length)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_handshaking(&mut self, length: usize) -> Result<(), ConnectionError> {
|
|
let cursor = &mut Cursor::new(&self.recv_buffer[..length]);
|
|
let id = VarInt::decode_packet(cursor)?.into();
|
|
|
|
match id {
|
|
serverbound::IntentionPacket::HANDSHAKE_ID => {
|
|
let packet = serverbound::IntentionPacket::decode_packet(cursor)?;
|
|
println!("[{} (Handshaking)] <- {:?}", self.address, packet,);
|
|
self.change_state_num(packet.next_state.into())?
|
|
}
|
|
// TODO: Legacy server list ping
|
|
_ => {
|
|
println!(
|
|
"[{} (Handshaking)] <- Unknown packet id: {} with length {} data: {:X?}",
|
|
self.address,
|
|
id,
|
|
length,
|
|
&self.recv_buffer[..length]
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_status(&mut self, length: usize) -> Result<(), ConnectionError> {
|
|
let cursor = &mut Cursor::new(&self.recv_buffer[..length]);
|
|
let id: i32 = VarInt::decode_packet(cursor)?.into();
|
|
|
|
match id {
|
|
serverbound::StatusRequestPacket::STATUS_ID => {
|
|
let packet = serverbound::StatusRequestPacket;
|
|
println!("[{} (Status)] <- {:?}", self.address, packet);
|
|
self.send_packet(&clientbound::StatusResponsePacket {
|
|
status: Json(status_response::StatusResponseData {
|
|
version: status_response::Version {
|
|
name: "Rust 1.21.4".to_owned(),
|
|
protocol: 769,
|
|
},
|
|
players: status_response::Players {
|
|
max: 500,
|
|
online: 0,
|
|
sample: vec![],
|
|
},
|
|
description: status_response::Description {
|
|
text: "A rust server!".to_owned(),
|
|
},
|
|
favicon: None,
|
|
enforce_secure_chat: false,
|
|
}),
|
|
})?;
|
|
}
|
|
serverbound::PingRequestPacket::STATUS_ID => {
|
|
let packet = serverbound::PingRequestPacket::decode_packet(cursor)?;
|
|
println!("[{} (Status)] <- {:?}", self.address, packet);
|
|
self.send_packet(&clientbound::PongResponsePacket {
|
|
timestamp: packet.timestamp,
|
|
})?;
|
|
self.stream.shutdown(Shutdown::Both)?;
|
|
return Err(ConnectionError::Finished);
|
|
}
|
|
|
|
_ => {
|
|
println!(
|
|
"[{} (Status)] <- Unknown packet id: {} with length {} data: {:X?}",
|
|
self.address,
|
|
id,
|
|
length,
|
|
&self.recv_buffer[..length]
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_login(&mut self, length: usize) -> Result<(), ConnectionError> {
|
|
let cursor = &mut Cursor::new(&self.recv_buffer[..length]);
|
|
let id: i32 = VarInt::decode_packet(cursor)?.into();
|
|
|
|
match id {
|
|
serverbound::HelloPacket::LOGIN_ID => {
|
|
let packet = serverbound::HelloPacket::decode_packet(cursor)?;
|
|
println!("[{} (Login)] <- {:?}", self.address, packet);
|
|
self.send_packet(&clientbound::LoginFinishedPacket {
|
|
uuid: packet.uuid,
|
|
username: packet.name,
|
|
properties: vec![],
|
|
})?;
|
|
}
|
|
serverbound::LoginAcknowledgedPacket::LOGIN_ID => {
|
|
let packet = serverbound::LoginAcknowledgedPacket::decode_packet(cursor)?;
|
|
println!("[{} (Login)] <- {:?}", self.address, packet);
|
|
self.change_state(ConnectionState::Configuration)?;
|
|
self.send_packet(&clientbound::SelectKnownPacksPacket {
|
|
packs: vec![Pack {
|
|
namespace: "minecraft".to_owned(),
|
|
id: "core".to_owned(),
|
|
version: "1.21.4".to_owned(),
|
|
}],
|
|
})?;
|
|
}
|
|
_ => {
|
|
println!(
|
|
"[{} (Login)] <- Unknown packet id: {} with length {} data: {:X?}",
|
|
self.address,
|
|
id,
|
|
length,
|
|
&self.recv_buffer[..length]
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_configuration(&mut self, length: usize) -> Result<(), ConnectionError> {
|
|
let cursor = &mut Cursor::new(&self.recv_buffer[..length]);
|
|
let id: i32 = VarInt::decode_packet(cursor)?.into();
|
|
|
|
match id {
|
|
serverbound::ClientInformationPacket::CONFIGURATION_ID => {
|
|
let packet = serverbound::ClientInformationPacket::decode_packet(cursor)?;
|
|
println!("[{} (Configuration)] <- {:?}", self.address, packet);
|
|
}
|
|
serverbound::CustomPayloadPacket::CONFIGURATION_ID => {
|
|
let packet = serverbound::CustomPayloadPacket::decode_packet(cursor)?;
|
|
println!("[{} (Configuration)] <- {:?}", self.address, packet);
|
|
}
|
|
serverbound::SelectKnownPacksPacket::CONFIGURATION_ID => {
|
|
let packet = serverbound::SelectKnownPacksPacket::decode_packet(cursor)?;
|
|
println!("[{} (Configuration)] <- {:?}", self.address, packet);
|
|
|
|
self.send_packet(&clientbound::RegistryDataPacket {
|
|
registry_id: Identifier::minecraft_str("dimension_type"),
|
|
entries: vec![RegistryDataEntry {
|
|
id: Identifier::minecraft_str("overworld"),
|
|
data: Some(Nbt(RegistryData::DimensionType(DimensionType {
|
|
fixed_time: None,
|
|
has_skylight: 1,
|
|
has_ceiling: 0,
|
|
ultrawarm: 0,
|
|
natural: 1,
|
|
coordinate_scale: 1.,
|
|
bed_works: 1,
|
|
respawn_anchor_works: 0,
|
|
min_y: 0,
|
|
height: 256,
|
|
logical_height: 256,
|
|
infiniburn: "#minecraft:infiniburn_overworld".to_owned(),
|
|
effects: "minecraft:overworld".to_owned(),
|
|
ambient_light: 0.,
|
|
piglin_safe: 1,
|
|
has_raids: 1,
|
|
monster_spawn_light_level: 0,
|
|
monster_spawn_block_light_limit: 0,
|
|
}))),
|
|
}],
|
|
})?;
|
|
|
|
self.send_packet(&clientbound::RegistryDataPacket {
|
|
registry_id: Identifier::minecraft_str("painting_variant"),
|
|
entries: vec![RegistryDataEntry {
|
|
id: Identifier::minecraft_str("backyard"),
|
|
data: Some(Nbt(RegistryData::PaintingVariant(PaintingVariant {
|
|
asset_id: "minecraft:backyard".to_owned(),
|
|
height: 2,
|
|
width: 2,
|
|
}))),
|
|
}],
|
|
})?;
|
|
|
|
self.send_packet(&clientbound::RegistryDataPacket {
|
|
registry_id: Identifier::minecraft_str("wolf_variant"),
|
|
entries: vec![RegistryDataEntry {
|
|
id: Identifier::minecraft_str("ashen"),
|
|
data: Some(Nbt(RegistryData::WolfVariant(WolfVariant {
|
|
wild_texture: "minecraft:entity/wolf/wolf_ashen".to_owned(),
|
|
tame_texture: "minecraft:entity/wolf/wolf_ashen_tame".to_owned(),
|
|
angry_texture: "minecraft:entity/wolf/wolf_ashen_angry".to_owned(),
|
|
biomes: vec![],
|
|
}))),
|
|
}],
|
|
})?;
|
|
|
|
let damage_type_registry_entries = self
|
|
.registries
|
|
.damage_types
|
|
.iter()
|
|
.map(|(key, value)| RegistryDataEntry {
|
|
id: key.clone(),
|
|
data: Some(Nbt(RegistryData::DamegeType(value.clone()))),
|
|
})
|
|
.collect();
|
|
|
|
self.send_packet(&clientbound::RegistryDataPacket {
|
|
registry_id: Identifier::minecraft_str("damage_type"),
|
|
entries: damage_type_registry_entries,
|
|
})?;
|
|
|
|
self.send_packet(&clientbound::RegistryDataPacket {
|
|
registry_id: Identifier::minecraft_str("worldgen/biome"),
|
|
entries: vec![RegistryDataEntry {
|
|
id: Identifier::minecraft_str("plains"),
|
|
data: Some(Nbt(RegistryData::Biome(Biome {
|
|
has_precipitation: 0,
|
|
temperature: 0.,
|
|
temperature_modifier: None,
|
|
downfall: 0.,
|
|
effects: BiomeEffects {
|
|
fog_color: 0,
|
|
water_color: 0,
|
|
water_fog_color: 0,
|
|
sky_color: 0,
|
|
foliage_color: None,
|
|
grass_color: None,
|
|
grass_color_modifier: None,
|
|
particle: None,
|
|
ambient_sound: None,
|
|
mood_sound: None,
|
|
additions_sound: None,
|
|
music: None,
|
|
},
|
|
}))),
|
|
}],
|
|
})?;
|
|
|
|
self.send_packet(&clientbound::FinishConfigurationPacket)?;
|
|
}
|
|
serverbound::FinishConfigurationPacket::CONFIGURATION_ID => {
|
|
let packet = serverbound::finish_configuration::FinishConfigurationPacket;
|
|
println!("[{} (Configuration)] <- {:?}", self.address, packet);
|
|
self.change_state(ConnectionState::Play)?;
|
|
self.send_packet(&clientbound::LoginPacket {
|
|
entity_id: 0,
|
|
is_hardcore: false,
|
|
dimension_names: vec![],
|
|
max_players: 500.into(),
|
|
view_distance: 10.into(),
|
|
simulation_distance: 10.into(),
|
|
reduced_debug_info: false,
|
|
enable_respawn_screen: true,
|
|
do_limited_crafting: false,
|
|
dimension_type: 0.into(),
|
|
dimension_name: Identifier::minecraft("overworld".to_owned()),
|
|
hashed_seed: 0,
|
|
game_mode: 1,
|
|
previous_game_mode: -1,
|
|
is_debug: false,
|
|
is_flat: false,
|
|
death_info: None,
|
|
portal_cooldown: 0.into(),
|
|
sea_level: 63.into(),
|
|
enforces_secure_chat: false,
|
|
})?;
|
|
|
|
self.send_packet(&GameEventPacket {
|
|
event: 13,
|
|
data: 0.,
|
|
})?;
|
|
|
|
self.send_packet(&SetChunkCacheCenterPacket {
|
|
x: 0.into(),
|
|
z: 0.into(),
|
|
})?;
|
|
}
|
|
_ => {
|
|
println!(
|
|
"[{} (Configuration)] <- Unknown packet id: {} with length {} data: {:X?}",
|
|
self.address,
|
|
id,
|
|
length,
|
|
&self.recv_buffer[..length]
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_play(&mut self, length: usize) -> Result<(), ConnectionError> {
|
|
let cursor = &mut Cursor::new(&self.recv_buffer[..length]);
|
|
let id: i32 = VarInt::decode_packet(cursor)?.into();
|
|
|
|
match id {
|
|
serverbound::ClientTickEndPacket::PLAY_ID => {
|
|
serverbound::ClientTickEndPacket::decode_packet(cursor)?;
|
|
}
|
|
serverbound::MovePlayerPosPacket::PLAY_ID => {
|
|
let _packet = serverbound::MovePlayerPosPacket::decode_packet(cursor)?;
|
|
// println!("[{} (Play)] <- {:?}", self.address, packet);
|
|
}
|
|
serverbound::MovePlayerPosRotPacket::PLAY_ID => {
|
|
let _packet = serverbound::MovePlayerPosRotPacket::decode_packet(cursor)?;
|
|
// println!("[{} (Play)] <- {:?}", self.address, packet);
|
|
}
|
|
serverbound::MovePlayerRotPacket::PLAY_ID => {
|
|
let _packet = serverbound::MovePlayerRotPacket::decode_packet(cursor)?;
|
|
// println!("[{} (Play)] <- {:?}", self.address, packet);
|
|
}
|
|
_ => {
|
|
println!(
|
|
"[{} (Play)] <- Unknown packet id: {} with length {} data: {:X?}",
|
|
self.address,
|
|
id,
|
|
length,
|
|
&self.recv_buffer[..length]
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn change_state_num(&mut self, state: i32) -> Result<(), ConnectionError> {
|
|
let new_state = match state {
|
|
1 => ConnectionState::Status,
|
|
2 => ConnectionState::Login,
|
|
_ => return Err(ConnectionError::InvalidNextState),
|
|
};
|
|
|
|
self.change_state(new_state)
|
|
}
|
|
|
|
fn change_state(&mut self, new_state: ConnectionState) -> Result<(), ConnectionError> {
|
|
println!("[{}] {:?} -> {:?}", self.address, self.state, new_state);
|
|
self.state = new_state;
|
|
|
|
Ok(())
|
|
}
|
|
}
|