Skip to content

Reach Server Select: fix C2S observability, sweep ansVerify, build ansSrvList - #11

Merged
Ashime merged 14 commits into
mainfrom
SUNCL-Ashime-Patch#2
Sep 4, 2026
Merged

Ashime merged 14 commits into
mainfrom
SUNCL-Ashime-Patch#2

Conversation

@Ashime

@Ashime Ashime commented Sep 4, 2026

Copy link
Copy Markdown
Owner

The game client now gets past login and reaches Server Select. Full evidence, per-run packet logs and a change record are in CLIENT-PROTOCOL-NOTES.md §10–§18.

Observability

  • NioServer honours [NETWORK] UNIQUE_IP_FILTER, which was read from the ini but never applied — the filter was added unconditionally. It allows one connection per IP, and the launcher deliberately holds a 127.0.0.1 connection open across the handoff, so it could take the game client's connection down. Set FALSE for local client testing.
  • ClientPacketHandler tags every line [conn-N] and reports per-connection duration and packet counts. Connects and zero-packet closes drop to DEBUG, silencing the launcher's five-second health check — previously indistinguishable from real client traffic in the log.

Protocol findings

  • ansVerify is not the gate it was assumed to be. All 32 opcodes 0x00–0x1F were swept against a live client; none is accepted as a verify reply.
  • A2U_ansAuthUser = 0x0E with flag 0x00 is accepted, and takes the client straight to U2A_askSrvList without it ever authenticating.
  • Re-sending 0x00 (ansReady) makes the client re-verify on the same connection, so a probe rotation now runs inside one client launch instead of costing a launch per candidate.
  • The EP1 ClientPackets/ServerPackets documentation matches this client for askVerify, askSrvList, askSrvSelect, ansServerList and ansChannelList.

Server list

  • AnsSrvList emits the documented layouts instead of zero-entry placeholders. The client accepted them — it displayed the list and replied with askSrvSelect.
  • GameServerRegistry keeps the ServerInfo row the S2S handshake already fetched. This is a stopgap on the wrong table: GameServerInfo and ChannelInfo are the player-facing ones and both are empty. §18 is the handoff for that work.
  • The auth gate on askSrvList/askSrvSelect is relaxed while the probe is on, because 0x0E makes the client skip askAuthUser entirely.

VerifyUser now parses its two fields by offset and logs why it rejects anything. The previous hex-string handling broke on any version component of 0 or >= 10 and failed silently, which is indistinguishable from the client ignoring the reply.

Note before merging

ANS_VERIFY_PROBE = TRUE is committed as diagnostic state. The client needs it to get past verify today, but it deliberately sends replies known to be wrong. Worth flipping to FALSE if main should stay honest.

This PR will show as conflicting — here is why, and the resolution

1b5dd94 Suncl ashime patch#2 (#8) was a squash merge (single parent), so main already holds this branch's earlier content with no ancestry link back to it. Git therefore treats every later branch change as a conflict. Six files: CLIENT-PROTOCOL-NOTES.md, Config/AuthServer/AuthServer.ini, AnsVerify.java, AnsVerifyProbe.java, VerifyUser.java, ClientPacketHandler.java.

main contributes nothing unique to any of them. Each conflicted file on main is byte-identical to a version already in this branch's history:

file main is identical to branch commit
CLIENT-PROTOCOL-NOTES.md 1898ffc
Config/AuthServer/AuthServer.ini 1898ffc
AnsVerifyProbe.java 1898ffc
VerifyUser.java 1898ffc
AnsVerify.java 42ae1c6
ClientPacketHandler.java 42ae1c6

Both are ancestors of this branch's head, so taking the branch side for all six is lossless. CLAUDE.md and .github/workflows/maven.yml are main-only and auto-merge cleanly.

git checkout main && git merge --no-ff origin/SUNCL-Ashime-Patch#2 --no-commit
# then for each of the six paths:
git checkout --theirs -- <path> && git add -- <path>
git commit && git push origin main

🤖 Generated with Claude Code

Ashime and others added 13 commits August 21, 2024 06:09
… a shell. Added additional features to the database server. Have some of the auth server built out. Still need to work on validating user credentials and handlers for the packets that follow.
Patch#1 (fd4b39e) had all the launcher/auth-server/database-server work
but was never pushed/merged to GitHub - the PR that landed on main was
cut before that commit existed. Resolves conflicts by keeping the two
dependabot version bumps from main (log4j-api, spring-boot-starter-mail)
and otherwise taking Patch#1's side (it's a strict superset - HEAD's
versions of these files were pre-refactor and missing whole methods).

Also fixes two SessionManager implementers (database-server, launcher)
that were missing the clearSessions() override added to the shared
commons.SessionManager abstract class, which is what actually broke the
build after merging.
Fixes the packet length header, which every encoder byte-swapped into
big-endian via Utility.flip. Our servers agreed with each other so this
stayed hidden, but the real game client reads the header little-endian and
timed out on every packet. Removed the swap at all 10 encoder/decoder sites
and added PacketFraming (shared little-endian frame decoder), which also
gives the pipelines proper frame reassembly - previously each decoder
assumed one TCP read equalled one packet.

With that fixed the real client (2.6.0.1) completes A2U_ansReady and replies
U2A_askVerify. A2U_ansVerify is still a placeholder and is the next blocker;
findings and remaining work are written up in CLIENT-PROTOCOL-NOTES.md.

Also:
- web-server: account registration over REST -> S2S -> CreateAccount
- launcher: login against auth-server, signed-in username, START GAME
  launches the client (with UAC elevation fallback)
- database-server: DB failures no longer escape the packet handler and
  tear down the shared S2S connection
- auth-server: fix client packet category read from message[2] to
  message[0], which crashed on the first packet of every connection
- fix ConcurrentModificationException from remove-during-iterate in
  auth-server/web-server ServerSessionManager
- relax registration validation to match the Profile table's nullability

Housekeeping: stop tracking out/artifacts jars and ignore Data/, archives,
logs and build output. Data/ is ~7.9GB of client assets that should not be
in git history, and *.7z archives of Config/ bundle the TLS private keys
that the existing ignore rules were meant to keep out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The game client now completes the A2U_ansReady / U2A_askVerify exchange
against auth-server. Two things were stopping it.

Environment: two outbound firewall rules blocking Sungame.exe/SUN.exe to
125.141.214.0/24 also stopped the client using the local login server.
Removing them restored connectivity with nothing else changed. Recorded in
the notes as resolved; those rules must not be re-added.

Code: VerifyUser.verify() was a port of the old LoginServer check, written
before this packet had ever been captured. It tested the payload for the
configured client protocol ("346") and client IP ("127001"), neither of
which appears in U2A_askVerify - it carries a hostname. So it always failed
and AnsVerify replied 0x01, a hard refusal. Rewritten against the layout
confirmed on a live client: a 3-byte 07 01 01 prefix then a 32-byte
null-padded host field ("connected1.sunc"). It validates the structure and
logs the host, but deliberately does not compare it to config - that value
lives in LOGIN.INI inside the client's System.wpk, which the server cannot
see, and gating a hard refusal on a check we cannot make is what caused
this.

Adds AnsVerifyProbe, off by default behind [PROBE] ANS_VERIFY_PROBE, which
serves a different ansVerify candidate per connection to hunt the still-
unknown response format, and records the close delay for each.

Docs correct two conclusions that turned out to be wrong:
- close-delay does not rank candidates. The same three bytes produced 3.8s,
  14.8s and 5.2s across runs. Judge on whether an inbound byte arrives.
- SERVICE_LOGIN_TRY_COUNTS = 10 does not give ten attempts per launch. The
  client makes exactly one askVerify then exits, so each candidate costs a
  full launch.

CLAUDE.md was stale in four places, all verified against the code: the
spring-boot:run commands fail because that goal uses the module directory
while configs resolve from the repo root; C2S is live, not commented out;
the pipeline omitted PacketFraming; and the S2S handshake documented an
AES/RSA exchange that mutual TLS has replaced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… protection'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Two conflicts, both places where this branch is strictly newer than main
(main still carries the pre-session state via the earlier PR merge #6).

ClientPacketHandler.java - three hunks, took this branch's side in each:
- the U2A_askVerify dispatch. Main still calls `new AnsVerify().createPacket()`,
  which no longer compiles: AnsVerify is now static and takes the request
  payload so it can validate it. This branch's version also carries the
  AnsVerifyProbe branch.
- channelInactive's PROBE result logging, absent on main.
- the connection-reset log at DEBUG rather than WARN.
Main's other changes to this file merged cleanly and are kept.

CLIENT-PROTOCOL-NOTES.md - add/add. Main has the 174-line pre-session
version; this branch's rewrite supersedes all eight of its sections, so took
ours. Checked main's copy for anything unique first and restored the one
detail the rewrite had dropped: the pristine LOGIN.INI MD5. Added a
reference-hash table alongside it.

Verified the merged tree compiles (commons, auth-server, web-server,
launcher). database-server was not rebuilt - it is running and holds a lock
on its jar - but nothing in this merge touches it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The A2U_ansVerify probe cursor now survives auth-server restarts, stored
in Config/AuthServer/AnsVerifyProbe.state (gitignored). Windows locks a
running jar so every rebuild forces a restart, and each replayed
candidate costs a full ~45s client launch. A fresh cursor starts at
candidate 4 - candidates 1-3 have been served to a live client and
failed.

CLIENT-PROTOCOL-NOTES records a session of reverse engineering the real
client:

- LOGIN_SERVER_TYPE = 1 selects a direct LOGIN_SERVER_IP/PORT path with
  no HEAD+index+TAIL composition, confirmed against a live client which
  now reports 127.0.0.1 in askVerify. This removes the hosts-file
  dependency entirely (notes 4, 5). Earlier attempts at this failed only
  because they set IP_HEAD, which TYPE=1 ignores.
- Corrects notes 4: LOGIN.INI is XOR 0x69 inside System.wpk, not
  plaintext. wpktool de-obfuscates on extract, and its -m refuses unless
  the replacement's first 4 bytes match the existing entry.
- Sungame.exe is MPRESS-packed; notes 9 records how to dump the unpacked
  image, the recovered VA map, and what full Ghidra analysis ruled out
  for the still-open ansVerify blocker.
- Result-byte polarity is not the problem: 33 02 01 fails exactly like
  33 02 00, so neither polarity is accepted.

VerifyUser's javadoc no longer claims the askVerify host field always
comes from IP_HEAD/IP_TAIL - both TYPE forms have now been observed on
the wire.

ANS_VERIFY_PROBE stays FALSE, its documented default. Flip it to TRUE to
resume the sweep; the persisted cursor picks up at candidate 9.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found incidentally while searching for the askVerify builder. It calls
CommandLineToArgvW, converts wide->multibyte, and tokenises on 0x7c
('|'), so it is the decoder for the handoff string in Protocol.java's
capture: 42126697|AioHaruka||<token>|2|1|1|2|2

That makes it the place to settle notes 7's open questions - how many
fields the client expects, what it does with each, and whether -User: or
-Username: is correct - by inspection rather than guesswork. Useless
until auth is reachable, so recorded rather than analysed now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sSrvList

The game client now gets past login and selects a server. Full evidence and
per-run logs in CLIENT-PROTOCOL-NOTES.md sections 10-18.

Observability

- NioServer honours [NETWORK] UNIQUE_IP_FILTER, which was read from the ini
  but never applied - the filter was added unconditionally. It allows one
  connection per IP, and the launcher deliberately holds a 127.0.0.1
  connection open across the handoff, so it could take the game client's
  connection down. Set FALSE for local client testing.
- ClientPacketHandler tags every line [conn-N] and reports per-connection
  duration and packet counts. Connects and zero-packet closes drop to DEBUG,
  silencing the launcher's five-second health check, which was previously
  indistinguishable from real client traffic.

Protocol findings

- ansVerify is not the gate it was assumed to be. All 32 opcodes 0x00-0x1F
  were swept against a live client and none is accepted as a verify reply.
- A2U_ansAuthUser = 0x0E with flag 0x00 IS accepted, and takes the client
  straight to U2A_askSrvList without it ever authenticating.
- Re-sending 0x00 (ansReady) makes the client re-verify on the same
  connection, so a probe rotation now runs inside one client launch rather
  than costing a launch per candidate.
- The EP1 ClientPackets/ServerPackets documentation matches this client for
  askVerify, askSrvList, askSrvSelect, ansServerList and ansChannelList.

Server list

- AnsSrvList emits the documented layouts instead of zero-entry
  placeholders. The client accepted them: it displayed the list and replied
  with askSrvSelect.
- GameServerRegistry keeps the ServerInfo row the S2S handshake already
  fetched. This is a stopgap on the wrong table - GameServerInfo and
  ChannelInfo are the player-facing ones, and both are empty. See section 18.
- The auth gate on askSrvList/askSrvSelect is relaxed while the probe is on,
  because 0x0E makes the client skip askAuthUser entirely.

VerifyUser now parses its two fields by offset and logs why it rejects
anything. The previous hex-string handling broke on any version component of
0 or >= 10 and failed silently, which is indistinguishable from the client
ignoring the reply.

ANS_VERIFY_PROBE = TRUE is diagnostic state: the client needs it to get past
verify today, but it deliberately sends replies known to be wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codacy-production

codacy-production Bot commented Sep 4, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 1 medium

Alerts:
⚠ 1 issue (≤ 0 issues of at least minor severity)

Results:
1 new issue

Category Results
UnusedCode 1 medium

View in Codacy

🟢 Metrics 9 complexity · 3 duplication

Metric Results
Complexity 9
Duplication 3

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@Ashime
Ashime merged commit 0c4f0fb into main Sep 4, 2026
4 of 5 checks passed
@Ashime
Ashime deleted the SUNCL-Ashime-Patch#2 branch September 4, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant