use psopacket::pso_packet; use crate::{PSOPacket, PacketParseError}; use std::io::{Read, Seek, SeekFrom}; pub const PATCH_FILE_CHUNK_SIZE: u16 = 0x8000; // 32kb #[allow(non_camel_case_types)] type u8_str = u8; #[pso_packet(0x03)] pub struct LoginWelcome { flag: u32, copyright: [u8_str; 0x60], server_key: [u8; 48], client_key: [u8; 48], } impl LoginWelcome { pub fn new(server_key: [u8; 48], client_key: [u8; 48]) -> LoginWelcome { let mut copyright = [0u8; 0x60]; copyright[..0x4B].clone_from_slice(b"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM."); LoginWelcome { flag: 0, copyright: copyright, server_key: server_key, client_key: client_key, } } } #[pso_packet(0x93)] pub struct Login { pub flag: u32, pub tag: u32, pub guildcard: u32, pub version: u16, pub unknown1: [u8; 6], pub team: u32, pub username: [u8_str; 16], pub unknown2: [u8; 32], pub password: [u8_str; 16], pub unknown3: [u8; 40], pub hwinfo: [u8; 8], pub security_data: [u8; 40], } #[derive(Debug, Clone, PartialEq)] pub enum AccountStatus { Ok, Error, InvalidPassword, InvalidPassword2, Maintenance, AlreadyOnline, Banned, Banned2, InvalidUser, PayUp, Locked, BadVersion, } impl AccountStatus { const SIZE: usize = 4; fn to_le_bytes(&self) -> [u8; 4] { [match self { AccountStatus::Ok => 0, AccountStatus::Error => 1, AccountStatus::InvalidPassword => 2, AccountStatus::InvalidPassword2 => 3, AccountStatus::Maintenance => 4, AccountStatus::AlreadyOnline => 5, AccountStatus::Banned => 6, AccountStatus::Banned2 => 7, AccountStatus::InvalidUser => 8, AccountStatus::PayUp => 9, AccountStatus::Locked => 10, AccountStatus::BadVersion => 11, },0,0,0] } fn from_le_bytes(bytes: [u8; 4]) -> Result { match bytes[0] { 0 => Ok(AccountStatus::Ok), 1 => Ok(AccountStatus::Error), 2 => Ok(AccountStatus::InvalidPassword), 3 => Ok(AccountStatus::InvalidPassword2), 4 => Ok(AccountStatus::Maintenance), 5 => Ok(AccountStatus::AlreadyOnline), 6 => Ok(AccountStatus::Banned), 7 => Ok(AccountStatus::Banned2), 8 => Ok(AccountStatus::InvalidUser), 9 => Ok(AccountStatus::PayUp), 10 => Ok(AccountStatus::Locked), 11 => Ok(AccountStatus::BadVersion), _ => Err(PacketParseError::InvalidValue), } } } #[pso_packet(0xE6)] pub struct LoginResponse { pub flag: u32, pub status: AccountStatus, pub tag: u32, pub guildcard: u32, pub team_id: u32, pub security_data: [u8; 40], pub caps: u32, } impl LoginResponse { pub fn by_status(status: AccountStatus, security_data: [u8; 40]) -> LoginResponse { LoginResponse { flag: 0, status: status, tag: 0x00010000, //tag: 0x00000100, guildcard: 0, team_id: 0, security_data: security_data, caps: 0x00000102, } } } #[pso_packet(0xE0)] pub struct RequestSettings { pub flag: u32 } #[pso_packet(0xE2)] pub struct SendKeyAndTeamSettings { flag: u32, unknown: [u8; 0x114], key_config: [u8; 0x16C], joystick_config: [u8; 0x38], guildcard: u32, team_id: u32, //team_info: [u32; 2], team_info: [u8; 8], team_priv: u16, unknown2: u16, //team_name: [u16; 16], team_name: [u8; 32], team_flag: [u8; 2048], team_rewards: [u8; 8], } impl SendKeyAndTeamSettings { pub fn new(key_config: [u8; 0x16C], joystick_config: [u8; 0x38], guildcard: u32, team_id: u32) -> SendKeyAndTeamSettings { SendKeyAndTeamSettings { flag: 0, unknown: [0; 0x114], key_config: key_config, joystick_config: joystick_config, guildcard: guildcard, team_id: team_id, //team_info: [0; 2], team_info: [0; 8], team_priv: 0, unknown2: 0, //team_name: [0; 16], team_name: [0; 32], team_flag: [0; 2048], team_rewards: [0; 8] } } } #[pso_packet(0x19)] pub struct RedirectClient { pub flag: u32, pub ip: u32, pub port: u16, pub padding: u16, } impl RedirectClient { pub fn new(ip: u32, port: u16) -> RedirectClient { RedirectClient { flag: 0, ip: ip, port: port, padding: 0, } } } #[pso_packet(0x1E8)] pub struct Checksum { pub flag: u32, } #[pso_packet(0x2E8)] pub struct ChecksumAck { pub flag: u32, pub ack: u32, } impl ChecksumAck { pub fn new(ack: u32) -> ChecksumAck { ChecksumAck { flag: 0, ack: ack, } } } #[cfg(test)] mod tests { #[test] fn test_account_status_enum() { use super::PSOPacket; let pkt = super::LoginResponse { flag: 0, status: super::AccountStatus::InvalidPassword, tag: 0, guildcard: 0, team_id: 0, security_data: [0; 40], caps: 0, }; let mut bytes = pkt.as_bytes(); assert!(bytes[8] == 2); bytes[8] = 8; let pkt = super::LoginResponse::from_bytes(&bytes).unwrap(); assert!(pkt.status == super::AccountStatus::InvalidUser); } #[test] fn test_key_settings_reply() { use super::PSOPacket; use rand::{Rng, RngCore}; let mut rng = rand::thread_rng(); let mut key_config = [0u8; 0x16C]; let mut joystick_config = [0u8; 0x38]; rng.fill(&mut key_config[..]); rng.fill(&mut joystick_config[..]); let pkt = super::SendKeyAndTeamSettings::new(key_config, joystick_config, 123, 456); let bytes = pkt.as_bytes(); assert!(bytes[2] == 0xe2); assert!(bytes[8 + 0x114] == key_config[0]); assert!(bytes[8 + 0x114 + 0x16C] == joystick_config[0]); } #[test] fn test_login_checksum_ack() { let mut checksum_ack = super::ChecksumAck::new(1); assert!(u32::to_le_bytes(checksum_ack.ack) == [0x01, 0x00, 0x00, 0x00]); } }