Commit Graph

1498 Commits

Author SHA1 Message Date
Houmgaor
210cfa1fd1 refactor(guild): extract disband, resign, leave, and scout logic into GuildService
Move business logic for guild disband, resign leadership, leave,
post scout, and answer scout from handlers into GuildService methods.
Handlers now delegate to the service layer and handle only protocol
concerns (packet parsing, ACK responses, cross-channel notifications).

Adds 22 new table-driven service tests and sentinel errors for typed
error handling (ErrNoEligibleLeader, ErrAlreadyInvited, etc.).
DonateRP left in handler due to Session coupling.
2026-02-23 23:35:28 +01:00
Houmgaor
2abca9fb23 refactor(guild): introduce service layer for guild member operations
Extract business logic from handleMsgMhfOperateGuildMember into
GuildService.OperateMember, establishing the handler→service→repo
layering pattern. The handler is now ~20 lines of protocol glue
(type-assert, map action, call service, send ACK, notify).

GuildService owns authorization checks, repo coordination, mail
composition, and best-effort mail delivery. It accepts plain Go
types (no mhfpacket or Session imports), making it fully testable
with mock repos. Cross-channel notification stays in the handler
since it requires Session.

Adds 7 table-driven service-level tests covering accept/reject/kick,
authorization, repo errors, mail errors, and unknown actions.
2026-02-23 23:26:46 +01:00
Houmgaor
48639942f6 style: run gofmt across entire codebase
330 non-vendor files had minor formatting inconsistencies
(comment alignment, whitespace). No logic changes.
2026-02-23 21:28:30 +01:00
Houmgaor
385b974adc feat(config): register all defaults in code, shrink example config
Only the database password is truly mandatory to get started, but
config.example.json was 267 lines with 90+ options. Newcomers faced a
wall of settings with no indication of what matters.

Add registerDefaults() with all sane defaults via Viper so a minimal
config (just DB credentials) produces a fully working server. Gameplay
multipliers default to 1.0 instead of Go's zero value 0.0, which
previously zeroed out all quest rewards for minimal configs. Uses
dot-notation defaults for GameplayOptions/DebugOptions so users can
override individual fields without losing other defaults.

- Shrink config.example.json from 267 to 10 lines
- Rename full original to config.reference.json as documentation
- Simplify wizard buildDefaultConfig() from ~220 to ~12 lines
- Fix latent bug: SaveDumps default used wrong key DevModeOptions
- Add tests: minimal config, backward compat, single-field override
- Update release workflow, README, CONTRIBUTING, docker/README
2026-02-23 21:25:44 +01:00
Houmgaor
27fb0faa1e feat(db): add embedded auto-migrating schema system
Replace 4 independent schema management code paths (Docker shell
script, setup wizard pg_restore, test helpers, manual psql) with a
single migration runner embedded in the server binary.

The new server/migrations/ package uses Go embed to bundle all SQL
schemas. On startup, Migrate() creates a schema_version tracking
table, detects existing databases (auto-marks baseline as applied),
and runs pending migrations in transactions.

Key changes:
- Consolidated init.sql + 9.2-update + 33 patches into 0001_init.sql
- Setup wizard simplified to single "Apply schema" checkbox
- Test helpers use migrations.Migrate() instead of pg_restore
- Docker no longer needs schema volume mounts or init script
- Seed data (shops, events, gacha) embedded and applied via API
- Future migrations just add 0002_*.sql files — no manual steps
2026-02-23 21:19:21 +01:00
Houmgaor
6a7db47723 feat(setup): add web-based first-run configuration wizard
When config.json is missing, Erupe now launches a temporary HTTP server
on port 8080 serving a guided setup wizard instead of exiting with a
cryptic error. The wizard walks users through database connection,
schema initialization (pg_restore + SQL migrations), and server settings,
then writes config.json and continues normal startup without restart.
2026-02-23 20:55:56 +01:00
Houmgaor
085dc57648 feat(api): add configurable landing page at /
Allow server operators to show new players how to install the game
client when they visit the server address in a browser. The page
content (title and HTML body) is fully configurable via config.json
and can be toggled on/off. Uses Go embed for a self-contained dark-
themed HTML template with zero new dependencies.
2026-02-23 20:38:47 +01:00
Houmgaor
a72ac43f1d feat(api): add /health endpoint with Docker healthchecks
Allow Docker to distinguish a running container from one actually
serving traffic by adding a /health endpoint that pings the database.
Returns 200 when healthy, 503 when the DB connection is lost.

Add HEALTHCHECK to Dockerfile and healthcheck config to the server
service in docker-compose.yml. Also add start_period to the existing
db healthcheck for consistency.
2026-02-23 20:34:20 +01:00
Houmgaor
b96cd0904b fix: ease onboarding with startup warnings and doc corrections
- Warn at startup when quest files are missing (clients crash without
  them) and point users to the download link
- Fix Host config description: it's the advertised IP, not a bind
  address — 0.0.0.0 was wrong advice
- Load bundled schemas (shops, events, gacha) in Docker init so new
  users get working demo data out of the box
- Renumber duplicate patch schema 28 → 32 to resolve numbering
  collision
- Fix patch schema example filename to use hyphens matching actual
  files
2026-02-23 20:23:08 +01:00
Houmgaor
7af41a7796 fix(decryption): eliminate data race in JPK decompression
Package-level globals mShiftIndex and mFlag were shared across
concurrent goroutines calling UnpackSimple from quest handlers.
Replace with a local jpkState struct scoped to each decompression
call. Add concurrent safety test validated with -race.
2026-02-23 20:01:38 +01:00
Houmgaor
f712e3c04d feat(pcap): complete replay system with filtering, metadata, and live replay
Wire ExcludeOpcodes config into RecordingConn so configured opcodes
(e.g. ping, nop, position) are filtered at record time. Add padded
metadata with in-place PatchMetadata to populate CharID/UserID after
login. Implement --mode replay using protbot's encrypted connection
with timing-aware packet sending, auto-ping response, concurrent
S→C collection, and byte-level payload diff reporting.
2026-02-23 19:34:30 +01:00
Houmgaor
7ef5efc549 feat(network): add protocol packet capture and replay system
Add a recording and replay foundation for the MHF network protocol.
A RecordingConn decorator wraps network.Conn to transparently capture
all decrypted packets to binary .mhfr files, with zero handler changes
and zero overhead when disabled.

- network/pcap: binary capture format (writer, reader, filters)
- RecordingConn: thread-safe Conn decorator with direction tracking
- CaptureOptions in config (disabled by default)
- Capture wired into all three server types (sign, entrance, channel)
- cmd/replay: CLI tool with dump, json, stats, and compare modes
- 19 new tests, all passing with -race
2026-02-23 18:50:44 +01:00
Houmgaor
e5ffc4d52d refactor(channelserver): move paper data tables out of handler function
The handleMsgMhfGetPaperData function was 565 lines, but most of
it was inline data table literals. Extract tower config (case 5)
and tower scaling (case 6) slices into package-level vars in
handlers_data_paper_tables.go, reducing the handler to 73 lines
while co-locating all paper data tables in one file.
2026-02-23 18:24:54 +01:00
Houmgaor
3dc0ac0728 docs: expand doc.go for channelserver and mhfpacket packages
Document the Unk field naming convention used across 300+ packet
structs so new contributors understand these are intentionally
unnamed reverse-engineered protocol fields. Expand channelserver
doc.go with handler registration workflow, repository pattern,
testing approach, and lock ordering.
2026-02-23 18:09:08 +01:00
Houmgaor
12f463e03b ci: replace codecov with local coverage threshold check
Codecov requires an account and token to function. Replace it with
a self-contained `go tool cover` step that fails the build if total
coverage drops below 50% (currently ~58%). This catches test
regressions without external service dependencies.
2026-02-23 17:16:09 +01:00
Houmgaor
7f13ee6a51 chore(mhfpacket): remove orphaned commented-out panics in stub parsers
Replace dead `//panic("Not implemented")` after return statements with
TODO comments indicating these are stub parsers with unknown fields.
2026-02-23 17:11:19 +01:00
Houmgaor
7dec7cbcef docs: mark all handler test gaps as resolved in tech debt tracker
The 5 remaining handler files (seibattle, kouryou, scenario, distitem,
guild_mission) were already covered by commit 6c0269d but the doc
was not updated at the time.
2026-02-23 17:06:57 +01:00
Houmgaor
4c4be1d336 test(channelserver): add unit tests for paper data handler
Covers all DataType branches (0/5/6/gift/>1000/unknown), ACK payload
structure with correct 10-byte header offset, earth succeed entry
counts, timetable content validation, PaperData/PaperGift serialization
round-trips, and paperGiftData table integrity checks.
2026-02-23 17:01:20 +01:00
Houmgaor
b96505df3e test(channelserver): expand chat command handler test coverage
Add 30 new tests to handlers_commands_test.go covering previously
untested paths: raviente with semaphore (start, multiplier, ZZ-only
sed/res commands, version gating), course enable/disable/locked/alias,
reload with other players and objects, help filtering for non-op vs op,
ban error paths and long-form duration aliases, and disabled-command
gating for all 12 commands. Total: 62 tests, all passing with -race.
2026-02-23 16:52:28 +01:00
Houmgaor
2d4f9aeefa fix(docker): correct volume mount paths to match container WORKDIR
The Dockerfile sets WORKDIR to /app but docker-compose.yml mounted
volumes to /app/erupe/, causing "config not found" on startup.

Closes #162
2026-02-22 22:42:08 +01:00
Houmgaor
e8963ce0cf ci: add release workflow to publish binaries on tag push
Users without a Go toolchain can now download pre-built binaries
directly from GitHub Releases (closes #161). The workflow triggers
on v* tags, builds Linux and Windows amd64 archives, and attaches
them along with SCHEMA.sql to an auto-generated release.

Also fixes the README badge URL (go-improved.yml → go.yml).
2026-02-22 20:08:25 +01:00
Houmgaor
69e23c0646 test(channelserver): add unit tests for chat command handlers
Cover parseChatCommand with 31 tests across all command types:
timer toggle, PSN set/exists, rights, discord token, playtime,
help, ban (permanent/timed/non-op/invalid CID/duration units),
teleport, key quest (version check/get/set), raviente, and
course. Includes mockUserRepoCommands for fine-grained tracking
of command side effects.
2026-02-22 19:53:08 +01:00
Houmgaor
6c0269d21f test(channelserver): add unit tests for helpers, kouryou, scenario, seibattle, and distitem handlers
Cover previously untested handler files with mock-based unit tests:
- handlers_helpers: load/save character data, ack helpers, updateRights
- handlers_kouryou: get/add/exchange points with success and error paths
- handlers_scenario: scenario counter serialization, 128-entry trim, category exchange flags
- handlers_seibattle: all type codes, Earth response format, data size validation
- handlers_distitem: enumerate/apply/acquire distributions, description retrieval
2026-02-22 18:55:31 +01:00
Houmgaor
bcb5086dbb chore: remove stale TODO, update codecov-action to v5, refresh tech debt doc
The guild daily RP rollover TODO in handlers_guild_ops.go was stale —
the feature was already implemented via lazy rollover in
handlers_guild.go. Several other items in technical-debt.md were also
resolved in prior commits (typos, db guard investigation). Updated
the doc to reflect current state and bumped codecov-action to v5.
2026-02-22 18:38:10 +01:00
Houmgaor
de00e41830 chore: fix typos, remove stale comment, and update codecov-action to v4
- Remove misleading "For Debuging" comment in sys_session.go
- Fix "offical" → "official" typo in handlers_session.go
- Update codecov-action@v3 → @v4 in CI workflow
- Consolidate technical-debt.md with completed items and updated TOC
2026-02-22 18:20:09 +01:00
Houmgaor
82b967b715 refactor: replace raw SQL with repository interfaces in entranceserver and API server
Extract all direct database calls from entranceserver (2 calls) and
API server (17 calls) into typed repository interfaces with PostgreSQL
implementations, matching the pattern established in signserver and
channelserver.

Entranceserver: EntranceServerRepo, EntranceSessionRepo
API server: APIUserRepo, APICharacterRepo, APISessionRepo

Also fix the 3 remaining fmt.Sprintf calls inside logger invocations
in handlers_commands.go and handlers_stage.go, replacing them with
structured zap fields.

Unskip 5 TestNewAuthData* tests that previously required a real
database — they now run with mock repos.
2026-02-22 17:04:58 +01:00
Houmgaor
f640cfee27 fix: log SJIS decoding errors instead of silently discarding them
Add SJISToUTF8Lossy() that wraps SJISToUTF8() and logs decode errors at
slog.Debug level. Replace all 31 call sites across 17 files that previously
discarded the error with `_, _ =`. This makes garbled text from malformed
SJIS client data debuggable without adding noise at default log levels.
2026-02-22 17:01:22 +01:00
Houmgaor
59fd722d37 refactor(channelserver): standardize on BeginTxx for all repository transactions
Replace db.Begin() with db.BeginTxx(context.Background(), nil) across all
8 remaining call sites in repo_guild.go, repo_guild_rp.go, repo_festa.go,
and repo_event.go. Use deferred Rollback() instead of explicit rollback
at each error return, eliminating 15 manual rollback calls.
2026-02-22 16:55:59 +01:00
Houmgaor
2acbb5d03a feat(channelserver): implement monthly guild item claim tracking
Players could never claim monthly guild items because the handler
always returned 0x01 (claimed). Now tracks per-character per-type
(standard/HLC/EXC) claim timestamps in the stamps table, comparing
against the current month boundary to determine claim eligibility.

Adds MonthStart() to gametime, extends StampRepo with
GetMonthlyClaimed/SetMonthlyClaimed, and includes schema migration
31-monthly-items.sql.
2026-02-22 16:46:57 +01:00
Houmgaor
302453ce8e refactor(channelserver): split repo_guild.go into domain-focused files
The 1004-line monolith covered 11 game subsystems across 62 methods.
Split into 7 files by domain (RP, posts, alliances, adventures, hunts,
cooking) while keeping core CRUD/membership/scouts in the original.
Same package, receiver, and interface — no behavior changes.
2026-02-22 16:42:03 +01:00
Houmgaor
1d507b3d11 fix: replace fmt.Sprintf in logger calls with structured fields and add LoopDelay default
fmt.Sprintf inside zap logger calls defeats structured logging,
making log aggregation and filtering harder. All 6 sites now use
proper zap fields (zap.Uint32, zap.Uint8, zap.String).

LoopDelay had no viper.SetDefault, so omitting it from config.json
caused a zero-value (0 ms) busy-loop in the recv loop. Default is
now 50 ms, matching config.example.json.
2026-02-22 16:32:43 +01:00
Houmgaor
b3f75232a3 refactor(signserver): replace raw SQL with repository interfaces
Extract all direct database access into three repository interfaces
(SignUserRepo, SignCharacterRepo, SignSessionRepo) matching the
pattern established in channelserver. This surfaces 9 previously
silenced errors that are now logged with structured context, and
makes the sign server testable with mock repos instead of go-sqlmock.

Security fix: GetFriends now uses parameterized ANY($1) queries
instead of string-concatenated WHERE clauses (SQL injection vector).
2026-02-22 16:30:24 +01:00
Houmgaor
53b5bb3b96 refactor(channelserver): remove Channels fallbacks, use Registry as sole cross-channel API
main.go always sets both Channels and Registry together, making the
Channels fallback paths dead code. This removes:

- Server.Channels field from the Server struct
- 3 if/else fallback blocks in handlers_session.go (replaced with
  Registry.FindChannelForStage, SearchSessions, SearchStages)
- 1 if/else fallback block in handlers_guild_ops.go (replaced with
  Registry.NotifyMailToCharID)
- 3 method fallbacks in sys_channel_server.go (WorldcastMHF,
  FindSessionByCharID, DisconnectUser now delegate directly)

Updates anti-patterns.md #6 to "accepted design" — Session struct is
appropriate for this game server's handler pattern, and cross-channel
coupling is now fully routed through the ChannelRegistry interface.
2026-02-22 16:16:44 +01:00
Houmgaor
cd630a7a58 test(channelserver): add handler tests for session, gacha, shop, and plate
Cover critical paths that previously had no test coverage:
- Session: login success/error paths, ping, logkey, record log,
  global sema lock/unlock, rights reload, announce
- Gacha: point queries, coin deduction, item receive with overflow
  and freeze, normal/stepup/box gacha play, stepup status lifecycle,
  weighted random selection
- Shop: enumeration across all shop types, exchange purchases,
  fpoint-to-item and item-to-fpoint exchange, fpoint exchange list
  with Z2 vs ZZ encoding
- Plate: load/save for platedata, platebox, platemyset with
  oversized payload rejection, diff path, and cache invalidation

Add mockSessionRepo, mockGachaRepo, mockShopRepo, and
mockUserRepoGacha to support the new test scenarios. Add
loadColumnErr field to mockCharacterRepo for diff-path error
testing.
2026-02-22 16:05:25 +01:00
Houmgaor
db34cb3f85 docs: update anti-patterns status for completed refactoring items
Mark #7 (mutex granularity), #9 (raw SQL), #13 (DB coupling),
and priority items #7-9 as resolved to reflect recent work:
StageMap replaces stagesLock, all inline SQL migrated to repos,
21 repository interfaces decouple handlers from PostgreSQL.
2026-02-22 15:49:30 +01:00
Houmgaor
ad4afb4d3b refactor(channelserver): replace global stagesLock with sync.Map-backed StageMap
The global stagesLock sync.RWMutex protected map[string]*Stage, causing
all stage operations to contend on a single lock even for unrelated
stages. Any stage creation or deletion blocked all reads server-wide.

Replace with a typed StageMap wrapper around sync.Map which provides
lock-free reads and allows concurrent writes to disjoint keys. Per-stage
sync.RWMutex remains unchanged for protecting individual stage state.

StageMap exposes Get, GetOrCreate, StoreIfAbsent, Store, Delete, and
Range methods. Updated ~50 call sites across 6 production files and
9 test files.
2026-02-22 15:47:21 +01:00
Houmgaor
2a5cd50e3f test(channelserver): add handler tests for guild info, alliance, cooking, adventure, and treasure
Cover 5 more handler files with mock-based unit tests, bringing
package coverage from 43.7% to 47.7%. Extend mockGuildRepoOps with
alliance, cooking, adventure, treasure hunt, and hunt data methods.
2026-02-21 18:10:19 +01:00
Houmgaor
7852e8505f test(channelserver): add handler tests for guild ops, scout, board, and items
Cover the 4 handler files that had no tests: handlers_guild_ops.go,
handlers_guild_scout.go, handlers_guild_board.go, and handlers_items.go.
44 new tests exercise the error-logging paths added in 8fe6f60 and the
core handler logic (disband, resign, apply, leave, accept/reject/kick,
scout answer, message board CRUD, weekly stamps, item box parsing).

New mock types: mockGuildRepoOps (enhanced guild repo with configurable
errors and state tracking), mockUserRepoForItems, mockStampRepoForItems,
mockHouseRepoForItems. Coverage rises from 41.1% to 43.7%.
2026-02-21 17:58:08 +01:00
Houmgaor
8fe6f60813 fix(channelserver): add error logging to silently ignored repo calls
19 repository calls across 8 handler files were silently discarding
errors with `_ =`, making database failures invisible. Add structured
logging at appropriate levels: Error for write operations where data
loss may occur (guild saves, member updates), Warn for read operations
where handlers continue with zero-value defaults (application checks,
warehouse loads, item box queries). No control flow changes.
2026-02-21 17:37:29 +01:00
Houmgaor
6fbd294575 refactor(channelserver): eliminate *sql.Tx from repository interfaces
Hide transaction management inside repository implementations so
interfaces only expose domain types, enabling clean mocking and
decoupling handlers from PostgreSQL internals.

- Replace BeginTx + UpdateEventQuestStartTime with batch
  UpdateEventQuestStartTimes that manages its own transaction
- Remove tx parameter from CreateApplication, add composite
  CreateApplicationWithMail for atomic scout+mail operations
- Remove SendMailTx from MailRepo (sole caller migrated)
- Remove database/sql import from repo_interfaces.go
2026-02-21 14:56:59 +01:00
Houmgaor
35d8471d59 fix(channelserver): resolve all golangci-lint issues and add handler tests
Fix errcheck violations across 11 repo files by wrapping deferred
rows.Close() and tx.Rollback() calls to discard the error return.
Fix unchecked Scan/Exec calls in guild store tests. Fix staticcheck
SA9003 empty branch in test helpers.

Add 6 mock-based unit tests for GetCharacterSaveData covering nil
savedata, sql.ErrNoRows, DB errors, compressed round-trip,
new-character skip, and config mode/pointer propagation.
2026-02-21 14:47:25 +01:00
Houmgaor
bd8e30d570 refactor(channelserver): eliminate *sql.Rows from EventRepo.GetEventQuests
Return []EventQuest instead of a raw database cursor, removing the last
*sql.Rows leak from the repository layer. The handler now iterates a
slice, and makeEventQuest reads fields from the struct directly instead
of scanning rows twice. This makes the method fully mockable and
eliminates the risk of unclosed cursors.
2026-02-21 14:37:29 +01:00
Houmgaor
f2f5696a22 test(channelserver): add store tests for new repository methods
Cover the three repository methods added in the previous commit:
- CharacterRepo.LoadSaveData: normal, new-character, and not-found cases
- EventRepo.GetEventQuests: empty, multi-row, ordering, tx commit/rollback
- UserRepo.BanUser: permanent, temporary, and both upsert directions
2026-02-21 14:21:32 +01:00
Houmgaor
2be589beae refactor(channelserver): eliminate *sqlx.Rows/*sql.Rows from repository interfaces
Move scan loops from handlers into repository methods so that interfaces
return typed slices instead of leaking database cursors. This fixes
resource leaks (7 of 12 call sites never closed rows) and makes all
12 methods mockable for unit tests.

Affected repos: CafeRepo, ShopRepo, EventRepo, RengokuRepo, DivaRepo,
ScenarioRepo, MiscRepo, MercenaryRepo. New structs: DivaEvent,
MercenaryLoan, GuildHuntCatUsage. EventRepo.GetEventQuests left as-is
(requires broader Server refactor).
2026-02-21 14:16:58 +01:00
Houmgaor
a9cca84bc3 refactor(channelserver): move remaining s.server.db calls into repositories
Eliminate the last three direct DB accesses from handler code:

- CharacterRepo.LoadSaveData: replaces db.Query in GetCharacterSaveData,
  using QueryRow instead of Query+Next for cleaner single-row access
- EventRepo.GetEventQuests, UpdateEventQuestStartTime, BeginTx: moves
  event quest enumeration and rotation queries behind the repo layer
- UserRepo.BanUser: consolidates permanent/temporary ban upserts into a
  single method with nil/*time.Time semantics
2026-02-21 14:08:01 +01:00
Houmgaor
9a473260b2 test(channelserver): add mock-based handler unit tests
Leverage the new repository interfaces to test handler logic without
a database. Adds shared mock implementations (achievement, mail,
character, goocoo, guild) and 32 new handler tests covering
achievement, mail, cafe/boost, and goocoo handlers.
2026-02-21 14:01:52 +01:00
Houmgaor
da1e62d7c6 test(channelserver): add store tests and document lock ordering
Add unit tests with race-detector coverage for QuestCache,
UserBinaryStore, and MinidataStore (18 tests covering hits, misses,
expiry, copy isolation, and concurrent access).

Document the lock acquisition order on the Server struct to prevent
future deadlocks: Server.Mutex → stagesLock → Stage → semaphoreLock.
2026-02-21 13:56:46 +01:00
Houmgaor
1d5026c3a5 refactor(channelserver): introduce repository interfaces for all 21 repos
Replace concrete pointer types on the Server struct with interfaces to
decouple handlers from PostgreSQL implementations. This enables mock/stub
injection for unit tests and opens the door to alternative storage
backends (SQLite, in-memory).

Also adds 9 missing repo initializations to SetTestDB() (event,
achievement, shop, cafe, goocoo, diva, misc, scenario, mercenary)
to match NewServer().
2026-02-21 13:52:28 +01:00
Houmgaor
3b044fb987 fix(channelserver): handle silently swallowed DB scan and exec errors
Several handlers discarded errors from rows.Scan() and db.Exec(),
masking data corruption or connection issues. Scan failures in diva
schedule, event quests, and trend weapons are now logged or returned.
InitializeWarehouse now surfaces its insert error to the caller.
2026-02-21 13:49:25 +01:00
Houmgaor
9da467e00f perf(channelserver): use package-level map for ignored opcodes
The ignored() function is called on every packet when debug logging
is enabled. It was rebuilding a slice and map from scratch each time.
Move to a package-level map initialized once at startup.
2026-02-21 13:42:33 +01:00