mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-20 14:47:45 +00:00
Detect when players enter exit zones, and transistion them to the correct place
It's a hack and only really works going from New -> Old Gridania, but this does work! It also means Kawari now actually requires a valid game installation, since it has to read layer group information.
This commit is contained in:
parent
dbe1ef208c
commit
059becf55f
8 changed files with 363 additions and 8 deletions
56
Cargo.lock
generated
56
Cargo.lock
generated
|
@ -25,9 +25,9 @@ checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.87"
|
||||
version = "0.1.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97"
|
||||
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -170,6 +170,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
@ -271,6 +277,16 @@ version = "0.31.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
version = "0.3.9"
|
||||
|
@ -366,6 +382,7 @@ dependencies = [
|
|||
"binrw",
|
||||
"md5",
|
||||
"minijinja",
|
||||
"physis",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -386,6 +403,15 @@ version = "0.2.171"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "902bc563b5d65ad9bba616b490842ef0651066a1a1dc3ce1087113ffcb873c8d"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
|
@ -450,9 +476,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.0"
|
||||
version = "1.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad"
|
||||
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
|
@ -460,6 +486,18 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "physis"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/redstrate/physis#3273070cef2057b9fe6454d3038a2488484e22fa"
|
||||
dependencies = [
|
||||
"binrw",
|
||||
"bitflags",
|
||||
"half",
|
||||
"libz-rs-sys",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.10"
|
||||
|
@ -691,9 +729,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.44.0"
|
||||
version = "1.44.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
@ -904,3 +942,9 @@ dependencies = [
|
|||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05"
|
||||
|
|
|
@ -47,3 +47,4 @@ rand = "0.8"
|
|||
minijinja = "2.0"
|
||||
binrw = { version = "0.14", features = ["std"], default-features = false }
|
||||
md5 = "0.7.0"
|
||||
physis = { git = "https://github.com/redstrate/physis" }
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use std::io::Cursor;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use binrw::BinRead;
|
||||
use kawari::client_select_data::ClientCustomizeData;
|
||||
use kawari::ipc::{ActorSetPos, GameMasterCommandType, IPCOpCode, IPCSegment, IPCStructData};
|
||||
use kawari::oodle::FFXIVOodle;
|
||||
|
@ -8,7 +12,7 @@ use kawari::packet::{
|
|||
};
|
||||
use kawari::world::{
|
||||
ActorControlSelf, ActorControlType, InitZone, PlayerSetup, PlayerSpawn, PlayerStats, Position,
|
||||
UpdateClassInfo,
|
||||
UpdateClassInfo, Zone,
|
||||
};
|
||||
use kawari::{CHAR_NAME, CONTENT_ID, CUSTOMIZE_DATA, WORLD_ID, ZONE_ID};
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
@ -34,6 +38,10 @@ async fn main() {
|
|||
player_id: None,
|
||||
};
|
||||
|
||||
let zone = Zone::load(010);
|
||||
|
||||
let mut exit_position = None;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buf = [0; 2056];
|
||||
loop {
|
||||
|
@ -523,6 +531,8 @@ async fn main() {
|
|||
0, // left finger
|
||||
0, // right finger
|
||||
],
|
||||
pos: exit_position
|
||||
.unwrap_or(Position::default()),
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
|
@ -540,6 +550,36 @@ async fn main() {
|
|||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// fade in?
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::PrepareZoning,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::PrepareZoning {
|
||||
unk: [0, 0, 0, 0],
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// wipe any exit position so it isn't accidentally reused
|
||||
exit_position = None;
|
||||
}
|
||||
IPCStructData::Unk1 { .. } => {
|
||||
tracing::info!("Recieved Unk1!");
|
||||
|
@ -720,6 +760,162 @@ async fn main() {
|
|||
IPCStructData::Unk12 { .. } => {
|
||||
tracing::info!("Recieved Unk12!");
|
||||
}
|
||||
IPCStructData::EnterZoneLine {
|
||||
exit_box_id,
|
||||
position,
|
||||
..
|
||||
} => {
|
||||
tracing::info!(
|
||||
"Character entered {exit_box_id} with a position of {position:#?}!"
|
||||
);
|
||||
|
||||
// find the exit box id
|
||||
let (_, exit_box) =
|
||||
zone.find_exit_box(*exit_box_id).unwrap();
|
||||
tracing::info!("exit box: {:#?}", exit_box);
|
||||
|
||||
// find the pop range on the other side
|
||||
let new_zone = Zone::load(exit_box.territory_type);
|
||||
let (destination_object, _) = new_zone
|
||||
.find_pop_range(exit_box.destination_instance_id)
|
||||
.unwrap();
|
||||
|
||||
// set the exit position
|
||||
exit_position = Some(Position {
|
||||
x: destination_object.transform.translation[0],
|
||||
y: destination_object.transform.translation[1],
|
||||
z: destination_object.transform.translation[2],
|
||||
});
|
||||
|
||||
// fade out?
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::PrepareZoning,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::PrepareZoning {
|
||||
unk: [0x01000000, 0, 0, 0],
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// fade out? x2
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::PrepareZoning,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::PrepareZoning {
|
||||
unk: [0, 0x00000085, 0x00030000, 0x000008ff], // last thing is probably a float?
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"sending them to {:#?}",
|
||||
exit_box.territory_type
|
||||
);
|
||||
|
||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
d.push("resources/tests/init_zone.bin");
|
||||
|
||||
let buffer = std::fs::read(d).unwrap();
|
||||
let mut buffer = Cursor::new(&buffer);
|
||||
|
||||
let init_zone = InitZone::read_le(&mut buffer).unwrap();
|
||||
|
||||
// Init Zone
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::InitZone,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::InitZone(InitZone {
|
||||
server_id: WORLD_ID,
|
||||
zone_id: exit_box.territory_type,
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// idk
|
||||
{
|
||||
let ipc = IPCSegment {
|
||||
unk1: 0,
|
||||
unk2: 0,
|
||||
op_code: IPCOpCode::PrepareZoning,
|
||||
server_id: 0,
|
||||
timestamp: timestamp_secs(),
|
||||
data: IPCStructData::PrepareZoning {
|
||||
unk: [0x01100000, 0, 0, 0],
|
||||
},
|
||||
};
|
||||
|
||||
let response_packet = PacketSegment {
|
||||
source_actor: state.player_id.unwrap(),
|
||||
target_actor: state.player_id.unwrap(),
|
||||
segment_type: SegmentType::Ipc { data: ipc },
|
||||
};
|
||||
send_packet(
|
||||
&mut write,
|
||||
&[response_packet],
|
||||
&mut state,
|
||||
CompressionType::Oodle,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
IPCStructData::Unk13 { .. } => {
|
||||
tracing::info!("Recieved Unk13!");
|
||||
}
|
||||
IPCStructData::Unk14 { .. } => {
|
||||
tracing::info!("Recieved Unk14!");
|
||||
}
|
||||
_ => panic!(
|
||||
"The server is recieving a IPC response or unknown packet!"
|
||||
),
|
||||
|
|
|
@ -13,6 +13,9 @@ pub struct Config {
|
|||
|
||||
#[serde(default)]
|
||||
pub boot_patches_location: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub game_location: String,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -22,6 +25,7 @@ impl Default for Config {
|
|||
login_open: false,
|
||||
boot_patches_location: String::new(),
|
||||
supported_platforms: default_supported_platforms(),
|
||||
game_location: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
32
src/ipc.rs
32
src/ipc.rs
|
@ -100,6 +100,15 @@ pub enum IPCOpCode {
|
|||
CharacterCreated = 0xE,
|
||||
// Unknown, client sends this for ???
|
||||
Unk12 = 0x0E9,
|
||||
// Sent by the client when the character walks into a zone transistion
|
||||
EnterZoneLine = 0x205,
|
||||
// Sent by the client after we sent a InitZone in TravelToZone??
|
||||
// TODO: Actually, I don't think is real...
|
||||
Unk13 = 0x2EE,
|
||||
// Sent by the server when it wants the client to... prepare to zone?
|
||||
PrepareZoning = 0x308,
|
||||
// Sent by the client for unknown reasons
|
||||
Unk14 = 0x87,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
|
@ -363,6 +372,23 @@ pub enum IPCStructData {
|
|||
Unk12 {
|
||||
unk: [u8; 8], // TODO: unknown
|
||||
},
|
||||
#[br(pre_assert(*magic == IPCOpCode::EnterZoneLine))]
|
||||
EnterZoneLine {
|
||||
exit_box_id: u32,
|
||||
position: Position,
|
||||
#[brw(pad_after = 4)] // empty
|
||||
landset_index: i32,
|
||||
},
|
||||
#[br(pre_assert(*magic == IPCOpCode::Unk13))]
|
||||
Unk13 {
|
||||
#[br(dbg)]
|
||||
unk: [u8; 16], // TODO: unknown
|
||||
},
|
||||
#[br(pre_assert(*magic == IPCOpCode::Unk14))]
|
||||
Unk14 {
|
||||
#[br(dbg)]
|
||||
unk: [u8; 8], // TODO: unknown
|
||||
},
|
||||
|
||||
// Server->Client IPC
|
||||
#[br(pre_assert(false))]
|
||||
|
@ -511,6 +537,8 @@ pub enum IPCStructData {
|
|||
#[brw(pad_after = 1136)] // empty
|
||||
details: CharacterDetails,
|
||||
},
|
||||
#[br(pre_assert(false))]
|
||||
PrepareZoning { unk: [u32; 4] },
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
|
@ -578,6 +606,10 @@ impl IPCSegment {
|
|||
IPCStructData::NameRejection { .. } => 536,
|
||||
IPCStructData::CharacterCreated { .. } => 2568,
|
||||
IPCStructData::Unk12 { .. } => todo!(),
|
||||
IPCStructData::EnterZoneLine { .. } => todo!(),
|
||||
IPCStructData::Unk13 { .. } => todo!(),
|
||||
IPCStructData::PrepareZoning { .. } => 16,
|
||||
IPCStructData::Unk14 { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ pub struct InitZone {
|
|||
pub layer_set_id: u32,
|
||||
pub layout_id: u32,
|
||||
#[br(dbg)]
|
||||
pub weather_id: u16,
|
||||
pub weather_id: u16, // index into Weather sheet probably?
|
||||
pub unk_really: u16,
|
||||
pub unk_bitmask1: u8,
|
||||
pub unk_bitmask2: u8,
|
||||
|
|
|
@ -22,3 +22,6 @@ pub use actor_control_self::ActorControlType;
|
|||
|
||||
mod init_zone;
|
||||
pub use init_zone::InitZone;
|
||||
|
||||
mod zone;
|
||||
pub use zone::Zone;
|
||||
|
|
75
src/world/zone.rs
Normal file
75
src/world/zone.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use physis::{
|
||||
common::Platform,
|
||||
gamedata::GameData,
|
||||
layer::{
|
||||
ExitRangeInstanceObject, InstanceObject, LayerEntryData, LayerGroup, PopRangeInstanceObject,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{config::get_config, world::Position};
|
||||
|
||||
/// Represents a loaded zone
|
||||
pub struct Zone {
|
||||
id: u16,
|
||||
layer_group: LayerGroup,
|
||||
}
|
||||
|
||||
impl Zone {
|
||||
pub fn load(id: u16) -> Self {
|
||||
let config = get_config();
|
||||
|
||||
let mut game_data =
|
||||
GameData::from_existing(Platform::Win32, &config.game_location).unwrap();
|
||||
let mdl;
|
||||
println!("loading {id}");
|
||||
if id == 133 {
|
||||
mdl = game_data
|
||||
.extract("bg/ffxiv/fst_f1/twn/f1t2/level/planmap.lgb")
|
||||
.unwrap();
|
||||
} else {
|
||||
mdl = game_data
|
||||
.extract("bg/ffxiv/fst_f1/twn/f1t1/level/planmap.lgb")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let layer_group = LayerGroup::from_existing(&mdl).unwrap();
|
||||
Self { id, layer_group }
|
||||
}
|
||||
|
||||
/// Search for an exit box matching an id.
|
||||
pub fn find_exit_box(
|
||||
&self,
|
||||
instance_id: u32,
|
||||
) -> Option<(&InstanceObject, &ExitRangeInstanceObject)> {
|
||||
// TODO: also check position!
|
||||
for group in &self.layer_group.layers {
|
||||
for object in &group.objects {
|
||||
if let LayerEntryData::ExitRange(exit_range) = &object.data {
|
||||
if object.instance_id == instance_id {
|
||||
return Some((object, exit_range));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_pop_range(
|
||||
&self,
|
||||
instance_id: u32,
|
||||
) -> Option<(&InstanceObject, &PopRangeInstanceObject)> {
|
||||
// TODO: also check position!
|
||||
for group in &self.layer_group.layers {
|
||||
for object in &group.objects {
|
||||
if let LayerEntryData::PopRange(pop_range) = &object.data {
|
||||
if object.instance_id == instance_id {
|
||||
return Some((object, pop_range));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue