You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

579 lines
14 KiB

use chrono::{DateTime, Utc};
use psopacket::{pso_packet, PSOPacketData};
use crate::{PSOPacket, PacketParseError, PSOPacketData, utf8_to_utf16_array};
use crate::character::character::SelectScreenCharacter;
use std::io::Read;
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;
#[pso_packet(0x03)]
pub struct LoginWelcome {
#[utf8]
copyright: [u8; 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 {
copyright: copyright,
server_key: server_key,
client_key: client_key,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SessionAction {
None,
SelectCharacter,
NewCharacter,
DressingRoom,
}
impl PSOPacketData for SessionAction {
fn from_bytes<R: Read>(cursor: &mut R) -> Result<Self, PacketParseError> {
let mut bytes = [0u8; 1];
let len = cursor.read(&mut bytes).map_err(|_| PacketParseError::ReadError)?;
if len != 1 {
return Err(PacketParseError::NotEnoughBytes)
}
match bytes[0] {
0 => Ok(SessionAction::None),
1 => Ok(SessionAction::SelectCharacter),
2 => Ok(SessionAction::NewCharacter),
3 => Ok(SessionAction::DressingRoom),
_ => Err(PacketParseError::InvalidValue)
}
}
fn as_bytes(&self) -> Vec<u8> {
vec![match self {
SessionAction::None => 0,
SessionAction::SelectCharacter => 1,
SessionAction::NewCharacter => 2,
SessionAction::DressingRoom => 3,
}]
}
}
#[derive(PSOPacketData, Clone, Copy)]
pub struct Session {
pub version: [u8; 30],
pub session_id: u32,
pub interserver_checksum: u32,
pub action: SessionAction,
pub character_slot: u8, // 1..=4
}
impl Session {
pub fn new() -> Session {
Session {
version: [0; 30],
session_id: 0,
interserver_checksum: 0,
action: SessionAction::None,
character_slot: 0,
}
}
}
#[pso_packet(0x93)]
pub struct Login {
pub tag: u32,
pub guildcard: u32,
pub version: u16,
pub unknown1: [u8; 6],
pub team: u32,
#[utf8]
pub username: [u8; 16],
pub unknown2: [u8; 32],
#[utf8]
pub password: [u8; 16],
pub unknown3: [u8; 40],
pub hwinfo: [u8; 8],
pub session: Session
//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 PSOPacketData for AccountStatus {
fn from_bytes<R: Read>(cursor: &mut R) -> Result<Self, PacketParseError> {
let mut bytes = [0u8; 4];
let len = cursor.read(&mut bytes).map_err(|_| PacketParseError::ReadError)?;
if len != 4 {
return Err(PacketParseError::NotEnoughBytes)
}
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),
}
}
fn as_bytes(&self) -> Vec<u8> {
vec![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]
}
}
#[pso_packet(0xE6)]
pub struct LoginResponse {
pub status: AccountStatus,
pub tag: u32,
pub guildcard: u32,
pub team_id: u32,
//pub security_data: [u8; 40],
pub session: Session,
pub caps: u32,
}
impl LoginResponse {
pub fn by_status(status: AccountStatus, session: Session) -> LoginResponse {
LoginResponse {
status: status,
tag: 0x00010000,
//tag: 0x00000100,
guildcard: 0,
team_id: 0,
session: session,
caps: 0x00000102,
}
}
pub fn by_char_select(guildcard: u32, team_id: u32, session: Session) -> LoginResponse {
LoginResponse {
status: AccountStatus::Ok,
tag: 0x00010000,
//tag: 0x00000100,
guildcard: guildcard,
team_id: team_id,
session: session,
caps: 0x00000102,
}
}
}
#[pso_packet(0xE0)]
pub struct RequestSettings {
}
#[pso_packet(0xE2)]
pub struct SendKeyAndTeamSettings {
unknown: [u8; 0x114],
keyboard_config: [u8; 0x16C],
gamepad_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],
#[nodebug]
team_flag: [u8; 2048],
team_rewards: [u8; 8],
}
impl SendKeyAndTeamSettings {
pub fn new(keyboard_config: [u8; 0x16C], gamepad_config: [u8; 0x38], guildcard: u32, team_id: u32) -> SendKeyAndTeamSettings {
SendKeyAndTeamSettings {
unknown: [0; 0x114],
keyboard_config: keyboard_config,
gamepad_config: gamepad_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 ip: u32,
pub port: u16,
pub padding: u16,
}
impl RedirectClient {
pub fn new(ip: u32, port: u16) -> RedirectClient {
RedirectClient {
ip: ip,
port: port,
padding: 0,
}
}
}
#[pso_packet(0x1E8)]
pub struct Checksum {
pub checksum: u32,
pub padding: u32,
}
#[pso_packet(0x2E8)]
pub struct ChecksumAck {
pub ack: u32,
}
impl ChecksumAck {
pub fn new(ack: u32) -> ChecksumAck {
ChecksumAck {
ack: ack,
}
}
}
#[pso_packet(0xE3)]
pub struct CharSelect {
pub slot: u32,
pub reason: u32, // TODO: enum?
}
#[pso_packet(0xE4)]
pub struct CharAck {
pub slot: u32,
pub code: u32, // TODO: enum?
}
impl PSOPacketData for SelectScreenCharacter {
fn from_bytes<R: Read>(cursor: &mut R) -> Result<Self, PacketParseError> {
let mut buf = [0u8; SelectScreenCharacter::SIZE];
cursor.read(&mut buf).map_err(|_| PacketParseError::ReadError)?;
SelectScreenCharacter::from_le_bytes(buf)
}
fn as_bytes(&self) -> Vec<u8> {
self.to_le_bytes().to_vec()
}
}
#[pso_packet(0xE5)]
pub struct CharacterPreview {
pub slot: u32,
pub character: SelectScreenCharacter,
}
#[pso_packet(0x3E8)]
pub struct GuildcardDataRequest {
}
#[pso_packet(0x1DC)]
pub struct GuildcardDataHeader {
one: u32,
len: u32,
checksum: u32,
}
impl GuildcardDataHeader {
pub fn new(len: usize, checksum: u32) -> GuildcardDataHeader {
GuildcardDataHeader {
one: 1,
len: len as u32,
checksum: checksum
}
}
}
#[pso_packet(0x3DC)]
pub struct GuildcardDataChunkRequest {
_unknown: u32,
pub chunk: u32,
pub again: u32,
}
pub struct GuildcardDataChunk {
_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 {
_unknown: 0,
chunk: chunk as u32,
buffer: buffer,
len: len,
}
}
}
impl PSOPacket for GuildcardDataChunk {
fn from_bytes(_data: &[u8]) -> Result<GuildcardDataChunk, PacketParseError> {
unimplemented!();
}
fn as_bytes(&self) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&u32::to_le_bytes(0));
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<u8> = 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", 0).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 {
}
#[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<ParamFile>
}
impl PSOPacket for ParamDataHeader {
fn from_bytes(_data: &[u8]) -> Result<ParamDataHeader, PacketParseError> {
unimplemented!();
}
fn as_bytes(&self) -> Vec<u8> {
let mut buf: Vec<u8> = 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<u8> = 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 {
}
#[pso_packet(0x2EB)]
pub struct ParamDataChunk {
pub chunk: u32,
#[nodebug]
pub data: [u8; 0x6800], // TODO: why wont the const work here? (blame macros?)
}
#[pso_packet(0xEC)]
pub struct SetFlag {
pub flags: u32,
}
#[pso_packet(0xB1)]
pub struct Timestamp {
#[utf8]
timestamp: [u8; 28],
}
impl Timestamp {
pub fn new(time: DateTime<Utc>) -> Timestamp {
let timestr = time.format("%Y:%m:%d: %H:%M:%S").to_string();
let timebytes = timestr.as_bytes();
let mut timebuf = [0u8; 28];
timebuf[..timebytes.len()].clone_from_slice(timebytes);
Timestamp {
timestamp: timebuf
}
}
}
#[derive(PSOPacketData, Copy, Clone)]
pub struct ShipListEntry {
pub menu: u32,
pub item: u32,
pub flags: u16,
pub name: [u16; 0x11],
}
#[pso_packet(0xA0)]
pub struct ShipList {
baseship: ShipListEntry,
pub ships: Vec<ShipListEntry>,
}
impl ShipList {
pub fn new(ships: Vec<ShipListEntry>) -> ShipList {
ShipList {
baseship: ShipListEntry {
menu: ships.get(0).map(|s| s.menu).unwrap_or(0),
item: 0,
flags: 0,
name: utf8_to_utf16_array!("Ship", 0x11),
},
ships: ships,
}
}
}
#[pso_packet(0x10)]
pub struct MenuSelect {
pub menu: u32,
pub item: u32,
}
#[cfg(test)]
mod tests {
#[test]
fn test_account_status_enum() {
use super::PSOPacket;
use super::Session;
let pkt = super::LoginResponse {
status: super::AccountStatus::InvalidPassword,
tag: 0,
guildcard: 0,
team_id: 0,
session: Session::new(),
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};
let mut rng = rand::thread_rng();
let mut keyboard_config = [0u8; 0x16C];
let mut gamepad_config = [0u8; 0x38];
rng.fill(&mut keyboard_config[..]);
rng.fill(&mut gamepad_config[..]);
let pkt = super::SendKeyAndTeamSettings::new(keyboard_config, gamepad_config, 123, 456);
let bytes = pkt.as_bytes();
assert!(bytes[2] == 0xe2);
assert!(bytes[8 + 0x114] == keyboard_config[0]);
assert!(bytes[8 + 0x114 + 0x16C] == gamepad_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]);
}
#[test]
fn test_session_size() {
use super::PSOPacketData;
let session = super::Session::new();
assert!(session.as_bytes().len() == 40);
}
}