From 412bbba1ee64d106c7a37b14b7536af74a2b13a7 Mon Sep 17 00:00:00 2001 From: Thinkofdeath Date: Mon, 7 Sep 2015 21:11:00 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 + Cargo.toml | 14 ++ gl/Cargo.toml | 13 ++ gl/build.rs | 19 +++ gl/src/lib.rs | 1 + src/bit/map.rs | 49 +++++++ src/bit/mod.rs | 5 + src/bit/set.rs | 43 ++++++ src/gl/mod.rs | 40 +++++ src/main.rs | 47 ++++++ src/protocol/mod.rs | 326 +++++++++++++++++++++++++++++++++++++++++ src/protocol/packet.rs | 96 ++++++++++++ 12 files changed, 655 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 gl/Cargo.toml create mode 100644 gl/build.rs create mode 100644 gl/src/lib.rs create mode 100644 src/bit/map.rs create mode 100644 src/bit/mod.rs create mode 100644 src/bit/set.rs create mode 100644 src/gl/mod.rs create mode 100644 src/main.rs create mode 100644 src/protocol/mod.rs create mode 100644 src/protocol/packet.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfe7ef5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.lock +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6074549 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] + +name = "steven_rust" +version = "0.0.1" +authors = [ "Thinkofdeath " ] + +[dependencies] +rustc-serialize = "0.3" +glfw = "0.1.0" +byteorder = "0.3.13" + +[dependencies.steven_gl] +path = "./gl" +version = "0" diff --git a/gl/Cargo.toml b/gl/Cargo.toml new file mode 100644 index 0000000..ef27522 --- /dev/null +++ b/gl/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "steven_gl" +version = "0.0.1" +authors = [ "Thinkofdeath " ] +build = "build.rs" + +[build-dependencies] +gl_generator = "0.0.27" +khronos_api = "0.0.8" + +[dependencies] +gl_common = "0.0.4" +libc = "*" diff --git a/gl/build.rs b/gl/build.rs new file mode 100644 index 0000000..b64bffc --- /dev/null +++ b/gl/build.rs @@ -0,0 +1,19 @@ +extern crate gl_generator; +extern crate khronos_api; + +use std::env; +use std::fs::File; +use std::io::BufWriter; +use std::path::Path; + +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + let dest = Path::new(&out_dir); + + let mut file = BufWriter::new(File::create(&dest.join("bindings.rs")).unwrap()); + gl_generator::generate_bindings(gl_generator::GlobalGenerator, + gl_generator::registry::Ns::Gl, + gl_generator::Fallbacks::All, + khronos_api::GL_XML, vec![], "3.2", "core", + &mut file).unwrap(); +} diff --git a/gl/src/lib.rs b/gl/src/lib.rs new file mode 100644 index 0000000..44ae78f --- /dev/null +++ b/gl/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/src/bit/map.rs b/src/bit/map.rs new file mode 100644 index 0000000..37398af --- /dev/null +++ b/src/bit/map.rs @@ -0,0 +1,49 @@ + +pub struct Map { + bits: Vec, + bitSize: usize, + length: usize +} + +#[test] +fn test_map() { + let mut map = Map::new(4096, 4); + for i in 0 .. 4096 { + for j in 0 .. 16 { + map.set(i, j); + if map.get(i) != j { + panic!("Fail"); + } + } + } +} + +impl Map { + pub fn new(len: usize, size: usize) -> Map { + let mut map = Map { + bitSize: size, + length: len, + bits: Vec::with_capacity((len*size)/64) + }; + for _ in 0 .. len { + map.bits.push(0) + } + map + } + + pub fn set(&mut self, i: usize, val: usize) { + let mut i = i * self.bitSize; + let pos = i / 64; + let mask = (1 << self.bitSize) - 1; + i %= 64; + self.bits[pos] = (self.bits[pos] & !(mask << i )) | ((val << i) as u64) + } + + pub fn get(&mut self, i: usize) -> usize { + let mut i = i * self.bitSize; + let pos = i / 64; + let mask = (1 << self.bitSize) - 1; + i %= 64; + ((self.bits[pos] >> i) & mask) as usize + } +} diff --git a/src/bit/mod.rs b/src/bit/mod.rs new file mode 100644 index 0000000..7ce0357 --- /dev/null +++ b/src/bit/mod.rs @@ -0,0 +1,5 @@ +pub mod set; +pub mod map; + +pub use self::set::Set; +pub use self::map::Map; diff --git a/src/bit/set.rs b/src/bit/set.rs new file mode 100644 index 0000000..3ed6f72 --- /dev/null +++ b/src/bit/set.rs @@ -0,0 +1,43 @@ + +pub struct Set { + data : Vec +} + +#[test] +fn test_set() { + let mut set = Set::new(200); + for i in 0 .. 200 { + if i % 3 == 0 { + set.set(i, true) + } + } + for i in 0 .. 200 { + if set.get(i) != (i%3 == 0) { + panic!("Fail") + } + } +} + +impl Set { + pub fn new(size: usize) -> Set { + let mut set = Set { + data: Vec::with_capacity(size) + }; + for _ in 0 .. size { + set.data.push(0) + } + set + } + + pub fn set(&mut self, i: usize, v: bool) { + if v { + self.data[i>>6] |= 1 << (i & 0x3F) + } else { + self.data[i>>6] &= !(1 << (i & 0x3F)) + } + } + + pub fn get(&mut self, i: usize) -> bool { + return (self.data[i>>6] & (1 << (i & 0x3F))) != 0 + } +} diff --git a/src/gl/mod.rs b/src/gl/mod.rs new file mode 100644 index 0000000..ad5095c --- /dev/null +++ b/src/gl/mod.rs @@ -0,0 +1,40 @@ +extern crate steven_gl as gl; +extern crate glfw; + +use std::ops::BitOr; + +pub fn init(window: &mut glfw::Window) { + gl::load_with(|s| window.get_proc_address(s)); +} + +pub fn clear_color(r: f32, g: f32, b: f32, a: f32) { + unsafe { gl::ClearColor(r, g, b, a); } +} + +pub enum ClearFlags { + Color, + Depth, + Internal(u32) +} + +impl ClearFlags { + fn internal(self) -> u32 { + match self { + ClearFlags::Color => gl::COLOR_BUFFER_BIT, + ClearFlags::Depth => gl::DEPTH_BUFFER_BIT, + ClearFlags::Internal(val) => val + } + } +} + +impl BitOr for ClearFlags { + type Output = ClearFlags; + + fn bitor(self, rhs: ClearFlags) -> ClearFlags { + ClearFlags::Internal(self.internal() | rhs.internal()) + } +} + +pub fn clear(flags: ClearFlags) { + unsafe { gl::Clear(flags.internal()) } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d2e2ec5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,47 @@ +extern crate glfw; +extern crate byteorder; + +pub mod bit; +pub mod protocol; + +mod gl; +use glfw::{Action, Context, Key}; + +fn main() { + let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap(); + + glfw.window_hint(glfw::WindowHint::ContextVersion(3, 2)); + glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core)); + glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true)); + glfw.window_hint(glfw::WindowHint::DepthBits(32)); + glfw.window_hint(glfw::WindowHint::StencilBits(0)); + + let (mut window, events) = glfw.create_window(854, 480, "Steven", glfw::WindowMode::Windowed) + .expect("Failed to create GLFW window"); + + gl::init(&mut window); + + window.set_key_polling(true); + window.make_current(); + glfw.set_swap_interval(1); + + while !window.should_close() { + gl::clear_color(1.0, 0.0, 0.0, 1.0); + gl::clear(gl::ClearFlags::Color | gl::ClearFlags::Depth); + + window.swap_buffers(); + glfw.poll_events(); + for (_, event) in glfw::flush_messages(&events) { + handle_window_event(&mut window, event); + } + } +} + +fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent) { + match event { + glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => { + window.set_should_close(true) + } + _ => {} + } +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..8c04af7 --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,326 @@ +#![allow(dead_code)] +extern crate byteorder; + +use std::net::TcpStream; +use std::io; +use std::io::{Write, Read}; +use std::convert; +use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; + +/// Serializes types into a buffer +macro_rules! serialize_type { + ($dst:expr, $name:expr, u16) => { + $dst.write_u16::($name).unwrap(); + }; + ($dst:expr, $name:expr, i64) => { + $dst.write_i64::($name).unwrap(); + }; + ($dst:expr, $name:expr, VarInt) => { + try!(write_varint($dst, $name)); + }; + ($dst:expr, $name:expr, String) => { + try!(write_varint($dst, $name.len() as i32)); + $dst.extend($name.bytes()); + }; + ($dst:expr, $name:expr, Empty) => { + + }; + ($dst:expr, $name:expr, $ftype:ident) => { + unimplemented!() + }; +} + +/// Deserializes types from a buffer +macro_rules! deserialize_type { + ($src:expr, String) => { + { + let len = read_variant(&mut $src).unwrap(); + let mut ret = String::new(); + (&mut $src).take(len as u64).read_to_string(&mut ret).unwrap(); + ret + } + }; + ($src:expr, i64) => { + $src.read_i64::().unwrap() + }; + ($src:expr, Empty) => { Empty }; + ($src:expr, $ftype:ident) => { + unimplemented!() + }; +} + +/// Helper macro for defining packets +#[macro_export] +macro_rules! state_packets { + ($($state:ident $stateName:ident { + $($dir:ident $dirName:ident { + $($name:ident => $id:expr { + $($field:ident: $field_type:ident),+ + }),* + })+ + })+) => { + use protocol::*; + use std::io; + use std::io::{Read, Write}; + use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; + + pub enum Packet { + $( + $( + $( + $name($state::$dir::$name), + )* + )+ + )+ + } + + $( + pub mod $state { + $( + pub mod $dir { + use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; + use protocol::*; + use std::io; + use std::io::{Read, Write}; + + $( + pub struct $name { + $(pub $field: $field_type),+, + } + + impl PacketType for $name { + + fn packet_id(&self) -> i32{ $id } + + fn write(self, buf: &mut Vec) -> Result<(), io::Error> { + $( + serialize_type!(buf, self.$field, $field_type); + )+ + + Result::Ok(()) + } + } + )* + } + )+ + } + )+ + + /// Returns the packet for the given state, direction and id after parsing the fields + /// from the buffer. + pub fn packet_by_id(state: State, dir: Direction, id: i32, mut buf: &mut io::Cursor>) -> Option { + match state { + $( + State::$stateName => { + match dir { + $( + Direction::$dirName => { + match id { + $( + $id => Option::Some(Packet::$name($state::$dir::$name { + $($field: deserialize_type!(buf, $field_type)),+, + })), + )* + _ => Option::None + } + } + )+ + } + } + )+ + } + } + } +} + +pub mod packet; + +/// VarInt have a variable size (between 1 and 5 bytes) when encoded based +/// on the size of the number +pub type VarInt = i32; + +/// Encodes a VarInt into the Writer +pub fn write_varint(buf: &mut io::Write, v: VarInt) -> Result<(), io::Error> { + const PART : u32 = 0x7F; + let mut val = v as u32; + loop { + if (val & !PART) == 0 { + try!(buf.write_u8(val as u8)); + return Result::Ok(()); + } + try!(buf.write_u8(((val & PART) | 0x80) as u8)); + val >>= 7; + } +} + +/// Decodes a VarInt from the Reader +pub fn read_variant(buf: &mut io::Read) -> Result { + const PART : u32 = 0x7F; + let mut size = 0; + let mut val = 0u32; + loop { + let b = try!(buf.read_u8()) as u32; + val |= (b & PART) << (size * 7); + size+=1; + if size > 5 { + return Result::Err(io::Error::new(io::ErrorKind::InvalidInput, Error::Err("VarInt too big".to_string()))) + } + if (b & 0x80) == 0 { + break + } + } + + Result::Ok(val as VarInt) +} + +/// Direction is used to define whether packets are going to the +/// server or the client. +pub enum Direction { + Serverbound, + Clientbound +} + +/// The protocol has multiple 'sub-protocols' or states which control which +/// packet an id points to. +#[derive(Clone, Copy)] +pub enum State { + Handshaking, + Play, + Status, + Login +} + +/// Return for any protocol related error. +#[derive(Debug)] +pub enum Error { + Err(String), + IOError(io::Error), +} + +impl convert::From for Error { + fn from(e : io::Error) -> Error { + Error::IOError(e) + } +} + +impl ::std::error::Error for Error { + fn description(&self) -> &str { + match self { + &Error::Err(ref val) => &val[..], + &Error::IOError(ref e) => e.description(), + } + } +} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f : &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + &Error::Err(ref val) => write!(f, "protocol error: {}", val), + &Error::IOError(ref e) => e.fmt(f) + } + } +} + + +/// Helper for empty structs +pub struct Empty; + +pub struct Conn { + stream: TcpStream, + host: String, + port: u16, + direction: Direction, + state: State, +} + +impl Conn { + pub fn new(target: &str) -> Result{ + // TODO SRV record support + let stream = match TcpStream::connect(target) { + Ok(val) => val, + Err(err) => return Result::Err(Error::IOError(err)) + }; + let parts = target.split(":").collect::>(); + Result::Ok(Conn { + stream: stream, + host: parts[0].to_owned(), + port: parts[1].parse().unwrap(), + direction: Direction::Serverbound, + state: State::Handshaking, + }) + } + + // TODO: compression and encryption + + pub fn write_packet(&mut self, packet: T) -> Result<(), Error> { + let mut buf = Vec::new(); + try!(write_varint(&mut buf, packet.packet_id())); + try!(packet.write(&mut buf)); + try!(write_varint(&mut self.stream, buf.len() as i32)); + try!(self.stream.write_all(&buf.into_boxed_slice())); + + Result::Ok(()) + } + + pub fn read_packet(&mut self) -> Result { + let len = try!(read_variant(&mut self.stream)) as usize; + let mut ibuf = Vec::with_capacity(len); + try!((&mut self.stream).take(len as u64).read_to_end(&mut ibuf)); + + let mut buf = io::Cursor::new(ibuf); + let id = try!(read_variant(&mut buf)); + + let dir = match self.direction { + Direction::Clientbound => Direction::Serverbound, + Direction::Serverbound => Direction::Clientbound, + }; + + let packet = packet::packet_by_id(self.state, dir, id, &mut buf); + + match packet { + Some(val) => { + let pos = buf.position() as usize; + let ibuf = buf.into_inner(); + if ibuf.len() < pos { + return Result::Err(Error::Err(format!("Failed to read all of packet 0x{:X}, had {} bytes left", id, ibuf.len() - pos))) + } + Result::Ok(val) + }, + None => Result::Err(Error::Err("missing packet".to_string())) + } + } +} + +pub trait PacketType { + fn packet_id(&self) -> i32; + + fn write(self, buf: &mut Vec) -> Result<(), io::Error>; +} + +#[test] +fn test() { + let mut c = Conn::new("localhost:25565").unwrap(); + + c.write_packet(packet::handshake::serverbound::Handshake{ + protocol_version: 69, + host: "localhost".to_string(), + port: 25565, + next: 1, + }).unwrap(); + c.state = State::Status; + c.write_packet(packet::status::serverbound::StatusRequest{empty: Empty}).unwrap(); + + match c.read_packet().unwrap() { + packet::Packet::StatusResponse(val) => println!("{}", val.status), + _ => panic!("Wrong packet"), + } + + c.write_packet(packet::status::serverbound::StatusPing{ping: 4433}).unwrap(); + + match c.read_packet().unwrap() { + packet::Packet::StatusPong(val) => println!("{}", val.ping), + _ => panic!("Wrong packet"), + } + + panic!("TODO!"); +} diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs new file mode 100644 index 0000000..4819c8a --- /dev/null +++ b/src/protocol/packet.rs @@ -0,0 +1,96 @@ + +state_packets!( + handshake Handshaking { + serverbound Serverbound { + // Handshake is the first packet sent in the protocol. + // Its used for deciding if the request is a client + // is requesting status information about the server + // (MOTD, players etc) or trying to login to the server. + // + // The host and port fields are not used by the vanilla + // server but are there for virtual server hosting to + // be able to redirect a client to a target server with + // a single address + port. + // + // Some modified servers/proxies use the handshake field + // differently, packing information into the field other + // than the hostname due to the protocol not providing + // any system for custom information to be transfered + // by the client to the server until after login. + Handshake => 0 { + // The protocol version of the connecting client + protocol_version: VarInt, + // The hostname the client connected to + host: String, + // The port the client connected to + port: u16, + // The next protocol state the client wants + next: VarInt + } + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + } + clientbound Clientbound { + } + } + login Login { + serverbound Serverbound { + } + clientbound Clientbound { + } + } + status Status { + serverbound Serverbound { + // StatusRequest is sent by the client instantly after + // switching to the Status protocol state and is used + // to signal the server to send a StatusResponse to the + // client + StatusRequest => 0 { + empty: Empty + }, + // StatusPing is sent by the client after recieving a + // StatusResponse. The client uses the time from sending + // the ping until the time of recieving a pong to measure + // the latency between the client and the server. + StatusPing => 1 { + ping: i64 + } + } + clientbound Clientbound { + // StatusResponse is sent as a reply to a StatusRequest. + // The Status should contain a json encoded structure with + // version information, a player sample, a description/MOTD + // and optionally a favicon. + // + // The structure is as follows + // { + // "version": { + // "name": "1.8.3", + // "protocol": 47, + // }, + // "players": { + // "max": 20, + // "online": 1, + // "sample": [ + // {"name": "Thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"} + // ] + // }, + // "description": "Hello world", + // "favicon": "data:image/png;base64," + // } + StatusResponse => 0 { + status: String + }, + // StatusPong is sent as a reply to a StatusPing. + // The Time field should be exactly the same as the + // one sent by the client. + StatusPong => 1 { + ping: i64 + } + } + } +);