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.
- 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
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.
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.
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
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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%.
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.
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.
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.
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
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).
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
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.
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.
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().
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.
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.
The userBinary and minidata maps with their locks were spread across
Server as raw fields with manual lock management. Cross-channel session
searches also required acquiring nested locks (server lock + binary
lock). Encapsulating in dedicated types eliminates the nested locking
and reduces Server's field count by 4.
The quest cache fields (lock, data map, expiry map) were spread across
Server with manual lock management. The old read path also missed the
RLock entirely, creating a data race. Encapsulating in a dedicated type
fixes the race and reduces Server's field count by 2.
All guild subsystem tables are now migrated into repo_guild.go.
21 repository files cover all major subsystems. Only 5 inline SQL
queries remain across 3 handler files. Updated summary table and
refactoring priority list to mark completed items.
A failed Begin() returned a nil tx that would panic on the next
tx.Exec() call. Now logs the error, closes rows, and returns an
empty response gracefully.
Move remaining raw s.server.db.* queries from handler files into
dedicated repository structs, completing the repository extraction
effort. Also adds SaveCharacterData and SaveHouseData to
CharacterRepository.
Fixes guild_hunts query to select both cats_used and start columns
to match the existing two-column Scan call. Adds slot index
validation in GoocooRepository to prevent SQL injection via
fmt.Sprintf.
The config package used `package _config` with a leading underscore,
which is unconventional in Go. Rename to `package config` (matching the
directory name) and use `cfg` as the standard import alias across all
93 importing files.
Move 22 raw SQL queries from 4 handler files into dedicated repository
structs, continuing the repository extraction pattern. Achievement
insert uses ON CONFLICT DO NOTHING to eliminate check-then-insert race,
and IncrementScore validates the column index to prevent SQL injection.