1,071 comment lines become 573; the code is unchanged. The world probe's field docs are its --help text, so the help is shorter now and matches what each flag runs: --quest takes quest 783 from Willem to McBride, --quest-item needs the starter item to survive, --giverstatus queries once, --spells makes three casts. Also corrected: - the aura-duration packet has a SessionEvent; - the SRP xor hash is SHA1(N) XOR SHA1(g), not a hash of it; - the cooldown probe picks Tri, else the first character. benilla sending a stored integrity hash instead of hashing the install is now marked as a deviation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
63 lines
2.2 KiB
Rust
63 lines
2.2 KiB
Rust
//! Live probe: the character-select park behaviours the glue screens rely on. A logout ends in
|
|
//! `SMSG_LOGOUT_COMPLETE` and a reconnect re-serves the roster; a socket parked at character
|
|
//! select for 130 s still logs in, since vmangos does not kick a quiet authenticated socket.
|
|
//!
|
|
//! Needs the local vmangos and account `two`/`ptwo`.
|
|
|
|
use std::time::Duration;
|
|
|
|
use benilla_protocol::{logon, WorldSession, WORLD_PORT};
|
|
|
|
fn connect(user: &str, pass: &str) -> anyhow::Result<WorldSession> {
|
|
let l = logon("localhost", user, pass)?;
|
|
let addr = l
|
|
.realms
|
|
.first()
|
|
.map(|r| r.address.clone())
|
|
.unwrap_or_else(|| format!("localhost:{WORLD_PORT}"));
|
|
WorldSession::connect(&addr, user, l.session_key)
|
|
}
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
let (user, pass) = ("two", "ptwo");
|
|
|
|
// ── 1 · logout round-trip ──
|
|
let mut s = connect(user, pass)?;
|
|
let chars = s.char_enum()?;
|
|
println!(
|
|
"roster: {:?}",
|
|
chars.iter().map(|c| &c.name).collect::<Vec<_>>()
|
|
);
|
|
let c = &chars[0];
|
|
s.player_login(c.guid)?;
|
|
s.set_active_mover(c.guid)?;
|
|
println!("logged in as {} — requesting logout in 2s", c.name);
|
|
std::thread::sleep(Duration::from_secs(2));
|
|
s.set_read_timeout(Some(Duration::from_secs(1)))?;
|
|
s.logout(Duration::from_secs(25))?;
|
|
println!("PASS: SMSG_LOGOUT_COMPLETE received");
|
|
drop(s);
|
|
|
|
// A fresh connection re-serves the roster, as the app's relist does.
|
|
let mut s = connect(user, pass)?;
|
|
let n = s.char_enum()?.len();
|
|
println!("PASS: post-logout reconnect roster has {n} chars");
|
|
drop(s);
|
|
|
|
// ── 2 · idle park ──
|
|
let mut s = connect(user, pass)?;
|
|
let chars = s.char_enum()?;
|
|
println!("parking at character select for 130s…");
|
|
std::thread::sleep(Duration::from_secs(130));
|
|
match s.player_login(chars[0].guid).and_then(|()| {
|
|
s.set_active_mover(chars[0].guid)?;
|
|
// Any post-login packet shows the world streams.
|
|
s.set_read_timeout(Some(Duration::from_secs(5)))?;
|
|
s.recv().map(|p| p.name())
|
|
}) {
|
|
Ok(pkt) => println!("PASS: idle-parked login works (first packet: {pkt})"),
|
|
Err(e) => println!("KICKED: idle park failed login: {e:#} (self-heals via relist)"),
|
|
}
|
|
Ok(())
|
|
}
|