use psopacket::pso_packet; use crate::{PSOPacket, PacketParseError}; use crate::character::character::SelectScreenCharacter; use std::io::{Read, Seek, SeekFrom}; pub const PATCH_FILE_CHUNK_SIZE: u16 = 0x8000; // 32kb pub const GUILD_CARD_CHUNK_SIZE: usize = 0x6800; pub const PARAM_DATA_CHUNK_SIZE: usize = 0x6800; #[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, pub checksum: u32, pub padding: 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, } } } #[pso_packet(0xE3)] pub struct CharSelect { pub flag: u32, pub slot: u32, pub reason: u32, // TODO: enum? } #[pso_packet(0xE4)] pub struct CharAck { pub flag: u32, pub slot: u32, pub code: u32, // TODO: enum? } #[pso_packet(0xE5)] pub struct CharacterPreview { pub flag: u32, pub slot: u32, pub character: SelectScreenCharacter, } #[pso_packet(0x3E8)] pub struct GuildcardDataRequest { flag: u32, } #[pso_packet(0x1DC)] pub struct GuildcardDataHeader { flag: u32, one: u32, len: u32, checksum: u32, } impl GuildcardDataHeader { pub fn new(len: usize, checksum: u32) -> GuildcardDataHeader { GuildcardDataHeader { flag: 0, one: 1, len: len as u32, checksum: checksum } } } #[pso_packet(0x3DC)] pub struct GuildcardDataChunkRequest { flag: u32, _unknown: u32, pub chunk: u32, pub again: u32, } pub struct GuildcardDataChunk { flag: u32, _unknown: u32, chunk: u32, pub buffer: [u8; GUILD_CARD_CHUNK_SIZE], len: usize, } impl GuildcardDataChunk { pub fn new(chunk: u32, buffer: [u8; GUILD_CARD_CHUNK_SIZE], len: usize) -> GuildcardDataChunk { GuildcardDataChunk { flag: 0, _unknown: 0, chunk: chunk as u32, buffer: buffer, len: len, } } } impl PSOPacket for GuildcardDataChunk { fn from_bytes(_data: &[u8]) -> Result { unimplemented!(); } fn as_bytes(&self) -> Vec { let mut buf: Vec = Vec::new(); buf.extend_from_slice(&u32::to_le_bytes(self.flag)); buf.extend_from_slice(&u32::to_le_bytes(self._unknown)); buf.extend_from_slice(&u32::to_le_bytes(self.chunk)); buf.extend_from_slice(&self.buffer[0..self.len]); while buf.len() % 4 != 0 { buf.push(0); } let pkt_len = (buf.len() + 4) as u16; let mut prebuf: Vec = Vec::new(); prebuf.extend_from_slice(&u16::to_le_bytes(pkt_len)); prebuf.extend_from_slice(&u16::to_le_bytes(0x2DC)); prebuf.append(&mut buf); prebuf } } impl std::fmt::Debug for GuildcardDataChunk { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "packet GuildcardDataChunk {{\n").unwrap(); write!(f, " flag: {:?}\n", self.flag).unwrap(); write!(f, " _unknown: {:X?}\n", self._unknown).unwrap(); write!(f, " chunk: {:X?}\n", self.chunk).unwrap(); write!(f, " buffer: [0..{:X}]\n", self.len).unwrap(); write!(f, "}}") } } #[pso_packet(0x4EB)] pub struct ParamDataRequest { flag: u32, } #[derive(Clone)] pub struct ParamFile { pub size: u32, pub checksum: u32, pub offset: u32, pub filename: [u8; 0x40], } #[derive(Clone)] pub struct ParamDataHeader { pub files: Vec } impl PSOPacket for ParamDataHeader { fn from_bytes(_data: &[u8]) -> Result { unimplemented!(); } fn as_bytes(&self) -> Vec { let mut buf: Vec = Vec::new(); buf.extend_from_slice(&u32::to_le_bytes(self.files.len() as u32)); for f in &self.files { buf.extend_from_slice(&u32::to_le_bytes(f.size)); buf.extend_from_slice(&u32::to_le_bytes(f.checksum)); buf.extend_from_slice(&u32::to_le_bytes(f.offset)); buf.extend_from_slice(&f.filename[..]); } while buf.len() % 4 != 0 { buf.push(0); } let pkt_len = (buf.len() + 4) as u16; let mut prebuf: Vec = Vec::new(); prebuf.extend_from_slice(&u16::to_le_bytes(pkt_len)); prebuf.extend_from_slice(&u16::to_le_bytes(0x1EB)); prebuf.append(&mut buf); prebuf } } impl std::fmt::Debug for ParamDataHeader { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "packet ParamDataHeader{{\n").unwrap(); write!(f, " files: [..]\n").unwrap(); write!(f, "}}") } } #[pso_packet(0x3EB)] pub struct ParamDataChunkRequest { flag: u32, } #[pso_packet(0x2EB)] pub struct ParamDataChunk { pub flag: u32, pub chunk: u32, pub data: [u8; 0x6800], // TODO: why wont the const work here? (blame macros?) } #[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() { use super::PSOPacket; let pkt = super::ChecksumAck::new(1); assert!(pkt.as_bytes() == [0xC, 0, 0xE8, 0x02, 0,0,0,0, 1,0,0,0]); } }