Skip to content

WC3 1.29.2 client support (with real password auth) - #501

Open
Vegasq wants to merge 10 commits into
pvpgn:masterfrom
Vegasq:wc3-1.29-support
Open

WC3 1.29.2 client support (with real password auth)#501
Vegasq wants to merge 10 commits into
pvpgn:masterfrom
Vegasq:wc3-1.29-support

Conversation

@Vegasq

@Vegasq Vegasq commented May 1, 2026

Copy link
Copy Markdown

WC3 1.29.2 client support (with real password auth)

Adds enough server- and client-side glue for a vanilla Warcraft III
1.29.2.9231 client to log into PvPGN with a properly verified password.

The SRP3 protocol divergence between PvPGN and 1.29 — PvPGN's
implementation was written for 1.27/1.28 and the proofs don't match —
is sidestepped W3CE-style: the client is patched to emit
bnet_hash(password) where SRP3 would have sent its M1, and PvPGN's
existing legacy passhash1 / hash_eq fallback validates that
without any server-side cryptographic bypass.

End-to-end working against an upstream-style PvPGN. Wrong passwords
fail. Login then proceeds to channel join, MOTD, etc. as normal.

Server-side changes

  • conf/versioncheck.json.in — adds 0x1d verbyte rows under
    both WAR3 and W3XP blocks for game version 1.29.2.15
    (CheckRevision 0x3cc7eaf6).
  • src/bnetd/handle_bnet.cpp:2951 — fix a pre-existing {fmt}
    crash in _client_motdw3. The line fmt::format_to(serverinfo, "{}" + '\n', ...) was C++ pointer arithmetic, not string
    concatenation, and crashed bnetd with FATAL ERROR: argument index out of range the first time the W3 MOTD was rendered. Fixed to
    "{}\n". Independent of 1.29 work but only manifested with 1.29
    because that was the first version that got far enough to request
    the W3 MOTD on this code path.

Client-side patcher (new, contrib/wc3-1.29-client-patch/)

A Linux-side EXE patcher that produces a self-contained patched
Warcraft III.exe — no DLLs, no loaders, no runtime injection.

Mirrors what
W3CE's
PvPGNPatcher does (analysed from W3CE-v0.2.1-alpha) but as a static
patch.

The patcher appends a new PE section (.pvpgnh) containing:

  • A port of PvPGN's bnet_hash() from src/common/bnethash.cpp
    (do_blizzard_hash variant), compiled from C with no libc and no
    external symbols.
  • An LphChecked replacement (replaces the SRP3 M1 computation —
    the client now sends bnet_hash(lower(password)) as the 20-byte
    logon proof).
  • A CreateAccount trampoline + wrapper that calls the original,
    then copies the plaintext password into the verifier slot — PvPGN
    reads this as a null-terminated plaintext string and stores
    passhash1 = bnet_hash(plaintext).

Plus four byte patches in .text:

Site VA Effect
auth_req 0x00831424 NOPs the JNE that fails the 128-byte server-signature compare. PvPGN sends an all-zero signature; this lets the client accept it.
M2 dispatch 0x00830DDB 74 21 → 75 20 flip. Lets the client accept an all-zero M2 reply (which is what PvPGN's legacy hash_eq success path emits).
LphChecked entry 0x0083A590 5-byte JMP to the new lph_checked_hook in the cave.
CreateAccount entry 0x00986A50 5-byte JMP to the new wrapper.

For 1.29.2.9231 the output is deterministic — current reference MD5
729ac6d7b8438d9579c9a1738ef2d42a.

Build:

sudo apt install gcc-multilib
python3 -m venv ~/venv && source ~/venv/bin/activate
pip install keystone-engine pefile capstone
cd contrib/wc3-1.29-client-patch
python3 build_payload.py /path/to/clean/Warcraft\ III.exe out.exe

Documentation

  • docs/wc3-1.29-pvpgn-notes.md — full binary-analysis writeup of
    the 1.29.2.9231 internals: where the gateway INI lives (note the
    1.29-specific BattleNet\ subfolder), how CheckRevision is keyed,
    the auth_req signature hunt, the M2 verifier hunt, the motdw3 bug.
  • docs/wc3-1.29-real-auth-plan.md — design plan + W3CE analysis
    that produced the patcher.
  • contrib/wc3-1.29-client-patch/README.md — usage + caveats.

Caveats

  • Authenticode signature is stripped on the client EXE. Modifying
    any byte breaks Blizzard's signature; the build also clears the
    Security data directory entry. Windows SmartScreen may warn
    ("Unknown Publisher") on first launch.
  • ASCII passwords only. The LphChecked hook's lowercasing is
    ASCII A–Z. PvPGN's own create-account also lowercases ASCII-only
    via strtolower, so this matches upstream behaviour.
  • Only 1.29.2.9231 is supported. Hardcoded against that build's
    byte offsets (vanilla MD5 c453b4a0bde1a47f41cb2475573edb84).
    Other 1.29.x sub-versions need offsets re-derived; methodology in
    the docs.
  • No support for Reforged (1.32+). Reforged dropped classic
    BNCS entirely.

Testing

Verified end-to-end on a 1.29.2.9231 install on Windows 10, against
this branch's bnetd built on Ubuntu 24.04:

  • Account creation stores passhash1 = bnet_hash(lower(password))
    and salt and verifier (the SRP3 ones from the client, unused
    by the legacy auth path).
  • Login with the correct password takes the legacy
    _client_logonproofreq hash_eq success branch — log message
    "<user>" logged in (right password).
  • Login with the wrong password is rejected — log message
    got wrong client password proof for "<user>".
  • Client proceeds to channel join (Warcraft 3 Frozen Throne), MOTD
    loads, anongame info requests work.

NickY and others added 9 commits May 1, 2026 11:39
Confirmed planned section lists for CLAUDE.md and docs/architecture.md
match the actual file inventory in src/, conf/, lua/, cmake/, and CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add docs/architecture.md covering repository layout, daemons and binaries,
bnetd/common/compat module maps, runtime model, configuration system,
storage layer, protocol handlers with port defaults, optional Lua
scripting, logging, and the CMake build option matrix. Every concrete
claim (file paths, function names, port defaults, namespaces, option
flags) is verified against the source in this snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Concise (~376 line) developer onboarding doc covering build/test/run
commands, codebase map, coding conventions, logging, configuration
workflow, storage backends, Lua scripting, command/protocol-handler
extension points, common pitfalls, and CI. Links out to README.md,
README.DEV, and docs/architecture.md for depth instead of duplicating
their content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final verification pass. Cross-checked every cited file path, function
name, CMake option, namespace, port number, and default constant against
the snapshot and corrected the drift that turned up:

- t_handler signature was missing the trailing const on the t_packet
  pointer in both docs (handlers.h declares
  int(*)(t_connection *, t_packet const * const)).
- Lua bridge teardown was misattributed to post_server_shutdown(); the
  function is only invoked from the SIGHUP rehash path in
  server_process() under restart_mode_lua / restart_mode_all.
- Chat-command guidance contradicted itself on static vs unnamed
  namespace; rewrote it to acknowledge that command.cpp predates the
  README.DEV 5.e rule and to advise matching the surrounding style.

CLAUDE.md is 383 lines (within the 300-500 target). No source, CMake,
or configuration files were modified by this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CLAUDE.md: udptest is not a standalone binary; clarify that
  src/client/udptest.cpp compiles into bnchat and bnstat
- docs/readme.md: index the existing docs/versioncheck.md, which
  was missing from the table

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Vegasq
Vegasq force-pushed the wc3-1.29-support branch from 6cd84c1 to f7f411c Compare May 2, 2026 13:15
Adds enough server- and client-side glue for a vanilla Warcraft III
1.29.2.9231 client to log into a PvPGN server with real password
verification. The SRP3 protocol divergence between PvPGN and 1.29 is
sidestepped W3CE-style: the client is patched to emit
bnet_hash(password) where SRP3 would have sent its M1, and PvPGN's
existing legacy passhash1 / hash_eq fallback validates that without
any server-side cryptographic bypass.

Server side
-----------
- conf/versioncheck.json.in: add 0x1d verbyte rows under both WAR3
  and W3XP for game version 1.29.2.15 (CheckRevision 0x3cc7eaf6).
- src/bnetd/handle_bnet.cpp:2951: fix a {fmt} crash in
  _client_motdw3 where '"{}" + \'\\n\'' was C++ pointer arithmetic,
  not concatenation. The daemon would FATAL on the first MOTD
  render. Pre-existing bug; only manifested with 1.29 because that
  was the first version that got far enough to request the W3 MOTD
  on this code path.

Client patcher (contrib/wc3-1.29-client-patch/)
-----------------------------------------------
- bnet_hash.c: clean C reference port of PvPGN's modified-SHA1
  (do_blizzard_hash variant of bnet_hash from
  src/common/bnethash.cpp). Compiled with `gcc -m32 -O2
  -ffreestanding -nostdlib`, no libc / no external symbols. Also
  defines lph_checked_hook, the LphChecked replacement that calls
  bnet_hash on the lowercased ASCII password.

- build_payload.py: orchestrator. Reads the input EXE, verifies
  MD5, compiles bnet_hash.c via gcc + ld --oformat=binary,
  assembles the CreateAccount trampoline + wrapper via
  keystone-engine, strips the (now-invalid) Authenticode signature,
  appends a new PE section ".pvpgnh" with the cave content, and
  patches four entry points in .text:

    0x00831424  6-byte JNE -> multibyte NOP        (auth_req)
    0x00830DDB  74 21 -> 75 20                     (M2-dispatch flip)
    0x0083A590  push ebp;... -> JMP lph_checked    (LphChecked entry)
    0x00986A50  push ebp;... -> JMP wrapper        (CreateAccount)

  Output for 1.29.2.9231 is deterministic; reference MD5
  729ac6d7b8438d9579c9a1738ef2d42a.

- README.md: usage + how it works + caveats.

Documentation
-------------
- docs/wc3-1.29-pvpgn-notes.md: full binary-analysis writeup of
  the 1.29.2.9231 internals (where the gateway INI lives, how
  CheckRevision is keyed, the auth_req signature hunt, the M2
  verifier hunt, the motdw3 bug). Historical bypass-mode
  intermediate work is referenced; that lives in the separate
  wc3-1.29-bypass-mode branch.
- docs/wc3-1.29-real-auth-plan.md: design plan + W3CE analysis
  that produced the patcher.
- TODO.md: short status. What's done, what's still open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Vegasq
Vegasq force-pushed the wc3-1.29-support branch from f7f411c to b13ccfb Compare May 2, 2026 13:17
@Vegasq
Vegasq marked this pull request as draft May 2, 2026 13:17
@Vegasq Vegasq changed the title Wc3 1.29 support WC3 Legacy 1.29 support May 2, 2026
@Vegasq Vegasq changed the title WC3 Legacy 1.29 support WC3 1.29.2 client support (with real password auth) May 2, 2026
@Vegasq
Vegasq marked this pull request as ready for review May 2, 2026 13:25
@Meanski

Meanski commented Aug 8, 2026

Copy link
Copy Markdown
Member

I don't have WC3 installed to confirm any of this. Accepting other reviewers to chime in

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants