initial commit

This commit is contained in:
TriMill 2022-12-02 18:27:53 -05:00
commit 0cdc71ab8a
12 changed files with 1292 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

230
Cargo.lock generated Normal file
View file

@ -0,0 +1,230 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"memchr",
]
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "flate2"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "hematite-nbt"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670d0784ee67cfb57393dc1837867d2951f9a59ca7db99a653499c854f745739"
dependencies = [
"byteorder",
"cesu8",
"flate2",
"serde",
]
[[package]]
name = "itoa"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "mlua"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4351dbcc863fb6249c81b3bd0c8001214e9d4d44d22cabda17026353a77fe612"
dependencies = [
"bstr",
"cc",
"num-traits",
"once_cell",
"pkg-config",
"rustc-hash",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "proc-macro2"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quectocraft"
version = "0.1.0"
dependencies = [
"hematite-nbt",
"mlua",
"serde",
"serde_json",
"uuid",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "serde"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "uuid"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"

13
Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "quectocraft"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hematite-nbt = "0.5"
serde_json = "1.0"
serde = "1.0"
mlua = { version = "0.8", features = ["lua54"] }
uuid = "1.2"

View file

@ -0,0 +1,23 @@
local plugin = {
id = "example_plugin",
name = "Example Plugin",
version = "0.1.0",
}
function plugin.init()
print("PLUGIN init")
end
function plugin.playerJoin(name, uuid)
print("PLUGIN player joined: " .. name .. " uuid " .. uuid)
end
function plugin.playerLeave(name, uuid)
print("PLUGIN player left: " .. name .. " uuid " .. uuid)
end
function plugin.chatMessage(message, author, authorUuid)
print("PLUGIN message from " .. author .. ": " .. message)
end
return plugin

30
src/main.rs Normal file
View file

@ -0,0 +1,30 @@
use std::time::Duration;
use mlua::Lua;
use network::NetworkServer;
use plugins::{Plugin, Plugins};
mod plugins;
mod protocol;
mod network;
fn main() {
let lua = Lua::new();
let plugin = Plugin::load("plugins/example_plugin/main.lua".into(), &lua).unwrap();
let mut plugins = Plugins::new(&lua).unwrap();
plugins.add_plugin(plugin);
let mut server = NetworkServer::new("127.0.0.1:25565".to_owned(), plugins);
let sleep_dur = Duration::from_millis(5);
let mut i = 0;
loop {
server.get_new_clients();
server.handle_connections();
std::thread::sleep(sleep_dur);
if i % 1024 == 0 {
server.send_keep_alive();
i = 0;
}
i += 1;
}
}

243
src/network/mod.rs Normal file
View file

@ -0,0 +1,243 @@
use std::{net::{TcpStream, TcpListener, Shutdown}, thread, io::Write, sync::mpsc::{Receiver, Sender, channel, TryRecvError}, time::Duration};
use serde_json::json;
use uuid::Uuid;
use crate::{protocol::{data::{PacketDecoder, PacketEncoder}, serverbound::*, clientbound::*, NetworkState}, plugins::Plugins};
pub struct NetworkServer<'lua> {
plugins: Plugins<'lua>,
new_clients: Receiver<NetworkClient>,
clients: Vec<NetworkClient>,
}
impl <'lua> NetworkServer<'lua> {
pub fn new(addr: String, plugins: Plugins<'lua>) -> Self {
let (send, recv) = channel();
thread::spawn(move || Self::listen(&addr, send));
plugins.init();
Self {
plugins,
new_clients: recv,
clients: Vec::new(),
}
}
fn listen(addr: &str, send_clients: Sender<NetworkClient>) {
println!("listening for connections");
let listener = TcpListener::bind(addr).unwrap();
for (id, stream) in listener.incoming().enumerate() {
let stream = stream.unwrap();
println!("got connection from {} (id {})", stream.peer_addr().unwrap(), id);
let stream_2 = stream.try_clone().unwrap();
let (send, recv) = channel();
thread::spawn(|| NetworkClient::listen(stream_2, send));
let client = NetworkClient {
id: id as i32,
play: false,
closed: false,
stream,
serverbound: recv,
player: None,
};
send_clients.send(client).unwrap();
}
}
pub fn get_new_clients(&mut self) {
while let Ok(client) = self.new_clients.try_recv() {
self.clients.push(client);
}
}
pub fn send_keep_alive(&mut self) {
let mut closed = Vec::new();
for client in self.clients.iter_mut() {
if client.play {
if let Err(_) = client.send_packet(ClientBoundPacket::KeepAlive(0)) {
client.close();
if let Some(pl) = &client.player {
self.plugins.player_leave(pl);
}
closed.push(client.id);
}
}
}
self.clients.retain(|x| !closed.contains(&x.id));
}
pub fn handle_connections(&mut self) {
let mut closed = Vec::new();
for i in 0..self.clients.len() {
let client: &mut NetworkClient = unsafe {
&mut *(self.clients.get_unchecked_mut(i) as *mut _)
};
let mut alive = true;
while let Some(packet) = client.recv_packet(&mut alive) {
if let Err(_) = self.handle_packet(client, packet) {
alive = false;
break;
}
}
if !alive && !client.closed {
closed.push(client.id);
if let Some(pl) = &client.player {
self.plugins.player_leave(pl);
}
client.close();
}
}
self.clients.retain(|x| !closed.contains(&x.id));
}
fn handle_packet(&mut self, client: &mut NetworkClient, packet: ServerBoundPacket) -> std::io::Result<()> {
match packet {
ServerBoundPacket::Ignored(_) => (),
ServerBoundPacket::Unknown(id) => println!("unknown: {}", id),
ServerBoundPacket::Handshake(_) => (),
ServerBoundPacket::StatusRequest()
=> client.send_packet(ClientBoundPacket::StatusResponse(
r#"{"version":{"name":"1.19.2","protocol":760}}"#.to_owned()
))?,
ServerBoundPacket::PingRequest(n) => {
client.send_packet(ClientBoundPacket::PingResponse(n))?;
client.close();
}
ServerBoundPacket::LoginStart(login_start) => {
client.player = Some(Player {
name: login_start.name.clone(),
uuid: login_start.uuid.unwrap(),
});
client.play = true;
client.send_packet(ClientBoundPacket::LoginSuccess(LoginSuccess {
name: login_start.name,
uuid: login_start.uuid.unwrap(),
}))?;
self.plugins.player_join(client.player.as_ref().unwrap());
self.post_login(client)?;
}
ServerBoundPacket::ChatMessage(msg) => {
self.plugins.chat_message(client.player.as_ref().unwrap(), &msg.message);
}
}
Ok(())
}
fn post_login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> {
client.send_packet(ClientBoundPacket::LoginPlay(LoginPlay {
eid: client.id,
is_hardcore: false,
gamemode: 1,
prev_gamemode: 1,
dimensions: vec![
"minecraft:world".to_owned(),
],
registry_codec: include_bytes!("../resources/registry_codec.nbt").to_vec(),
dimension_type: "minecraft:the_end".to_owned(),
dimension_name: "minecraft:world".to_owned(),
seed_hash: 0,
max_players: 0,
view_distance: 8,
sim_distance: 8,
reduced_debug_info: false,
respawn_screen: false,
is_debug: false,
is_flat: false,
death_location: None,
}))?;
client.send_packet(ClientBoundPacket::PluginMessage(PluginMessage {
channel: "minecraft:brand".to_owned(),
data: {
let mut data = Vec::new();
data.write_string(32767, "QuectoCraft");
data
}
}))?;
client.send_packet(ClientBoundPacket::PlayerAbilities(0x0d, 0.05, 0.1))?;
let mut chunk_data: Vec<u8> = Vec::new();
for _ in 0..(384 / 16) {
// number of non-air blocks
chunk_data.write_short(0);
// block states
chunk_data.write_ubyte(0);
chunk_data.write_varint(0);
chunk_data.write_varint(0);
// biomes
chunk_data.write_ubyte(0);
chunk_data.write_varint(0);
chunk_data.write_varint(0);
}
let hmdata = vec![0i64; 37];
let mut heightmap = nbt::Blob::new();
heightmap.insert("MOTION_BLOCKING", hmdata).unwrap();
client.send_packet(ClientBoundPacket::ChunkData(ChunkData {
x: 0,
z: 0,
heightmap,
chunk_data,
}))?;
client.send_packet(ClientBoundPacket::SyncPlayerPosition(SyncPlayerPosition {
x: 0.0,
y: 64.0,
z: 0.0,
yaw: 0.0,
pitch: 0.0,
flags: 0,
teleport_id: 0,
dismount: false
}))?;
Ok(())
}
}
#[derive(Debug)]
pub struct Player {
pub name: String,
pub uuid: Uuid,
}
struct NetworkClient {
pub id: i32,
pub play: bool,
pub closed: bool,
stream: TcpStream,
serverbound: Receiver<ServerBoundPacket>,
player: Option<Player>,
}
impl NetworkClient {
pub fn listen(mut stream: TcpStream, send: Sender<ServerBoundPacket>) {
let mut state = NetworkState::Handshake;
let dur = Duration::from_millis(5);
loop {
if let Some(decoder) = PacketDecoder::decode(&mut stream) {
let packet = ServerBoundPacket::decode(&mut state, decoder);
send.send(packet).unwrap();
}
thread::sleep(dur)
}
}
pub fn recv_packet(&mut self, alive: &mut bool) -> Option<ServerBoundPacket> {
match self.serverbound.try_recv() {
Ok(packet) => Some(packet),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => {
*alive = false;
None
}
}
}
pub fn send_packet(&mut self, packet: ClientBoundPacket) -> std::io::Result<()> {
self.stream.write_all(&packet.encode())?;
Ok(())
}
pub fn close(&mut self) {
println!("closed connection with {}", self.id);
let _ = self.stream.shutdown(Shutdown::Both);
self.closed = true;
}
}

128
src/plugins.rs Normal file
View file

@ -0,0 +1,128 @@
use std::{path::{Path, PathBuf}, str::FromStr};
use mlua::{Function, Lua, Error, Table};
use uuid::Uuid;
use crate::network::Player;
pub struct EventHandlers<'lua> {
init: Option<Function<'lua>>,
player_join: Option<Function<'lua>>,
player_leave: Option<Function<'lua>>,
chat_message: Option<Function<'lua>>,
}
pub struct Plugin<'lua> {
pub id: String,
pub name: String,
pub version: String,
pub event_handlers: EventHandlers<'lua>,
}
impl <'lua> Plugin<'lua> {
pub fn load(path: &str, lua: &'lua Lua) -> Result<Self, Box<dyn std::error::Error>> {
let path = PathBuf::from_str(path).unwrap();
let chunk = lua.load(&path);
let module: Table = chunk.eval()?;
let id: String = module.get("id")?;
let name: String = module.get("name").unwrap_or_else(|_| id.clone());
let version: String = module.get("version").unwrap_or_else(|_| "?".to_owned());
let init: Option<Function<'lua>> = module.get("init").ok();
let player_join: Option<Function<'lua>> = module.get("playerJoin").ok();
let player_leave: Option<Function<'lua>> = module.get("playerLeave").ok();
let chat_message: Option<Function<'lua>> = module.get("chatMessage").ok();
let event_handlers = EventHandlers { init, player_join, player_leave, chat_message };
Ok(Plugin { id, name, version, event_handlers })
}
}
pub struct Plugins<'lua> {
lua: &'lua Lua,
plugins: Vec<Plugin<'lua>>
}
impl <'lua> Plugins<'lua> {
pub fn new(lua: &'lua Lua) -> Result<Self, mlua::Error> {
let server = lua.create_table()?;
let players = lua.create_table()?;
server.set("players", players)?;
let fn_send = lua.create_function(|_, (uuid, message): (String, String)| {
Ok(())
})?;
server.set("send", fn_send)?;
lua.globals().set("server", server)?;
Ok(Self {
lua,
plugins: Vec::new(),
})
}
pub fn add_plugin(&mut self, pl: Plugin<'lua>) {
self.plugins.push(pl);
}
pub fn init(&self) {
for pl in &self.plugins {
if let Some(init) = &pl.event_handlers.init {
if let Err(e) = init.call::<_, ()>(()) {
println!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
pub fn player_join(&self, player: &Player) {
if let Err(e) = self.add_player(player) {
println!("Error adding player: {}", e);
return
}
for pl in &self.plugins {
if let Some(init) = &pl.event_handlers.player_join {
if let Err(e) = init.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) {
println!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
fn add_player(&self, player: &Player) -> Result<(), mlua::Error> {
let server: Table = self.lua.globals().get("server")?;
let players: Table = server.get("players")?;
players.set(player.uuid.to_string(), player.name.as_str())?;
Ok(())
}
pub fn player_leave(&self, player: &Player) {
if let Err(e) = self.remove_player(player.uuid) {
println!("Error removing player: {}", e);
return
}
for pl in &self.plugins {
if let Some(func) = &pl.event_handlers.player_leave {
if let Err(e) = func.call::<_, ()>((player.name.as_str(), player.uuid.to_string())) {
println!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
fn remove_player(&self, uuid: Uuid) -> Result<(), mlua::Error> {
let server: Table = self.lua.globals().get("server")?;
let players: Table = server.get("players")?;
players.set(uuid.to_string(), mlua::Nil)?;
Ok(())
}
pub fn chat_message(&self, player: &Player, message: &str) {
for pl in &self.plugins {
if let Some(func) = &pl.event_handlers.chat_message {
if let Err(e) = func.call::<_, ()>((message, player.name.as_str(), player.uuid.to_string())) {
println!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
}

204
src/protocol/clientbound.rs Normal file
View file

@ -0,0 +1,204 @@
use uuid::Uuid;
use super::{data::{PacketEncoder, finalize_packet}, Position};
#[derive(Debug)]
pub struct LoginSuccess {
pub uuid: Uuid,
pub name: String,
}
impl LoginSuccess {
pub fn encode(self, encoder: &mut impl PacketEncoder) {
encoder.write_uuid(self.uuid);
encoder.write_string(16, &self.name);
encoder.write_varint(0);
}
}
#[derive(Debug)]
pub struct LoginPlay {
pub eid: i32,
pub is_hardcore: bool,
pub gamemode: u8,
pub prev_gamemode: u8,
pub dimensions: Vec<String>,
pub registry_codec: Vec<u8>,
pub dimension_type: String,
pub dimension_name: String,
pub seed_hash: i64,
pub max_players: i32,
pub view_distance: i32,
pub sim_distance: i32,
pub reduced_debug_info: bool,
pub respawn_screen: bool,
pub is_debug: bool,
pub is_flat: bool,
pub death_location: Option<(String, Position)>
}
impl LoginPlay {
pub fn encode(self, encoder: &mut impl PacketEncoder) {
encoder.write_int(self.eid);
encoder.write_bool(self.is_hardcore);
encoder.write_ubyte(self.gamemode);
encoder.write_ubyte(self.prev_gamemode);
encoder.write_varint(self.dimensions.len() as i32);
for dim in self.dimensions {
encoder.write_string(32767, &dim);
}
encoder.write_bytes(&self.registry_codec);
encoder.write_string(32767, &self.dimension_type);
encoder.write_string(32767, &self.dimension_name);
encoder.write_long(self.seed_hash);
encoder.write_varint(self.max_players);
encoder.write_varint(self.view_distance);
encoder.write_varint(self.sim_distance);
encoder.write_bool(self.reduced_debug_info);
encoder.write_bool(self.respawn_screen);
encoder.write_bool(self.is_debug);
encoder.write_bool(self.is_flat);
encoder.write_bool(self.death_location.is_some());
if let Some(dl) = self.death_location {
encoder.write_string(32767, &dl.0);
encoder.write_position(dl.1);
}
}
}
#[derive(Debug)]
pub struct PluginMessage {
pub channel: String,
pub data: Vec<u8>,
}
impl PluginMessage {
pub fn encode(self, encoder: &mut impl PacketEncoder) {
encoder.write_string(32767, &self.channel);
encoder.write_bytes(&self.data);
}
}
#[derive(Debug)]
pub struct SyncPlayerPosition {
pub x: f64,
pub y: f64,
pub z: f64,
pub yaw: f32,
pub pitch: f32,
pub flags: i8,
pub teleport_id: i32,
pub dismount: bool,
}
impl SyncPlayerPosition {
pub fn encode(self, encoder: &mut impl PacketEncoder) {
encoder.write_double(self.x);
encoder.write_double(self.y);
encoder.write_double(self.z);
encoder.write_float(self.yaw);
encoder.write_float(self.pitch);
encoder.write_byte(self.flags);
encoder.write_varint(self.teleport_id);
encoder.write_bool(self.dismount);
}
}
#[derive(Debug)]
pub struct ChunkData {
pub x: i32,
pub z: i32,
pub heightmap: nbt::Blob,
pub chunk_data: Vec<u8>,
}
impl ChunkData {
pub fn encode(self, encoder: &mut impl PacketEncoder) {
encoder.write_int(self.x);
encoder.write_int(self.z);
self.heightmap.to_writer(encoder).unwrap();
encoder.write_varint(self.chunk_data.len() as i32);
encoder.write_bytes(&self.chunk_data);
// number of block entities
encoder.write_varint(0);
// trust edges
encoder.write_bool(true);
// light masks
encoder.write_varint(0);
encoder.write_varint(0);
encoder.write_varint(0);
encoder.write_varint(0);
// sky light array
encoder.write_varint(0);
// block light array
encoder.write_varint(0);
}
}
#[derive(Debug)]
pub enum ClientBoundPacket {
// status
StatusResponse(String),
PingResponse(i64),
// login
LoginSuccess(LoginSuccess),
// play
LoginPlay(LoginPlay),
PluginMessage(PluginMessage),
SyncPlayerPosition(SyncPlayerPosition),
ChunkData(ChunkData),
KeepAlive(i64),
PlayerAbilities(i8, f32, f32),
SystemChatMessage(serde_json::Value, bool),
}
impl ClientBoundPacket {
pub fn encode(self) -> Vec<u8> {
let mut packet = Vec::new();
match self {
Self::StatusResponse(status) => {
packet.write_string(32767, &status);
finalize_packet(packet, 0)
},
Self::PingResponse(n) => {
packet.write_long(n);
finalize_packet(packet, 1)
},
Self::LoginSuccess(login_success) => {
login_success.encode(&mut packet);
finalize_packet(packet, 2)
}
Self::PluginMessage(plugin_message) => {
plugin_message.encode(&mut packet);
finalize_packet(packet, 22)
}
Self::SyncPlayerPosition(sync_player_position) => {
sync_player_position.encode(&mut packet);
finalize_packet(packet, 57)
}
Self::LoginPlay(login_play) => {
login_play.encode(&mut packet);
finalize_packet(packet, 37)
}
Self::ChunkData(chunk_data) => {
chunk_data.encode(&mut packet);
finalize_packet(packet, 33)
}
Self::KeepAlive(n) => {
packet.write_long(n);
finalize_packet(packet, 32)
}
Self::PlayerAbilities(flags, speed, view) => {
packet.write_byte(flags);
packet.write_float(speed);
packet.write_float(view);
finalize_packet(packet, 49)
}
Self::SystemChatMessage(msg, overlay) => {
packet.write_string(262144, &msg.to_string());
packet.write_bool(overlay);
finalize_packet(packet, 98)
}
}
}
}

284
src/protocol/data.rs Normal file
View file

@ -0,0 +1,284 @@
use std::io::{Write, Read};
use serde::Serialize;
use uuid::Uuid;
use super::Position;
pub trait PacketEncoder: Write {
fn write_bytes(&mut self, data: &[u8]) {
self.write_all(data).unwrap();
}
fn write_bool(&mut self, data: bool) {
self.write_all(&[data as u8]).unwrap();
}
fn write_byte(&mut self, data: i8) {
self.write_all(&[data as u8]).unwrap();
}
fn write_ubyte(&mut self, data: u8) {
self.write_all(&[data]).unwrap();
}
fn write_short(&mut self, data: i16) {
self.write_all(&data.to_be_bytes()).unwrap();
}
fn write_ushort(&mut self, data: u16) {
self.write_all(&data.to_be_bytes()).unwrap();
}
fn write_int(&mut self, data: i32) {
self.write_all(&data.to_be_bytes()).unwrap();
}
fn write_long(&mut self, data: i64) {
self.write_all(&data.to_be_bytes()).unwrap();
}
fn write_uuid(&mut self, data: Uuid) {
self.write_all(&data.as_u128().to_be_bytes()).unwrap();
}
fn write_float(&mut self, data: f32) {
self.write_all(&data.to_be_bytes()).unwrap();
}
fn write_double(&mut self, data: f64) {
self.write_all(&data.to_be_bytes()).unwrap();
}
fn write_varint(&mut self, mut data: i32) {
loop {
let mut byte = (data & 0b11111111) as u8;
data = data >> 7;
if data != 0 {
byte |= 0b10000000;
}
self.write_all(&[byte]).unwrap();
if data == 0 {
break
}
}
}
fn write_varlong(&mut self, mut data: i64) {
loop {
let mut byte = (data & 0b11111111) as u8;
data = data >> 7;
if data != 0 {
byte |= 0b10000000;
}
self.write_all(&[byte]).unwrap();
if data == 0 {
break
}
}
}
fn write_position(&mut self, position: Position) {
self.write_long(
(((position.x & 0x3ffffff) as i64) << 38)
| (((position.z & 0x3ffffff) as i64) << 12)
| (position.y & 0xfff) as i64
)
}
fn write_nbt(&mut self, nbt: &impl Serialize) {
nbt::to_writer(self, nbt, None).unwrap();
}
fn write_string(&mut self, max_len: usize, val: &str) {
if val.len() > max_len * 4 + 3 {
panic!("exceeded max string length")
}
self.write_varint(val.len() as i32);
self.write_all(val.as_bytes()).unwrap();
}
fn to_data(self) -> Vec<u8>;
}
impl PacketEncoder for Vec<u8> {
fn to_data(self) -> Vec<u8> { self }
}
fn encode_varint(mut data: i32) -> Vec<u8> {
let mut res = Vec::new();
loop {
let mut byte = (data & 0b11111111) as u8;
data = data >> 7;
if data != 0 {
byte |= 0b10000000;
}
res.push(byte);
if data == 0 {
break
}
}
res
}
pub fn finalize_packet(packet: impl PacketEncoder, packet_id: i32) -> Vec<u8> {
let mut id = encode_varint(packet_id);
let mut data = packet.to_data();
let mut result = encode_varint((id.len() + data.len()) as i32);
result.append(&mut id);
result.append(&mut data);
result
}
pub struct PacketDecoder {
data: Vec<u8>,
idx: usize,
packet_id: i32,
}
impl PacketDecoder {
pub fn decode(read: &mut impl Read) -> Option<PacketDecoder> {
let size = read_varint(read)? as usize;
let mut data = vec![0; size];
read.read_exact(&mut data).unwrap();
let mut decoder = PacketDecoder {
data,
idx: 0,
packet_id: 0
};
decoder.packet_id = decoder.read_varint();
return Some(decoder);
}
pub fn packet_id(&self) -> i32 {
return self.packet_id;
}
pub fn read_bytes(&mut self, n: usize) -> &[u8] {
let ret = &self.data[self.idx..self.idx+n];
self.idx += n;
ret
}
pub fn read_bool(&mut self) -> bool {
self.idx += 1;
self.data[self.idx-1] != 0
}
pub fn read_byte(&mut self) -> i8 {
self.idx += 1;
self.data[self.idx-1] as i8
}
pub fn read_ubyte(&mut self) -> u8 {
self.idx += 1;
self.data[self.idx-1]
}
pub fn read_short(&mut self) -> i16 {
let mut buf = [0; 2];
buf.copy_from_slice(&self.data[self.idx..self.idx+2]);
self.idx += 2;
i16::from_be_bytes(buf)
}
pub fn read_ushort(&mut self) -> u16 {
let mut buf = [0; 2];
buf.copy_from_slice(&self.data[self.idx..self.idx+2]);
self.idx += 2;
u16::from_be_bytes(buf)
}
pub fn read_int(&mut self) -> i32 {
let mut buf = [0; 4];
buf.copy_from_slice(&self.data[self.idx..self.idx+4]);
self.idx += 4;
i32::from_be_bytes(buf)
}
pub fn read_long(&mut self) -> i64 {
let mut buf = [0; 8];
buf.copy_from_slice(&self.data[self.idx..self.idx+8]);
self.idx += 8;
i64::from_be_bytes(buf)
}
pub fn read_uuid(&mut self) -> Uuid {
let mut buf = [0; 16];
buf.copy_from_slice(&self.data[self.idx..self.idx+16]);
self.idx += 16;
Uuid::from_u128(u128::from_be_bytes(buf))
}
pub fn read_float(&mut self) -> f32 {
let mut buf = [0; 4];
buf.copy_from_slice(&self.data[self.idx..self.idx+4]);
self.idx += 4;
f32::from_be_bytes(buf)
}
pub fn read_double(&mut self) -> f64 {
let mut buf = [0; 8];
buf.copy_from_slice(&self.data[self.idx..self.idx+8]);
self.idx += 8;
f64::from_be_bytes(buf)
}
pub fn read_varint(&mut self) -> i32 {
let mut result = 0;
let mut count = 0;
loop {
let byte = self.read_ubyte();
result |= ((byte & 0x7f) as i32) << (7 * count);
count += 1;
if count > 5 {
panic!("varint too long")
}
if byte & 0x80 == 0 {
break
}
}
result
}
pub fn read_varlong(&mut self) -> i64 {
let mut result = 0;
let mut count = 0;
loop {
let byte = self.read_ubyte();
result |= ((byte & 0x7f) as i64) << (7 * count);
count += 1;
if count > 10 {
panic!("varint too long")
}
if byte & 0x80 == 0 {
break
}
}
result
}
pub fn read_string(&mut self) -> String {
let len = self.read_varint() as usize;
String::from_utf8(self.read_bytes(len).to_vec()).unwrap()
}
}
fn read_varint(read: &mut impl Read) -> Option<i32> {
let mut result = 0;
let mut count = 0;
loop {
let mut byte = [0];
read.read_exact(&mut byte).ok()?;
let byte = byte[0];
result |= ((byte & 0x7f) as i32) << (7 * count);
count += 1;
if count > 5 {
panic!("varint too long")
}
if byte & 0x80 == 0 {
break
}
}
Some(result)
}

18
src/protocol/mod.rs Normal file
View file

@ -0,0 +1,18 @@
pub mod data;
pub mod serverbound;
pub mod clientbound;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NetworkState {
Handshake,
Status,
Login,
Play
}
#[derive(Clone, Copy, Debug)]
pub struct Position {
x: i32,
y: i16,
z: i32
}

118
src/protocol/serverbound.rs Normal file
View file

@ -0,0 +1,118 @@
use uuid::Uuid;
use super::{data::PacketDecoder, NetworkState};
#[derive(Debug)]
pub struct Handshake {
pub version: i32,
pub addr: String,
pub port: u16,
pub next_state: i32,
}
impl Handshake {
pub fn decode(mut decoder: PacketDecoder) -> Self {
let version = decoder.read_varint();
let addr = decoder.read_string();
let port = decoder.read_ushort();
let next_state = decoder.read_varint();
Self { version, addr, port, next_state }
}
}
#[derive(Debug)]
pub struct SigData {
pub timestamp: i64,
pub pubkey: Vec<u8>,
pub sig: Vec<u8>,
}
#[derive(Debug)]
pub struct LoginStart {
pub name: String,
pub sig_data: Option<SigData>,
pub uuid: Option<Uuid>,
}
impl LoginStart {
pub fn decode(mut decoder: PacketDecoder) -> Self {
let name = decoder.read_string();
let has_sig_data = decoder.read_bool();
let sig_data = if has_sig_data {
let timestamp = decoder.read_long();
let pubkey_len = decoder.read_varint();
let pubkey = decoder.read_bytes(pubkey_len as usize).to_vec();
let sig_len = decoder.read_varint();
let sig = decoder.read_bytes(sig_len as usize).to_vec();
Some(SigData { timestamp, pubkey, sig })
} else {
None
};
let has_uuid = decoder.read_bool();
let uuid = if has_uuid {
Some(decoder.read_uuid())
} else {
None
};
Self { name, sig_data, uuid }
}
}
#[derive(Debug)]
pub struct ChatMessage {
pub message: String,
pub timestamp: i64,
}
impl ChatMessage {
pub fn decode(mut decoder: PacketDecoder) -> Self {
let message = decoder.read_string();
let timestamp = decoder.read_long();
// TODO read rest of packet
Self { message, timestamp }
}
}
#[derive(Debug)]
pub enum ServerBoundPacket {
Unknown(i32),
Ignored(i32),
// handshake
Handshake(Handshake),
// status
StatusRequest(),
PingRequest(i64),
// login
LoginStart(LoginStart),
// play
ChatMessage(ChatMessage),
}
impl ServerBoundPacket {
pub fn decode(state: &mut NetworkState, mut decoder: PacketDecoder) -> ServerBoundPacket {
use NetworkState as NS;
match (*state, decoder.packet_id()) {
(NS::Handshake, 0) => {
let hs = Handshake::decode(decoder);
match hs.next_state {
1 => *state = NS::Status,
2 => *state = NS::Login,
state => panic!("invalid next state: {}", state)
}
ServerBoundPacket::Handshake(hs)
},
(NS::Status, 0)
=> ServerBoundPacket::StatusRequest(),
(NS::Status, 1)
=> ServerBoundPacket::PingRequest(decoder.read_long()),
(NS::Login, 0) => {
*state = NS::Play;
ServerBoundPacket::LoginStart(LoginStart::decode(decoder))
},
(NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)),
(NS::Play, id @ (18 | 20 | 21 | 22 | 30)) => ServerBoundPacket::Ignored(id),
(_, id) => ServerBoundPacket::Unknown(id),
}
}
}

Binary file not shown.