Files
benilla-octo/crates/benilla-protocol/src/world/mod.rs
T
SamandClaude Opus 5.5 972cfc06e8 comments: protocol, the world session, reader, wire helpers and the first writer sends
1,071 comment lines become 370 across 23 files; the code is unchanged.
The Warden refusal and the billing-time default now carry their reasons.
A gap note on cyclic splines described the app, not this crate, and is
gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 20:12:21 +07:00

75 lines
2.7 KiB
Rust

//! The world server (`mangosd`) connection, opened with the realm logon's SRP6 session key.
//!
//! `SMSG_AUTH_CHALLENGE` (server seed) and `CMSG_AUTH_SESSION` (build, uppercased account, client
//! seed, SHA1 over key and seeds) travel plain; every header after them is encrypted (4-byte
//! server, 6-byte client), bodies never, starting with `SMSG_AUTH_RESPONSE`.
use std::io::{Read, Write};
use std::net::TcpStream;
use anyhow::{anyhow, Result};
use benilla_srp::vanilla_header::{DecrypterHalf, EncrypterHalf};
use crate::messages::{self, ServerPacket};
mod movement;
mod reader;
mod session;
mod writer;
pub use reader::WorldReader;
pub use session::{WardenRequired, WorldAuthReject, WorldSession};
pub use writer::WorldWriter;
/// The stock `mangosd` port, for probes that dial the world server without a realm list.
pub const WORLD_PORT: u16 = 8085;
/// Read, decrypt and parse one packet; `decrypter` is `None` only for `SMSG_AUTH_CHALLENGE`.
pub(super) fn recv_packet(
stream: &mut TcpStream,
decrypter: Option<&mut DecrypterHalf>,
) -> Result<ServerPacket> {
let mut header = [0u8; 4];
stream
.read_exact(&mut header)
.map_err(|e| anyhow!("reading world header: {e}"))?;
if let Some(d) = decrypter {
d.decrypt(&mut header);
}
// size is big-endian and counts the opcode (2) but not the size field; opcode is little-endian.
let size = u16::from_be_bytes([header[0], header[1]]);
let opcode = u16::from_le_bytes([header[2], header[3]]);
let body_len = size.saturating_sub(2) as usize;
let mut body = vec![0u8; body_len];
stream
.read_exact(&mut body)
.map_err(|e| anyhow!("reading world body (opcode {opcode:#x}, {body_len} bytes): {e}"))?;
messages::parse_server(opcode, &body).map_err(|e| anyhow!("parsing opcode {opcode:#x}: {e}"))
}
/// Write one client packet: a 6-byte header, its size counting opcode and body, then the body.
pub(super) fn send_packet(
stream: &mut TcpStream,
encrypter: Option<&mut EncrypterHalf>,
opcode: u16,
body: &[u8],
) -> Result<()> {
let size = (body.len() + 4) as u16;
let mut header = {
let s = size.to_be_bytes();
// The client header's opcode field is 4 bytes wide; widen the 16-bit opcode to fill it.
let o = u32::from(opcode).to_le_bytes();
[s[0], s[1], o[0], o[1], o[2], o[3]]
};
if let Some(e) = encrypter {
e.encrypt(&mut header);
}
// One write, not two: header and body leave as one segment.
let mut packet = Vec::with_capacity(header.len() + body.len());
packet.extend_from_slice(&header);
packet.extend_from_slice(body);
stream
.write_all(&packet)
.map_err(|e| anyhow!("sending opcode {opcode:#x}: {e}"))
}