fix(otp): restore TW password retrieval on the legacy launcher (#368) (#376) - #380
Draft
hiimyusheng wants to merge 2 commits into
Draft
fix(otp): restore TW password retrieval on the legacy launcher (#368) (#376)#380hiimyusheng wants to merge 2 commits into
hiimyusheng wants to merge 2 commits into
Conversation
…n#368) (pungin#376) beanfun moved TW game starts to the Gamania Games Manager in August 2026 and the credential flow moved with it, so 5.9.1 fails every 獲取密碼 with 「Query String Error」. This ports the fix from the current branch. The game-start page no longer builds an OTP URL; it emits an `m_objData` handoff for the native launcher, whose `data` blob decodes locally into whatever that game's own OTP request needs. The route is chosen by the page's shape rather than by region, so a page carrying the handoff takes the new path and everything else — HK included — keeps the old one. The blob carries two payloads and both are live: MapleStory hands over a `LaunchTicket` for `get_webstart_otp_v2.ashx`, while Mabinogi, Elsword, CSO and others hand over the pre-v2 `ppppp` parameters for the endpoint that was there all along. Accepting a decode only when it held a `LaunchTicket` made a correct decode of the second kind look like a wrong table, which is why every game but MapleStory failed with a decryption error for data that had decrypted perfectly. `ppppp` matters most: the 64-character constant this client pinned is stale, and the live 96-character value arrives in the blob. Three things move together with the endpoint. Handlers under `generic_handlers` began checking `Referer` and answer a complaint about a null referrer without one. The request has to name which launcher build is asking — a version and a file hash — read from the installed manager when there is one and from compiled-in constants otherwise. And the page's `GetResultByLongPolling` call is the launcher's installation check, unrelated to the password and holding the connection open, so the new path skips it. Those values are also resolvable without a release: a pin the user set, then the newer of the installed GGM and the published `ggm-client.json` cached for six hours, then the compiled-in pair. Preferring the installed copy outright looked right since GGM self-updates — but it self-updates when it runs, and the people this app exists for never run it, so the stalest machines would be the only ones a hotfix could never reach. Every layer is best-effort: a network hiccup must not cost someone their password when a good compiled-in pair is right there, and a run that reaches no mirror stops retrying. A migrated page has dropped the literals the old flow read, so step 1 reads the handoff first — it decides the route — and only then demands what that route needs. Decoding algorithm published by @takidog on pungin#368; the four-title characterisation of the two payloads by @ToooAir on pungin#376. Ref pungin#368 Ref pungin#376
This branch references log4net but never configures an appender, so every `log.Info` here wrote to nothing — silent precisely when someone is trying to find out which route ran and which CV was sent. Console, like the rest of this file, shows up when the app is started from a terminal. Also prints the chosen route once per retrieval, which is the first thing worth knowing when a game fails and another one works. Ref pungin#368
hiimyusheng
marked this pull request as ready for review
August 18, 2026 04:04
lshw54
self-requested a review
August 21, 2026 06:55
Collaborator
|
關於小型UI 的更新後續會跟據B151 進行調整, 這個pr會先暫作保留 |
lshw54
marked this pull request as draft
August 21, 2026 07:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports the #368 / #376 fix to the legacy WPF launcher. Without it, 5.9.1 fails every 獲取密碼 on TW with
Query String Error.What changed on beanfun
Same as #372 and #377, in short:
game_start_step2.aspxno longer builds an OTP URL. It emits anm_objDatahandoff for the Gamania Games Manager, and the obfuscateddatablob inside carries whatever that game's own OTP request needs. The full write-up lives in those two PRs; only the parts that shaped this port are repeated below.What was ported
1. The launch-data decoder —
Beanfun/API/LaunchData.cs(new)Eight substitution tables, an 8-character ASCII DES key lifted out of the normalized hex, then DES-ECB with no padding. Steps 3–5 are the same construction the pre-v2 OTP envelope used, so the existing
WCDESComp.DecryStrHexhandles them unchanged; only the substitution layer and the field parse are new.Which table the selector names is not settled —
n % 4decodes every sample seen so far, but there are eight tables and one sample cannot tell that apart from the table order simply differing. So each table is tried until one yields a plaintext carrying a known marker. A wrong table gives noise, and noise does not spell a field name by accident.2. Two payloads, two endpoints — the #376 half
m_objData.datacarries either aLaunchTicketforget_webstart_otp_v2.ashxor the pre-v2pppppparameters forget_webstart_otp.ashx, and both are live. Both kinds of page declarem_objDataand are indistinguishable from outside, so the blob has to be decoded before the route is known. Accepting a decode only when it held aLaunchTicketwould make a correct decode of the second kind look like a wrong table — which is how every game but MapleStory ended in a decryption error for data that had decrypted perfectly.pppppis the value that matters most here: this client pinned a 64-character constant whose provenance nobody knew, and the live one arrives in the blob at 96 characters.webTokenandsecretCodeare taken from the page when it declares them, and fall back to the session and toget_cookies.ashxwhen it does not.3. The three things that move with the endpoint —
Beanfun/Tools/ClientIntegrity.cs(new)generic_handlersbegan checkingReferer, and answer a complaint about a null referrer without one. Every such request now sends the game-start page's own URL.CV,Hash,arch. Read from the installed Game Manager when there is one — located via thegamaniagames://protocol handler, then the conventional install roots — and from compiled-in constants otherwise, which is the case for most users of this app.GetResultByLongPollingcall is the launcher's installation check, unrelated to the password and holding the connection open, so the handoff path does not wait on it.record_service_startis best-effort there for the same reason.Those values also resolve without a release, same order as #374: a pin the user set, then the newer of the installed Game Manager and the published
ggm-client.jsoncached for six hours, then the compiled-in pair. This branch reads the sameggm-client.jsonfromcode, so a hotfix published there reaches these users too.Preferring the installed copy outright looked right, since the Game Manager self-updates — but it self-updates when it runs, and the people this app exists for are the ones who never run it. An install untouched since Gamania's last release reports exactly the values beanfun stops accepting, so the machines most likely to be refused would be the only ones the lever could never reach. Comparing versions and letting the newer build win, with a tie going to the installed file, avoids that.
Every layer is best-effort and falls through on failure. One addition over the Rust version: a run that reaches no mirror stops retrying for the rest of the process, so someone behind a block that reaches none of them does not pay four timeouts on every password.
Routing reads the handoff first. A migrated page has dropped the literals the old flow read — there is no
GetResultByLongPolling&key=on it any more. Demanding them up front would fail the retrieval before the handoff in the same page had been read, naming a literal that is meant to be gone.The route is chosen by the page's own shape rather than by region, so a page carrying the handoff takes the new path and everything else keeps the old one. HK needs no change now, and none later if it migrates.
Verification
Built on Windows with .NET 8 and run against the live service. Retrieval succeeds on 新楓之谷, 新龍之谷, 瑪奇, CSO and 艾爾之光.
Both payload kinds are covered by that set, per the characterisation on #376 — which is the point of testing more than MapleStory.
Before that, the decoder was checked by round-tripping both payload shapes through all 16 selectors, plus the malformed-input and block-alignment cases, and confirmed to fail with the #376 acceptance test reverted.
Not tested: HK. No account to try it with. Its page carries no
m_objData, so it takes the unchanged branch — but that is reasoning, not a measurement.This branch has no test project, so the round-trip check was a scratch script rather than something committed; the live run above is the real evidence.
Notes
log4netcalls forConsole. This branch references log4net but never configures an appender, so those lines wrote to nothing — silent exactly when someone is trying to find out which route ran and whichCVwas sent. It also prints the chosen route once per retrieval.errmsgstring.Decoding algorithm published by @takidog on #368. The four-title characterisation of the two payloads by @ToooAir on #376.