Commit Graph

1567 Commits

Author SHA1 Message Date
Houmgaor
4e300a227c Update issue templates
More details on server/client.
2026-02-25 15:26:49 +01:00
Houmgaor
d9f90e3b46 fix(shop): resolve ambiguous column and missing unique constraint in RecordPurchase
The ON CONFLICT upsert referenced unqualified "bought" which PostgreSQL
rejected as ambiguous, and the table lacked the UNIQUE constraint needed
for ON CONFLICT. Adds a unique index on (character_id, shop_item_id) via
migration 0003 and qualifies the column as shop_items_bought.bought.
2026-02-24 17:06:38 +01:00
Houmgaor
f9d4252860 test(repos): add SQL integration tests for 17 untested repo files
Add 148 integration tests exercising actual SQL against PostgreSQL for
all previously untested repository files. Includes 6 new fixture helpers
in testhelpers_db.go and CI PostgreSQL service configuration.

Discovered and documented existing RecordPurchase SQL bug (ambiguous
column reference in ON CONFLICT clause).
2026-02-24 16:57:47 +01:00
Houmgaor
c5fd0444f4 docs: update architecture docs to reflect 6-service layer
The CLAUDE.md layered architecture section implied all handlers go
through services. In practice, services handle cross-repo coordination
while handlers call repos directly for simple CRUD. Updated the
diagram, added a services table, and added an "Adding Business Logic"
guide. Marked improvements.md item 4 as done.
2026-02-24 16:18:31 +01:00
Houmgaor
4d3ec8164c refactor(festa): extract festa logic into FestaService
The festa handler contained event lifecycle management (cleanup expired
events, create new ones) and the repo enforced a business rule (skip
zero-value soul submissions). Move these into a new FestaService to
keep repos as pure data access and consolidate business logic.
2026-02-24 16:12:40 +01:00
Houmgaor
7a56810e78 refactor(tower): extract tower logic into TowerService
The tower repo had business logic beyond simple CRUD: AddGem used a
fetch-transform-save pattern, progress capping was inline in the
handler, and RP donation orchestrated multiple repo calls with
conditional page advancement. Move these into a new TowerService
following the established service layer pattern.
2026-02-24 16:07:37 +01:00
Houmgaor
76d139538b refactor(items): extract inline data tables from handleMsgMhfEnumeratePrice
Static data (GZ monster prices, LB prices, wanted list) cluttered the
handler with 130 lines of table literals. Moving them to a dedicated
tables file keeps the handler focused on serialization logic.
2026-02-24 15:34:51 +01:00
Houmgaor
759988ae8e test(mocks): add mock implementations for 8 unmocked repo interfaces
Enables isolated unit tests for tower, festa, rengoku, diva, event,
misc, mercenary, and cafe handlers. All 21 repo interfaces now have
mock implementations in repo_mocks_test.go.
2026-02-24 15:24:08 +01:00
Houmgaor
41a103af9d refactor(test): consolidate two GuildRepo mocks into one
mockGuildRepoForMail and mockGuildRepoOps each implemented different
subsets of the 68-method GuildRepo interface. Adding any new method
required updating both mocks. Merged into a single mockGuildRepo with
configurable struct fields for error injection and no-op defaults for
the rest.
2026-02-24 14:13:20 +01:00
Houmgaor
c1fadd09c3 fix(commands): validate argument parsing in chat commands
KeyQuest set, Rights, and Teleport commands silently used zero values
when given malformed arguments (bad hex, non-integer coords). Now they
send the existing i18n error messages back to the player instead.
2026-02-24 13:57:58 +01:00
Houmgaor
8fead0b1f3 fix(handlers): add error handling for swallowed repo/service errors
Several handler files discarded errors from repository and service
calls, creating nil-dereference risks and silent data corruption:

- guild_adventure: 3 GetByCharID calls could panic on nil guild
- gacha: GetGachaPoints silently returned zero balances on DB error
- house: HasApplication called before nil check on guild;
  GetHouseContents error discarded with 7 return values
- distitem: 3 distRepo calls had no error logging
- guild_ops: Disband/Leave service errors were invisible
- shop: gacha type/weight/fpoint lookups had no error logging
- discord: bcrypt error could result in nil password being set
2026-02-24 13:55:49 +01:00
Houmgaor
2f92b4ff62 feat(db): add catch-up migration for partially-patched databases
The migration consolidation (27fb0fa) merged 33 incremental patches
into 0001_init.sql and marks the baseline as applied for any existing
database. Users who only ran some of the 33 patches have schema gaps
that cause runtime errors.

0002_catch_up_patches.sql replays all 33 patches (skipping 15 and 20,
which are destructive data resets) with idempotency guards so it is a
no-op on fresh or fully-patched databases and fills gaps otherwise.
2026-02-24 11:37:37 +01:00
Houmgaor
077c08fd49 refactor(mail): extract mail logic into MailService
Introduce MailService as a convenience layer between handlers/services
and MailRepo. Provides Send, SendSystem, SendGuildInvite, and
BroadcastToGuild methods that encapsulate the boolean flag combinations.

GuildService now depends on MailService instead of MailRepo directly,
simplifying its mail-sending calls from verbose SendMail(..., false, true)
to clean SendSystem(recipientID, subject, body).

Guild mail broadcast logic moved from handleMsgMhfSendMail into
MailService.BroadcastToGuild.
2026-02-24 00:05:56 +01:00
Houmgaor
1e9de7920d refactor(gacha): extract gacha logic into GachaService
Move payment processing, reward selection, stepup state management,
and box gacha tracking from handlers into a dedicated service layer.
Handlers now delegate to GachaService methods and only handle
protocol serialization.
2026-02-23 23:57:54 +01:00
Houmgaor
32c5a9bf9c docs: add CLAUDE.md with project-specific guidance 2026-02-23 23:44:05 +01:00
Houmgaor
daacb76fb8 refactor(achievement): extract achievement logic into AchievementService
Move EnsureExists + GetAllScores + compute loop from handler into
AchievementService.GetAll, and validation + ensure + increment into
AchievementService.Increment. Handlers now delegate to the service
layer for business logic while retaining protocol response building.

GetAchData stays as a pure function in handlers_achievement.go per plan.
2026-02-23 23:43:39 +01:00
Houmgaor
bcdc4e0b7e fix(setup): reduce friction in installation procedure
- Add Language default ("jp") so missing field no longer produces empty
  string, and include language selector in setup wizard
- Add --setup flag to re-run wizard even when config.json exists,
  providing a recovery path for corrupted configs
- Auto-apply seed data on fresh databases so users who skip the wizard
  still get shops, events, and gacha
- Fix stale docs referencing non-existent init/setup.sh and
  schemas/patch-schema/ in docker/README, CONTRIBUTING, and README
2026-02-23 23:39:49 +01:00
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