Instead of a generic "Failed to ping" message, detect the actual
cause and show a targeted fix:
- net error → PostgreSQL not reachable, check if it's running
- pq 28P01 → wrong password, update config.json
- pq 3D000 → database doesn't exist, create it
An MMO server without multiplayer defeats the purpose. PostgreSQL
is the right choice and Docker Compose already solves the setup
pain. This reverts the common/db wrapper, SQLite schema, config
Driver field, modernc.org/sqlite dependency, and all repo type
changes while keeping the dashboard, wizard, and CI improvements
from the previous commit.
Add zero-dependency SQLite mode so users can run Erupe without
PostgreSQL. A transparent db.DB wrapper auto-translates PostgreSQL
SQL ($N placeholders, now(), ::casts, ILIKE, public. prefix,
TRUNCATE) for SQLite at runtime — all 28 repo files use the wrapper
with no per-query changes needed.
Setup wizard gains two new steps: quest file detection with download
link, and gameplay presets (solo/small/community/rebalanced). The API
server gets a /dashboard endpoint with auto-refreshing stats.
CI release workflow now builds and pushes Docker images to GHCR
alongside binary artifacts on tag push.
Key changes:
- common/db: DB/Tx wrapper with 6 SQL translation rules
- server/migrations/sqlite: full SQLite schema (0001-0005)
- config: Database.Driver field ("postgres" or "sqlite")
- main.go: SQLite connection with WAL mode, single writer
- server/setup: quest check + preset selection steps
- server/api: /dashboard with live stats
- .github/workflows: Docker in release, deduplicate docker.yml
The constant used file-order bytes (0x6563641a) instead of the value
produced by binary.LittleEndian.Uint32 (0x1A646365), causing
loadRengokuBinary to reject every real ECD-encrypted rengoku_data.bin.
The unit tests masked this because they round-tripped with the same
wrong constant.
The else branch redeclared guildInfo with := scoping it to the block,
so the outer guildInfo remained nil when reaching ListInvitedCharacters.
Restructure the conditional to assign to the existing variable instead.
Load and validate rengoku_data.bin once during server initialization
instead of reading it from disk on every client request. The file is
static ECD-encrypted config data (~4.9 KB) that never changes at
runtime. Validation checks file size and ECD magic bytes, logging a
warning if the file is missing or invalid so misconfiguration is
caught before any client connects.
scanAllianceWithGuilds dereferences guild pointers returned by GetByID
without checking for nil. Since GetByID returns (nil, nil) when a guild
is missing, alliances referencing deleted guilds cause nil-pointer
panics. The panic is caught by session recovery but no ACK is sent,
softlocking the client.
Add nil checks in scanAllianceWithGuilds, handleMsgMhfOperateJoint,
handleMsgMhfInfoJoint, and handleMsgMhfInfoGuild so that missing
guilds or alliances produce proper error responses instead of panics.
The test ran handleMsgMhfSavedata and logoutPlayer concurrently on the
same session, triggering data races on s.playtime and Save(). In
production the dispatch loop processes packets sequentially per session,
so this overlap is impossible. Run the operations sequentially to match
real behavior while still validating no data loss.
Cover the two most complex untested handlers in handlers_session.go:
- logoutPlayer (8 tests): basic logout, character save path, cafe course
RP accrual, stage cleanup, host disconnect with MsgSysStageDestruct,
error resilience for ReadInt/LoadSaveData failures, concurrent logout
- saveAllCharacterData (4 tests): nil save data, load error propagation,
RP capping logic, playtime accumulation
- handleMsgMhfTransitMessage (6 tests): search by charID (found/not
found), search by name, search by lobby (IP+port+stageID), party
finder with stage prefix and rank filtering, localhost IP rewrite
Without this guard, migration 0004 fails on databases where the column
already exists, such as during the existing-DB-without-schema-version
upgrade path where 0001 baseline is auto-marked and 0002-0005 re-applied.
Backfill key changes from 900 commits since 9.2.0 including
architecture rewrite, game system overhauls, client version
support, and developer tooling. Tag Unreleased as 9.3.0-rc1.
The distribution table had a `data bytea NOT NULL` column that was never
read by the Go code — item data is stored in distribution_items instead.
The NOT NULL constraint forced dummy values in seed data and test inserts.
Remove the column from the baseline schema, seed data, and tests, and
add migration 0005 to drop it from existing databases.
Lobby search now returns only quest-bound players (QuestReserved) instead
of all reserved slots, matching retail behavior. The new field is
pre-collected under server lock before stage iteration to respect
Server.Mutex → Stage.RWMutex lock ordering.
Replaced three TODOs with RE documentation from Ghidra decompilation of
mhfo-hd.dll ZZ:
- Log key off-by-one: putRecord_log/putTerminal_log pass size 0 for the
key field in ZZ, so the stored key is unused beyond issuance
- User search padding: ZZ per-entry parser confirms 40-byte block via
memcpy(dst, src+8, 0x28); G2 DLL analysis inconclusive (stripped)
- Player count: field at entry offset 0x08 maps to struct param_1[0xe]
The pBookshelfData offsets for G1-Z2, F4-F5, and S6 were off by -14810,
placing bookshelf before houseData in the save blob and reading garbage.
All other 12 save fields have consistent inter-version deltas (36000,
32000, 48000); only bookshelf broke the pattern. Correcting by +14810
restores the gallery-bookshelf gap to 136 bytes (matching ZZ) and aligns
all field deltas across versions.
Supersedes Mezeporta/Erupe#155 (same fix, merge conflict on renamed file).
Alliance applications were hardcoded to always-open. Add a `recruiting`
column to guild_alliances and handle OperateJoint actions 0x06 (Allow)
and 0x07 (Deny) confirmed via Wii U debug symbols. Only the parent
guild leader can toggle the setting, matching the existing disband guard.
Filed #164-#168 for all 8 remaining TODOs. Added Tracker column
with issue links, fixed stale line numbers, added MhfAddUdPoint
stub entry, and expanded the suggested execution order.
Add bounds check (0 to MaxUint32) before casting strconv.Atoi result
to uint32 in the rights command handler. Replace manual allowlist
validation with pq.QuoteIdentifier for CREATE DATABASE to eliminate
the SQL injection finding.
The migrations and channelserver packages both DROP and recreate the
public schema on the shared erupe_test database. With parallel package
execution, they destroy each other's schema mid-migration. Adding -p 1
serializes package execution while preserving t.Parallel() within each
package.
Add REST-idiomatic DELETE method as alias for POST .../delete.
Add 8 router-level tests exercising Bearer auth, invalid tokens,
soft delete, export body decoding, and MaxLauncherHR capping.
Create OpenAPI 3.1.0 specification covering all v2 endpoints.
Cover all previously-untested session handlers (authenticate,
handleDSGN, handleWIIUSGN, handlePSSGN, handlePSNLink, sendCode)
plus validateLogin ban/bcrypt paths, registerPsnToken, and
sanitizeAddr/startSignCapture. Uses a spyConn implementing
network.Conn to capture plaintext packets for assertion.
Raises signserver coverage from 46% to 63%.
Introduces incremental API improvements for custom launcher support
(mhf-iel, stratic-dev's Rust launcher):
- Standardize all error responses to JSON envelopes with error/message
- Add Bearer token auth middleware for v2 routes (legacy body-token preserved)
- Add `returning` (>90d inactive) and `courses` fields to auth response
- Add /v2/ route prefix with HTTP method enforcement
- Add GET /v2/server/status for MezFes, featured weapon, and event status
- Add APIEventRepo for read-only event data access
Closes#44
Extract a Session interface from *discordgo.Session so DiscordBot methods
can be tested with a mock — no live Discord connection required. Add
AddHandler, RegisterCommands, and UserID methods to DiscordBot so
external callers (main.go, sys_channel_server.go) no longer reach
through bot.Session directly. Rewrite tests with a mockSession,
raising discordbot coverage from 12.5% to 66.7%.
CharacterSaveData.Save() silently returned on failure (nil decompressed
data, compression error, DB error) while the caller unconditionally
logged "Saved character data successfully". This made diagnosing save
failures difficult (ref #163).
Save() now returns an error, and all six call sites check it. The
success log in saveAllCharacterData only fires when the save actually
persisted.
The G1 client binary expects 8 uint32 fields (ID, rank restrictions,
MinGR, MinHR) before the name string in the gacha listing response.
PR #150 only wrote these for GG+, causing G1–G32 clients to misparse
the stream. Verified against Wii U G1 RPX decompilation of
import_gacha_list at 0x02C594FC.
Move ~300 test functions from 21 catch-all files (handlers_core_test.go,
handlers_coverage*_test.go, *_coverage_test.go) into the *_test.go file
matching each handler's source file. This makes tests discoverable by
convention: tests for handlers_guild.go live in handlers_guild_test.go.
New files: handlers_guild_mission_test.go, sys_time_test.go.
No test logic changed — pure file reorganization.
Add four new test files covering previously-untested handler functions
to raise total coverage from 57.7% to 60.0%:
- handlers_misc_coverage_test.go: minidata, trend weapons, etc points,
equip skin history
- handlers_cafe_coverage_test.go: cafe duration bonuses, daily cafe,
cafe duration
- handlers_festa_coverage_test.go: mezfes data, festa voting, entry,
charge, prizes, state queries, member enumeration
- handlers_event_coverage_test.go: weekly schedule, login boost,
scenario data, friends/blacklist operations
Also make mockCharacterRepo.ReadEtcPoints configurable to support
etc points handler tests.
PR #150 introduced a double brace `{ {` on handlers_shop.go:109 that
broke compilation. Migration tests were also hardcoded for 1 migration
but 3 now exist (0001–0003).
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.