diff --git a/Cargo.toml b/Cargo.toml index 41b5db9..4c9e84b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,9 @@ mio = "0.6" mio-extras = "2.0.5" crc = "^1.0.0" bcrypt = "0.4" -threadpool = "1.0" \ No newline at end of file +threadpool = "1.0" +chrono = "*" + + +[patch."http://git.sharnoth.com/jake/libpso"] +libpso = { path = "../libpso" } \ No newline at end of file diff --git a/src/login/character.rs b/src/login/character.rs index 914dd41..76df05c 100644 --- a/src/login/character.rs +++ b/src/login/character.rs @@ -10,11 +10,11 @@ use libpso::crypto::bb::PSOBBCipher; use elseware::common::cipherkeys::{ELSEWHERE_PRIVATE_KEY, ELSEWHERE_PARRAY}; use elseware::common::serverstate::{SendServerPacket, RecvServerPacket, ServerState, OnConnect, ClientId}; -use elseware::utf8_to_array; +use elseware::{utf8_to_array, utf8_to_utf16_array}; use crate::dataaccess::DataAccess; use crate::login::get_login_status; -use crate::entities::{UserAccount, Character}; +use crate::entities::{UserAccount, Character, USERFLAG_NEWCHAR, USERFLAG_DRESSINGROOM}; pub const CHARACTER_PORT: u16 = 12001; @@ -33,6 +33,8 @@ pub enum RecvCharacterPacket { GuildcardDataChunkRequest(GuildcardDataChunkRequest), ParamDataRequest(ParamDataRequest), ParamDataChunkRequest(ParamDataChunkRequest), + CharacterPreview(CharacterPreview), + SetFlag(SetFlag) } impl RecvServerPacket for RecvCharacterPacket { @@ -46,6 +48,8 @@ impl RecvServerPacket for RecvCharacterPacket { 0x3DC => Ok(RecvCharacterPacket::GuildcardDataChunkRequest(GuildcardDataChunkRequest::from_bytes(data)?)), 0x4EB => Ok(RecvCharacterPacket::ParamDataRequest(ParamDataRequest::from_bytes(data)?)), 0x3EB => Ok(RecvCharacterPacket::ParamDataChunkRequest(ParamDataChunkRequest::from_bytes(data)?)), + 0xE5 => Ok(RecvCharacterPacket::CharacterPreview(CharacterPreview::from_bytes(data)?)), + 0xEC => Ok(RecvCharacterPacket::SetFlag(SetFlag::from_bytes(data)?)), _ => Err(PacketParseError::WrongPacketForServerType) } } @@ -64,6 +68,8 @@ pub enum SendCharacterPacket { GuildcardDataChunk(GuildcardDataChunk), ParamDataHeader(ParamDataHeader), ParamDataChunk(ParamDataChunk), + Timestamp(Timestamp), + ShipList(ShipList) } impl SendServerPacket for SendCharacterPacket { @@ -79,6 +85,8 @@ impl SendServerPacket for SendCharacterPacket { SendCharacterPacket::GuildcardDataChunk(pkt) => pkt.as_bytes(), SendCharacterPacket::ParamDataHeader(pkt) => pkt.as_bytes(), SendCharacterPacket::ParamDataChunk(pkt) => pkt.as_bytes(), + SendCharacterPacket::Timestamp(pkt) => pkt.as_bytes(), + SendCharacterPacket::ShipList(pkt) => pkt.as_bytes(), //SendLoginPacket::RedirectClient(pkt) => pkt.as_bytes(), } } @@ -120,6 +128,7 @@ struct ClientState { user: Option, characters: Option<[Option; 4]>, guildcard_data_buffer: Option>, + security_data: [u8; 40], } impl ClientState { @@ -129,6 +138,7 @@ impl ClientState { user: None, characters: None, guildcard_data_buffer: None, + security_data: [0; 40], } } } @@ -157,18 +167,46 @@ impl CharacterServerState { let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?; Ok(match get_login_status(&self.data_access, pkt) { Ok(user) => { - let mut response = LoginResponse::by_status(AccountStatus::Ok, pkt.security_data); + let mut response = LoginResponse::by_status(AccountStatus::Ok, [0; 40]); response.guildcard = user.guildcard.map_or(0, |gc| gc) as u32; response.team_id = user.team_id.map_or(0, |ti| ti) as u32; client.user = Some(user); + client.security_data = pkt.security_data; vec![SendCharacterPacket::LoginResponse(response)] }, Err(err) => { - vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, pkt.security_data))] + vec![SendCharacterPacket::LoginResponse(LoginResponse::by_status(err, [0; 40]))] } }) } + fn send_ship_list(&mut self, id: ClientId, pkt: &Login) -> Result, CharacterError> { + Ok(vec![SendCharacterPacket::Timestamp(Timestamp::new(chrono::Utc::now())), + SendCharacterPacket::ShipList(ShipList { + flag: 0, + ships: vec![ + ShipListEntry { + menu: 0, + item: 1, + flags: 0, + name: utf8_to_utf16_array!("Sona-Nyl", 0x11), + }, + ShipListEntry { + menu: 0, + item: 2, + flags: 0, + name: utf8_to_utf16_array!("Dylath-Leen", 0x11), + }, + ShipListEntry { + menu: 0, + item: 2, + flags: 0, + name: utf8_to_utf16_array!("Innsmouth", 0x11), + } + ] + })]) + } + fn get_settings(&mut self, id: ClientId) -> Result, CharacterError> { let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?; let user = client.user.as_ref().unwrap(); @@ -192,21 +230,38 @@ impl CharacterServerState { client.characters = Some(self.data_access.get_characters_by_user(client.user.as_ref().unwrap())); } - let chars = client.characters.as_ref().unwrap(); - Ok(if let Some(char) = &chars[select.slot as usize] { - vec![SendCharacterPacket::CharacterPreview(CharacterPreview { - flag: 0, - slot: select.slot, - character: char.character.as_select_screen(), - })] + if select.reason == 0 { + let chars = client.characters.as_ref().unwrap(); + Ok(if let Some(char) = &chars[select.slot as usize] { + vec![SendCharacterPacket::CharacterPreview(CharacterPreview { + flag: 0, + slot: select.slot, + character: char.character.as_select_screen(), + })] + } + else { + vec![SendCharacterPacket::CharAck(CharAck { + flag: 0, + slot: select.slot, + code: 2, + })] + }) } else { - vec![SendCharacterPacket::CharAck(CharAck { - flag: 0, - slot: select.slot, - code: 2, - })] - }) + let user = client.user.as_ref().unwrap(); + client.security_data[0..4].clone_from_slice(&[1,3,3,7]); + client.security_data[4] = select.slot as u8; + client.security_data[5] = 1; + Ok(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_char_select(user.guildcard.unwrap_or(1), + user.team_id.unwrap_or(1), + client.security_data)), + SendCharacterPacket::CharAck(CharAck { + flag: 0, + slot: select.slot, + code: 1, + }) + ]) + } } fn validate_checksum(&mut self) -> Vec { @@ -271,7 +326,12 @@ impl ServerState for CharacterServerState { -> Result>, CharacterError> { Ok(match pkt { RecvCharacterPacket::Login(login) => { - Box::new(self.validate_login(id, login)?.into_iter().map(move |pkt| (id, pkt))) + if login.security_data[0..4] == [1,3,3,7] { + Box::new(self.send_ship_list(id, login)?.into_iter().map(move |pkt| (id, pkt))) + } + else { + Box::new(self.validate_login(id, login)?.into_iter().map(move |pkt| (id, pkt))) + } }, RecvCharacterPacket::RequestSettings(_req) => { Box::new(self.get_settings(id)?.into_iter().map(move |pkt| (id, pkt))) @@ -291,6 +351,13 @@ impl ServerState for CharacterServerState { RecvCharacterPacket::ParamDataRequest(_request) => { Box::new(vec![SendCharacterPacket::ParamDataHeader(self.param_header.clone())].into_iter().map(move |pkt| (id, pkt))) }, + RecvCharacterPacket::SetFlag(flags) => { + let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?; + let mut user = client.user.as_mut().unwrap(); + user.flags = flags.flags; + self.data_access.set_user(&user); + Box::new(None.into_iter()) + }, RecvCharacterPacket::ParamDataChunkRequest(_request) => { let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?; let chunk = client.param_index; @@ -309,6 +376,36 @@ impl ServerState for CharacterServerState { data: data, } )].into_iter().map(move |pkt| (id, pkt))) + }, + RecvCharacterPacket::CharacterPreview(preview) => { + let client = self.clients.get_mut(&id).ok_or(CharacterError::ClientNotFound(id))?; + let mut user = client.user.as_mut().unwrap(); + if user.flags == USERFLAG_NEWCHAR { + let char = Character { + id: 9, + user_id: user.id, + character: preview.character.as_character() + }; + self.data_access.set_character_by_user(&user, preview.slot, char); + } + if user.flags == USERFLAG_DRESSINGROOM { + // TODO: dressing room stuff + } + + client.security_data[0..4].clone_from_slice(&[1,3,3,7]); + client.security_data[4] = preview.slot as u8; + client.security_data[5] = 1; + user.flags = 0; + self.data_access.set_user(&user); + Box::new(vec![SendCharacterPacket::LoginResponse(LoginResponse::by_char_select(user.guildcard.unwrap_or(0), + user.team_id.unwrap_or(0), + client.security_data)), + SendCharacterPacket::CharAck(CharAck { + flag: 0, + slot: preview.slot, + code: 0 + }) + ].into_iter().map(move |pkt| (id, pkt))) } }) } @@ -347,6 +444,7 @@ mod test { banned: false, muted_until: SystemTime::now(), created_at: SystemTime::now(), + flags: 0, }); server.clients.insert(ClientId(5), clientstate); diff --git a/src/login/main.rs b/src/login/main.rs index 1f1c890..6df293a 100644 --- a/src/login/main.rs +++ b/src/login/main.rs @@ -4,6 +4,7 @@ mod dataaccess; mod entities; use std::thread; +use std::collections::HashMap; use bcrypt; @@ -22,12 +23,17 @@ use std::time::SystemTime; #[derive(Clone)] struct LoginStubData { + users: HashMap, + characters: [Option ;4], } -impl DataAccess for LoginStubData { - fn get_user_by_name(&self, username: String) -> Option { - if username.as_str() == "hi" { - Some(UserAccount { +impl LoginStubData { + fn new() -> LoginStubData { + let mut c = pso_character::Character::default(); + c.name = utf8_to_utf16_array!("Test Char", 16); + + let mut users = HashMap::new(); + users.insert("hi".to_string(), UserAccount { id: 1, username: "hi".to_owned(), password: bcrypt::hash("qwer", 5).unwrap(), @@ -36,12 +42,26 @@ impl DataAccess for LoginStubData { banned: false, muted_until: SystemTime::now(), created_at: SystemTime::now(), - }) - } - else { - None + flags: 0, + }); + + LoginStubData { + users: users, + + characters: [Some(Character { + id: 1, + user_id: 1, + character: c, + }), + None, None, None] } } +} + +impl DataAccess for LoginStubData { + fn get_user_by_name(&self, username: String) -> Option { + self.users.get(&username).map(|user| user.clone()) + } fn get_user_settings_by_user(&self, user: &UserAccount) -> Option { Some(UserSettings { @@ -51,15 +71,16 @@ impl DataAccess for LoginStubData { }) } - fn get_characters_by_user(&self, user: &UserAccount) -> [Option; 4] { - let mut c = pso_character::Character::default(); - c.name = utf8_to_utf16_array!("Test Char", 16); - [Some(Character { - id: 1, - user_id: user.id, - character: c, - }), - None, None, None] + fn set_user(&mut self, user: &UserAccount) { + self.users.insert(user.username.clone(), user.clone()); + } + + fn get_characters_by_user(&self, _user: &UserAccount) -> [Option; 4] { + self.characters + } + + fn set_character_by_user(&mut self, _user: &UserAccount, slot: u32, char: Character) { + self.characters[slot as usize] = Some(char); } fn get_guild_card_data_by_user(&self, user: &UserAccount) -> GuildCardData { @@ -74,13 +95,12 @@ impl DataAccess for LoginStubData { fn main() { println!("[login+character] starting server"); - // TODO: character mainloop let auth_thread = thread::spawn(|| { - let auth_state = LoginServerState::new(LoginStubData {}); + let auth_state = LoginServerState::new(LoginStubData::new()); elseware::common::mainloop::mainloop(auth_state, login::LOGIN_PORT); }); let char_thread = thread::spawn(|| { - let char_state = CharacterServerState::new(LoginStubData {}); + let char_state = CharacterServerState::new(LoginStubData::new()); elseware::common::mainloop::mainloop(char_state, character::CHARACTER_PORT); });