Commit Graph

1400 Commits

Author SHA1 Message Date
Houmgaor
263e207ba2 docs: clarify object ID rework descriptions and remove stale comment
Replace vague "Alpelo object system backport" references in CHANGELOG
and AUTHORS with accurate descriptions of the per-session object ID
allocation rework. Remove stale test comment referencing the deleted
NextObjectID method.
2026-02-20 18:42:08 +01:00
Houmgaor
5f3c843082 refactor(config): eliminate ErupeConfig global variable
Replace the mutable global `_config.ErupeConfig` with dependency
injection across 79 files. Config is now threaded through existing
paths: `ClientContext.RealClientMode` for packet encoding, `s.server.
erupeConfig` for channel handlers, and explicit parameters for utility
functions. This removes hidden coupling, enables test parallelism
without global save/restore, and prevents low-level packages from
reaching up to the config layer.

Key changes:
- Enrich ClientContext with RealClientMode for packet files
- Add mode parameter to CryptConn, mhfitem, mhfcourse functions
- Convert handlers_commands init() to lazy sync.Once initialization
- Delete global var, init(), and helper functions from config.go
- Update all tests to pass config explicitly
2026-02-20 17:07:42 +01:00
Houmgaor
8c7e95ce18 Merge pull request #159 from Mezeporta/feat/independent-channel-servers
Summary

  - Each channel server now runs as a fully independent instance with its own listener, goroutines, and
   state — one channel crashing or shutting down no longer affects the others
  - Introduces a ChannelRegistry interface for cross-channel operations (find session, disconnect user,
   worldcast) replacing direct iteration over a shared []*Server slice
  - Adds cmd/protbot, a headless MHF protocol bot that exercises the full sign → entrance → channel
  flow for automated testing
  - Fixes several data races and panics found by the race detector during isolation testing

  Changes

  Channel server isolation (server/channelserver/)
  - ChannelRegistry interface + LocalChannelRegistry implementation for cross-channel lookups
  - done channel for clean goroutine shutdown signaling, idempotent Shutdown()
  - Race-free acceptClients/manageSessions using select on done instead of closing acceptConns
  - invalidateSessions rewritten with proper locking (snapshot under lock, process outside)
  - logoutPlayer guards nil DB and logs errors instead of panicking
  - Session loops use per-server erupeConfig instead of global _config.ErupeConfig
  - Per-channel Enabled flag in config for selectively disabling channels

  Protocol bot (cmd/protbot/)
  - Standalone Blowfish connection package (no dependency on server config)
  - Sign, entrance, and channel protocol implementations
  - 5 scenario actions: login, lobby, session, chat, quests
  - 19 unit tests covering packet building, parsing, and connection handling

  Bug fixes
  - Nil decompSave panic on disconnect before character data loads
  - Docker Postgres 18 volume mount path (/var/lib/postgresql/ not /data/)

  Test plan

  - go test -race ./... passes (27 packages, 0 races)
  - 5 channel isolation tests verify: independent shutdown, listener failure recovery, session panic
  containment, cross-channel registry after shutdown, stage isolation
  - Protbot live-tested against Docker stack (all 5 actions)
  - Existing config.json files work unchanged (Enabled defaults to false but config.example.json sets
  it explicitly)
2026-02-20 16:10:32 +01:00
Houmgaor
c8996e0672 chore: add protbot binary to gitignore 2026-02-20 14:38:35 +01:00
Houmgaor
eab7d1fc4f fix(channelserver): eliminate data races in shutdown and session lifecycle
The channel server had several concurrency issues found by the race
detector during isolation testing:

- acceptClients could send on a closed acceptConns channel during
  shutdown, causing a panic. Replace close(acceptConns) with a done
  channel and select-based shutdown signaling in both acceptClients
  and manageSessions.
- invalidateSessions read isShuttingDown and iterated sessions without
  holding the lock. Rewrite with ticker + done channel select and
  snapshot sessions under lock before processing timeouts.
- sendLoop/recvLoop accessed global _config.ErupeConfig.LoopDelay
  which races with tests modifying the global. Use the per-server
  erupeConfig instead.
- logoutPlayer panicked on DB errors and crashed on nil DB (no-db
  test scenarios). Guard with nil check and log errors instead.
- Shutdown was not idempotent, double-calling caused double-close
  panic on done channel.

Add 5 channel isolation tests verifying independent shutdown,
listener failure, session panic recovery, cross-channel registry
after shutdown, and stage isolation.
2026-02-20 14:36:37 +01:00
Houmgaor
486be65a38 fix(protbot,channelserver): fix sign protocol and entrance parsing, guard nil save data
The protbot sent "DSGN:\x00" as the sign request type, but the server
strips the last 3 characters as a version suffix. Send "DSGN:041"
(ZZ client mode 41) to match the real client format.

The entrance channel entry parser read 14 bytes for remaining fields
but the server writes 18 bytes (9 uint16, not 7), causing a panic
when parsing the server list.

The channel server panicked on disconnect when a session had no
decompressed save data (e.g. protbot or early client disconnect).
Guard Save() against nil decompSave.

Also fix docker-compose volume mount for Postgres 18 which changed
its data directory layout.
2026-02-20 14:17:40 +01:00
Houmgaor
0e84377e21 feat(protbot): add headless MHF protocol bot as cmd/protbot
Copy MHBridge into the Erupe module as cmd/protbot/ so it can be
built, tested, and maintained alongside the server. The bot
implements the full sign → entrance → channel login flow and
supports lobby entry, chat, session setup, and quest enumeration.

The conn/ package keeps its own Blowfish crypto primitives to avoid
importing erupe-ce/config (which requires a config file at init).
2026-02-20 02:49:23 +01:00
Houmgaor
754b5a3bff feat(channelserver): decouple channel servers for independent operation (#33)
Enable multiple Erupe instances to share a single PostgreSQL database
without destroying each other's state, fix existing data races in
cross-channel access, and lay groundwork for future distributed
channel server deployments.

Phase 1 — DB safety:
- Scope DELETE FROM servers/sign_sessions to this instance's server IDs
- Fix ci++ bug where failed channel start shifted subsequent IDs

Phase 2 — Fix data races in cross-channel access:
- Lock sessions map in FindSessionByCharID and DisconnectUser
- Lock stagesLock in handleMsgSysLockGlobalSema
- Snapshot sessions/stages under lock in TransitMessage types 1-4
- Lock channel when finding mail notification targets

Phase 3 — ChannelRegistry interface:
- Define ChannelRegistry interface with 7 cross-channel operations
- Implement LocalChannelRegistry with proper locking
- Add SessionSnapshot/StageSnapshot immutable copy types
- Delegate WorldcastMHF, FindSessionByCharID, DisconnectUser to Registry
- Migrate LockGlobalSema and guild mail handlers to use Registry
- Add comprehensive tests including concurrent access

Phase 4 — Per-channel enable/disable:
- Add Enabled *bool to EntranceChannelInfo (nil defaults to true)
- Skip disabled channels in startup loop, preserving ID stability
- Add IsEnabled() helper with backward-compatible default
- Update config.example.json with Enabled field
2026-02-19 18:13:34 +01:00
Houmgaor
ba9fce153d refactor(mhfpacket): rename MsgSysCreateStage.Unk0 to CreateType
Wii U decompilation of all 6 callers of snj_stage_create confirms
the field distinguishes new stage creation (1) from entering an
existing stage (2): lobby/myhouse/quest pass 1, guild room and
move operations pass 2.
2026-02-19 00:46:57 +01:00
Houmgaor
c2eba51b29 fix(channelserver): add max-size guards to binary blob save handlers
A malicious or buggy client could send arbitrarily large payloads
that get written directly to PostgreSQL, wasting disk and memory.
Each save handler now rejects payloads exceeding a generous upper
bound derived from the known data format sizes.

Covers all remaining items from #158: partner, hunternavi,
savemercenary, scenariodata, platedata, platebox, platemyset,
rengokudata, mezfes, savefavoritequest, house_furniture, mission.

Closes #158
2026-02-19 00:28:28 +01:00
Houmgaor
604d53d6d7 fix(channelserver): validate packet fields before use in handlers
Several handlers used packet fields as array indices or SQL column
names without bounds checking, allowing crafted packets to panic the
server or produce malformed SQL.

Panic fixes (high severity):
- handlers_mail: bounds check AccIndex against mailList length
- handlers_misc: validate ArmourID >= 10000 and MogType <= 4
- handlers_mercenary: check RawDataPayload length before slicing
- handlers_house: check RawDataPayload length in SaveDecoMyset
- handlers_register: guard empty RawDataPayload in OperateRegister

SQL column name fixes (medium severity):
- handlers_misc: early return on unknown PointType
- handlers_items: reject unknown StampType in weekly stamp handlers
- handlers_achievement: cap AchievementID at 32
- handlers_goocoo: skip goocoo.Index > 4
- handlers_house: cap BoxIndex for warehouse operations
- handlers_tower: fix MissionIndex=0 bypassing normalization guard
2026-02-19 00:23:04 +01:00
Houmgaor
99e544e0cf perf(channelserver): move UserBinary and minidata to memory-only
UserBinary type1-5 and EnhancedMinidata are transient session state
resent by the client on every login. Persisting them to the DB on
every set was unnecessary I/O. Both are now served exclusively from
server-scoped in-memory maps (userBinaryParts, minidataParts).

Includes a schema migration to drop the now-unused type2/type3
columns from user_binary and minidata column from characters.

Ref #158
2026-02-19 00:05:20 +01:00
Houmgaor
b2b1c426a5 fix(channelserver): validate client binary blobs before saving
- Reject BinaryType outside 1-5 in SetUserBinary to prevent
  dynamic column name with unchecked client input
- Check rengoku payload length before DB write and fixed-offset
  reads to prevent panic on short payloads
- Require MercData >= 4 bytes before ReadUint32 to prevent panic

Ref: Mezeporta/Erupe#158
2026-02-18 23:39:29 +01:00
Houmgaor
2ac8c8cf62 fix(stage): return valid response for empty stage binary requests
GetStageBinary and WaitStageBinary silently dropped the ACK when
the requested stage did not exist, leaving the client waiting
indefinitely. Additionally, BinaryType1 == 4 and unknown binary
types returned a completely empty response (zero bytes), which
earlier clients cannot parse as a counted structure.

Return a 4-byte zero response (empty entry count) in all fallback
paths so the client always receives a valid ACK it can parse.
2026-02-18 23:02:44 +01:00
Houmgaor
151af1a202 refactor(mhfpacket): rename MsgMhfUpdateHouse.Unk1 to HasPassword
Verified via Wii U decompilation of putUpdate_house: the field is set
to 0 when no password is provided, and 1 when a password string is
present. The previous comment "Always 0x01" was inaccurate.
2026-02-18 22:57:18 +01:00
Houmgaor
0d07a1f698 refactor(mhfpacket): rename 15 Unk fields with identified meanings
Replace unknown field names with descriptive names based on handler
logic analysis, switch dispatch patterns, DB query context, and
inline comments:

- ObjectHandleID, IsQuest, ItemIDCount, MaxCount, TokenLength,
  FormatVersion, LogoutType (high confidence from comments/constants)
- QueryType, DataType, MissionIndex, CheckOnly, RequestType,
  ExchangeType, TournamentID (confirmed by handler switch/if usage)

Also fix MsgSysLogout.Build calling ReadUint8 instead of WriteUint8.
2026-02-18 21:48:08 +01:00
Houmgaor
2bd5f98f32 docs: add doc.go files and godoc comments to all packages
Add package-level documentation (doc.go) to all 22 first-party
packages and godoc comments to ~150 previously undocumented
exported symbols across common/, network/, and server/.
2026-02-18 21:39:13 +01:00
Houmgaor
b9cb274ced refactor(festa): rename reward fields Unk5/6/7 to MinHR/MinSR/MinGR
Ghidra decompilation of hf_gp_main in the Wii U binary revealed that
these three fields are reward eligibility thresholds checked against
the player's Hunter Rank, max Skill Rank, and G Rank respectively.
2026-02-18 21:35:55 +01:00
Houmgaor
c34f682f70 fix(festa): correct reward field version gate from G1 to G3
The extra reward fields (Unk5, Unk6, Unk7) in the InfoFesta response
were gated at >= G1, but G1 clients do not expect these 5 extra bytes
per reward entry. This caused the entire packet after the rewards
section to be misaligned, corrupting MaximumFP, leaderboards, and
bonus rates — which broke the festa UI including trial voting.

Wii U disassembly of import_festa_info (0x02C470EC, 1068 bytes)
confirms G3-Z2 reads these fields. G1 binary analysis shows only
8 festa packets (vs 12 in ZZ), and the intermediate/personal prize
systems were not added until G5.2/G7 respectively.
2026-02-18 20:59:58 +01:00
Houmgaor
3e4f3a3bc6 chore(api): remove unused test helpers
Remove MockDB, NewTestAPIServer, CleanupTestData, and
GetTestDBConnection — all flagged as unreachable by deadcode.
2026-02-18 20:52:18 +01:00
Houmgaor
9e8dc034be refactor(channelserver): split handlers.go into goocoo, scenario, and misc files
Replace the grab-bag handlers.go with thematically organized files:
- handlers_goocoo.go: Goocoo/Guacot pet handlers
- handlers_scenario.go: Scenario counter struct and handler
- handlers_misc.go: remaining unrelated handlers (etc points, earth,
  equip skin history, trend weapons, minidata, lobby crowd, etc.)
2026-02-18 19:42:59 +01:00
Houmgaor
e5802a053e refactor(channelserver): extract CharacterSaveData model into model_character.go
Move SavePointer type/constants, CharacterSaveData struct, getPointers,
Compress, Decompress, and save data serialization methods out of
handlers_character.go into a dedicated model file.
2026-02-18 19:40:33 +01:00
Houmgaor
264b0ced6b refactor(channelserver): extract Raviente struct and methods into raviente.go
Consolidate Raviente-related code from sys_channel_server.go and
handlers_register.go into a dedicated file for better organization.
2026-02-18 19:38:22 +01:00
Houmgaor
ed2a9597f2 refactor(channelserver): extract guild model, chat commands, and seibattle
Split three large files into focused modules:
- handlers_guild.go: extract types/ORM into guild_model.go
- handlers_cast_binary.go: extract command parser into handlers_commands.go
- handlers.go: move seibattle types/handlers into handlers_seibattle.go
2026-02-18 18:24:36 +01:00
Houmgaor
3f5651209d refactor(channelserver): split handlers_shop_gacha.go into shop and gacha
Separate the two distinct systems into focused files:
- handlers_shop.go: item shops, exchange shops, frontier point trading
- handlers_gacha.go: normal/stepup/box/free gacha, coin management
2026-02-18 18:17:21 +01:00
Houmgaor
cc7883b8a1 refactor(channelserver): split handlers.go into sub-files
Extract from the 1638-line catch-all handlers.go into focused files:
- handlers_helpers.go: shared doAck* helpers and updateRights
- handlers_session.go: login/logout, save, system protocol handlers
- handlers_items.go: items, prices, stamps, stampcard
- handlers.go: remaining misc handlers (goocoo, earth, seibattle, etc.)
2026-02-18 17:59:15 +01:00
Houmgaor
a30ff33a09 chore: add editor artifacts, coverage output, and .claude/ to gitignore 2026-02-18 17:33:41 +01:00
Houmgaor
a2609e26a0 fix: resolve 4 pre-existing test failures in channelserver
- Guard nil listener/acceptConns in Server.Shutdown() to prevent panic
  in test servers that don't bind a network listener
- Remove redundant userBinaryPartsLock in TestHandleMsgMhfLoaddata that
  caused a deadlock with handleMsgMhfLoaddata's own lock acquisition
- Increase test save blob size from 200 to 150000 bytes to accommodate
  ZZ save pointer offsets (up to 146728)
- Initialize MHFEquipment.Sigils[].Effects slices in test data to
  prevent index-out-of-range panic in SerializeWarehouseEquipment
- Insert warehouse row before updating it (UPDATE on 0 rows is not an
  error, so the INSERT fallback never triggered)
- Use COALESCE for nullable kouryou_point column in kill counter test
- Fix duplicate-add test expectation (CSV helper correctly deduplicates)
2026-02-18 15:59:36 +01:00
Houmgaor
898ada3d99 fix(api): prevent path traversal in ScreenShot endpoint
Anchor the token regex to ^[A-Za-z0-9]+$ so partial matches on
traversal strings like "../../etc/passwd" are rejected. Refactor
the handler to use early returns so execution stops immediately
on validation failure instead of falling through to os.Create
with tainted input.
2026-02-18 14:10:45 +01:00
Houmgaor
e353906e1c refactor(channelserver): split handlers_data.go into sub-files
Separate the 1,580-line handlers_data.go into three focused files:
- handlers_data.go (~210 lines): character save/load handlers
- handlers_data_paper.go (~616 lines): tower/paper types and handler
- handlers_data_paper_tables.go (~765 lines): paperGiftData reward map

Mirrors the earlier handlers_guild.go split pattern.
2026-02-18 00:47:14 +01:00
Houmgaor
35665a46d8 refactor(channelserver): split handlers_guild.go into sub-files
handlers_guild.go was 2090 lines mixing unrelated guild subsystems.
Extract handlers into focused files following the existing pattern:

- handlers_guild_ops.go: OperateGuild switch + member operations
- handlers_guild_info.go: InfoGuild + EnumerateGuild display
- handlers_guild_mission.go: guild mission system
- handlers_guild_cooking.go: meals, weekly bonus, hunt data
- handlers_guild_board.go: message board system

Core types, methods, and DB functions remain in handlers_guild.go
(now ~1000 lines).
2026-02-18 00:19:11 +01:00
Houmgaor
c64dabc3ba fix: check all Close() return values for errcheck lint
Add explicit error discards (_ =) for Close() calls on network
connections, SQL rows, and file handles across 28 files. Also add
.golangci.yml with standard linter defaults to match CI configuration.
2026-02-17 23:57:14 +01:00
Houmgaor
abb40f163c ci: remove --out-format flag for golangci-lint v2 compatibility
golangci-lint v2 removed the --out-format CLI flag, causing the lint
job to fail. The golangci-lint-action v7 already uses problem matchers
to surface issues natively in GitHub Actions.
2026-02-17 23:43:45 +01:00
Houmgaor
47f7a1f636 fix(channelserver): handle bare Exec errors and filter expected ErrNoRows
138 bare db.Exec calls across 22 handler files silently dropped write
errors. Each is now wrapped with error check and zap logging.

4 QueryRow sites that legitimately return sql.ErrNoRows during normal
operation (new player mezfes, festa rankings, empty guild item box)
now filter it out to reduce log noise.
2026-02-17 23:33:44 +01:00
Houmgaor
88fc17e790 fix: purge excess guild posts and clarify alliance TODO
Purge oldest guild posts beyond the limit (100 messages, 4 news) after
each new post is created. Replace misleading alliance application TODO
with a note that the feature is not yet implemented.
2026-02-17 18:12:07 +01:00
Houmgaor
46bbb6adf9 fix: resolve all remaining lint errors (errcheck) across 49 files
Fix unchecked error returns on bf.Seek(), db.Exec(), QueryRow().Scan(),
pkt.Build(), logger.Sync(), and binary.Write() calls. The linter now
passes with 0 errors, build compiles, and all tests pass with -race.
2026-02-17 18:07:38 +01:00
Houmgaor
2a0e3e2c84 fix: re-enable CI lint job and fix ~65 lint errors (partial)
Re-enable the golangci-lint job in CI (disabled Oct 2025), update to
Go 1.25 and golangci-lint-action v7. Fix errcheck, gosimple S1009,
staticcheck SA4031 and SA2001 errors across 54 files. Remaining ~39
lint errors will be addressed in follow-up commits.
2026-02-17 17:59:00 +01:00
Houmgaor
d2b5bb72f8 refactor: extract gametime package, replace fmt.Printf with zap logging
Move time utilities (TimeAdjusted, TimeMidnight, TimeWeekStart, TimeWeekNext,
TimeGameAbsolute) from channelserver into common/gametime to break the
inappropriate dependency where signserver, entranceserver, and api imported
the 38K-line channelserver package just for time functions.

Replace all fmt.Printf debug logging in sys_session.go and handlers_object.go
with structured zap logging for consistent observability.
2026-02-17 17:54:51 +01:00
Houmgaor
fb3e86f429 fix: handle Query/QueryRow/transaction errors in channel server handlers
Add error checking and logging for ~25 database call sites that were
silently dropping errors, preventing resource leaks (unclosed rows),
nil pointer panics, and silent data corruption in festa transactions.
2026-02-17 17:44:35 +01:00
Houmgaor
645c4ddd38 test: increase code coverage from 45.1% to 48.3%
Add unit tests across multiple packages:
- byteframe: SetBE/SetLE byte order switching
- config: Mode.String() for all safe version ranges
- mhfpacket: 28 Parse methods, 5 Build methods, empty packet builds,
  variable-length packets, NOT IMPLEMENTED error paths, UpdateWarehouse
- network: PacketID.String() for known IDs, out-of-range, and all valid
- channelserver: handleMsgMhfGetPaperData (6 switch cases), grpToGR
  (11 input values), gacha handlers, TimeGameAbsolute, equipSkinHistSize
  (4 config branches), guild mission handlers, dumpSaveData disabled path
- entranceserver: makeHeader with various inputs
2026-02-17 17:32:54 +01:00
Houmgaor
a8f70df1fb fix: clean up Docker setup (compose, README, gitignore)
- Remove deprecated version field from docker-compose.yml
- Pin Postgres to 18-alpine (matches existing db-data)
- Remove undocumented web (Apache) service
- Fix config/bin volume mounts to use docker/ directory
- Gitignore docker/savedata, docker/bin, docker/config.json
- Rewrite docker/README.md: fix typos, use docker compose V2
  commands, match actual compose file behavior
- Link docker/README.md from main README Docker section
2026-02-17 15:16:57 +01:00
Houmgaor
3413ff638a doc: restructure README for new users and fix dead community links
Reorganize README to put Quick Start first with three install paths
(Docker/binary/source), give quest files their own section, consolidate
updating instructions, trim configuration to essentials with wiki link,
and move informational sections (features, architecture) below setup.

Absorb community tool links from the now-removed pastebin FAQ and update
dead URLs: Ferias → English Project, damage calc → fist.moe, armor set
searcher → mhfz-ass GitHub releases.
2026-02-17 15:04:01 +01:00
Houmgaor
63a91a879a doc: describe Mogapedia as French MH community and Erupe maintainers 2026-02-17 14:46:25 +01:00
Houmgaor
85cdac036e fix: validate quest file existence in seasonConversion fallbacks
The final fallback in seasonConversion blindly constructed a filename
without checking if it existed on disk. When the file was missing,
handleMsgSysGetFile would send doAckBufFail, but the original Frontier
client does not gracefully handle this during quest loading — causing a
softlock instead of showing the built-in error dialog.

Now every fallback path validates file existence before returning, and
also tries the opposite time-of-day variant as a last resort. If no
file variant exists at all, the original filename is returned with a
warning log so the failure ack is still sent.
2026-02-17 01:19:26 +01:00
Houmgaor
25fba1c724 fix: remove untracked www/ from Dockerfile COPY
www/ only contains gitignored content (www/jp/) so it doesn't exist
in the CI checkout, causing the Docker build to fail.
2026-02-17 00:34:15 +01:00
Houmgaor
89d97a15b6 ci: remove redundant go.yml workflow
go-improved.yml is a superset (tests + build). Also fix its path
trigger to reference itself and include main.go.
2026-02-17 00:32:11 +01:00
Houmgaor
7d54dd0ee6 ci: add Docker CD workflow to push images to GHCR
Multi-stage Dockerfile for smaller runtime image, CD workflow triggers
on main branch pushes and version tags, docker-compose defaults to the
prebuilt GHCR image.
2026-02-17 00:28:37 +01:00
Houmgaor
220de4cf3b doc: replace Codecov badge with Go version badge 2026-02-17 00:22:22 +01:00
Houmgaor
6eb9ed66cc doc: add Codecov coverage badge to README 2026-02-17 00:13:26 +01:00
Houmgaor
5724a605a2 doc: add CI, CodeQL, and release badges to README 2026-02-17 00:12:02 +01:00