Main part of the protocol complete
This commit is contained in:
parent
c015ae9e55
commit
6f6d3c96ca
|
@ -1,2 +1,2 @@
|
|||
*.lock
|
||||
/target
|
||||
target/
|
||||
|
|
|
@ -8,7 +8,15 @@ authors = [ "Thinkofdeath <thinkofdeath@spigotmc.org>" ]
|
|||
rustc-serialize = "0.3"
|
||||
glfw = "0.1.0"
|
||||
byteorder = "0.3.13"
|
||||
hyper = "0.6.13"
|
||||
serde = "0.6.0"
|
||||
serde_json = "0.6.0"
|
||||
flate2 = "0.2.9"
|
||||
|
||||
[dependencies.steven_gl]
|
||||
path = "./gl"
|
||||
version = "0"
|
||||
|
||||
[dependencies.steven_openssl]
|
||||
path = "./openssl"
|
||||
version = "0"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "steven_openssl"
|
||||
version = "0.0.1"
|
||||
authors = [ "Thinkofdeath <thinkofdeath@spigotmc.org>" ]
|
||||
|
||||
[dependencies]
|
||||
libc = "*"
|
|
@ -0,0 +1,209 @@
|
|||
#![allow(dead_code)]
|
||||
extern crate libc;
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
struct SHA_CTX {
|
||||
h0: libc::c_uint,
|
||||
h1: libc::c_uint,
|
||||
h2: libc::c_uint,
|
||||
h3: libc::c_uint,
|
||||
h4: libc::c_uint,
|
||||
N1: libc::c_uint,
|
||||
NH: libc::c_uint,
|
||||
data: [libc::c_uint; 16],
|
||||
num: libc::c_uint
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct RSA;
|
||||
|
||||
#[repr(C)]
|
||||
struct EVP_CIPHER_CTX;
|
||||
|
||||
#[repr(C)]
|
||||
struct EVP_CIPHER;
|
||||
|
||||
#[repr(C)]
|
||||
struct ENGINE;
|
||||
|
||||
const RSA_PKCS1_PADDING : libc::c_int = 1;
|
||||
|
||||
#[link(name = "crypto")]
|
||||
extern {
|
||||
fn SHA1_Init(c: *mut SHA_CTX) -> libc::c_int;
|
||||
fn SHA1_Update(c: *mut SHA_CTX, data: *const u8, len: libc::size_t) -> libc::c_int;
|
||||
fn SHA1_Final(md: *const u8, c: *mut SHA_CTX) -> libc::c_int;
|
||||
|
||||
fn d2i_RSA_PUBKEY(a: *mut *mut RSA, data: *const *const u8, len: libc::c_int) -> *mut RSA;
|
||||
fn RSA_free(rsa: *mut RSA);
|
||||
fn RSA_size(rsa: *mut RSA) -> libc::c_int;
|
||||
fn RSA_public_encrypt(flen: libc::c_int, from: *const u8, to: *mut u8, rsa: *mut RSA, padding: libc::c_int) -> libc::c_int;
|
||||
|
||||
fn RAND_bytes(buf: *mut u8, len: libc::c_int) -> libc::c_int;
|
||||
|
||||
fn EVP_CIPHER_CTX_init(a: *mut EVP_CIPHER_CTX);
|
||||
fn EVP_EncryptInit_ex(ctx: *mut EVP_CIPHER_CTX, cipher: *const EVP_CIPHER, engine: *mut ENGINE, key: *const u8, iv: *const u8) -> libc::c_int;
|
||||
fn EVP_DecryptInit_ex(ctx: *mut EVP_CIPHER_CTX, cipher: *const EVP_CIPHER, engine: *mut ENGINE, key: *const u8, iv: *const u8) -> libc::c_int;
|
||||
fn EVP_EncryptUpdate(ctx: *mut EVP_CIPHER_CTX, out: *mut u8, outl: *mut libc::c_int, input: *const u8, inl: libc::c_int) -> libc::c_int;
|
||||
fn EVP_DecryptUpdate(ctx: *mut EVP_CIPHER_CTX, out: *mut u8, outl: *mut libc::c_int, input: *const u8, inl: libc::c_int) -> libc::c_int;
|
||||
fn EVP_aes_128_cfb8() -> *const EVP_CIPHER;
|
||||
fn EVP_CIPHER_block_size(cipher: *const EVP_CIPHER) -> libc::c_int;
|
||||
fn EVP_CIPHER_CTX_free(ctx: *mut EVP_CIPHER_CTX);
|
||||
fn EVP_CIPHER_CTX_cleanup(ctx: *mut EVP_CIPHER_CTX) -> libc::c_int;
|
||||
fn EVP_CIPHER_CTX_new() -> *mut EVP_CIPHER_CTX;
|
||||
}
|
||||
|
||||
pub struct EVPCipher {
|
||||
internal: *mut EVP_CIPHER_CTX,
|
||||
|
||||
block_size: usize,
|
||||
}
|
||||
|
||||
impl EVPCipher {
|
||||
pub fn new(key: &Vec<u8>, iv: &Vec<u8>, decrypt: bool) -> EVPCipher {
|
||||
unsafe {
|
||||
let e = EVPCipher {
|
||||
internal: EVP_CIPHER_CTX_new(),
|
||||
block_size: EVP_CIPHER_block_size(EVP_aes_128_cfb8()) as usize,
|
||||
};
|
||||
EVP_CIPHER_CTX_init(e.internal);
|
||||
if decrypt {
|
||||
if EVP_DecryptInit_ex(e.internal, EVP_aes_128_cfb8(), ptr::null_mut(), key.as_ptr(), iv.as_ptr()) == 0 {
|
||||
panic!("Init error");
|
||||
}
|
||||
} else {
|
||||
if EVP_EncryptInit_ex(e.internal, EVP_aes_128_cfb8(), ptr::null_mut(), key.as_ptr(), iv.as_ptr()) == 0 {
|
||||
panic!("Init error");
|
||||
}
|
||||
}
|
||||
e
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
unsafe {
|
||||
let mut out = Vec::with_capacity(data.len());
|
||||
let mut out_len: libc::c_int = 0;
|
||||
if EVP_DecryptUpdate(self.internal, out.as_mut_ptr(), &mut out_len, data.as_ptr(), data.len() as libc::c_int) == 0 {
|
||||
panic!("Decrypt error")
|
||||
}
|
||||
out.set_len(out_len as usize);
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt(&mut self, data: &[u8]) -> Vec<u8> {
|
||||
unsafe {
|
||||
let mut out = Vec::with_capacity(data.len());
|
||||
let mut out_len: libc::c_int = 0;
|
||||
if EVP_EncryptUpdate(self.internal, out.as_mut_ptr(), &mut out_len, data.as_ptr(), data.len() as libc::c_int) == 0 {
|
||||
panic!("Encrypt error")
|
||||
}
|
||||
out.set_len(out_len as usize);
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Drop for EVPCipher {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
EVP_CIPHER_CTX_cleanup(self.internal);
|
||||
EVP_CIPHER_CTX_free(self.internal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_random(len: usize) -> Vec<u8> {
|
||||
unsafe {
|
||||
let mut data = Vec::with_capacity(len);
|
||||
data.set_len(len);
|
||||
RAND_bytes(data.as_mut_ptr(), len as libc::c_int);
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct PublicKey {
|
||||
rsa: *mut RSA
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
pub fn new(data: &Vec<u8>) -> PublicKey {
|
||||
unsafe {
|
||||
let cert = d2i_RSA_PUBKEY(ptr::null_mut(), &data.as_ptr(), data.len() as libc::c_int);
|
||||
if cert.is_null() {
|
||||
panic!("Error");
|
||||
}
|
||||
PublicKey { rsa: cert }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt(&mut self, data: &Vec<u8>) -> Vec<u8> {
|
||||
unsafe {
|
||||
let size = RSA_size(self.rsa) as usize;
|
||||
let mut out = Vec::with_capacity(size);
|
||||
out.set_len(size);
|
||||
RSA_public_encrypt(data.len() as libc::c_int, data.as_ptr(), out.as_mut_ptr(), self.rsa, RSA_PKCS1_PADDING);
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PublicKey {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
RSA_free(self.rsa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SHA1 {
|
||||
internal: SHA_CTX,
|
||||
}
|
||||
|
||||
impl SHA1 {
|
||||
pub fn new() -> SHA1 {
|
||||
unsafe {
|
||||
let mut s : SHA1 = mem::uninitialized();
|
||||
if SHA1_Init(&mut s.internal) == 0 {
|
||||
panic!("error init sha1");
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, data: &[u8]) {
|
||||
unsafe {
|
||||
if SHA1_Update(&mut self.internal, data.as_ptr(), data.len() as libc::size_t) == 0 {
|
||||
panic!("sha1 error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes(mut self) -> Vec<u8> {
|
||||
unsafe {
|
||||
let mut dst = Vec::with_capacity(20);
|
||||
dst.set_len(20);
|
||||
if SHA1_Final(dst.as_mut_ptr(), &mut self.internal) == 0 {
|
||||
panic!("sha1 error");
|
||||
}
|
||||
dst
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{SHA1};
|
||||
#[test]
|
||||
fn test_sha1() {
|
||||
let mut sha1 = SHA1::new();
|
||||
sha1.update(b"Hello world");
|
||||
assert!(sha1.bytes().iter().map(|b| format!("{:02X}", b)).collect::<Vec<String>>().connect("") == "7B502C3A1F48C8609AE212CDFB639DEE39673F5E");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
extern crate serde_json;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Component {
|
||||
Text(TextComponent),
|
||||
None
|
||||
}
|
||||
|
||||
impl Component {
|
||||
pub fn from_value(v: &serde_json::Value) -> Self {
|
||||
let modifier = Modifier::from_value(v);
|
||||
if let Some(val) = v.as_string() {
|
||||
Component::Text(TextComponent{text: val.to_owned(), modifier: modifier})
|
||||
} else if v.find("text").is_some(){
|
||||
Component::Text(TextComponent::from_value(v, modifier))
|
||||
} else {
|
||||
Component::None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> serde_json::Value {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Component {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Component::Text(ref txt) => write!(f, "{}", txt),
|
||||
Component::None => Result::Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Component {
|
||||
fn default() -> Self {
|
||||
Component::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Modifier {
|
||||
pub extra: Option<Vec<Component>>,
|
||||
pub bold: Option<bool>,
|
||||
pub italic: Option<bool>,
|
||||
pub underlined: Option<bool>,
|
||||
pub strikethrough: Option<bool>,
|
||||
pub obfuscated: Option<bool>,
|
||||
pub color: Option<Color>,
|
||||
|
||||
// click_event
|
||||
// hover_event
|
||||
// insertion
|
||||
}
|
||||
|
||||
impl Modifier {
|
||||
pub fn from_value(v: &serde_json::Value) -> Self {
|
||||
let mut m = Modifier {
|
||||
bold: v.find("bold").map_or(Option::None, |v| v.as_boolean()),
|
||||
italic: v.find("italic").map_or(Option::None, |v| v.as_boolean()),
|
||||
underlined: v.find("underlined").map_or(Option::None, |v| v.as_boolean()),
|
||||
strikethrough: v.find("strikethrough").map_or(Option::None, |v| v.as_boolean()),
|
||||
obfuscated: v.find("obfuscated").map_or(Option::None, |v| v.as_boolean()),
|
||||
color: v.find("color").map_or(Option::None, |v| v.as_string()).map(|v| Color::from_string(&v.to_owned())),
|
||||
extra: Option::None,
|
||||
};
|
||||
if let Some(extra) = v.find("extra") {
|
||||
if let Some(data) = extra.as_array() {
|
||||
let mut ex = Vec::new();
|
||||
for e in data {
|
||||
ex.push(Component::from_value(e));
|
||||
}
|
||||
m.extra = Some(ex);
|
||||
}
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> serde_json::Value {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextComponent {
|
||||
pub text: String,
|
||||
pub modifier: Modifier,
|
||||
}
|
||||
|
||||
impl TextComponent {
|
||||
pub fn from_value(v: &serde_json::Value, modifier: Modifier) -> Self {
|
||||
TextComponent {
|
||||
text: v.find("text").unwrap().as_string().unwrap_or("").to_owned(),
|
||||
modifier: modifier,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> serde_json::Value {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TextComponent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
try!(write!(f, "{}", self.text));
|
||||
if let Some(ref extra) = self.modifier.extra {
|
||||
for c in extra {
|
||||
try!(write!(f, "{}", c));
|
||||
}
|
||||
}
|
||||
Result::Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
DarkBlue,
|
||||
DarkGreen,
|
||||
DarkAqua,
|
||||
DarkRed,
|
||||
DarkPurple,
|
||||
Gold,
|
||||
Gray,
|
||||
DarkGray,
|
||||
Blue,
|
||||
Green,
|
||||
Aqua,
|
||||
Red,
|
||||
LightPurple,
|
||||
Yellow,
|
||||
White,
|
||||
RGB(u8, u8, u8)
|
||||
}
|
||||
|
||||
impl fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
fn from_string(val: &String) -> Self {
|
||||
match val.as_ref() {
|
||||
"black" => Color::Black,
|
||||
"dark_blue" => Color::DarkBlue,
|
||||
"dark_green" => Color::DarkGreen,
|
||||
"dark_aqua" => Color::DarkAqua,
|
||||
"dark_red" => Color::DarkRed,
|
||||
"dark_purple" => Color::DarkPurple,
|
||||
"gold" => Color::Gold,
|
||||
"gray" => Color::Gray,
|
||||
"dark_gray" => Color::DarkGray,
|
||||
"blue" => Color::Blue,
|
||||
"green" => Color::Green,
|
||||
"aqua" => Color::Aqua,
|
||||
"red" => Color::Red,
|
||||
"light_purple" => Color::LightPurple,
|
||||
"yellow" => Color::Yellow,
|
||||
"white" => Color::White,
|
||||
val if val.len() == 7 && val.as_bytes()[0] == b'#' => {
|
||||
let r = match u8::from_str_radix(&val[1..3], 16) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return Color::White,
|
||||
};
|
||||
let g = match u8::from_str_radix(&val[3..5], 16) {
|
||||
Ok(g) => g,
|
||||
Err(_) => return Color::White,
|
||||
};
|
||||
let b = match u8::from_str_radix(&val[5..7], 16) {
|
||||
Ok(b) => b,
|
||||
Err(_) => return Color::White,
|
||||
};
|
||||
Color::RGB(
|
||||
r,
|
||||
g,
|
||||
b
|
||||
)
|
||||
}
|
||||
_ => Color::White,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_string(&self) -> String {
|
||||
match *self {
|
||||
Color::Black => "black".to_owned(),
|
||||
Color::DarkBlue => "dark_blue".to_owned(),
|
||||
Color::DarkGreen => "dark_green".to_owned(),
|
||||
Color::DarkAqua => "dark_aqua".to_owned(),
|
||||
Color::DarkRed => "dark_red".to_owned(),
|
||||
Color::DarkPurple => "dark_purple".to_owned(),
|
||||
Color::Gold => "gold".to_owned(),
|
||||
Color::Gray => "gray".to_owned(),
|
||||
Color::DarkGray => "dark_gray".to_owned(),
|
||||
Color::Blue => "blue".to_owned(),
|
||||
Color::Green => "green".to_owned(),
|
||||
Color::Aqua => "aqua".to_owned(),
|
||||
Color::Red => "red".to_owned(),
|
||||
Color::LightPurple => "light_purple".to_owned(),
|
||||
Color::Yellow => "yellow".to_owned(),
|
||||
Color::White => "white".to_owned(),
|
||||
Color::RGB(r, g, b) => format!("#{:02X}{:02X}{:02X}", r, g, b),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn to_rgb(&self) -> (u8, u8, u8) {
|
||||
match *self {
|
||||
Color::Black =>(0, 0, 0),
|
||||
Color::DarkBlue =>(0, 0, 170),
|
||||
Color::DarkGreen =>(0, 170, 0),
|
||||
Color::DarkAqua =>(0, 170, 170),
|
||||
Color::DarkRed =>(170, 0, 0),
|
||||
Color::DarkPurple =>(170, 0, 170),
|
||||
Color::Gold =>(255, 170, 0),
|
||||
Color::Gray =>(170, 170, 170),
|
||||
Color::DarkGray =>(85, 85, 85),
|
||||
Color::Blue =>(85, 85, 255),
|
||||
Color::Green =>(85, 255, 85),
|
||||
Color::Aqua =>(85, 255, 255),
|
||||
Color::Red =>(255, 85, 85),
|
||||
Color::LightPurple =>(255, 85, 255),
|
||||
Color::Yellow =>(255, 255, 85),
|
||||
Color::White =>(255, 255, 255),
|
||||
Color::RGB(r, g, b) => (r, g, b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_color_from() {
|
||||
let test = Color::from_string(&"#FF0000".to_owned());
|
||||
match test {
|
||||
Color::RGB(r, g, b) => assert!(r == 255 && g == 0 && b == 0),
|
||||
_ => panic!("Wrong type"),
|
||||
}
|
||||
let test = Color::from_string(&"#123456".to_owned());
|
||||
match test {
|
||||
Color::RGB(r, g, b) => assert!(r == 0x12 && g == 0x34 && b == 0x56),
|
||||
_ => panic!("Wrong type"),
|
||||
}
|
||||
let test = Color::from_string(&"red".to_owned());
|
||||
match test {
|
||||
Color::Red => {},
|
||||
_ => panic!("Wrong type"),
|
||||
}
|
||||
let test = Color::from_string(&"dark_blue".to_owned());
|
||||
match test {
|
||||
Color::DarkBlue => {},
|
||||
_ => panic!("Wrong type"),
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
extern crate glfw;
|
||||
extern crate byteorder;
|
||||
|
||||
pub mod bit;
|
||||
pub mod protocol;
|
||||
pub mod format;
|
||||
|
||||
mod gl;
|
||||
use glfw::{Action, Context, Key};
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
#![allow(dead_code)]
|
||||
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::net::TcpStream;
|
||||
use std::io;
|
||||
use std::io::{Write, Read};
|
||||
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
|
||||
#[macro_export]
|
||||
|
@ -14,13 +22,12 @@ macro_rules! state_packets {
|
|||
($($state:ident $stateName:ident {
|
||||
$($dir:ident $dirName:ident {
|
||||
$($name:ident => $id:expr {
|
||||
$($field:ident: $field_type:ident),+
|
||||
}),*
|
||||
$($field:ident: $field_type:ty = $(when ($cond:expr))*, )+
|
||||
})*
|
||||
})+
|
||||
})+) => {
|
||||
use protocol::*;
|
||||
use std::io;
|
||||
use protocol::{Serializable};
|
||||
|
||||
pub enum Packet {
|
||||
$(
|
||||
|
@ -36,9 +43,10 @@ macro_rules! state_packets {
|
|||
pub mod $state {
|
||||
$(
|
||||
pub mod $dir {
|
||||
#![allow(unused_imports)]
|
||||
use protocol::*;
|
||||
use std::io;
|
||||
use protocol::{Serializable};
|
||||
use format;
|
||||
|
||||
$(
|
||||
#[derive(Default)]
|
||||
|
@ -52,7 +60,9 @@ macro_rules! state_packets {
|
|||
|
||||
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(())
|
||||
|
@ -76,9 +86,12 @@ macro_rules! state_packets {
|
|||
match 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)))
|
||||
},
|
||||
|
@ -97,11 +110,23 @@ macro_rules! state_packets {
|
|||
|
||||
pub mod packet;
|
||||
|
||||
trait Serializable {
|
||||
pub trait Serializable {
|
||||
fn read_from(buf: &mut io::Read) -> Result<Self, 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 {
|
||||
fn read_from(buf: &mut io::Read) -> Result<String, io::Error> {
|
||||
let len = try!(VarInt::read_from(buf)).0;
|
||||
|
@ -117,17 +142,90 @@ impl Serializable for String {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serializable for Empty {
|
||||
fn read_from(buf: &mut io::Read) -> Result<Empty, io::Error> {
|
||||
Result::Ok(Empty)
|
||||
impl Serializable for format::Component {
|
||||
fn read_from(buf: &mut io::Read) -> Result<Self, io::Error> {
|
||||
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> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Empty {
|
||||
fn default() -> Empty { Empty }
|
||||
pub struct Position(u64);
|
||||
|
||||
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 {
|
||||
|
@ -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 {
|
||||
fn read_from(buf: &mut io::Read) -> Result<u16, io::Error> {
|
||||
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
|
||||
/// on the size of the number
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct VarInt(i32);
|
||||
|
||||
impl Lengthable for VarInt {}
|
||||
|
||||
impl Serializable for VarInt {
|
||||
/// Decodes a VarInt from the Reader
|
||||
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) }
|
||||
}
|
||||
|
||||
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
|
||||
/// server or the client.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Direction {
|
||||
Serverbound,
|
||||
Clientbound
|
||||
|
@ -252,16 +423,18 @@ impl ::std::fmt::Display for Error {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Helper for empty structs
|
||||
pub struct Empty;
|
||||
|
||||
pub struct Conn {
|
||||
stream: TcpStream,
|
||||
host: String,
|
||||
port: u16,
|
||||
direction: Direction,
|
||||
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 {
|
||||
|
@ -278,27 +451,58 @@ impl Conn {
|
|||
port: parts[1].parse().unwrap(),
|
||||
direction: Direction::Serverbound,
|
||||
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> {
|
||||
let mut buf = Vec::new();
|
||||
try!(VarInt(packet.packet_id()).write_to(&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(())
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
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 dir = match self.direction {
|
||||
|
@ -317,7 +521,70 @@ impl Conn {
|
|||
}
|
||||
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),
|
||||
host: "localhost".to_string(),
|
||||
port: 25565,
|
||||
next: VarInt(1),
|
||||
next: VarInt(2),
|
||||
}).unwrap();
|
||||
c.state = State::Status;
|
||||
c.write_packet(packet::status::serverbound::StatusRequest{empty: Empty}).unwrap();
|
||||
c.state = State::Login;
|
||||
c.write_packet(packet::login::serverbound::LoginStart{username: "Think".to_string()}).unwrap();
|
||||
|
||||
match c.read_packet().unwrap() {
|
||||
packet::Packet::StatusResponse(val) => println!("{}", val.status),
|
||||
let packet = match c.read_packet().unwrap() {
|
||||
packet::Packet::EncryptionRequest(val) => val,
|
||||
_ => 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() {
|
||||
packet::Packet::StatusPong(val) => println!("{}", val.ping),
|
||||
_ => panic!("Wrong packet"),
|
||||
}
|
||||
let shared_e = key.encrypt(&shared);
|
||||
let token_e = key.encrypt(&packet.verify_token.data);
|
||||
|
||||
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
|
||||
// any system for custom information to be transfered
|
||||
// by the client to the server until after login.
|
||||
Handshake => 0 {
|
||||
Handshake => 0x00 {
|
||||
// The protocol version of the connecting client
|
||||
protocol_version: VarInt,
|
||||
protocol_version: VarInt =,
|
||||
// The hostname the client connected to
|
||||
host: String,
|
||||
host: String =,
|
||||
// The port the client connected to
|
||||
port: u16,
|
||||
port: u16 =,
|
||||
// The next protocol state the client wants
|
||||
next: VarInt
|
||||
next: VarInt =,
|
||||
}
|
||||
}
|
||||
clientbound Clientbound {
|
||||
|
@ -33,14 +33,113 @@ state_packets!(
|
|||
}
|
||||
play Play {
|
||||
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 {
|
||||
ServerMessage => 15 {
|
||||
message: format::Component =,
|
||||
position: u8 =,
|
||||
}
|
||||
}
|
||||
}
|
||||
login Login {
|
||||
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 {
|
||||
// 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 {
|
||||
|
@ -50,14 +149,14 @@ state_packets!(
|
|||
// to signal the server to send a StatusResponse to the
|
||||
// client
|
||||
StatusRequest => 0 {
|
||||
empty: 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
|
||||
ping: i64 =,
|
||||
}
|
||||
}
|
||||
clientbound Clientbound {
|
||||
|
@ -83,13 +182,13 @@ state_packets!(
|
|||
// "favicon": "data:image/png;base64,<data>"
|
||||
// }
|
||||
StatusResponse => 0 {
|
||||
status: String
|
||||
},
|
||||
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
|
||||
ping: i64 =,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue