From d7352b818d320c1103c25a2c423f323077fac7fb Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 12 Mar 2026 00:27:00 +0300 Subject: [PATCH] added documentation and decompiled client --- PLANS.md | 64 +++++++++++++++ PROTOCOL-NOTES.md | 189 +++++++++++++++++++++++++++++++++++++++++++++ README-dev.md | 61 --------------- README.md | 5 ++ docker-compose.yml | 88 --------------------- hs-client | 1 + 6 files changed, 259 insertions(+), 149 deletions(-) create mode 100644 PLANS.md create mode 100644 PROTOCOL-NOTES.md delete mode 100644 README-dev.md delete mode 100644 docker-compose.yml create mode 160000 hs-client diff --git a/PLANS.md b/PLANS.md new file mode 100644 index 0000000..3408e95 --- /dev/null +++ b/PLANS.md @@ -0,0 +1,64 @@ + Proposed Direction + + - Single core language: Go for all server runtime (gateway + lobby + game logic). + - Web/UI: TypeScript (Next.js or simple React/Vite) only. + - Goal: Keep binary protocol compatibility via a Gateway while the Core Monolith evolves. + + ——— + + Architecture (Monolith First) + + [Game Client] ⇄ [Go Gateway] ⇄ [Go Core Monolith] + ├─ Lobby module + ├─ Matchmaking module + ├─ Game loop module + └─ Data access layer + + - Gateway: speaks binary protocol; maps opcodes to internal RPC/handlers. + - Core Monolith: simple Go service with clear module boundaries; no network protocol details here. + - Data: Postgres + Redis (optional) behind a DAL. + + ——— + + Phase 0: Baseline Observability (immediate) + + - Add request/session IDs at the lobby entrypoint. + - Add JSON logs (opcode, account, session, latency). + - Add packet capture/replay harness (proxy or gateway shim). + + This sets up parity testing for any rewrite. + + ——— + + Phase 1: Go Gateway (binary protocol fidelity) + + - Implement binary parsing and encoding in Go. + - Map each opcode to a handler interface. + - For now, proxy handlers to legacy C servers (so clients keep working). + + This becomes the compatibility anchor. + + ——— + + Phase 2: Go Core Monolith (first rewrite) + + - Implement core modules in Go and progressively cut traffic over. + - Start with lobby/auth/deck APIs (lower risk). + - Add game loop later with deterministic state checks. + + ——— + + Phase 3: Data Migration + + - Introduce Postgres schemas. + - Mirror writes (old → new), then flip reads. + - Eventually remove Couchbase. + + ——— + + Why this fits your preferences + + - Only Go + TS. + - Monolith first with future separation if needed. + - Fully off C while maintaining client compatibility. + diff --git a/PROTOCOL-NOTES.md b/PROTOCOL-NOTES.md new file mode 100644 index 0000000..1e35b8a --- /dev/null +++ b/PROTOCOL-NOTES.md @@ -0,0 +1,189 @@ +# Protocol Notes + +## Opcode Decoder Map (Client) + +Source: `hs-client/Assembly-CSharp/ConnectAPI.cs`. + +- `116` → `PongPacketDecoder` +- `169` → `Deadend` +- `167` → `DeadendUtil` +- `123` → `DebugConsoleCommand` +- `124` → `DebugConsoleResponse` +- `14` → `AllOptions` +- `5` → `DebugMessage` +- `17` → `EntityChoices` +- `13` → `EntitiesChosen` +- `16` → `GameSetup` +- `19` → `PowerHistory` +- `15` → `UserUI` +- `9` → `TurnTimer` +- `10` → `NAckOption` +- `12` → `GameCanceled` +- `23` → `ServerResult` +- `24` → `SpectatorNotify` +- `289` → `Disconnected` +- `202` → `DeckList` +- `207` → `Collection` +- `215` → `GetDeckContentsResponse` +- `216` → `DBAction` +- `217` → `DeckCreated` +- `218` → `DeckDeleted` +- `219` → `DeckRenamed` +- `212` → `ProfileNotices` +- `224` → `BoosterList` +- `226` → `BoosterContent` +- `208` → `GamesInfo` +- `231` → `ProfileDeckLimit` +- `262` → `ArcaneDustBalance` +- `278` → `GoldBalance` +- `233` → `ProfileProgress` +- `270` → `PlayerRecords` +- `271` → `RewardProgress` +- `232` → `MedalInfo` +- `241` → `ClientOptions` +- `246` → `DraftBeginning` +- `247` → `DraftRetired` +- `248` → `DraftChoicesAndContents` +- `249` → `DraftChosen` +- `288` → `DraftRewardsAcked` +- `251` → `DraftError` +- `252` → `Achieves` +- `285` → `ValidateAchieveResponse` +- `282` → `CancelQuestResponse` +- `264` → `GuardianVars` +- `260` → `CardValues` +- `258` → `BoughtSoldCard` +- `269` → `MassDisenchantResponse` +- `265` → `BattlePayStatusResponse` +- `295` → `ThirdPartyPurchaseStatusResponse` +- `272` → `PurchaseMethod` +- `275` → `CancelPurchaseResponse` +- `256` → `PurchaseResponse` +- `238` → `BattlePayConfigResponse` +- `280` → `PurchaseWithGoldResponse` +- `283` → `HeroXP` +- `254` → `NoOpPacketDecoder` +- `286` → `PlayQueue` +- `330` → `CheckAccountLicensesResponse` +- `331` → `CheckGameLicensesResponse` +- `236` → `CardBacks` +- `292` → `SetCardBackResponse` +- `296` → `SetProgressResponse` +- `299` → `TriggerEventResponse` +- `300` → `NotSoMassiveLoginReply` +- `304` → `AssetsVersionResponse` +- `306` → `AdventureProgressResponse` +- `307` → `UpdateLoginComplete` +- `311` → `AccountLicenseAchieveResponse` +- `315` → `SubscribeResponse` +- `316` → `TavernBrawlInfo` +- `317` → `TavernBrawlPlayerRecordResponse` +- `318` → `FavoriteHeroesResponse` +- `320` → `SetFavoriteHeroResponse` +- `324` → `DebugCommandResponse` +- `325` → `AccountLicensesInfoResponse` +- `326` → `GenericResponse` +- `328` → `ClientRequestResponse` +- `322` → `GetAssetResponse` + +## Opcode Encoder Map (Client → Server) + +Game server outbound (QueueGamePacket): + +- `22` → `SpectatorHandshake` +- `168` → `Handshake` +- `1` → `GetGameState` +- `115` → `Ping` +- `11` → `Concede` +- `3` → `ChooseEntities` +- `2` → `ChooseOption` +- `15` → `UserUI` (emote + mouse) +- `25` → `InviteToSpectate` +- `26` → `RemoveSpectators` +- `123` → `DebugConsoleCommand` + +Debug console outbound (QueueDebugPacket): + +- `124` → `DebugConsoleResponse` + +Util server outbound (ClientRequestManager/UtilOutbound): + +- `319` → `SetFavoriteHero` +- `279` → `PurchaseWithGold` +- `312` → `StartThirdPartyPurchase` +- `293` → `SubmitThirdPartyReceipt` +- `294` → `GetThirdPartyPurchaseStatus` +- `250` → `GetPurchaseMethod` +- `273` → `DoPurchase` +- `274` → `CancelPurchase` +- `237` → `GetBattlePayConfig` +- `255` → `GetBattlePayStatus` +- `268` → `MassDisenchantRequest` +- `235` → `DraftBegin` +- `242` → `DraftRetire` +- `287` → `DraftAckRewards` +- `244` → `DraftGetPicksAndContents` +- `245` → `DraftMakePick` +- `201` → `GetAccountInfo` +- `327` → `GenericRequestList` +- `205` → `UpdateLogin` +- `214` → `GetDeckContents` +- `209` → `CreateDeck` +- `210` → `DeleteDeck` +- `211` → `RenameDeck` +- `332` → `DeckSetTemplateSource` +- `222` → `DeckSetData` +- `213` → `AckNotice` +- `225` → `OpenBooster` +- `230` → `SetProgress` +- `298` → `TriggerLaunchDayEvent` +- `303` → `GetAssetsVersion` +- `308` → `AckWingProgress` +- `309` → `AcknowledgeBanner` +- `310` → `SetAdventureOptions` +- `223` → `AckCardSeen` +- `240` → `GetOptions` +- `239` → `SetOptions` +- `253` → `GetAchieves` +- `284` → `ValidateAchieve` +- `281` → `CancelQuest` +- `243` → `AckAchieveProgress` +- `297` → `CheckAccountLicenseAchieve` +- `305` → `GetAdventureProgress` +- `257` → `BuySellCard` +- `267` → `CheckAccountLicenses` +- `276` → `CheckGameLicenses` +- `291` → `SetCardBack` +- `321` → `GetAssetRequest` +- `329` → `Unsubscribe` +- `322` → `DebugCommandRequest` + +## Learnings + +- Client maintains deferred response maps for async requests in `Network.cs`. +- Opcode map is a key compatibility anchor for the Go gateway. + +## Protobuf Definitions (Source) + +The repo does not contain the protobuf class definitions for most message +types. They live in the client assemblies that were not decompiled here. +To generate Go structs, extract `.proto` from the client install: + +- `PegasusGame.dll` (game server packets) +- `PegasusUtil.dll` (utility/account/collection packets) +- `SpectatorProto.dll` (spectator packets) +- `BobNetProto.dll` (misc/legacy) +- `PegasusShared.dll` (shared types: `CardDef`, `BnetId`, enums) + +Suggested mapping (verify by decompiling those DLLs): + +- `PegasusGame`: `GameSetup`, `PowerHistory`, `EntityChoices`, `EntitiesChosen`, + `UserUI`, `TurnTimer`, `NAckOption`, `GameCanceled`, `ServerResult`, + `Disconnected`, `Handshake`, `GetGameState`, `Ping`, `Concede`, + `ChooseEntities`, `ChooseOption`. +- `SpectatorProto`: `SpectatorHandshake`, `SpectatorNotify`, `InviteToSpectate`, + `RemoveSpectators`. +- `PegasusUtil`: everything in the decoder/encoder maps that relates to + collection, login, decks, purchases, achievements, and assets (most of the + `UtilOutbound` messages). +- `BobNetProto`: `Deadend`, `DeadendUtil` (verify). diff --git a/README-dev.md b/README-dev.md deleted file mode 100644 index bcbbde1..0000000 --- a/README-dev.md +++ /dev/null @@ -1,61 +0,0 @@ -## Development Environment Recommendations - -This stack is most maintainable when the dev environment is repeatable and -configuration is centralized. The current scripts assume Ubuntu 16.04 and -hard-coded dependencies. The goal here is to make the environment predictable -without touching the protocol or rewriting core services. - -Key recommendations: - -- Standardize the dev workflow with containers (prefer `docker-compose`). -- Centralize ports, credentials, and paths into a single `.env` file. -- Provide a single entrypoint script to build/start/stop/log services. -- Keep database and service initialization scripted and idempotent. - -## Focused Plan (Maintainability Only) - -Phase 1: Audit and Baseline (1–3 days) - -- Inventory the current scripts and dependencies in `hearthmod/`. -- Confirm service ports and startup order (gameserver → lobbyserver → web). -- Capture required Couchbase buckets and data seed steps. - -Phase 2: Repeatable Local Environment (1–2 weeks) - -- Add `docker-compose.yml` to orchestrate Couchbase, gameserver, lobbyserver, - and web services. -- Add `.env.example` for all required configuration values. -- Create a single `./dev` or `./scripts/dev.sh` entrypoint: - - `dev build` (build C services and web assets) - - `dev up` (start all services) - - `dev down` (stop services) - - `dev logs` (tail logs) - -Phase 3: Configuration Cleanup (1–2 weeks) - -- Replace hard-coded ports and credentials with env-configured values. -- Add a minimal config loader in `hm_web` and in C servers (config file or env). -- Update nginx config to use env-substituted paths. - -Phase 4: Minimal Tests + Docs (1 week) - -- Add a simple smoke test script that validates service ports and Couchbase - connectivity. -- Document the workflow in `README-dev.md` and update top-level README with a - pointer. - -## Deliverables Checklist - -- `docker-compose.yml` with defined services and volumes -- `.env.example` with required config variables -- A single dev entrypoint script -- Updated docs (this file) and a quick-start section - -## Non-Goals (Intentionally Deferred) - -- Updating Hearthstone protocol compatibility -- Rewriting the database or switching away from Couchbase -- Migrating C services to a different language - -If you want, I can start by drafting the `docker-compose.yml` and a `dev` script -tailored to the current repo layout. diff --git a/README.md b/README.md index c4de723..a0e59c9 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,8 @@ behavior while enabling modernization behind compatibility boundaries. - Keep configuration centralized (env or config files). - Prefer a single entrypoint script or container orchestration for local dev. - Add small smoke tests to validate service health and database connectivity. + +## Protocol Notes + +See `PROTOCOL-NOTES.md` for the current opcode decoder map and client-derived +learnings used for compatibility work. diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 843f965..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,88 +0,0 @@ -version: "3.8" - -services: - couchbase: - image: ${COUCHBASE_IMAGE:-couchbase:community-6.6.0} - env_file: .env - ports: - - "8091-8096:8091-8096" - - "11210:11210" - volumes: - - couchbase_data:/opt/couchbase/var - - gameserver: - build: - context: . - dockerfile: docker/Dockerfile.gameserver - env_file: .env - depends_on: - - couchbase - ports: - - "3724:3724" - volumes: - - ./hm_gameserver:/workspace/hm_gameserver - - ./hm_base:/workspace/hm_base - - ./hm_database:/workspace/hm_database - - ./hm_log:/workspace/hm_log - working_dir: /workspace - command: ["./hm_gameserver/hm_gameserver", "--log=/workspace/hm_log/hm_gameserver.log"] - - lobbyserver: - build: - context: . - dockerfile: docker/Dockerfile.lobbyserver - env_file: .env - depends_on: - - couchbase - - gameserver - ports: - - "45678:45678" - volumes: - - ./hm_lobbyserver:/workspace/hm_lobbyserver - - ./hm_base:/workspace/hm_base - - ./hm_database:/workspace/hm_database - - ./hm_log:/workspace/hm_log - working_dir: /workspace - command: - - "./hm_lobbyserver/hm_lobbyserver" - - "--gameserver=gameserver" - - "--log=/workspace/hm_log/hm_lobbyserver.log" - - sunwell: - build: - context: ./hm_sunwell - dockerfile: docker/Dockerfile.sunwell - env_file: .env - ports: - - "3000:3000" - - web: - build: - context: ./hm_web - dockerfile: docker/Dockerfile.web - env_file: .env - depends_on: - - couchbase - - sunwell - ports: - - "9002:9002" - volumes: - - ./hm_web:/workspace/hm_web - - ./hm_sunwell:/workspace/hm_sunwell - working_dir: /workspace/hm_web - command: ["python", "app.py"] - - nginx: - build: - context: ./hm_nginx - dockerfile: docker/Dockerfile.nginx - env_file: .env - depends_on: - - web - ports: - - "8080:80" - volumes: - - ./hm_web/static:/usr/local/web/static:ro - -volumes: - couchbase_data: diff --git a/hs-client b/hs-client new file mode 160000 index 0000000..67b3ddf --- /dev/null +++ b/hs-client @@ -0,0 +1 @@ +Subproject commit 67b3ddf12ce2fe07a121f41229c618af49c77a0a