573 lines
11 KiB
Rust
573 lines
11 KiB
Rust
use psopacket::{pso_packet, PSOPacketData};
|
|
use crate::{PSOPacket, PacketParseError, PSOPacketData};
|
|
use crate::utf8_to_utf16_array;
|
|
use crate::packet::messages::GameMessage;
|
|
//use character::character::FullCharacter;
|
|
use crate::character::character as character;
|
|
use crate::character::character::BankItem;
|
|
use crate::ConsumingBlob;
|
|
|
|
use std::io::Read;
|
|
|
|
pub const BLOCK_MENU_ID: u32 = 1;
|
|
pub const ROOM_MENU_ID: u32 = 2;
|
|
pub const LOBBY_MENU_ID: u32 = 3;
|
|
|
|
#[pso_packet(0x03)]
|
|
pub struct ShipWelcome {
|
|
#[utf8]
|
|
copyright: [u8; 0x60],
|
|
server_key: [u8; 48],
|
|
client_key: [u8; 48],
|
|
}
|
|
|
|
impl ShipWelcome {
|
|
pub fn new(server_key: [u8; 48], client_key: [u8; 48]) -> ShipWelcome {
|
|
let mut copyright = [0u8; 0x60];
|
|
copyright[..0x4B].clone_from_slice(b"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.");
|
|
ShipWelcome {
|
|
copyright: copyright,
|
|
server_key: server_key,
|
|
client_key: client_key,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub struct BlockEntry {
|
|
menu: u32,
|
|
item: u32,
|
|
flags: u16,
|
|
name: [u16; 0x11],
|
|
}
|
|
|
|
impl PSOPacketData for BlockEntry {
|
|
fn from_bytes<R: Read>(_cursor: &mut R) -> Result<Self, PacketParseError> {
|
|
unimplemented!();
|
|
}
|
|
|
|
fn as_bytes(&self) -> Vec<u8> {
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&u32::to_le_bytes(self.menu));
|
|
bytes.extend_from_slice(&u32::to_le_bytes(self.item));
|
|
bytes.extend_from_slice(&u16::to_le_bytes(self.flags));
|
|
bytes.extend_from_slice(&unsafe { std::mem::transmute::<[u16; 0x11], [u8; 0x11*2]>(self.name) });
|
|
bytes
|
|
}
|
|
}
|
|
|
|
#[pso_packet(0xA1)]
|
|
pub struct ShipBlockList {
|
|
shipblock: BlockEntry,
|
|
blocks: Vec<BlockEntry>
|
|
}
|
|
|
|
impl ShipBlockList {
|
|
pub fn new(shipname: &str, num_blocks: usize) -> ShipBlockList {
|
|
ShipBlockList {
|
|
shipblock: BlockEntry {
|
|
menu: BLOCK_MENU_ID,
|
|
item: 0,
|
|
flags: 0,
|
|
name: utf8_to_utf16_array!(shipname, 0x11)
|
|
},
|
|
blocks: (0..num_blocks).map(|i| BlockEntry {
|
|
menu: BLOCK_MENU_ID,
|
|
item: i as u32 + 1,
|
|
flags: 0,
|
|
name: utf8_to_utf16_array!(format!("Block {}", i+1), 0x11)
|
|
}).collect()
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: menu should be an enum
|
|
// TODO: or perhaps MenuSelect should be broken up into different structs based on menu
|
|
// TODO: i.e. ShipMenuSelect, BlockMenuSelect, etc
|
|
#[pso_packet(0x10)]
|
|
pub struct MenuSelect {
|
|
pub menu: u32,
|
|
pub item: u32,
|
|
}
|
|
|
|
#[pso_packet(0x09)]
|
|
pub struct MenuDetail {
|
|
pub menu: u32,
|
|
pub item: u32,
|
|
}
|
|
|
|
#[pso_packet(0x10)]
|
|
pub struct RoomPasswordReq {
|
|
pub menu: u32,
|
|
pub item: u32,
|
|
pub password: [u16; 16],
|
|
}
|
|
|
|
#[pso_packet(0x84)]
|
|
pub struct LobbySelect {
|
|
pub menu: u32,
|
|
pub lobby: u32,
|
|
}
|
|
|
|
#[pso_packet(0xE7)]
|
|
pub struct FullCharacter {
|
|
#[no_debug]
|
|
character: character::FullCharacter,
|
|
}
|
|
|
|
|
|
#[pso_packet(0x95)]
|
|
pub struct CharDataRequest {
|
|
}
|
|
|
|
|
|
// TODO: what does this even do?
|
|
#[pso_packet(0x61)]
|
|
pub struct CharData {
|
|
_unknown: [u8; 0x828]
|
|
}
|
|
|
|
|
|
#[pso_packet(0x60)]
|
|
pub struct BurstDone72 {
|
|
msg: u8,
|
|
len: u8,
|
|
client: u8,
|
|
target: u8,
|
|
}
|
|
|
|
impl BurstDone72 {
|
|
pub fn new() -> BurstDone72 {
|
|
BurstDone72 {
|
|
msg: 0x72,
|
|
len: 3,
|
|
client: 0x18,
|
|
target: 0x08,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[pso_packet(0x60)]
|
|
pub struct Message {
|
|
msg: GameMessage,
|
|
}
|
|
|
|
impl Message {
|
|
pub fn new(msg: GameMessage) -> Message {
|
|
Message {
|
|
msg: msg,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pso_packet(0x62, manual_flag)]
|
|
pub struct DirectMessage {
|
|
flag: u32,
|
|
msg: GameMessage
|
|
}
|
|
|
|
impl DirectMessage {
|
|
pub fn new(target: u32, msg: GameMessage) -> DirectMessage {
|
|
DirectMessage {
|
|
flag: target,
|
|
msg: msg,
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is a bit of a weird packet, 0x6C is in the 0x60 family in terms of function
|
|
// but this is the only packet I could find that uses it
|
|
#[pso_packet(0x6C, no_flag)]
|
|
struct BankItemList {
|
|
pub aflag: u32,
|
|
pub cmd: u8, // 0xBC
|
|
pub unknown: [u8; 3],
|
|
pub size: u32,
|
|
pub checksum: u32,
|
|
pub item_count: u32,
|
|
pub meseta: u32,
|
|
pub items: Vec<BankItem>,
|
|
}
|
|
|
|
|
|
#[pso_packet(0x6D, manual_flag)]
|
|
struct Like62ButCooler {
|
|
pub flag: u32,
|
|
pub blob: ConsumingBlob,
|
|
}
|
|
|
|
|
|
#[derive(PSOPacketData, Clone, Copy, Default)]
|
|
pub struct PlayerHeader {
|
|
pub tag: u32,
|
|
pub guildcard: u32,
|
|
pub _unknown1: [u32; 5],
|
|
pub client_id: u32,
|
|
pub name: [u16; 16],
|
|
pub _unknown2: u32,
|
|
}
|
|
|
|
#[derive(PSOPacketData, Clone)]
|
|
pub struct PlayerInfo {
|
|
pub header: PlayerHeader,
|
|
pub inventory: character::Inventory,
|
|
pub character: character::Character,
|
|
}
|
|
|
|
#[pso_packet(0x67)]
|
|
pub struct JoinLobby {
|
|
pub client: u8,
|
|
pub leader: u8,
|
|
pub one: u8,
|
|
pub lobby: u8,
|
|
pub block: u16,
|
|
pub event: u16,
|
|
pub padding: u32,
|
|
pub playerinfo: Vec<PlayerInfo>,
|
|
}
|
|
|
|
#[pso_packet(0x68, manual_flag)]
|
|
pub struct AddToLobby {
|
|
flag: u32,
|
|
pub client: u8,
|
|
pub leader: u8,
|
|
pub one: u8,
|
|
pub lobby: u8,
|
|
pub block: u16,
|
|
pub event: u16,
|
|
pub padding: u32,
|
|
pub playerinfo: PlayerInfo,
|
|
}
|
|
|
|
#[pso_packet(0xC1)]
|
|
pub struct CreateRoom {
|
|
unknown: [u32; 2],
|
|
name: [u16; 16],
|
|
password: [u16; 16],
|
|
difficulty: u8,
|
|
battle: u8,
|
|
challenge: u8,
|
|
episode: u8,
|
|
single_player: u8,
|
|
padding: [u8; 3],
|
|
}
|
|
|
|
|
|
#[pso_packet(0x01)]
|
|
pub struct SmallDialog {
|
|
padding: [u32; 0x02],
|
|
msg: String,
|
|
}
|
|
|
|
impl SmallDialog {
|
|
pub fn new(msg: String) -> SmallDialog {
|
|
SmallDialog {
|
|
padding: [0; 0x02],
|
|
msg: msg,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pso_packet(0x11)]
|
|
pub struct SmallLeftDialog {
|
|
padding: [u32; 0x02],
|
|
msg: String,
|
|
}
|
|
|
|
impl SmallLeftDialog {
|
|
pub fn new(msg: String) -> SmallLeftDialog {
|
|
SmallLeftDialog {
|
|
padding: [0x00004500, 0x45004500],
|
|
msg: msg,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pso_packet(0x64, manual_flag)]
|
|
pub struct JoinRoom {
|
|
pub flag: u32, // # of elements in players
|
|
pub maps: [u32; 0x20],
|
|
pub players: [PlayerHeader; 4],
|
|
pub client: u8,
|
|
pub leader: u8,
|
|
pub one: u8,
|
|
pub difficulty: u8, // TODO: enum
|
|
pub battle: u8,
|
|
pub event: u8,
|
|
pub section: u8,
|
|
pub challenge: u8,
|
|
pub random_seed: u32,
|
|
pub episode: u8,
|
|
pub one2: u8,
|
|
pub single_player: u8,
|
|
pub unknown: u8,
|
|
}
|
|
|
|
#[pso_packet(0x65, manual_flag)]
|
|
pub struct AddToRoom {
|
|
pub flag: u32,
|
|
pub client: u8,
|
|
pub leader: u8,
|
|
pub one: u8,
|
|
pub lobby: u8,
|
|
pub block: u16,
|
|
pub event: u16,
|
|
pub padding: u32,
|
|
pub playerinfo: PlayerInfo,
|
|
}
|
|
|
|
#[pso_packet(0x69)]
|
|
pub struct LeaveLobby {
|
|
client: u8,
|
|
leader: u8,
|
|
_padding: u16,
|
|
}
|
|
|
|
impl LeaveLobby {
|
|
pub fn new(client: u8, leader: u8) -> LeaveLobby {
|
|
LeaveLobby {
|
|
client: client,
|
|
leader: leader,
|
|
_padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pso_packet(0x66)]
|
|
pub struct LeaveRoom {
|
|
client: u8,
|
|
leader: u8,
|
|
_padding: u16,
|
|
}
|
|
|
|
impl LeaveRoom {
|
|
pub fn new(client: u8, leader: u8) -> LeaveRoom {
|
|
LeaveRoom {
|
|
client: client,
|
|
leader: leader,
|
|
_padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pso_packet(0x06)]
|
|
pub struct PlayerChat {
|
|
pub unknown: u32,
|
|
pub guildcard: u32,
|
|
pub message: String,
|
|
}
|
|
|
|
impl PlayerChat {
|
|
pub fn new(guildcard: u32, message: String) -> PlayerChat {
|
|
PlayerChat {
|
|
unknown: 0x00010000,
|
|
guildcard: guildcard,
|
|
message: message,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[pso_packet(0x8A)]
|
|
pub struct RoomNameRequest {
|
|
}
|
|
|
|
#[pso_packet(0x8A)]
|
|
pub struct RoomNameResponse {
|
|
pub name: String,
|
|
}
|
|
|
|
#[pso_packet(0x7ED)]
|
|
pub struct UpdateConfig{
|
|
pub config: [u8; 0xE8],
|
|
}
|
|
|
|
#[pso_packet(0xD8)]
|
|
pub struct ViewInfoboardRequest {
|
|
}
|
|
|
|
#[derive(PSOPacketData, Clone)]
|
|
pub struct InfoboardResponse {
|
|
pub name: [u16; 16],
|
|
pub message: [u16; 172],
|
|
}
|
|
|
|
#[pso_packet(0xD8)]
|
|
pub struct ViewInfoboardResponse {
|
|
pub response: Vec<InfoboardResponse>,
|
|
}
|
|
|
|
#[pso_packet(0xD9)]
|
|
pub struct WriteInfoboard {
|
|
pub message: String,
|
|
}
|
|
|
|
#[pso_packet(0x08)]
|
|
pub struct RoomListRequest {
|
|
|
|
}
|
|
|
|
#[derive(PSOPacketData, Clone)]
|
|
pub struct RoomList {
|
|
pub menu_id: u32,
|
|
pub item_id: u32,
|
|
pub difficulty: u8,
|
|
pub players: u8,
|
|
pub name: [u16; 16],
|
|
pub episode: u8,
|
|
pub flags: u8,
|
|
}
|
|
|
|
#[pso_packet(0x08)]
|
|
pub struct RoomListResponse {
|
|
pub baseroom: RoomList,
|
|
pub rooms: Vec<RoomList>,
|
|
}
|
|
|
|
#[derive(PSOPacketData, Clone, Copy, Default)]
|
|
pub struct LobbyEntry {
|
|
menu_id: u32,
|
|
item_id: u32,
|
|
padding: u32,
|
|
}
|
|
|
|
impl LobbyEntry {
|
|
pub fn new(menu_id: u32, lobby_id: u32) -> LobbyEntry {
|
|
LobbyEntry {
|
|
menu_id: menu_id,
|
|
item_id: lobby_id,
|
|
padding: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pso_packet(0x83, manual_flag)]
|
|
pub struct LobbyList {
|
|
flag: u32,
|
|
entries: [LobbyEntry; 16],
|
|
}
|
|
|
|
impl LobbyList {
|
|
pub fn new() -> LobbyList {
|
|
let lobbies = (0..16).fold([LobbyEntry::default(); 16],
|
|
|mut acc, index| {
|
|
acc[index].menu_id = LOBBY_MENU_ID;
|
|
acc[index].item_id = index as u32;
|
|
acc
|
|
});
|
|
LobbyList {
|
|
flag: 0x0F,
|
|
entries: lobbies,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pso_packet(0x6F)]
|
|
pub struct DoneBursting {
|
|
}
|
|
|
|
#[pso_packet(0x16F)]
|
|
pub struct DoneBursting2 {
|
|
}
|
|
|
|
#[pso_packet(0x98)]
|
|
pub struct ClientCharacterData {
|
|
pub data: [u8; 2088],
|
|
}
|
|
|
|
#[pso_packet(0xA2)]
|
|
pub struct RequestQuestList {
|
|
}
|
|
|
|
#[derive(PSOPacketData, Clone, Copy)]
|
|
pub struct QuestCategory {
|
|
pub menu_id: u32,
|
|
pub option_id: u32,
|
|
pub name: [u16; 32],
|
|
pub description: [u16; 122],
|
|
}
|
|
|
|
#[pso_packet(0xA2)]
|
|
pub struct QuestCategoryList {
|
|
pub quest_categories: Vec<QuestCategory>,
|
|
}
|
|
|
|
|
|
#[derive(PSOPacketData, Clone, Copy)]
|
|
pub struct QuestEntry {
|
|
pub menu_id: u32,
|
|
pub category_id: u16,
|
|
pub quest_id: u16,
|
|
pub name: [u16; 32],
|
|
pub description: [u16; 122],
|
|
}
|
|
|
|
#[pso_packet(0xA2)]
|
|
pub struct QuestOptionList {
|
|
pub quests: Vec<QuestEntry>,
|
|
}
|
|
|
|
#[pso_packet(0xA3)]
|
|
pub struct QuestDetail {
|
|
description: [u16; 288]
|
|
}
|
|
|
|
#[pso_packet(0x09)]
|
|
pub struct QuestDetailRequest {
|
|
pub menu: u32,
|
|
pub category: u16,
|
|
pub quest: u16,
|
|
}
|
|
|
|
#[pso_packet(0x10)]
|
|
pub struct QuestMenuSelect {
|
|
pub menu: u32,
|
|
pub category: u16,
|
|
pub quest: u16,
|
|
}
|
|
|
|
#[pso_packet(0x44)]
|
|
pub struct QuestHeader {
|
|
pub unknown1: [u8; 0x24],
|
|
pub filename: [u8; 16],
|
|
pub length: u32,
|
|
pub name: [u8; 16],
|
|
pub unknown2: [u8; 8],
|
|
}
|
|
|
|
#[pso_packet(0x44)]
|
|
pub struct QuestFileRequest {
|
|
pub filename: [u8; 16],
|
|
}
|
|
|
|
#[pso_packet(0x13, no_flag)]
|
|
pub struct QuestChunk {
|
|
pub chunk_num: u32,
|
|
pub filename: [u8; 16],
|
|
pub blob: [u8; 0x400],
|
|
pub blob_length: u32,
|
|
pub unknown: u32,
|
|
}
|
|
|
|
#[pso_packet(0x13, no_flag)]
|
|
pub struct QuestChunkAck {
|
|
pub chunk_num: u32,
|
|
filename: [u8; 16],
|
|
}
|
|
|
|
#[pso_packet(0xAC)]
|
|
pub struct DoneLoadingQuest {
|
|
}
|
|
|
|
|
|
#[pso_packet(0xE7)]
|
|
pub struct FullCharacterData {
|
|
pub character: character::FullCharacter
|
|
}
|
|
|
|
#[pso_packet(0x1ED)]
|
|
pub struct SaveOptions {
|
|
pub options: u32,
|
|
} |