diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 6549828..0fcf3a1 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -16,6 +16,7 @@ use openssl; use serde_json; +use hyper; pub mod mojang; @@ -633,6 +634,8 @@ pub enum Error { Err(String), Disconnect(format::Component), IOError(io::Error), + Json(serde_json::Error), + Hyper(hyper::Error), } impl convert::From for Error { @@ -641,12 +644,26 @@ impl convert::From for Error { } } +impl convert::From for Error { + fn from(e: serde_json::Error) -> Error { + Error::Json(e) + } +} + +impl convert::From for Error { + fn from(e: hyper::Error) -> Error { + Error::Hyper(e) + } +} + impl ::std::error::Error for Error { fn description(&self) -> &str { match *self { Error::Err(ref val) => &val[..], Error::Disconnect(_) => "Disconnect", Error::IOError(ref e) => e.description(), + Error::Json(ref e) => e.description(), + Error::Hyper(ref e) => e.description(), } } } @@ -657,6 +674,8 @@ impl ::std::fmt::Display for Error { Error::Err(ref val) => write!(f, "protocol error: {}", val), Error::Disconnect(ref val) => write!(f, "{}", val), Error::IOError(ref e) => e.fmt(f), + Error::Json(ref e) => e.fmt(f), + Error::Hyper(ref e) => e.fmt(f), } } } diff --git a/src/protocol/mojang.rs b/src/protocol/mojang.rs index e8b7efe..d3aeded 100644 --- a/src/protocol/mojang.rs +++ b/src/protocol/mojang.rs @@ -14,8 +14,10 @@ use openssl; use serde_json; +use serde_json::builder::ObjectBuilder; use hyper; +#[derive(Clone, Debug)] pub struct Profile { pub username: String, pub id: String, @@ -23,9 +25,85 @@ pub struct Profile { } const JOIN_URL: &'static str = "https://sessionserver.mojang.com/session/minecraft/join"; +const LOGIN_URL: &'static str = "https://authserver.mojang.com/authenticate"; +const REFRESH_URL: &'static str = "https://authserver.mojang.com/refresh"; +const VALIDATE_URL: &'static str = "https://authserver.mojang.com/validate"; impl Profile { - pub fn join_server(&self, server_id: &String, shared_key: &Vec, public_key: &Vec) { + pub fn login(username: &str, password: &str, token: &str) -> Result { + let req_msg = ObjectBuilder::new() + .insert("username", username) + .insert("password", password) + .insert("clientToken", token) + .insert_object("agent", |b| b + .insert("name", "Minecraft") + .insert("version", 1) + ) + .unwrap(); + let req = try!(serde_json::to_string(&req_msg)); + + let client = hyper::Client::new(); + let res = try!(client.post(LOGIN_URL) + .body(&req) + .header(hyper::header::ContentType("application/json".parse().unwrap())) + .send()); + + let ret: serde_json::Value = try!(serde_json::from_reader(res)); + if let Some(error) = ret.find("error").and_then(|v| v.as_string()) { + return Err(super::Error::Err(format!( + "{}: {}", + error, + ret.find("errorMessage").and_then(|v| v.as_string()).unwrap()) + )); + } + Ok(Profile { + username: ret.lookup("selectedProfile.name").and_then(|v| v.as_string()).unwrap().to_owned(), + id: ret.lookup("selectedProfile.id").and_then(|v| v.as_string()).unwrap().to_owned(), + access_token: ret.find("accessToken").and_then(|v| v.as_string()).unwrap().to_owned(), + }) + } + + pub fn refresh(self, token: &str) -> Result { + let req_msg = ObjectBuilder::new() + .insert("accessToken", self.access_token.clone()) + .insert("clientToken", token) + .unwrap(); + let req = try!(serde_json::to_string(&req_msg)); + + let client = hyper::Client::new(); + let res = try!(client.post(VALIDATE_URL) + .body(&req) + .header(hyper::header::ContentType("application/json".parse().unwrap())) + .send()); + + if res.status != hyper::status::StatusCode::NoContent { + println!("Rerefeshing"); + // Refresh needed + let res = try!(client.post(REFRESH_URL) + .body(&req) + .header(hyper::header::ContentType("application/json".parse().unwrap())) + .send()); + + let ret: serde_json::Value = try!(serde_json::from_reader(res)); + if let Some(error) = ret.find("error").and_then(|v| v.as_string()) { + return Err(super::Error::Err(format!( + "{}: {}", + error, + ret.find("errorMessage").and_then(|v| v.as_string()).unwrap()) + )); + } + return Ok(Profile { + username: ret.lookup("selectedProfile.name").and_then(|v| v.as_string()).unwrap().to_owned(), + id: ret.lookup("selectedProfile.id").and_then(|v| v.as_string()).unwrap().to_owned(), + access_token: ret.find("accessToken").and_then(|v| v.as_string()).unwrap().to_owned(), + }); + } + println!("Reuse"); + Ok(self) + } + + // TODO + pub fn join_server(&self, server_id: &str, shared_key: &Vec, public_key: &Vec) { let mut sha1 = openssl::SHA1::new(); sha1.update(server_id.as_bytes()); sha1.update(&shared_key[..]); @@ -46,7 +124,7 @@ impl Profile { hash_val.to_owned() }; - let join_msg = serde_json::builder::ObjectBuilder::new() + let join_msg = ObjectBuilder::new() .insert("accessToken", &self.access_token) .insert("selectedProfile", &self.id) .insert("serverId", hash_str) @@ -66,6 +144,10 @@ impl Profile { }; panic!("{:?}", ret); } + + pub fn is_complete(&self) -> bool { + !self.username.is_empty() && !self.id.is_empty() && !self.access_token.is_empty() + } } fn twos_compliment(data: &mut Vec) { diff --git a/src/screen/connecting.rs b/src/screen/connecting.rs index f3b6859..eacc2be 100644 --- a/src/screen/connecting.rs +++ b/src/screen/connecting.rs @@ -93,9 +93,10 @@ impl super::Screen for Connecting { fn tick(&mut self, _delta: f64, renderer: &mut render::Renderer, - ui_container: &mut ui::Container) { + ui_container: &mut ui::Container) -> Option>{ let elements = self.elements.as_mut().unwrap(); elements.logo.tick(renderer, ui_container); + None } } diff --git a/src/screen/login.rs b/src/screen/login.rs index e084580..379dbd0 100644 --- a/src/screen/login.rs +++ b/src/screen/login.rs @@ -14,9 +14,19 @@ use std::sync::{Arc, Mutex}; +use std::cell::Cell; +use std::rc::Rc; +use std::sync::mpsc; +use std::thread; + +use rand::{self, Rng}; + use ui; use render; use console; +use protocol; +use protocol::mojang; +use auth; pub struct Login { elements: Option, @@ -26,6 +36,17 @@ pub struct Login { struct UIElements { logo: ui::logo::Logo, elements: ui::Collection, + + login_btn: ui::ElementRef, + login_btn_text: ui::ElementRef, + login_error: ui::ElementRef, + username_txt: ui::ElementRef, + password_txt: ui::ElementRef, + try_login: Rc>, + refresh: bool, + login_res: Option>>, + + profile: mojang::Profile, } @@ -40,6 +61,8 @@ impl super::Screen for Login { let logo = ui::logo::Logo::new(renderer.resources.clone(), renderer, ui_container); let mut elements = ui::Collection::new(); + let try_login = Rc::new(Cell::new(false)); + // Login let (mut login, mut txt) = super::new_button_text(renderer, "Login", 0.0, 100.0, 400.0, 40.0); login.set_v_attach(ui::VAttach::Middle); @@ -47,15 +70,21 @@ impl super::Screen for Login { let re = ui_container.add(login); txt.set_parent(&re); let tre = ui_container.add(txt); + let tl = try_login.clone(); super::button_action(ui_container, re.clone(), Some(tre.clone()), - |game, _| { - game.screen_sys - .replace_screen(Box::new(super::ServerList::new(None))); - }); - elements.add(re); - elements.add(tre); + move |_, _| { + tl.set(true); + }); + let login_btn = elements.add(re); + let login_btn_text = elements.add(tre); + + // Login Error + let mut login_error = ui::Text::new(renderer, "", 0.0, 150.0, 255, 50, 50); + login_error.set_v_attach(ui::VAttach::Middle); + login_error.set_h_attach(ui::HAttach::Center); + let login_error = elements.add(ui_container.add(login_error)); // Username let mut username = ui::TextBox::new(renderer, "", 0.0, -20.0, 400.0, 40.0); @@ -67,7 +96,7 @@ impl super::Screen for Login { let ure = ui_container.add(username); let mut username_label = ui::Text::new(renderer, "Username/Email:", 0.0, -18.0, 255, 255, 255); username_label.set_parent(&ure); - elements.add(ure); + let username_txt = elements.add(ure); elements.add(ui_container.add(username_label)); // Password @@ -75,13 +104,14 @@ impl super::Screen for Login { password.set_v_attach(ui::VAttach::Middle); password.set_h_attach(ui::HAttach::Center); password.set_password(true); - password.add_submit_func(|_, _| { - println!("Submit!"); + let tl = try_login.clone(); + password.add_submit_func(move |_, _| { + tl.set(true); }); let pre = ui_container.add(password); let mut password_label = ui::Text::new(renderer, "Password:", 0.0, -18.0, 255, 255, 255); password_label.set_parent(&pre); - elements.add(pre); + let password_txt = elements.add(pre); elements.add(ui_container.add(password_label)); // Disclaimer @@ -96,9 +126,28 @@ impl super::Screen for Login { warn.set_h_attach(ui::HAttach::Right); elements.add(ui_container.add(warn)); + let console = self.console.lock().unwrap(); + let profile = mojang::Profile { + username: console.get(auth::CL_USERNAME).clone(), + id: console.get(auth::CL_UUID).clone(), + access_token: console.get(auth::AUTH_TOKEN).clone(), + }; + let refresh = profile.is_complete(); + try_login.set(refresh); + self.elements = Some(UIElements { logo: logo, elements: elements, + profile: profile, + login_btn: login_btn, + login_btn_text: login_btn_text, + login_error: login_error, + try_login: try_login, + refresh: refresh, + login_res: None, + + username_txt: username_txt, + password_txt: password_txt, }); } fn on_deactive(&mut self, _renderer: &mut render::Renderer, ui_container: &mut ui::Container) { @@ -114,9 +163,79 @@ impl super::Screen for Login { fn tick(&mut self, _delta: f64, renderer: &mut render::Renderer, - ui_container: &mut ui::Container) { + ui_container: &mut ui::Container) -> Option> { let elements = self.elements.as_mut().unwrap(); + if elements.try_login.get() && elements.login_res.is_none() { + elements.try_login.set(false); + let (tx, rx) = mpsc::channel(); + elements.login_res = Some(rx); + { + let btn = ui_container.get_mut(&elements.login_btn); + btn.set_disabled(true); + } + { + let txt = ui_container.get_mut(&elements.login_btn_text); + txt.set_text(renderer, "Logging in..."); + } + let mut console = self.console.lock().unwrap(); + let mut client_token = console.get(auth::AUTH_CLIENT_TOKEN).clone(); + if client_token.is_empty() { + client_token = rand::thread_rng().gen_ascii_chars().take(20).collect::(); + console.set(auth::AUTH_CLIENT_TOKEN, client_token); + } + let client_token = console.get(auth::AUTH_CLIENT_TOKEN).clone(); + let username = { + let txt = ui_container.get(&elements.username_txt); + txt.get_input() + }; + let password = { + let txt = ui_container.get(&elements.password_txt); + txt.get_input() + }; + let refresh = elements.refresh; + let profile = elements.profile.clone(); + thread::spawn(move || { + if refresh { + tx.send(profile.refresh(&client_token)).unwrap(); + } else { + tx.send(mojang::Profile::login(&username, &password, &client_token)).unwrap(); + } + }); + } + let mut done = false; + if let Some(rx) = elements.login_res.as_ref() { + if let Ok(res) = rx.try_recv() { + done = true; + { + let btn = ui_container.get_mut(&elements.login_btn); + btn.set_disabled(false); + } + { + let txt = ui_container.get_mut(&elements.login_btn_text); + txt.set_text(renderer, "Login"); + } + match res { + Ok(val) => { + let mut console = self.console.lock().unwrap(); + console.set(auth::CL_USERNAME, val.username.clone()); + console.set(auth::CL_UUID, val.id.clone()); + console.set(auth::AUTH_TOKEN, val.access_token.clone()); + elements.profile = val; + return Some(Box::new(super::ServerList::new(None))); + }, + Err(err) => { + let login_error = ui_container.get_mut(&elements.login_error); + login_error.set_text(renderer, &format!("{}", err)); + }, + } + } + } + if done { + elements.login_res = None; + } + elements.logo.tick(renderer, ui_container); + None } } diff --git a/src/screen/mod.rs b/src/screen/mod.rs index 04d9e15..5af9941 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -37,7 +37,7 @@ pub trait Screen { fn tick(&mut self, delta: f64, renderer: &mut render::Renderer, - ui_container: &mut ui::Container); + ui_container: &mut ui::Container) -> Option>; // Events fn on_scroll(&mut self, x: f64, y: f64) { @@ -106,18 +106,22 @@ impl ScreenSystem { screen.screen.on_deactive(renderer, ui_container); } } - let current = self.screens.last_mut().unwrap(); - if !current.init { - current.init = true; - current.screen.init(renderer, ui_container); - } - if !current.active { - current.active = true; - current.screen.on_active(renderer, ui_container); - } - + let swap = { + let current = self.screens.last_mut().unwrap(); + if !current.init { + current.init = true; + current.screen.init(renderer, ui_container); + } + if !current.active { + current.active = true; + current.screen.on_active(renderer, ui_container); + } + current.screen.tick(delta, renderer, ui_container) + }; // Handle current - current.screen.tick(delta, renderer, ui_container); + if let Some(swap) = swap { + self.replace_screen(swap); + } } pub fn on_scroll(&mut self, x: f64, y: f64) { @@ -149,15 +153,19 @@ pub fn button_action(ui_contai click: F) { let button = ui_container.get_mut(&btn); button.add_hover_func(move |over, _, ui_container| { - let txt = txt.clone(); - if let Some(txt) = txt { - let text = ui_container.get_mut(&txt); - text.set_b(if over { - 160 - } else { - 255 - }); - } + let disabled = { + let button = ui_container.get_mut(&btn); + button.is_disabled() + }; + let txt = txt.clone(); + if let Some(txt) = txt { + let text = ui_container.get_mut(&txt); + text.set_b(if over && !disabled { + 160 + } else { + 255 + }); + } }); button.add_click_func(click); } diff --git a/src/screen/server_list.rs b/src/screen/server_list.rs index 5e687c9..95b79be 100644 --- a/src/screen/server_list.rs +++ b/src/screen/server_list.rs @@ -459,7 +459,7 @@ impl super::Screen for ServerList { fn tick(&mut self, delta: f64, renderer: &mut render::Renderer, - ui_container: &mut ui::Container) { + ui_container: &mut ui::Container) -> Option> { if *self.needs_reload.borrow() { self.reload_server_list(renderer, ui_container); } @@ -551,6 +551,7 @@ impl super::Screen for ServerList { } } } + None } fn on_scroll(&mut self, _: f64, y: f64) { diff --git a/src/ui/textbox.rs b/src/ui/textbox.rs index 797b077..82c48ce 100644 --- a/src/ui/textbox.rs +++ b/src/ui/textbox.rs @@ -119,8 +119,8 @@ impl TextBox { (self.width, self.height) } - pub fn get_input(&self) -> &str { - &self.input + pub fn get_input(&self) -> String { + self.input.clone() } pub fn set_input(&mut self, renderer: &render::Renderer, input: &str) {