Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b8a45b4324 |
+257
@@ -0,0 +1,257 @@
|
|||||||
|
# ---> Python
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# UV
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
#uv.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||||
|
.pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
# Ruff stuff:
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# PyPI configuration file
|
||||||
|
.pypirc
|
||||||
|
|
||||||
|
# ---> C
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Linker output
|
||||||
|
*.ilk
|
||||||
|
*.map
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
*.su
|
||||||
|
*.idb
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Kernel Module Compile Results
|
||||||
|
*.mod*
|
||||||
|
*.cmd
|
||||||
|
.tmp_versions/
|
||||||
|
modules.order
|
||||||
|
Module.symvers
|
||||||
|
Mkfile.old
|
||||||
|
dkms.conf
|
||||||
|
|
||||||
|
# ---> Go
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# env file
|
||||||
|
.env
|
||||||
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Repository Guidelines
|
|
||||||
|
|
||||||
## Project Structure & Module Organization
|
|
||||||
|
|
||||||
- `hearthmod/` contains legacy orchestration scripts and original docs.
|
|
||||||
- Core C services live in `hm_gameserver/`, `hm_lobbyserver/`, and shared code in `hm_base/`.
|
|
||||||
- Web UI is in `hm_web/` (Python `web.py`, templates, and static assets).
|
|
||||||
- Card rendering lives in `hm_sunwell/` (Node-based renderer).
|
|
||||||
- TLS proxy and web server components live in `hm_stud/` and `hm_nginx/`.
|
|
||||||
- Database snapshot/seed data is in `hm_database/`.
|
|
||||||
|
|
||||||
## Build, Test, and Development Commands
|
|
||||||
|
|
||||||
- `make -C hm_base target=game` builds the shared base library.
|
|
||||||
- `make -C hm_gameserver` builds the gameserver binary.
|
|
||||||
- `make -C hm_lobbyserver` builds the lobbyserver binary.
|
|
||||||
- `bash hearthmod/host_ctl_ubuntu.sh start <ip>` starts the legacy stack.
|
|
||||||
|
|
||||||
When adding new workflows, prefer a single entrypoint script (e.g., `./dev`) or
|
|
||||||
container orchestration; keep legacy scripts untouched unless required.
|
|
||||||
|
|
||||||
## Coding Style & Naming Conventions
|
|
||||||
|
|
||||||
- C code uses 4-space indentation and snake_case identifiers.
|
|
||||||
- Python uses 4-space indentation; prefer explicit names over abbreviations.
|
|
||||||
- Avoid one-letter variable names except for simple loop counters.
|
|
||||||
- Centralize ports and credentials in config or env variables.
|
|
||||||
|
|
||||||
## Testing Guidelines
|
|
||||||
|
|
||||||
- There is no dedicated test suite today.
|
|
||||||
- If you add tests, keep them close to the component (e.g., `hm_web/tests/`).
|
|
||||||
- Favor smoke tests that validate service ports and Couchbase connectivity.
|
|
||||||
|
|
||||||
## Commit & Pull Request Guidelines
|
|
||||||
|
|
||||||
- Git history is limited here; use concise imperative commit messages.
|
|
||||||
- Pull requests should include a short summary and testing notes.
|
|
||||||
- If you touch configuration or workflows, update the README.
|
|
||||||
|
|
||||||
## Modernization Roadmap (Maintainability)
|
|
||||||
|
|
||||||
The long-term goal is to replace components one at a time while keeping protocol
|
|
||||||
compatibility. Suggested phases:
|
|
||||||
|
|
||||||
- Baseline: document current behavior and add logs/health checks.
|
|
||||||
- Gateway: add a thin protocol gateway to route traffic to legacy services.
|
|
||||||
- Lobby: reimplement lobby features behind the gateway with canary routing.
|
|
||||||
- Game: reimplement game loop and compare state snapshots for parity.
|
|
||||||
- Data: introduce a new data layer, mirror writes, then flip reads.
|
|
||||||
- Decommission: remove unused legacy components once traffic is migrated.
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
# 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).
|
|
||||||
@@ -1,77 +1,2 @@
|
|||||||
# Hearthmod Modernization Notes
|
# hsmod
|
||||||
|
|
||||||
This workspace contains the Hearthmod stack (game server, lobby server, web UI,
|
|
||||||
and supporting components). The current goal is to stabilize the development
|
|
||||||
environment and improve maintainability without changing client protocol
|
|
||||||
compatibility.
|
|
||||||
|
|
||||||
## Component-by-Component Replacement Plan
|
|
||||||
|
|
||||||
Replacing services incrementally is the safest path. It preserves existing
|
|
||||||
behavior while enabling modernization behind compatibility boundaries.
|
|
||||||
|
|
||||||
1. **Baseline and observability**: document startup order, ports, and data flows;
|
|
||||||
add basic health checks and structured logs.
|
|
||||||
2. **Gateway layer**: introduce a thin gateway that speaks the legacy protocol
|
|
||||||
and routes to existing lobby/game services.
|
|
||||||
3. **Lobby replacement**: implement lobby endpoints behind the gateway, canary
|
|
||||||
traffic to validate parity.
|
|
||||||
4. **Game server replacement**: implement the game loop and compare deterministic
|
|
||||||
state snapshots with the legacy server before shifting traffic.
|
|
||||||
5. **Data layer swap**: add a DAL in new services, mirror writes to a new store,
|
|
||||||
then flip reads after validation.
|
|
||||||
6. **Decommission legacy**: remove unused services and dependencies once traffic
|
|
||||||
is fully migrated.
|
|
||||||
|
|
||||||
## Maintainability Priorities
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
## Local Dev (C Services)
|
|
||||||
|
|
||||||
If Couchbase is running on `localhost:8091` with bucket `hbs` (password `aci`),
|
|
||||||
use the repo-local entrypoint to build and run the core servers:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./dev build
|
|
||||||
./dev start
|
|
||||||
./dev logs
|
|
||||||
```
|
|
||||||
|
|
||||||
Override targets via env vars:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
HM_GAMESERVER_IP=127.0.0.1 HM_GAMESERVER_PORT=3724 ./dev start
|
|
||||||
```
|
|
||||||
|
|
||||||
Stop with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./dev stop
|
|
||||||
```
|
|
||||||
|
|
||||||
For the full Ubuntu install flow (Couchbase + web + client), use
|
|
||||||
`hearthmod/host_ctl_ubuntu.sh`.
|
|
||||||
|
|
||||||
## Docker Dev
|
|
||||||
|
|
||||||
Docker runs Couchbase plus the C servers. Start with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./dev docker-build
|
|
||||||
./dev docker-start
|
|
||||||
./dev docker-logs
|
|
||||||
```
|
|
||||||
|
|
||||||
Stop with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
./dev docker-stop
|
|
||||||
```
|
|
||||||
|
|
||||||
## Protocol Notes
|
|
||||||
|
|
||||||
See `PROTOCOL-NOTES.md` for the current opcode decoder map and client-derived
|
|
||||||
learnings used for compatibility work.
|
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
||||||
|
|
||||||
LOG_DIR=${HM_LOG_DIR:-"$ROOT_DIR/hm_log"}
|
|
||||||
GAMESERVER_IP=${HM_GAMESERVER_IP:-"127.0.0.1"}
|
|
||||||
GAMESERVER_PORT=${HM_GAMESERVER_PORT:-"3724"}
|
|
||||||
|
|
||||||
GAMESERVER_BIN="$ROOT_DIR/hm_gameserver/hm_gameserver"
|
|
||||||
LOBBYSERVER_BIN="$ROOT_DIR/hm_lobbyserver/hm_lobbyserver"
|
|
||||||
|
|
||||||
GAMESERVER_PID="$LOG_DIR/hm_gameserver.pid"
|
|
||||||
LOBBYSERVER_PID="$LOG_DIR/hm_lobbyserver.pid"
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<EOF
|
|
||||||
Usage: ./dev <command>
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
build Build gameserver and lobbyserver
|
|
||||||
start Start gameserver and lobbyserver
|
|
||||||
stop Stop running servers
|
|
||||||
status Show server status
|
|
||||||
logs Tail server logs
|
|
||||||
docker-build Build docker images
|
|
||||||
docker-start Start dockerized stack
|
|
||||||
docker-stop Stop dockerized stack
|
|
||||||
docker-status Show docker stack status
|
|
||||||
docker-logs Tail dockerized logs
|
|
||||||
|
|
||||||
Environment:
|
|
||||||
HM_LOG_DIR Log directory (default: ./hm_log)
|
|
||||||
HM_GAMESERVER_IP Lobby points to this IP (default: 127.0.0.1)
|
|
||||||
HM_GAMESERVER_PORT Lobby points to this port (default: 3724)
|
|
||||||
HM_SKIP_COUCHBASE_CHECK=1 Skip Couchbase availability check
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
docker_compose() {
|
|
||||||
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
|
||||||
docker compose "$@"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if command -v docker-compose >/dev/null 2>&1; then
|
|
||||||
docker-compose "$@"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "docker compose not found" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
require_bin() {
|
|
||||||
if ! command -v "$1" >/dev/null 2>&1; then
|
|
||||||
echo "Missing required binary: $1" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_couchbase() {
|
|
||||||
if [ "${HM_SKIP_COUCHBASE_CHECK:-}" = "1" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if command -v curl >/dev/null 2>&1; then
|
|
||||||
if ! curl -s --max-time 2 http://localhost:8091/pools >/dev/null; then
|
|
||||||
echo "Couchbase not reachable on localhost:8091." >&2
|
|
||||||
echo "Start Couchbase and restore bucket 'hbs' or set HM_SKIP_COUCHBASE_CHECK=1." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "curl not found; cannot verify Couchbase availability." >&2
|
|
||||||
echo "Install curl or set HM_SKIP_COUCHBASE_CHECK=1." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
build() {
|
|
||||||
require_bin make
|
|
||||||
make -C "$ROOT_DIR/hm_gameserver"
|
|
||||||
make -C "$ROOT_DIR/hm_lobbyserver"
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
check_couchbase
|
|
||||||
mkdir -p "$LOG_DIR"
|
|
||||||
|
|
||||||
if [ ! -x "$GAMESERVER_BIN" ] || [ ! -x "$LOBBYSERVER_BIN" ]; then
|
|
||||||
echo "Binaries missing; run ./dev build first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$GAMESERVER_PID" ] || [ -f "$LOBBYSERVER_PID" ]; then
|
|
||||||
echo "PID files exist; run ./dev stop first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
nohup "$GAMESERVER_BIN" > "$LOG_DIR/hm_gameserver.out" 2>&1 &
|
|
||||||
echo $! > "$GAMESERVER_PID"
|
|
||||||
|
|
||||||
nohup "$LOBBYSERVER_BIN" \
|
|
||||||
--gameserver="$GAMESERVER_IP" \
|
|
||||||
--gameserver_port="$GAMESERVER_PORT" \
|
|
||||||
> "$LOG_DIR/hm_lobbyserver.out" 2>&1 &
|
|
||||||
echo $! > "$LOBBYSERVER_PID"
|
|
||||||
|
|
||||||
echo "Servers started. Logs in $LOG_DIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
if [ -f "$GAMESERVER_PID" ]; then
|
|
||||||
kill "$(cat "$GAMESERVER_PID")" 2>/dev/null || true
|
|
||||||
rm -f "$GAMESERVER_PID"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$LOBBYSERVER_PID" ]; then
|
|
||||||
kill "$(cat "$LOBBYSERVER_PID")" 2>/dev/null || true
|
|
||||||
rm -f "$LOBBYSERVER_PID"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
status() {
|
|
||||||
if [ -f "$GAMESERVER_PID" ] && kill -0 "$(cat "$GAMESERVER_PID")" 2>/dev/null; then
|
|
||||||
echo "hm_gameserver running (pid $(cat "$GAMESERVER_PID"))"
|
|
||||||
else
|
|
||||||
echo "hm_gameserver not running"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$LOBBYSERVER_PID" ] && kill -0 "$(cat "$LOBBYSERVER_PID")" 2>/dev/null; then
|
|
||||||
echo "hm_lobbyserver running (pid $(cat "$LOBBYSERVER_PID"))"
|
|
||||||
else
|
|
||||||
echo "hm_lobbyserver not running"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
logs() {
|
|
||||||
tail -f "$LOG_DIR/hm_gameserver.out" "$LOG_DIR/hm_lobbyserver.out"
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${1:-}" in
|
|
||||||
build)
|
|
||||||
build
|
|
||||||
;;
|
|
||||||
start)
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
status
|
|
||||||
;;
|
|
||||||
logs)
|
|
||||||
logs
|
|
||||||
;;
|
|
||||||
docker-build)
|
|
||||||
docker_compose build
|
|
||||||
;;
|
|
||||||
docker-start)
|
|
||||||
docker_compose up -d
|
|
||||||
;;
|
|
||||||
docker-stop)
|
|
||||||
docker_compose down
|
|
||||||
;;
|
|
||||||
docker-status)
|
|
||||||
docker_compose ps
|
|
||||||
;;
|
|
||||||
docker-logs)
|
|
||||||
docker_compose logs -f --tail=200 gameserver lobbyserver
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
|
||||||
couchbase:
|
|
||||||
image: couchbase:community-6.6.0
|
|
||||||
ports:
|
|
||||||
- "8091-8096:8091-8096"
|
|
||||||
- "11210:11210"
|
|
||||||
volumes:
|
|
||||||
- couchbase-data:/opt/couchbase/var
|
|
||||||
|
|
||||||
couchbase-init:
|
|
||||||
image: couchbase:community-6.6.0
|
|
||||||
depends_on:
|
|
||||||
- couchbase
|
|
||||||
entrypoint: ["/bin/bash", "/init-couchbase.sh"]
|
|
||||||
volumes:
|
|
||||||
- ./docker/couchbase-init.sh:/init-couchbase.sh:ro
|
|
||||||
environment:
|
|
||||||
COUCHBASE_HOST: couchbase
|
|
||||||
COUCHBASE_ADMIN_USER: Administrator
|
|
||||||
COUCHBASE_ADMIN_PASS: password
|
|
||||||
COUCHBASE_BUCKET: hbs
|
|
||||||
COUCHBASE_BUCKET_PASS: aci
|
|
||||||
|
|
||||||
gameserver:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
depends_on:
|
|
||||||
- couchbase-init
|
|
||||||
working_dir: /workspace
|
|
||||||
volumes:
|
|
||||||
- .:/workspace
|
|
||||||
environment:
|
|
||||||
HM_COUCHBASE_HOST: couchbase
|
|
||||||
command: ["bash", "-lc", "make -C hm_gameserver && ./hm_gameserver/hm_gameserver"]
|
|
||||||
ports:
|
|
||||||
- "3724:3724"
|
|
||||||
|
|
||||||
lobbyserver:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
depends_on:
|
|
||||||
- couchbase-init
|
|
||||||
- gameserver
|
|
||||||
working_dir: /workspace
|
|
||||||
volumes:
|
|
||||||
- .:/workspace
|
|
||||||
environment:
|
|
||||||
HM_COUCHBASE_HOST: couchbase
|
|
||||||
command: ["bash", "-lc", "make -C hm_lobbyserver && ./hm_lobbyserver/hm_lobbyserver --gameserver=gameserver --gameserver_port=3724"]
|
|
||||||
ports:
|
|
||||||
- "45678:45678"
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
couchbase-data:
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
FROM ubuntu:18.04
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
gnupg \
|
|
||||||
software-properties-common \
|
|
||||||
&& add-apt-repository universe \
|
|
||||||
&& curl -fsSL https://packages.couchbase.com/ubuntu/couchbase.key \
|
|
||||||
| gpg --dearmor -o /usr/share/keyrings/couchbase.gpg \
|
|
||||||
&& echo "deb [signed-by=/usr/share/keyrings/couchbase.gpg] https://packages.couchbase.com/ubuntu bionic bionic/main" \
|
|
||||||
> /etc/apt/sources.list.d/couchbase.list \
|
|
||||||
&& apt-get update && apt-get install -y \
|
|
||||||
build-essential \
|
|
||||||
cmake \
|
|
||||||
libev-dev \
|
|
||||||
libjson-c-dev \
|
|
||||||
libcouchbase-dev \
|
|
||||||
python \
|
|
||||||
python-pip \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /workspace
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
HOST=${COUCHBASE_HOST:-couchbase}
|
|
||||||
ADMIN_USER=${COUCHBASE_ADMIN_USER:-Administrator}
|
|
||||||
ADMIN_PASS=${COUCHBASE_ADMIN_PASS:-password}
|
|
||||||
BUCKET=${COUCHBASE_BUCKET:-hbs}
|
|
||||||
BUCKET_PASS=${COUCHBASE_BUCKET_PASS:-aci}
|
|
||||||
|
|
||||||
echo "Waiting for Couchbase at ${HOST}:8091..."
|
|
||||||
until curl -s "http://${HOST}:8091/pools" >/dev/null; do
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
status=$(curl -s -o /dev/null -w "%{http_code}" "http://${HOST}:8091/pools/default")
|
|
||||||
if [ "$status" != "200" ]; then
|
|
||||||
couchbase-cli cluster-init -c "${HOST}:8091" \
|
|
||||||
--cluster-username "${ADMIN_USER}" \
|
|
||||||
--cluster-password "${ADMIN_PASS}" \
|
|
||||||
--cluster-ramsize 512 \
|
|
||||||
--services data
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! couchbase-cli bucket-list -c "${HOST}:8091" -u "${ADMIN_USER}" -p "${ADMIN_PASS}" | grep -q "${BUCKET}"; then
|
|
||||||
couchbase-cli bucket-create -c "${HOST}:8091" \
|
|
||||||
-u "${ADMIN_USER}" -p "${ADMIN_PASS}" \
|
|
||||||
--bucket="${BUCKET}" \
|
|
||||||
--bucket-password="${BUCKET_PASS}" \
|
|
||||||
--bucket-type=couchbase \
|
|
||||||
--bucket-ramsize=200 \
|
|
||||||
--bucket-replica=1 \
|
|
||||||
--wait
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Couchbase initialized."
|
|
||||||
-1
Submodule hearthmod deleted from a15949d603
-1
Submodule hm_base deleted from 48079d6c74
-1
Submodule hm_client deleted from e4a2c8baac
-1
Submodule hm_database deleted from 8f07d16612
Submodule hm_gameserver deleted from 4bf34a228b
Submodule hm_lobbyserver deleted from 6537d9989a
-1
Submodule hm_nginx deleted from 8fac3084e2
-1
Submodule hm_stud deleted from 5ef075eda2
-1
Submodule hm_sunwell deleted from 8d9f22cdb2
-1
Submodule hm_web deleted from a198222e7c
-1
Submodule hs-client deleted from 67b3ddf12c
Reference in New Issue
Block a user