Main part of the protocol complete
This commit is contained in:
parent
1ed844a699
commit
11c33e2d8d
|
@ -1,12 +1,20 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
extern crate hyper;
|
||||||
|
extern crate steven_openssl as openssl;
|
||||||
|
extern crate flate2;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
pub mod mojang;
|
||||||
|
|
||||||
|
use format;
|
||||||
use std::default;
|
use std::default;
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Write, Read};
|
use std::io::{Write, Read};
|
||||||
use std::convert;
|
use std::convert;
|
||||||
use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt};
|
use self::byteorder::{BigEndian, WriteBytesExt, ReadBytesExt};
|
||||||
|
use self::flate2::read::{ZlibDecoder, ZlibEncoder};
|
||||||
|
|
||||||
/// Helper macro for defining packets
|
/// Helper macro for defining packets
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -14,13 +22,12 @@ macro_rules! state_packets {
|
||||||
($($state:ident $stateName:ident {
|
($($state:ident $stateName:ident {
|
||||||
$($dir:ident $dirName:ident {
|
$($dir:ident $dirName:ident {
|
||||||
$($name:ident => $id:expr {
|
$($name:ident => $id:expr {
|
||||||
$($field:ident: $field_type:ident),+
|
$($field:ident: $field_type:ty = $(when ($cond:expr))*, )+
|
||||||
}),*
|
})*
|
||||||
})+
|
})+
|
||||||
})+) => {
|
})+) => {
|
||||||
use protocol::*;
|
use protocol::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
use protocol::{Serializable};
|
|
||||||
|
|
||||||
pub enum Packet {
|
pub enum Packet {
|
||||||
$(
|
$(
|
||||||
|
@ -36,9 +43,10 @@ macro_rules! state_packets {
|
||||||
pub mod $state {
|
pub mod $state {
|
||||||
$(
|
$(
|
||||||
pub mod $dir {
|
pub mod $dir {
|
||||||
|
#![allow(unused_imports)]
|
||||||
use protocol::*;
|
use protocol::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
use protocol::{Serializable};
|
use format;
|
||||||
|
|
||||||
$(
|
$(
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -52,7 +60,9 @@ macro_rules! state_packets {
|
||||||
|
|
||||||
fn write(self, buf: &mut Vec<u8>) -> Result<(), io::Error> {
|
fn write(self, buf: &mut Vec<u8>) -> Result<(), io::Error> {
|
||||||
$(
|
$(
|
||||||
try!(self.$field.write_to(buf));
|
if true $(&& ($cond(&self)))* {
|
||||||
|
try!(self.$field.write_to(buf));
|
||||||
|
}
|
||||||
)+
|
)+
|
||||||
|
|
||||||
Result::Ok(())
|
Result::Ok(())
|
||||||
|
@ -76,9 +86,12 @@ macro_rules! state_packets {
|
||||||
match id {
|
match id {
|
||||||
$(
|
$(
|
||||||
$id => {
|
$id => {
|
||||||
let mut packet : $state::$dir::$name = $state::$dir::$name::default();
|
use self::$state::$dir::$name;
|
||||||
|
let mut packet : $name = $name::default();
|
||||||
$(
|
$(
|
||||||
packet.$field = try!($field_type::read_from(&mut buf));
|
if true $(&& ($cond(&packet)))* {
|
||||||
|
packet.$field = try!(Serializable::read_from(&mut buf));
|
||||||
|
}
|
||||||
)+
|
)+
|
||||||
Result::Ok(Option::Some(Packet::$name(packet)))
|
Result::Ok(Option::Some(Packet::$name(packet)))
|
||||||
},
|
},
|
||||||
|
@ -97,11 +110,23 @@ macro_rules! state_packets {
|
||||||
|
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
|
|
||||||
trait Serializable {
|
pub trait Serializable {
|
||||||
fn read_from(buf: &mut io::Read) -> Result<Self, io::Error>;
|
fn read_from(buf: &mut io::Read) -> Result<Self, io::Error>;
|
||||||
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>;
|
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl <T> Serializable for Option<T> where T : Serializable {
|
||||||
|
fn read_from(buf: &mut io::Read) -> Result<Option<T>, io::Error> {
|
||||||
|
Result::Ok(Some(try!(T::read_from(buf))))
|
||||||
|
}
|
||||||
|
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
|
||||||
|
if self.is_some() {
|
||||||
|
try!(self.as_ref().unwrap().write_to(buf));
|
||||||
|
}
|
||||||
|
Result::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serializable for String {
|
impl Serializable for String {
|
||||||
fn read_from(buf: &mut io::Read) -> Result<String, io::Error> {
|
fn read_from(buf: &mut io::Read) -> Result<String, io::Error> {
|
||||||
let len = try!(VarInt::read_from(buf)).0;
|
let len = try!(VarInt::read_from(buf)).0;
|
||||||
|
@ -117,17 +142,90 @@ impl Serializable for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serializable for Empty {
|
impl Serializable for format::Component {
|
||||||
fn read_from(buf: &mut io::Read) -> Result<Empty, io::Error> {
|
fn read_from(buf: &mut io::Read) -> Result<Self, io::Error> {
|
||||||
Result::Ok(Empty)
|
let len = try!(VarInt::read_from(buf)).0;
|
||||||
|
let mut ret = String::new();
|
||||||
|
try!(buf.take(len as u64).read_to_string(&mut ret));
|
||||||
|
let val : serde_json::Value = serde_json::from_str(&ret[..]).unwrap();
|
||||||
|
Result::Ok(Self::from_value(&val))
|
||||||
}
|
}
|
||||||
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
|
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
|
||||||
|
let val = serde_json::to_string(&self.to_value()).unwrap();
|
||||||
|
let bytes = val.as_bytes();
|
||||||
|
try!(VarInt(bytes.len() as i32).write_to(buf));
|
||||||
|
try!(buf.write_all(bytes));
|
||||||
Result::Ok(())
|
Result::Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Empty {
|
pub struct Position(u64);
|
||||||
fn default() -> Empty { Empty }
|
|
||||||
|
impl Position {
|
||||||
|
fn new(x: i32, y: i32, z: i32) -> Position {
|
||||||
|
Position(
|
||||||
|
(((x as u64) & 0x3FFFFFF) << 38) |
|
||||||
|
(((y as u64) & 0xFFF) << 26) |
|
||||||
|
((z as u64) & 0x3FFFFFF)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_x(&self) -> i32 {
|
||||||
|
((self.0 as i64) >> 38) as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_y(&self) -> i32 {
|
||||||
|
(((self.0 as i64) >> 26) & 0xFFF) as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_z(&self) -> i32 {
|
||||||
|
((self.0 as i64) << 38 >> 38) as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Position {
|
||||||
|
fn default() -> Position {
|
||||||
|
Position(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serializable for Position {
|
||||||
|
fn read_from(buf: &mut io::Read) -> Result<Position, io::Error> {
|
||||||
|
Result::Ok(Position(try!(buf.read_u64::<BigEndian>())))
|
||||||
|
}
|
||||||
|
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
|
||||||
|
try!(buf.write_u64::<BigEndian>(self.0));
|
||||||
|
Result::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serializable for () {
|
||||||
|
fn read_from(_: &mut io::Read) -> Result<(), io::Error> {
|
||||||
|
Result::Ok(())
|
||||||
|
}
|
||||||
|
fn write_to(&self, _: &mut io::Write) -> Result<(), io::Error> {
|
||||||
|
Result::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serializable for bool {
|
||||||
|
fn read_from(buf: &mut io::Read) -> Result<bool, io::Error> {
|
||||||
|
Result::Ok(try!(buf.read_u8()) != 0)
|
||||||
|
}
|
||||||
|
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
|
||||||
|
try!(buf.write_u8(if *self { 1 } else { 0 }));
|
||||||
|
Result::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serializable for i16 {
|
||||||
|
fn read_from(buf: &mut io::Read) -> Result<i16, io::Error> {
|
||||||
|
Result::Ok(try!(buf.read_i16::<BigEndian>()))
|
||||||
|
}
|
||||||
|
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
|
||||||
|
try!(buf.write_i16::<BigEndian>(*self));
|
||||||
|
Result::Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serializable for i32 {
|
impl Serializable for i32 {
|
||||||
|
@ -150,6 +248,16 @@ impl Serializable for i64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serializable for u8 {
|
||||||
|
fn read_from(buf: &mut io::Read) -> Result<u8, io::Error> {
|
||||||
|
Result::Ok(try!(buf.read_u8()))
|
||||||
|
}
|
||||||
|
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
|
||||||
|
try!(buf.write_u8(*self));
|
||||||
|
Result::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serializable for u16 {
|
impl Serializable for u16 {
|
||||||
fn read_from(buf: &mut io::Read) -> Result<u16, io::Error> {
|
fn read_from(buf: &mut io::Read) -> Result<u16, io::Error> {
|
||||||
Result::Ok(try!(buf.read_u16::<BigEndian>()))
|
Result::Ok(try!(buf.read_u16::<BigEndian>()))
|
||||||
|
@ -160,10 +268,60 @@ impl Serializable for u16 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Lengthable : Serializable + Into<usize> + From<usize> + Copy + Default {}
|
||||||
|
|
||||||
|
pub struct LenPrefixed<L: Lengthable, V> {
|
||||||
|
len: L,
|
||||||
|
pub data: Vec<V>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <L: Lengthable, V: Default> LenPrefixed<L, V> {
|
||||||
|
fn new(data: Vec<V>) -> LenPrefixed<L, V> {
|
||||||
|
return LenPrefixed {
|
||||||
|
len: Default::default(),
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <L: Lengthable, V: Serializable> Serializable for LenPrefixed<L, V> {
|
||||||
|
fn read_from(buf: &mut io::Read) -> Result<LenPrefixed<L, V>, io::Error> {
|
||||||
|
let len_data : L = try!(Serializable::read_from(buf));
|
||||||
|
let len : usize = len_data.into();
|
||||||
|
let mut data : Vec<V> = Vec::with_capacity(len);
|
||||||
|
for _ in 0 .. len {
|
||||||
|
data.push(try!(Serializable::read_from(buf)));
|
||||||
|
}
|
||||||
|
Result::Ok(LenPrefixed{len: len_data, data: data})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> {
|
||||||
|
let len_data : L = self.data.len().into();
|
||||||
|
try!(len_data.write_to(buf));
|
||||||
|
let ref data = self.data;
|
||||||
|
for val in data {
|
||||||
|
try!(val.write_to(buf));
|
||||||
|
}
|
||||||
|
Result::Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <L: Lengthable, V: Default> Default for LenPrefixed<L, V> {
|
||||||
|
fn default() -> Self {
|
||||||
|
LenPrefixed {
|
||||||
|
len: default::Default::default(),
|
||||||
|
data: default::Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// VarInt have a variable size (between 1 and 5 bytes) when encoded based
|
/// VarInt have a variable size (between 1 and 5 bytes) when encoded based
|
||||||
/// on the size of the number
|
/// on the size of the number
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub struct VarInt(i32);
|
pub struct VarInt(i32);
|
||||||
|
|
||||||
|
impl Lengthable for VarInt {}
|
||||||
|
|
||||||
impl Serializable for VarInt {
|
impl Serializable for VarInt {
|
||||||
/// Decodes a VarInt from the Reader
|
/// Decodes a VarInt from the Reader
|
||||||
fn read_from(buf: &mut io::Read) -> Result<VarInt, io::Error> {
|
fn read_from(buf: &mut io::Read) -> Result<VarInt, io::Error> {
|
||||||
|
@ -204,8 +362,21 @@ impl default::Default for VarInt {
|
||||||
fn default() -> VarInt { VarInt(0) }
|
fn default() -> VarInt { VarInt(0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl convert::Into<usize> for VarInt {
|
||||||
|
fn into(self) -> usize {
|
||||||
|
self.0 as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl convert::From<usize> for VarInt {
|
||||||
|
fn from(u: usize) -> VarInt {
|
||||||
|
VarInt(u as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Direction is used to define whether packets are going to the
|
/// Direction is used to define whether packets are going to the
|
||||||
/// server or the client.
|
/// server or the client.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Serverbound,
|
Serverbound,
|
||||||
Clientbound
|
Clientbound
|
||||||
|
@ -252,16 +423,18 @@ impl ::std::fmt::Display for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Helper for empty structs
|
|
||||||
pub struct Empty;
|
|
||||||
|
|
||||||
pub struct Conn {
|
pub struct Conn {
|
||||||
stream: TcpStream,
|
stream: TcpStream,
|
||||||
host: String,
|
host: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
state: State,
|
state: State,
|
||||||
|
|
||||||
|
cipher: Option<openssl::EVPCipher>,
|
||||||
|
|
||||||
|
compression_threshold: i32,
|
||||||
|
compression_read: Option<ZlibDecoder<io::Cursor<Vec<u8>>>>, // Pending reset support
|
||||||
|
compression_write: Option<ZlibEncoder<io::Cursor<Vec<u8>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Conn {
|
impl Conn {
|
||||||
|
@ -278,27 +451,58 @@ impl Conn {
|
||||||
port: parts[1].parse().unwrap(),
|
port: parts[1].parse().unwrap(),
|
||||||
direction: Direction::Serverbound,
|
direction: Direction::Serverbound,
|
||||||
state: State::Handshaking,
|
state: State::Handshaking,
|
||||||
|
cipher: Option::None,
|
||||||
|
compression_threshold: -1,
|
||||||
|
compression_read: Option::None,
|
||||||
|
compression_write: Option::None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: compression and encryption
|
|
||||||
|
|
||||||
pub fn write_packet<T: PacketType>(&mut self, packet: T) -> Result<(), Error> {
|
pub fn write_packet<T: PacketType>(&mut self, packet: T) -> Result<(), Error> {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
try!(VarInt(packet.packet_id()).write_to(&mut buf));
|
try!(VarInt(packet.packet_id()).write_to(&mut buf));
|
||||||
try!(packet.write(&mut buf));
|
try!(packet.write(&mut buf));
|
||||||
try!(VarInt(buf.len() as i32).write_to(&mut self.stream));
|
|
||||||
try!(self.stream.write_all(&buf.into_boxed_slice()));
|
let mut extra = if self.compression_threshold >= 0 { 1 } else { 0 };
|
||||||
|
if self.compression_threshold >= 0 && buf.len() as i32 > self.compression_threshold {
|
||||||
|
extra = 0;
|
||||||
|
let uncompressed_size = buf.len();
|
||||||
|
let mut new = Vec::new();
|
||||||
|
try!(VarInt(uncompressed_size as i32).write_to(&mut new));
|
||||||
|
let mut write = self.compression_write.as_mut().unwrap();
|
||||||
|
write.reset(io::Cursor::new(buf));
|
||||||
|
try!(write.read_to_end(&mut new));
|
||||||
|
buf = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(VarInt(buf.len() as i32 + extra).write_to(self));
|
||||||
|
if self.compression_threshold >= 0 && extra == 1 {
|
||||||
|
try!(VarInt(0).write_to(self));
|
||||||
|
}
|
||||||
|
try!(self.write_all(&buf.into_boxed_slice()));
|
||||||
|
|
||||||
Result::Ok(())
|
Result::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_packet(&mut self) -> Result<packet::Packet, Error> {
|
pub fn read_packet(&mut self) -> Result<packet::Packet, Error> {
|
||||||
let len = try!(VarInt::read_from(&mut self.stream)).0 as usize;
|
let len = try!(VarInt::read_from(self)).0 as usize;
|
||||||
let mut ibuf = Vec::with_capacity(len);
|
let mut ibuf = Vec::with_capacity(len);
|
||||||
try!((&mut self.stream).take(len as u64).read_to_end(&mut ibuf));
|
try!(self.take(len as u64).read_to_end(&mut ibuf));
|
||||||
|
|
||||||
let mut buf = io::Cursor::new(ibuf);
|
let mut buf = io::Cursor::new(ibuf);
|
||||||
|
|
||||||
|
if self.compression_threshold >= 0 {
|
||||||
|
let uncompressed_size = try!(VarInt::read_from(&mut buf)).0;
|
||||||
|
if uncompressed_size != 0 {
|
||||||
|
let mut new = Vec::with_capacity(uncompressed_size as usize);
|
||||||
|
{
|
||||||
|
let mut reader = self.compression_read.as_mut().unwrap();
|
||||||
|
reader.reset(buf);
|
||||||
|
try!(reader.read_to_end(&mut new));
|
||||||
|
}
|
||||||
|
buf = io::Cursor::new(new);
|
||||||
|
}
|
||||||
|
}
|
||||||
let id = try!(VarInt::read_from(&mut buf)).0;
|
let id = try!(VarInt::read_from(&mut buf)).0;
|
||||||
|
|
||||||
let dir = match self.direction {
|
let dir = match self.direction {
|
||||||
|
@ -317,7 +521,70 @@ impl Conn {
|
||||||
}
|
}
|
||||||
Result::Ok(val)
|
Result::Ok(val)
|
||||||
},
|
},
|
||||||
None => Result::Err(Error::Err("missing packet".to_string()))
|
// FIXME
|
||||||
|
None => Result::Ok(packet::Packet::StatusRequest(packet::status::serverbound::StatusRequest{empty:()}))//Result::Err(Error::Err("missing packet".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_encyption(&mut self, key: &Vec<u8>, decrypt: bool) {
|
||||||
|
self.cipher = Option::Some(openssl::EVPCipher::new(key, key, decrypt));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_compresssion(&mut self, threshold: i32, read: bool) {
|
||||||
|
self.compression_threshold = threshold;
|
||||||
|
if !read {
|
||||||
|
self.compression_write = Some(ZlibEncoder::new(io::Cursor::new(Vec::new()), flate2::Compression::Default));
|
||||||
|
} else {
|
||||||
|
self.compression_read = Some(ZlibDecoder::new(io::Cursor::new(Vec::new())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Conn {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self.cipher.as_mut() {
|
||||||
|
Option::None => self.stream.read(buf),
|
||||||
|
Option::Some(cipher) => {
|
||||||
|
let ret = try!(self.stream.read(buf));
|
||||||
|
let data = cipher.decrypt(&buf[..ret]);
|
||||||
|
for i in 0 .. ret {
|
||||||
|
buf[i] = data[i];
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Conn {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
match self.cipher.as_mut() {
|
||||||
|
Option::None => self.stream.write(buf),
|
||||||
|
Option::Some(cipher) => {
|
||||||
|
let data = cipher.encrypt(buf);
|
||||||
|
try!(self.stream.write_all(&data[..]));
|
||||||
|
Ok(buf.len())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.stream.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Conn {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Conn {
|
||||||
|
stream: self.stream.try_clone().unwrap(),
|
||||||
|
host: self.host.clone(),
|
||||||
|
port: self.port,
|
||||||
|
direction: self.direction,
|
||||||
|
state: self.state,
|
||||||
|
cipher: Option::None,
|
||||||
|
compression_threshold: self.compression_threshold,
|
||||||
|
compression_read: Option::None,
|
||||||
|
compression_write: Option::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,22 +603,77 @@ fn test() {
|
||||||
protocol_version: VarInt(69),
|
protocol_version: VarInt(69),
|
||||||
host: "localhost".to_string(),
|
host: "localhost".to_string(),
|
||||||
port: 25565,
|
port: 25565,
|
||||||
next: VarInt(1),
|
next: VarInt(2),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
c.state = State::Status;
|
c.state = State::Login;
|
||||||
c.write_packet(packet::status::serverbound::StatusRequest{empty: Empty}).unwrap();
|
c.write_packet(packet::login::serverbound::LoginStart{username: "Think".to_string()}).unwrap();
|
||||||
|
|
||||||
match c.read_packet().unwrap() {
|
let packet = match c.read_packet().unwrap() {
|
||||||
packet::Packet::StatusResponse(val) => println!("{}", val.status),
|
packet::Packet::EncryptionRequest(val) => val,
|
||||||
_ => panic!("Wrong packet"),
|
_ => panic!("Wrong packet"),
|
||||||
}
|
};
|
||||||
|
|
||||||
c.write_packet(packet::status::serverbound::StatusPing{ping: 4433}).unwrap();
|
let mut key = openssl::PublicKey::new(&packet.public_key.data);
|
||||||
|
let shared = openssl::gen_random(16);
|
||||||
|
|
||||||
match c.read_packet().unwrap() {
|
let shared_e = key.encrypt(&shared);
|
||||||
packet::Packet::StatusPong(val) => println!("{}", val.ping),
|
let token_e = key.encrypt(&packet.verify_token.data);
|
||||||
_ => panic!("Wrong packet"),
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!("TODO!");
|
let profile = mojang::Profile{
|
||||||
|
username: "Think".to_string(),
|
||||||
|
id: "b1184d43168441cfa2128b9a3df3b6ab".to_string(),
|
||||||
|
access_token: "".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
profile.join_server(&packet.server_id, &shared, &packet.public_key.data);
|
||||||
|
|
||||||
|
c.write_packet(packet::login::serverbound::EncryptionResponse{
|
||||||
|
shared_secret: LenPrefixed::new(shared_e),
|
||||||
|
verify_token: LenPrefixed::new(token_e),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut read = c.clone();
|
||||||
|
let mut write = c.clone();
|
||||||
|
|
||||||
|
read.enable_encyption(&shared, true);
|
||||||
|
write.enable_encyption(&shared, false);
|
||||||
|
|
||||||
|
loop { match read.read_packet().unwrap() {
|
||||||
|
packet::Packet::LoginDisconnect(val) => {
|
||||||
|
panic!("Discconect {}", val.reason);
|
||||||
|
},
|
||||||
|
packet::Packet::SetInitialCompression(val) => {
|
||||||
|
read.set_compresssion(val.threshold.0, true);
|
||||||
|
write.set_compresssion(val.threshold.0, false);
|
||||||
|
println!("Compression: {}", val.threshold.0)
|
||||||
|
},
|
||||||
|
packet::Packet::LoginSuccess(val) => {
|
||||||
|
println!("Login: {} {}", val.username, val.uuid);
|
||||||
|
read.state = State::Play;
|
||||||
|
write.state = State::Play;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => panic!("Unknown packet"),
|
||||||
|
} }
|
||||||
|
|
||||||
|
let mut first = true;
|
||||||
|
let mut count = 0;
|
||||||
|
loop { match read.read_packet().unwrap() {
|
||||||
|
packet::Packet::ServerMessage(val) => println!("MSG: {}", val.message),
|
||||||
|
_ => {
|
||||||
|
if first {
|
||||||
|
println!("got packet");
|
||||||
|
write.write_packet(packet::play::serverbound::ChatMessage{
|
||||||
|
message: "Hello world".to_string(),
|
||||||
|
});
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
if count > 200 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} }
|
||||||
|
|
||||||
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
extern crate steven_openssl as openssl;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate hyper;
|
||||||
|
|
||||||
|
pub struct Profile {
|
||||||
|
pub username: String,
|
||||||
|
pub id: String,
|
||||||
|
pub access_token: String
|
||||||
|
}
|
||||||
|
|
||||||
|
const JOIN_URL: &'static str = "https://sessionserver.mojang.com/session/minecraft/join";
|
||||||
|
|
||||||
|
impl Profile {
|
||||||
|
pub fn join_server(&self, server_id: &String, shared_key: &Vec<u8>, public_key: &Vec<u8>) {
|
||||||
|
let mut sha1 = openssl::SHA1::new();
|
||||||
|
sha1.update(server_id.as_bytes());
|
||||||
|
sha1.update(&shared_key[..]);
|
||||||
|
sha1.update(&public_key[..]);
|
||||||
|
let mut hash = sha1.bytes();
|
||||||
|
|
||||||
|
// Mojang uses a hex method which allows for
|
||||||
|
// negatives so we have to account for that.
|
||||||
|
let negative = hash[0] & 0x80 == 0x80;
|
||||||
|
if negative {
|
||||||
|
twos_compliment(&mut hash);
|
||||||
|
}
|
||||||
|
let hash_str = hash.iter().map(|b| format!("{:02X}", b)).collect::<Vec<String>>().connect("");
|
||||||
|
let hash_val = hash_str.trim_matches('0');
|
||||||
|
let hash_str = if negative {
|
||||||
|
"-".to_owned() + &hash_val[..]
|
||||||
|
} else {
|
||||||
|
hash_val.to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let join_msg = serde_json::builder::ObjectBuilder::new()
|
||||||
|
.insert("accessToken", &self.access_token)
|
||||||
|
.insert("selectedProfile", &self.id)
|
||||||
|
.insert("serverId", hash_str)
|
||||||
|
.unwrap();
|
||||||
|
let join = serde_json::to_string(&join_msg).unwrap();
|
||||||
|
|
||||||
|
let client = hyper::Client::new();
|
||||||
|
let res = client.post(JOIN_URL)
|
||||||
|
.body(&join)
|
||||||
|
.header(hyper::header::ContentType("application/json".parse().unwrap()))
|
||||||
|
.send().unwrap();
|
||||||
|
|
||||||
|
let ret: serde_json::Value = match serde_json::from_reader(res) {
|
||||||
|
Result::Ok(val) => val,
|
||||||
|
Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
panic!("{:?}", ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn twos_compliment(data: &mut Vec<u8>) {
|
||||||
|
let mut carry = true;
|
||||||
|
for i in (0 .. data.len()).rev() {
|
||||||
|
data[i] = !data[i];
|
||||||
|
if carry {
|
||||||
|
carry = data[i] == 0xFF;
|
||||||
|
data[i] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,15 +17,15 @@ state_packets!(
|
||||||
// than the hostname due to the protocol not providing
|
// than the hostname due to the protocol not providing
|
||||||
// any system for custom information to be transfered
|
// any system for custom information to be transfered
|
||||||
// by the client to the server until after login.
|
// by the client to the server until after login.
|
||||||
Handshake => 0 {
|
Handshake => 0x00 {
|
||||||
// The protocol version of the connecting client
|
// The protocol version of the connecting client
|
||||||
protocol_version: VarInt,
|
protocol_version: VarInt =,
|
||||||
// The hostname the client connected to
|
// The hostname the client connected to
|
||||||
host: String,
|
host: String =,
|
||||||
// The port the client connected to
|
// The port the client connected to
|
||||||
port: u16,
|
port: u16 =,
|
||||||
// The next protocol state the client wants
|
// The next protocol state the client wants
|
||||||
next: VarInt
|
next: VarInt =,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clientbound Clientbound {
|
clientbound Clientbound {
|
||||||
|
@ -33,14 +33,113 @@ state_packets!(
|
||||||
}
|
}
|
||||||
play Play {
|
play Play {
|
||||||
serverbound Serverbound {
|
serverbound Serverbound {
|
||||||
|
// TabComplete is sent by the client when the client presses tab in
|
||||||
|
// the chat box.
|
||||||
|
TabComplete => 0x00 {
|
||||||
|
text: String =,
|
||||||
|
has_target: bool =,
|
||||||
|
target: Option<Position> = when(|p: &TabComplete| p.has_target == true),
|
||||||
|
}
|
||||||
|
// ChatMessage is sent by the client when it sends a chat message or
|
||||||
|
// executes a command (prefixed by '/').
|
||||||
|
ChatMessage => 0x01 {
|
||||||
|
message: String =,
|
||||||
|
}
|
||||||
|
// ClientStatus is sent to update the client's status
|
||||||
|
ClientStatus => 0x02 {
|
||||||
|
action_id: VarInt =,
|
||||||
|
}
|
||||||
|
// ClientSettings is sent by the client to update its current settings.
|
||||||
|
ClientSettings => 0x03 {
|
||||||
|
locale: String =,
|
||||||
|
view_distance: u8 =,
|
||||||
|
chat_mode: u8 =,
|
||||||
|
chat_colors: bool =,
|
||||||
|
displayed_skin_parts: u8 =,
|
||||||
|
main_hand: VarInt =,
|
||||||
|
}
|
||||||
|
// ConfirmTransactionServerbound is a reply to ConfirmTransaction.
|
||||||
|
ConfirmTransactionServerbound => 0x04 {
|
||||||
|
id: u8 =,
|
||||||
|
action_number: i16 =,
|
||||||
|
accepted: bool =,
|
||||||
|
}
|
||||||
|
// EnchantItem is sent when the client enchants an item.
|
||||||
|
EnchantItem => 0x05 {
|
||||||
|
id: u8 =,
|
||||||
|
enchantment: u8 =,
|
||||||
|
}
|
||||||
|
// ClickWindow is sent when the client clicks in a window.
|
||||||
|
ClickWindow => 0x06 {
|
||||||
|
id: u8 =,
|
||||||
|
slot: i16 =,
|
||||||
|
button: u8 =,
|
||||||
|
action_number: u16 =,
|
||||||
|
mode: u8 =,
|
||||||
|
clicked_item: ()=, // TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clientbound Clientbound {
|
clientbound Clientbound {
|
||||||
|
ServerMessage => 15 {
|
||||||
|
message: format::Component =,
|
||||||
|
position: u8 =,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
login Login {
|
login Login {
|
||||||
serverbound Serverbound {
|
serverbound Serverbound {
|
||||||
|
// LoginStart is sent immeditately after switching into the login
|
||||||
|
// state. The passed username is used by the server to authenticate
|
||||||
|
// the player in online mode.
|
||||||
|
LoginStart => 0 {
|
||||||
|
username: String =,
|
||||||
|
}
|
||||||
|
// EncryptionResponse is sent as a reply to EncryptionRequest. All
|
||||||
|
// packets following this one must be encrypted with AES/CFB8
|
||||||
|
// encryption.
|
||||||
|
EncryptionResponse => 1 {
|
||||||
|
// The key for the AES/CFB8 cipher encrypted with the
|
||||||
|
// public key
|
||||||
|
shared_secret: LenPrefixed<VarInt, u8> =,
|
||||||
|
// The verify token from the request encrypted with the
|
||||||
|
// public key
|
||||||
|
verify_token: LenPrefixed<VarInt, u8> =,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clientbound Clientbound {
|
clientbound Clientbound {
|
||||||
|
// LoginDisconnect is sent by the server if there was any issues
|
||||||
|
// authenticating the player during login or the general server
|
||||||
|
// issues (e.g. too many players).
|
||||||
|
LoginDisconnect => 0 {
|
||||||
|
reason: format::Component =,
|
||||||
|
}
|
||||||
|
// EncryptionRequest is sent by the server if the server is in
|
||||||
|
// online mode. If it is not sent then its assumed the server is
|
||||||
|
// in offline mode.
|
||||||
|
EncryptionRequest => 1 {
|
||||||
|
// Generally empty, left in from legacy auth
|
||||||
|
// but is still used by the client if provided
|
||||||
|
server_id: String =,
|
||||||
|
// A RSA Public key serialized in x.509 PRIX format
|
||||||
|
public_key: LenPrefixed<VarInt, u8> =,
|
||||||
|
// Token used by the server to verify encryption is working
|
||||||
|
// correctly
|
||||||
|
verify_token: LenPrefixed<VarInt, u8> =,
|
||||||
|
}
|
||||||
|
// LoginSuccess is sent by the server if the player successfully
|
||||||
|
// authenicates with the session servers (online mode) or straight
|
||||||
|
// after LoginStart (offline mode).
|
||||||
|
LoginSuccess => 2 {
|
||||||
|
// String encoding of a uuid (with hyphens)
|
||||||
|
uuid: String =,
|
||||||
|
username: String =,
|
||||||
|
}
|
||||||
|
// SetInitialCompression sets the compression threshold during the
|
||||||
|
// login state.
|
||||||
|
SetInitialCompression => 3 {
|
||||||
|
// Threshold where a packet should be sent compressed
|
||||||
|
threshold: VarInt =,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status Status {
|
status Status {
|
||||||
|
@ -50,14 +149,14 @@ state_packets!(
|
||||||
// to signal the server to send a StatusResponse to the
|
// to signal the server to send a StatusResponse to the
|
||||||
// client
|
// client
|
||||||
StatusRequest => 0 {
|
StatusRequest => 0 {
|
||||||
empty: Empty
|
empty: () =,
|
||||||
},
|
}
|
||||||
// StatusPing is sent by the client after recieving a
|
// StatusPing is sent by the client after recieving a
|
||||||
// StatusResponse. The client uses the time from sending
|
// StatusResponse. The client uses the time from sending
|
||||||
// the ping until the time of recieving a pong to measure
|
// the ping until the time of recieving a pong to measure
|
||||||
// the latency between the client and the server.
|
// the latency between the client and the server.
|
||||||
StatusPing => 1 {
|
StatusPing => 1 {
|
||||||
ping: i64
|
ping: i64 =,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clientbound Clientbound {
|
clientbound Clientbound {
|
||||||
|
@ -83,13 +182,13 @@ state_packets!(
|
||||||
// "favicon": "data:image/png;base64,<data>"
|
// "favicon": "data:image/png;base64,<data>"
|
||||||
// }
|
// }
|
||||||
StatusResponse => 0 {
|
StatusResponse => 0 {
|
||||||
status: String
|
status: String =,
|
||||||
},
|
}
|
||||||
// StatusPong is sent as a reply to a StatusPing.
|
// StatusPong is sent as a reply to a StatusPing.
|
||||||
// The Time field should be exactly the same as the
|
// The Time field should be exactly the same as the
|
||||||
// one sent by the client.
|
// one sent by the client.
|
||||||
StatusPong => 1 {
|
StatusPong => 1 {
|
||||||
ping: i64
|
ping: i64 =,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue