Files
benilla-octo/crates/benilla-srp/examples/provision_probe.rs
T
SamandClaude Fable 5.1 b05d5d18d5 a fresh clone gets the whole project, and the maintainer's tooling moves out
What a clone lacked: the gates went green with a thousand skipped tests and said nothing, every
login script assumed a worktree-pool slot and its probe account, the client defaulted to the
maintainer's account, a docker path and a home directory were in the code, the temp files broke
on GNU coreutils, and two generators could only run against a sibling repo.

Now the test gates print how many data-gated tests skipped and why; a scripted run logs in as
the account its checkout declares in `.probe-identity` or as the three variables name, never a
default, and the client refuses a scripted login on any other account; the event and message
tables regenerate from vendored inputs, byte for byte; the docs state the real prerequisites.

Out of the tree, into the maintainer's own tooling: the worktree pool, its hooks, the perf leg
runner, the cross-platform compile and the Vulkan container leg — a way of working, and setups
not every machine has. One hook stays: no writes into the install.

Two tests that could never run are gone with their subject: the quest-timer geometry diff
compared a copy of the window that no longer exists (and joined a chain path onto a folder, so
it always skipped), and the FrameXML flag gate now reads the stock files off the player's own
patch chain instead of an extracted folder only one machine had.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 17:27:14 +07:00

91 lines
4.1 KiB
Rust

//! Provision a probe account for the local vmangos `realmd` DB (decision 0450: a scripted run
//! logs in as `probeN`/`pprobeN`/`Probe<N-spelled>`, one account per checkout that runs probes).
//! A new checkout means provisioning its account first; this is that recipe as one command
//! (decision 0530 — a checkout once went without it, and had no working identity).
//!
//! No DB access here — it prints the SQL and the char-create probe line; run those yourself.
//!
//! Validate the stored-hex convention against an existing row first (0450's discipline):
//! cargo run -p benilla-srp --example provision_probe -- check <USER> <PASS> <s-hex> <v-hex>
//! then emit the new slot's row:
//! cargo run -p benilla-srp --example provision_probe -- emit <N>
//!
//! Hex convention (validated against PROBE0's stored row, matching vmangos `BigNumber`): the DB
//! stores `v`/`s` as big-endian hex of the number; the salt bytes fed to SHA1 are that number's
//! little-endian bytes.
use benilla_srp::{generate_account, password_verifier, NormalizedString};
/// Stored big-endian hex → the 32 little-endian bytes SRP feeds to SHA1.
fn le_from_hex(hex: &str) -> [u8; 32] {
let hex = hex.trim();
assert!(hex.len() <= 64, "hex too long for a 32-byte value");
let padded = format!("{hex:0>64}");
let mut out = [0u8; 32];
for (i, chunk) in (0..64).step_by(2).enumerate() {
// big-endian byte i lands at little-endian index 31-i
out[31 - i] = u8::from_str_radix(&padded[chunk..chunk + 2], 16).expect("hex digit");
}
out
}
/// Little-endian bytes → the big-endian hex string the DB stores.
fn hex_from_le(le: &[u8; 32]) -> String {
le.iter().rev().map(|b| format!("{b:02X}")).collect()
}
fn spelled(n: u32) -> &'static str {
[
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
][n as usize]
}
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
match args.first().map(String::as_str) {
Some("check") if args.len() == 5 => {
let user = NormalizedString::new(&args[1]).expect("username");
let pass = NormalizedString::new(&args[2]).expect("password");
let v = password_verifier(&user, &pass, &le_from_hex(&args[3]));
let got = hex_from_le(&v);
let want = format!("{:0>64}", args[4].trim().to_uppercase());
if got == want {
println!("MATCH — convention validated against the stored row");
} else {
println!("MISMATCH\n computed v = {got}\n stored v = {want}");
std::process::exit(1);
}
}
Some("emit") if args.len() == 2 => {
let n: u32 = args[1].parse().expect("slot number 0-9");
assert!(n <= 9, "slot number 0-9");
let user_s = format!("probe{n}");
let pass_s = format!("pprobe{n}");
let user = NormalizedString::new(&user_s).expect("username");
let pass = NormalizedString::new(&pass_s).expect("password");
let (salt, verifier) = generate_account(&user, &pass);
println!(
"INSERT INTO account (username, gmlevel, v, s) VALUES ('{}', 3, '{}', '{}');",
user_s.to_uppercase(),
hex_from_le(&verifier),
hex_from_le(&salt),
);
println!("-- then mint the character over the real wire (0423's probe; KEEP it):");
println!(
"-- WOW_USER={user_s} WOW_PASS={pass_s} WOW_PROBE_CHARCREATE=Probe{} \\",
spelled(n)
);
println!("-- WOW_PROBE_CHARCREATE_KEEP=1 WOW_DATA=<Data> cargo run -q -p benilla");
println!("-- then GM-on survival for the new character (extra_flags bit 1):");
println!(
"-- UPDATE characters.characters SET extra_flags = extra_flags | 1 WHERE name = 'Probe{}';",
spelled(n)
);
}
_ => {
eprintln!("usage: provision_probe check <USER> <PASS> <s-hex> <v-hex> | emit <N>");
std::process::exit(2);
}
}
}