1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-26 16:37:46 +00:00

Move out most of the large IPC structures

This commit is contained in:
Joshua Goins 2025-03-12 18:05:41 -04:00
parent aa6e818bfb
commit 29954be1a6
10 changed files with 327 additions and 410 deletions

View file

@ -1,11 +1,14 @@
use std::time::{SystemTime, UNIX_EPOCH};
use kawari::ipc::{ActorControlType, IPCOpCode, IPCSegment, IPCStructData};
use kawari::ipc::{IPCOpCode, IPCSegment, IPCStructData};
use kawari::oodle::FFXIVOodle;
use kawari::packet::{
CompressionType, PacketSegment, SegmentType, State, parse_packet, send_keep_alive, send_packet,
};
use kawari::world::{PlayerSpawnData, Position};
use kawari::world::{
ActorControlSelf, ActorControlType, InitZone, PlayerSetup, PlayerSpawn, PlayerStats,
UpdateClassInfo,
};
use kawari::{CONTENT_ID, WORLD_ID, ZONE_ID};
use tokio::io::AsyncReadExt;
use tokio::net::TcpListener;
@ -194,15 +197,18 @@ async fn main() {
op_code: IPCOpCode::ActorControlSelf,
server_id: 0,
timestamp: timestamp_secs(),
data: IPCStructData::ActorControlSelf {
category: ActorControlType::SetCharaGearParamUI,
param1: 1,
param2: 1,
param3: 0,
param4: 0,
param5: 0,
param6: 0,
},
data: IPCStructData::ActorControlSelf(
ActorControlSelf {
category:
ActorControlType::SetCharaGearParamUI,
param1: 1,
param2: 1,
param3: 0,
param4: 0,
param5: 0,
param6: 0,
},
),
};
let response_packet = PacketSegment {
@ -227,39 +233,12 @@ async fn main() {
op_code: IPCOpCode::PlayerStats,
server_id: 0,
timestamp: timestamp_secs(),
data: IPCStructData::PlayerStats {
data: IPCStructData::PlayerStats(PlayerStats {
strength: 1,
dexterity: 0,
vitality: 0,
intelligence: 0,
mind: 0,
piety: 0,
hp: 100,
mp: 100,
tp: 0,
gp: 0,
cp: 0,
delay: 0,
tenacity: 0,
attack_power: 0,
defense: 0,
direct_hit_rate: 0,
evasion: 0,
magic_defense: 0,
critical_hit: 0,
attack_magic_potency: 0,
healing_magic_potency: 0,
elemental_bonus: 0,
determination: 0,
skill_speed: 0,
spell_speed: 0,
haste: 0,
craftmanship: 0,
control: 0,
gathering: 0,
perception: 0,
unk1: [0; 26],
},
..Default::default()
}),
};
let response_packet = PacketSegment {
@ -284,127 +263,13 @@ async fn main() {
op_code: IPCOpCode::PlayerSetup,
server_id: 0,
timestamp: timestamp_secs(),
data: IPCStructData::PlayerSetup {
data: IPCStructData::PlayerSetup(PlayerSetup {
content_id: CONTENT_ID,
crest: 0,
unknown10: 0,
char_id: 0,
rested_exp: 0,
companion_current_exp: 0,
unknown1c: 0,
fish_caught: 0,
use_bait_catalog_id: 0,
unknown28: 0,
unknown_pvp2c: 0,
unknown2e: 0,
pvp_frontline_overall_campaigns: 0,
unknown_timestamp34: 0,
unknown_timestamp38: 0,
unknown3c: 0,
unknown40: 0,
unknown44: 0,
companion_time_passed: 0.0,
unknown4c: 0,
unknown50: 0,
unknown_pvp52: [0; 4],
pvp_series_exp: 0,
player_commendations: 0,
unknown64: [0; 8],
pvp_rival_wings_total_matches: 0,
pvp_rival_wings_total_victories: 0,
pvp_rival_wings_weekly_matches: 0,
pvp_rival_wings_weekly_victories: 0,
max_level: 0,
expansion: 0,
unknown76: 0,
unknown77: 0,
unknown78: 0,
race: 0,
tribe: 0,
gender: 0,
current_job: 0,
current_class: 0,
deity: 0,
nameday_month: 0,
nameday_day: 0,
city_state: 0,
homepoint: 0,
unknown8d: [0; 3],
companion_rank: 0,
companion_stars: 0,
companion_sp: 0,
companion_unk93: 0,
companion_color: 0,
companion_fav_feed: 0,
fav_aetheryte_count: 0,
unknown97: [0; 5],
sightseeing21_to_80_unlock: 0,
sightseeing_heavensward_unlock: 0,
unknown9e: [0; 26],
exp: [10000; 32],
pvp_total_exp: 0,
unknown_pvp124: 0,
pvp_exp: 0,
pvp_frontline_overall_ranks: [0; 3],
unknown138: 0,
levels: [100; 32],
unknown194: [0; 218],
companion_name: [0; 21],
companion_def_rank: 0,
companion_att_rank: 0,
companion_heal_rank: 0,
mount_guide_mask: [0; 33],
ornament_mask: [0; 4],
unknown281: [0; 23],
name: "KAWARI".to_string(),
unknown293: [0; 16],
unknown2a3: 0,
unlock_bitmask: [0; 64],
aetheryte: [0; 26],
favorite_aetheryte_ids: [0; 4],
free_aetheryte_id: 0,
ps_plus_free_aetheryte_id: 0,
discovery: [0; 480],
howto: [0; 36],
unknown554: [0; 4],
minions: [0; 60],
chocobo_taxi_mask: [0; 12],
watched_cutscenes: [0; 159],
companion_barding_mask: [0; 12],
companion_equipped_head: 0,
companion_equipped_body: 0,
companion_equipped_legs: 0,
unknown_mask: [0; 287],
pose: [0; 7],
unknown6df: [0; 3],
challenge_log_complete: [0; 13],
secret_recipe_book_mask: [0; 12],
unknown_mask6f7: [0; 29],
relic_completion: [0; 12],
sightseeing_mask: [0; 37],
hunting_mark_mask: [0; 102],
triple_triad_cards: [0; 45],
unknown895: 0,
unknown7d7: [0; 15],
unknown7d8: 0,
unknown7e6: [0; 49],
regional_folklore_mask: [0; 6],
orchestrion_mask: [0; 87],
hall_of_novice_completion: [0; 3],
anima_completion: [0; 11],
unknown85e: [0; 41],
unlocked_raids: [0; 28],
unlocked_dungeons: [0; 18],
unlocked_guildhests: [0; 10],
unlocked_trials: [0; 12],
unlocked_pvp: [0; 5],
cleared_raids: [0; 28],
cleared_dungeons: [0; 18],
cleared_guildhests: [0; 10],
cleared_trials: [0; 12],
cleared_pvp: [0; 5],
unknown948: [0; 15],
},
..Default::default()
}),
};
let response_packet = PacketSegment {
@ -429,14 +294,15 @@ async fn main() {
op_code: IPCOpCode::UpdateClassInfo,
server_id: 69, // lol
timestamp: timestamp_secs(),
data: IPCStructData::UpdateClassInfo {
class_id: 35,
unknown: 1,
is_specialist: 0,
synced_level: 90,
class_level: 90,
role_actions: [0; 10],
},
data: IPCStructData::UpdateClassInfo(
UpdateClassInfo {
class_id: 35,
unknown: 1,
synced_level: 90,
class_level: 90,
..Default::default()
},
),
};
let response_packet = PacketSegment {
@ -461,29 +327,11 @@ async fn main() {
op_code: IPCOpCode::InitZone,
server_id: 0,
timestamp: timestamp_secs(),
data: IPCStructData::InitZone {
data: IPCStructData::InitZone(InitZone {
server_id: WORLD_ID,
zone_id: ZONE_ID,
zone_index: 0,
content_finder_condition_id: 0,
layer_set_id: 0,
layout_id: 0,
weather_id: 1,
unk_bitmask1: 0x10,
unk_bitmask2: 0,
unk1: 0,
unk2: 0,
festival_id: 0,
additional_festival_id: 0,
unk3: 0,
unk4: 0,
unk5: 0,
unk6: [0; 4],
unk7: [0; 3],
position: Position::default(),
unk8: [0; 4],
unk9: 0,
},
..Default::default()
}),
};
let response_packet = PacketSegment {
@ -514,7 +362,7 @@ async fn main() {
server_id: 0,
timestamp: timestamp_secs(),
data: IPCStructData::PlayerSpawn(
PlayerSpawnData::default(),
PlayerSpawn::default(),
),
};

View file

@ -2,7 +2,10 @@ use binrw::binrw;
use crate::{
common::{read_string, write_string},
world::{PlayerSpawnData, Position},
world::{
ActorControlSelf, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
UpdateClassInfo,
},
};
// NOTE: See https://github.com/karashiiro/FFXIVOpcodes/blob/master/FFXIVOpcodes/Ipcs.cs for opcodes
@ -150,13 +153,6 @@ pub enum LobbyCharacterAction {
Request = 0x15,
}
#[binrw]
#[derive(Debug, Eq, PartialEq, Clone)]
#[brw(repr = u16)]
pub enum ActorControlType {
SetCharaGearParamUI = 0x260,
}
#[binrw]
#[br(import(magic: &IPCOpCode))]
#[derive(Debug, Clone)]
@ -350,212 +346,17 @@ pub enum IPCStructData {
unk2: u32,
},
#[br(pre_assert(false))]
InitZone {
server_id: u16,
zone_id: u16,
zone_index: u16,
content_finder_condition_id: u16,
layer_set_id: u32,
layout_id: u32,
weather_id: u32,
unk_bitmask1: u8,
unk_bitmask2: u8,
unk1: u8,
unk2: u32,
festival_id: u16,
additional_festival_id: u16,
unk3: u32,
unk4: u32,
unk5: u32,
unk6: [u32; 4],
unk7: [u32; 3],
position: Position,
unk8: [u32; 4],
unk9: u32,
},
InitZone(InitZone),
#[br(pre_assert(false))]
ActorControlSelf {
#[brw(pad_after = 2)]
category: ActorControlType,
param1: u32,
param2: u32,
param3: u32,
param4: u32,
param5: u32,
#[brw(pad_after = 4)]
param6: u32,
},
ActorControlSelf(ActorControlSelf),
#[br(pre_assert(false))]
PlayerStats {
strength: u32,
dexterity: u32,
vitality: u32,
intelligence: u32,
mind: u32,
piety: u32,
hp: u32,
mp: u32,
tp: u32,
gp: u32,
cp: u32,
delay: u32,
tenacity: u32,
attack_power: u32,
defense: u32,
direct_hit_rate: u32,
evasion: u32,
magic_defense: u32,
critical_hit: u32,
attack_magic_potency: u32,
healing_magic_potency: u32,
elemental_bonus: u32,
determination: u32,
skill_speed: u32,
spell_speed: u32,
haste: u32,
craftmanship: u32,
control: u32,
gathering: u32,
perception: u32,
unk1: [u32; 26],
},
PlayerStats(PlayerStats),
#[br(pre_assert(false))]
PlayerSetup {
content_id: u64,
crest: u64,
unknown10: u64,
char_id: u32,
rested_exp: u32,
companion_current_exp: u32,
unknown1c: u32,
fish_caught: u32,
use_bait_catalog_id: u32,
unknown28: u32,
unknown_pvp2c: u16,
unknown2e: u16,
pvp_frontline_overall_campaigns: u32,
unknown_timestamp34: u32,
unknown_timestamp38: u32,
unknown3c: u32,
unknown40: u32,
unknown44: u32,
companion_time_passed: f32,
unknown4c: u32,
unknown50: u16,
unknown_pvp52: [u16; 4],
pvp_series_exp: u16,
player_commendations: u16,
unknown64: [u16; 8],
pvp_rival_wings_total_matches: u16,
pvp_rival_wings_total_victories: u16,
pvp_rival_wings_weekly_matches: u16,
pvp_rival_wings_weekly_victories: u16,
max_level: u8,
expansion: u8,
unknown76: u8,
unknown77: u8,
unknown78: u8,
race: u8,
tribe: u8,
gender: u8,
current_job: u8,
current_class: u8,
deity: u8,
nameday_month: u8,
nameday_day: u8,
city_state: u8,
homepoint: u8,
unknown8d: [u8; 3],
companion_rank: u8,
companion_stars: u8,
companion_sp: u8,
companion_unk93: u8,
companion_color: u8,
companion_fav_feed: u8,
fav_aetheryte_count: u8,
unknown97: [u8; 5],
sightseeing21_to_80_unlock: u8,
sightseeing_heavensward_unlock: u8,
unknown9e: [u8; 26],
exp: [u32; 32],
pvp_total_exp: u32,
unknown_pvp124: u32,
pvp_exp: u32,
pvp_frontline_overall_ranks: [u32; 3],
unknown138: u32,
levels: [u16; 32],
unknown194: [u8; 218],
companion_name: [u8; 21],
companion_def_rank: u8,
companion_att_rank: u8,
companion_heal_rank: u8,
mount_guide_mask: [u8; 33],
ornament_mask: [u8; 4],
unknown281: [u8; 23],
#[br(count = 32)]
#[bw(pad_size_to = 32)]
#[br(map = read_string)]
#[bw(map = write_string)]
name: String,
unknown293: [u8; 16],
unknown2a3: u8,
unlock_bitmask: [u8; 64],
aetheryte: [u8; 26],
favorite_aetheryte_ids: [u16; 4],
free_aetheryte_id: u16,
ps_plus_free_aetheryte_id: u16,
discovery: [u8; 480],
howto: [u8; 36],
unknown554: [u8; 4],
minions: [u8; 60],
chocobo_taxi_mask: [u8; 12],
watched_cutscenes: [u8; 159],
companion_barding_mask: [u8; 12],
companion_equipped_head: u8,
companion_equipped_body: u8,
companion_equipped_legs: u8,
unknown_mask: [u8; 287],
pose: [u8; 7],
unknown6df: [u8; 3],
challenge_log_complete: [u8; 13],
secret_recipe_book_mask: [u8; 12],
unknown_mask6f7: [u8; 29],
relic_completion: [u8; 12],
sightseeing_mask: [u8; 37],
hunting_mark_mask: [u8; 102],
triple_triad_cards: [u8; 45],
unknown895: u8,
unknown7d7: [u8; 15],
unknown7d8: u8,
unknown7e6: [u8; 49],
regional_folklore_mask: [u8; 6],
orchestrion_mask: [u8; 87],
hall_of_novice_completion: [u8; 3],
anima_completion: [u8; 11],
unknown85e: [u8; 41],
unlocked_raids: [u8; 28],
unlocked_dungeons: [u8; 18],
unlocked_guildhests: [u8; 10],
unlocked_trials: [u8; 12],
unlocked_pvp: [u8; 5],
cleared_raids: [u8; 28],
cleared_dungeons: [u8; 18],
cleared_guildhests: [u8; 10],
cleared_trials: [u8; 12],
cleared_pvp: [u8; 5],
unknown948: [u8; 15],
},
PlayerSetup(PlayerSetup),
#[br(pre_assert(false))]
UpdateClassInfo {
class_id: u16,
unknown: u8,
is_specialist: u8,
synced_level: u16,
class_level: u16,
role_actions: [u32; 10],
},
UpdateClassInfo(UpdateClassInfo),
#[br(pre_assert(false))]
PlayerSpawn(PlayerSpawnData),
PlayerSpawn(PlayerSpawn),
}
#[binrw]
@ -651,15 +452,7 @@ mod tests {
entitled_expansion: 0,
characters: Vec::new(),
},
IPCStructData::ActorControlSelf {
category: ActorControlType::SetCharaGearParamUI,
param1: 0,
param2: 0,
param3: 0,
param4: 0,
param5: 0,
param6: 0,
},
IPCStructData::ActorControlSelf(ActorControlSelf::default()),
IPCStructData::InitializeChat { unk: [0; 8] },
];

View file

@ -0,0 +1,23 @@
use binrw::binrw;
#[binrw]
#[derive(Debug, Eq, PartialEq, Clone, Default)]
#[brw(repr = u16)]
pub enum ActorControlType {
#[default]
SetCharaGearParamUI = 0x260,
}
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct ActorControlSelf {
#[brw(pad_after = 2)]
pub category: ActorControlType,
pub param1: u32,
pub param2: u32,
pub param3: u32,
pub param4: u32,
pub param5: u32,
#[brw(pad_after = 4)]
pub param6: u32,
}

29
src/world/init_zone.rs Normal file
View file

@ -0,0 +1,29 @@
use binrw::binrw;
use super::Position;
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct InitZone {
pub server_id: u16,
pub zone_id: u16,
pub zone_index: u16,
pub content_finder_condition_id: u16,
pub layer_set_id: u32,
pub layout_id: u32,
pub weather_id: u32,
pub unk_bitmask1: u8,
pub unk_bitmask2: u8,
pub unk1: u8,
pub unk2: u32,
pub festival_id: u16,
pub additional_festival_id: u16,
pub unk3: u32,
pub unk4: u32,
pub unk5: u32,
pub unk6: [u32; 4],
pub unk7: [u32; 3],
pub position: Position,
pub unk8: [u32; 4],
pub unk9: u32,
}

View file

@ -1,8 +1,24 @@
mod player_spawn;
pub use player_spawn::PlayerSpawnData;
pub use player_spawn::PlayerSpawn;
mod position;
pub use position::Position;
mod status_effect;
pub use status_effect::StatusEffect;
mod update_class_info;
pub use update_class_info::UpdateClassInfo;
mod player_setup;
pub use player_setup::PlayerSetup;
mod player_stats;
pub use player_stats::PlayerStats;
mod actor_control_self;
pub use actor_control_self::ActorControlSelf;
pub use actor_control_self::ActorControlType;
mod init_zone;
pub use init_zone::InitZone;

159
src/world/player_setup.rs Normal file
View file

@ -0,0 +1,159 @@
use binrw::binrw;
use crate::common::{read_string, write_string};
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct PlayerSetup {
pub content_id: u64,
pub crest: u64,
pub unknown10: u64,
pub char_id: u32,
pub rested_exp: u32,
pub companion_current_exp: u32,
pub unknown1c: u32,
pub fish_caught: u32,
pub use_bait_catalog_id: u32,
pub unknown28: u32,
pub unknown_pvp2c: u16,
pub unknown2e: u16,
pub pvp_frontline_overall_campaigns: u32,
pub unknown_timestamp34: u32,
pub unknown_timestamp38: u32,
pub unknown3c: u32,
pub unknown40: u32,
pub unknown44: u32,
pub companion_time_passed: f32,
pub unknown4c: u32,
pub unknown50: u16,
pub unknown_pvp52: [u16; 4],
pub pvp_series_exp: u16,
pub player_commendations: u16,
pub unknown64: [u16; 8],
pub pvp_rival_wings_total_matches: u16,
pub pvp_rival_wings_total_victories: u16,
pub pvp_rival_wings_weekly_matches: u16,
pub pvp_rival_wings_weekly_victories: u16,
pub max_level: u8,
pub expansion: u8,
pub unknown76: u8,
pub unknown77: u8,
pub unknown78: u8,
pub race: u8,
pub tribe: u8,
pub gender: u8,
pub current_job: u8,
pub current_class: u8,
pub deity: u8,
pub nameday_month: u8,
pub nameday_day: u8,
pub city_state: u8,
pub homepoint: u8,
pub unknown8d: [u8; 3],
pub companion_rank: u8,
pub companion_stars: u8,
pub companion_sp: u8,
pub companion_unk93: u8,
pub companion_color: u8,
pub companion_fav_feed: u8,
pub fav_aetheryte_count: u8,
pub unknown97: [u8; 5],
pub sightseeing21_to_80_unlock: u8,
pub sightseeing_heavensward_unlock: u8,
pub unknown9e: [u8; 26],
pub exp: [u32; 32],
pub pvp_total_exp: u32,
pub unknown_pvp124: u32,
pub pvp_exp: u32,
pub pvp_frontline_overall_ranks: [u32; 3],
pub unknown138: u32,
pub levels: [u16; 32],
#[br(count = 218)]
#[bw(pad_size_to = 218)]
pub unknown194: Vec<u8>,
pub companion_name: [u8; 21],
pub companion_def_rank: u8,
pub companion_att_rank: u8,
pub companion_heal_rank: u8,
#[br(count = 33)]
#[bw(pad_size_to = 33)]
pub mount_guide_mask: Vec<u8>,
pub ornament_mask: [u8; 4],
pub unknown281: [u8; 23],
#[br(count = 32)]
#[bw(pad_size_to = 32)]
#[br(map = read_string)]
#[bw(map = write_string)]
pub name: String,
pub unknown293: [u8; 16],
pub unknown2a3: u8,
#[br(count = 64)]
#[bw(pad_size_to = 64)]
pub unlock_bitmask: Vec<u8>,
pub aetheryte: [u8; 26],
pub favorite_aetheryte_ids: [u16; 4],
pub free_aetheryte_id: u16,
pub ps_plus_free_aetheryte_id: u16,
#[br(count = 480)]
#[bw(pad_size_to = 480)]
pub discovery: Vec<u8>,
#[br(count = 36)]
#[bw(pad_size_to = 36)]
pub howto: Vec<u8>,
pub unknown554: [u8; 4],
#[br(count = 60)]
#[bw(pad_size_to = 60)]
pub minions: Vec<u8>,
pub chocobo_taxi_mask: [u8; 12],
#[br(count = 159)]
#[bw(pad_size_to = 159)]
pub watched_cutscenes: Vec<u8>,
pub companion_barding_mask: [u8; 12],
pub companion_equipped_head: u8,
pub companion_equipped_body: u8,
pub companion_equipped_legs: u8,
#[br(count = 287)]
#[bw(pad_size_to = 287)]
pub unknown_mask: Vec<u8>,
pub pose: [u8; 7],
pub unknown6df: [u8; 3],
pub challenge_log_complete: [u8; 13],
pub secret_recipe_book_mask: [u8; 12],
pub unknown_mask6f7: [u8; 29],
pub relic_completion: [u8; 12],
#[br(count = 37)]
#[bw(pad_size_to = 37)]
pub sightseeing_mask: Vec<u8>,
#[br(count = 102)]
#[bw(pad_size_to = 102)]
pub hunting_mark_mask: Vec<u8>,
#[br(count = 45)]
#[bw(pad_size_to = 45)]
pub triple_triad_cards: Vec<u8>,
pub unknown895: u8,
pub unknown7d7: [u8; 15],
pub unknown7d8: u8,
#[br(count = 49)]
#[bw(pad_size_to = 49)]
pub unknown7e6: Vec<u8>,
pub regional_folklore_mask: [u8; 6],
#[br(count = 87)]
#[bw(pad_size_to = 87)]
pub orchestrion_mask: Vec<u8>,
pub hall_of_novice_completion: [u8; 3],
pub anima_completion: [u8; 11],
#[br(count = 41)]
#[bw(pad_size_to = 41)]
pub unknown85e: Vec<u8>,
pub unlocked_raids: [u8; 28],
pub unlocked_dungeons: [u8; 18],
pub unlocked_guildhests: [u8; 10],
pub unlocked_trials: [u8; 12],
pub unlocked_pvp: [u8; 5],
pub cleared_raids: [u8; 28],
pub cleared_dungeons: [u8; 18],
pub cleared_guildhests: [u8; 10],
pub cleared_trials: [u8; 12],
pub cleared_pvp: [u8; 5],
pub unknown948: [u8; 15],
}

View file

@ -4,8 +4,8 @@ use super::position::Position;
use super::status_effect::StatusEffect;
#[binrw]
#[derive(Debug, Clone, Default)]
pub struct PlayerSpawnData {
#[derive(Debug, Clone, Copy, Default)]
pub struct PlayerSpawn {
pub title: u16,
pub u1b: u16,
pub current_world_id: u16,

37
src/world/player_stats.rs Normal file
View file

@ -0,0 +1,37 @@
use binrw::binrw;
#[binrw]
#[derive(Debug, Clone, Copy, Default)]
pub struct PlayerStats {
pub strength: u32,
pub dexterity: u32,
pub vitality: u32,
pub intelligence: u32,
pub mind: u32,
pub piety: u32,
pub hp: u32,
pub mp: u32,
pub tp: u32,
pub gp: u32,
pub cp: u32,
pub delay: u32,
pub tenacity: u32,
pub attack_power: u32,
pub defense: u32,
pub direct_hit_rate: u32,
pub evasion: u32,
pub magic_defense: u32,
pub critical_hit: u32,
pub attack_magic_potency: u32,
pub healing_magic_potency: u32,
pub elemental_bonus: u32,
pub determination: u32,
pub skill_speed: u32,
pub spell_speed: u32,
pub haste: u32,
pub craftmanship: u32,
pub control: u32,
pub gathering: u32,
pub perception: u32,
pub unk1: [u32; 26],
}

View file

@ -1,7 +1,7 @@
use binrw::binrw;
#[binrw]
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, Copy, Default)]
pub struct Position {
pub x: f32,
pub y: f32,

View file

@ -0,0 +1,12 @@
use binrw::binrw;
#[binrw]
#[derive(Debug, Clone, Copy, Default)]
pub struct UpdateClassInfo {
pub class_id: u16,
pub unknown: u8,
pub is_specialist: u8,
pub synced_level: u16,
pub class_level: u16,
pub role_actions: [u32; 10],
}