feat: join diagnostics -- KitsuneJoinDiag mod + KC Join Attempts panel - #96
Merged
Merged
Conversation
The data dir was anchored to GameIO.GetSaveGameDir(), which returns the *current world's* save folder. Every new world (or any 7DTD boot that landed on a different save dir for any reason) produced an empty KitsuneCommand DB and re-ran AuthService.EnsureAdminExists, silently rotating the admin password and writing a fresh FIRST_RUN_PASSWORD.txt. Observed on a live server: four "FIRST RUN" blocks in two days, each generating a new random password and invalidating the operator's stored panel creds with no obvious cause. The only fingerprint was the recurring banner in the nssm log. Fix: anchor KC's persistent data (SQLite DB, appsettings.json override, FIRST_RUN_PASSWORD.txt, RESET_PASSWORD.txt) to the 7DTD user-data root (parent of Saves/) instead of inside any specific save. Survives world regen, save deletion, and PackRelay mod re-installs. - ConfigManager.ResolveWorldAgnosticDataDir() — new public static, the single source of truth for the data dir path. AuthService and WebServerHost both call it instead of duplicating the path walk. - ConfigManager.TryMigrateLegacyDataDir() — idempotent best-effort copy from the legacy per-world location so existing operators don't lose their DB on upgrade. - Two reassurance log lines after the FIRST RUN banner so the operator knows subsequent restarts will not rotate the password. - Bumped to 2.7.4 with a CHANGELOG entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ib DisconnectReason
Vanilla 7DTD catches LiteNetLib's OnPeerDisconnected with the real
DisconnectReason (PeerNotFound, Timeout, ConnectionFailed,
InvalidProtocol, etc.) and then throws it away, showing the player a
generic "Could not retrieve server information" dialog. With that
string alone, neither the player nor an admin can tell whether the
failure is NAT, rate-limit, version mismatch, firewall, or anything
else.
KitsuneJoinDiag is a tiny standalone mod that Harmony-postfixes
NetworkClientLiteNetLib.OnDisconnectedFromServer and logs the real
DisconnectInfo at ERR level in Player.log, plus a short hint mapping
each reason to player-actionable advice. Harmless on dedicated servers
(the client-side code path doesn't fire there), so safe to ship in
either client-only or whole-pack mod distributions.
v0.1 has two known cosmetic issues filed as Kitsunebi card #216:
- peer:Port logs LiteNetLib's local source port, not the typed
target port
- timeSinceLastPkt is a NetManager-global counter, meaningless on
fresh failures
Also includes tools/test-joindiag.ps1: a PowerShell harness that
extracts the diag block from a ModLauncher profile's output_log.txt
(one-shot or -Watch mode), so you can iterate on the format without
fishing the block out of a 2MB log every time.
LiteNetLib.dll added to refs/ so the mod's csproj can resolve
DisconnectReason / DisconnectInfo at compile time. The DLL is
referenced as Private=false because the game ships its own copy; we
only need the type metadata.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…apper events Server-side companion to KitsuneJoinDiag. Where the client-side mod surfaces a player's own DisconnectReason to their Player.log, this panel gives the admin a view of all join activity hitting the server, whether or not the connecting client ran any mods. Backend ------- - Diagnostics/JoinAttemptEvent.cs: data record for a single observed event (timestamp, peer endpoint, event type, result, deliveryMethod, bytes, channel, extra data bytes, auth state count). - Diagnostics/JoinAttemptRing.cs: lock-protected ring buffer, 500 events, with Record() / Snapshot(limit, sinceUtc) / Clear() / TotalRecorded. Single lock is fine at the expected event rate. - GameIntegration/Harmony/AuthWrapperServerDiagnostics.cs: five Harmony patches on NetworkServerLiteNetLib+LiteNetLibAuthWrapperServer capturing ConnectionRequestCheck, OnNetworkReceiveEvent, OnPeerConnectedEvent, OnPeerDisconnectedEvent, and Update. Each feeds JoinAttemptRing.Record(). Verbose console-log gating via a static Enabled flag so we can hot-toggle the noisy console output without losing the ring data (it's always recorded). - Web/Controllers/JoinAttemptsController.cs: admin-only REST under /api/join-attempts: GET (with limit + since query), POST /clear, POST /verbose for the Enabled flag. - KitsuneCommand.csproj: reference LiteNetLib (Private=false; the game ships its own copy, we only need the type metadata) so the diagnostics patches can name DisconnectReason / DeliveryMethod / ConnectionRequest directly without reflection. Frontend -------- - views/JoinAttemptsView.vue: PrimeVue DataTable view with auto-refresh, peer grouping, and per-event detail. Admin-only (server enforces, client checks roles before showing the nav entry). - api/joinAttempts.ts: typed thin wrapper over the REST endpoints. - router/index.ts: route registration. - components/layout/AppLayout.vue: nav entry visible to admins. - i18n: keys added across en/de/es/fr/ja/ko/zh-CN/zh-TW. Non-English Asian locales (ja, ko, zh-CN, zh-TW) use English placeholders for now; de/es/fr have proper translations. Pairs with KitsuneJoinDiag v0.1 (sibling mod, separate commit) to give both sides of a failed-join investigation: the player sees the real DisconnectReason in their Player.log, the admin sees the event hit the server's ring buffer (or sees that it didn't, which is also diagnostic). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
AdaInTheLab
added a commit
that referenced
this pull request
May 29, 2026
…og test types (#99) The v2.8.0 tag build failed two ways: - x64/libSkiaSharp.dll and .so are gitignored (only sqlite3.dll was force-added), but #96 added <None Include="x64\libSkiaSharp.*"> to the csproj. CI checks out without them -> MSB3030 "could not copy ... not found". Force-add both (like sqlite3.dll) so the release zip bundles the natives the mod loads from x64/. - PlayerEditDialog.test.ts: the `player` mock had 5 of PlayerInfo's 20 fields, and mockGetMetadata was inferred to return `undefined` so it rejected the seeded {playerId, vipTier}. Completed the PlayerInfo mock, typed the metadata mock as PlayerMetadata | undefined, and completed the seeded object. `vue-tsc --noEmit` now passes locally. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
Two complementary additions for diagnosing failed 7DTD joins, finished during the 2026-05-28 session:
src/KitsuneJoinDiag/(new mod) — client-side Harmony postfix onNetworkClientLiteNetLib.OnDisconnectedFromServer. Vanilla 7DTD catches the realDisconnectReasonand then throws it away in favor of a generic "Could not retrieve server information" dialog; this mod logs the actual reason + a short player-actionable hint at ERR level inPlayer.log. Verified live on 2026-05-28 — caught a realConnectionFailedon the goodtimes playit tunnel and surfaced the block exactly as designed.NetworkServerLiteNetLib+LiteNetLibAuthWrapperServer. Admin-only REST under/api/join-attempts(GET with limit + since, POST /clear, POST /verbose). Vue admin view auto-refreshes and groups by peer. i18n added across all 8 locales (de/es/fr properly translated; ja/ko/zh-CN/zh-TW use English placeholders).Together they give both sides of a failed-join investigation: the player sees the real
DisconnectReasonin theirPlayer.log, the admin sees the event hit the server's ring buffer (or sees that it didn't, which is also diagnostic).Companion Kitsunebi cards on
packrelay-raunk:timeSinceLastPktis NetManager-global and meaningless on fresh failuresTest plan
dotnet build src/KitsuneJoinDiag/KitsuneJoinDiag.csproj -c Releaseproduces a cleanKitsuneJoinDiag.dlldotnet build src/KitsuneCommand/KitsuneCommand.csprojstill succeeds (LiteNetLib reference added)cd frontend && npm run buildsucceeds (new view + locale keys + router)[KitsuneJoinDiag] CONNECTION FAILEDblock lands inPlayer.logwith the expected fields/api/join-attemptsand in the new admin-only Vue panelsrc/KitsuneJoinDiag/tools/test-joindiag.ps1 -Watchwhile triggering a failed connect — confirm the script captures the block🤖 Generated with Claude Code