Commit Graph

1577 Commits

Author SHA1 Message Date
Houmgaor
d578e68b79 docs(gacha): clarify G1-GG vs ZZ configuration to prevent client crashes (#175)
The G1-GG gacha code path (PR #150) included example data in comments
that used non-zero item_type/item_number/item_quantity on entry_type=100
rows. Users copying these values for ZZ servers caused client crashes
because the ZZ client interprets those fields as material requirements.

- Replace unclear PR #150 comment with explicit WARNING against using
  G1-GG example values for ZZ servers
- Add gacha configuration guide header to GachaDemo.sql explaining the
  3-table system, entry types, and item type codes
- Add Mega Potion example showing correct way to add custom items
- Reference Enumerations.md for the full item type list
2026-03-17 19:11:59 +01:00
Houmgaor
b40217c7fe feat(savedata): add tier 1 data integrity protections
Prevent savedata corruption and denial-of-service by adding four layers
of protection to the save pipeline:

- Bounded decompression (nullcomp.DecompressWithLimit): caps output size
  to prevent OOM from crafted payloads that expand to exhaust memory
- Bounds-checked delta patching (deltacomp.ApplyDataDiffWithLimit):
  validates offsets before writing, returns errors for negative offsets,
  truncated patches, and oversized output; ApplyDataDiff now returns
  original data on error instead of partial corruption
- Size limits on save handlers: rejects compressed payloads >512KB and
  decompressed data >1MB before processing; applied to main savedata,
  platedata, and platebox diff paths
- Rotating savedata backups: 3 slots per character with 30-minute
  interval, snapshots the previous state before overwriting, backed by
  new savedata_backups table (migration 0007)
2026-03-17 19:03:43 +01:00
Houmgaor
5009a37d19 fix: create user_binary row on character creation (#176)
New characters were missing their user_binary record, preventing them
from entering their house. Both sign server and API character creation
paths now insert the row. A backfill migration fixes existing databases.
2026-03-16 17:11:55 +01:00
Houmgaor
1ae7dffe7b fix(gacha): add items to reward pool test entries
GetRewardPool now requires entries to have items (EXISTS clause from
2365b63), but the ordering test created entries without items.
2026-03-16 00:07:46 +01:00
Houmgaor
7657ddbd50 fix: prevent startup panics on databases missing base tables
The catch-up migration now creates the servers table if missing, like
sign_sessions. Startup cleanup queries in main.go use Exec instead of
MustExec so missing tables log warnings rather than panicking.
2026-03-16 00:04:45 +01:00
Houmgaor
31aa02a8e2 fix(migrations): create sign_sessions table before altering it
The catch-up migration assumed sign_sessions already existed, but
databases created from older schema dumps may not have this table.
Adding CREATE TABLE IF NOT EXISTS prevents the migration from failing
with "relation does not exist" on those databases.
2026-03-15 23:53:10 +01:00
Houmgaor
72b5d319f1 refactor: rename GetStepupStatus Unk field to GachaType
Ghidra decompilation of mhfo-hd.dll shows all gacha packets use a
trailing uint8 for gacha type (0=normal, 1=stepup, 4=box). The server
ignores it since it derives the type from GachaID.
2026-03-10 13:37:54 +01:00
Houmgaor
c3d089cca1 chore: update version string from 9.3b to 9.3.0 2026-03-10 11:44:52 +01:00
Houmgaor
2365b63e9c fix(gacha): validate reward pool before charging currency (#175)
Filter out gacha entries with no items at the query level (EXISTS
subquery) and reorder Play methods to validate the pool before
calling transact(), so players are never charged for a misconfigured
gacha.
2026-03-10 11:28:11 +01:00
Houmgaor
915e9bc0b0 fix(gacha): prevent panics from misconfigured gacha entries (#175)
getRandomEntries panics with rand.Intn(0) when box gacha has more rolls
than available entries. Fix by clamping rolls, guarding empty entries
and zero-weight pools, and fixing an out-of-bounds slice in the receive
handler overflow path.
2026-03-10 11:19:22 +01:00
Houmgaor
eaac12fac5 docs: remove Docker install option from README
The Docker image is not public, so the "Option A: Docker" install
method doesn't work for users. Removed it and renumbered the
remaining options. Simplified the Updating section with a link
to docker/README.md for Docker users.
2026-03-10 11:10:56 +01:00
Houmgaor
bfc5319cb6 fix(guild): fix nil panics causing clan menu softlock (#171)
The crash was in handleMsgMhfGetGuildManageRight where a variable
shadowing bug (guild, err := instead of guild, err =) left the
outer guild pointer nil after the inner GetByID lookup succeeded,
panicking at guild.MemberCount. This is confirmed by the user's
stack trace pointing to handlers_guild.go:234.

Also fix 6 other nil-dereference risks across guild handlers:
- handleMsgMhfArrangeGuildMember: guild nil after GetByID
- handleMsgMhfEnumerateGuildMember: alliance nil when AllianceID > 0
- handleMsgMhfUpdateGuildIcon: guild and characterInfo nil
- handleMsgMhfOperateGuild: guild nil, characterGuildInfo nil
- handleAvoidLeadershipUpdate: characterGuildData nil

Improve panic recovery to log opcode and full stack trace so
future panics can be diagnosed from console screenshots.
2026-03-06 00:15:53 +01:00
Houmgaor
69ad4ca54a fix: diagnose specific database connection errors at startup
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
2026-03-05 23:25:00 +01:00
Houmgaor
ba7ec122f8 revert: remove SQLite support
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.
2026-03-05 23:05:55 +01:00
Houmgaor
ecfe58ffb4 feat: add SQLite support, setup wizard enhancements, and live dashboard
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
2026-03-05 18:00:30 +01:00
Houmgaor
03adb21e99 fix(channelserver): post-RC1 stabilization sprint
Fix rasta_id=0 overwriting NULL in SaveMercenary, which prevented
game state saving for characters without a mercenary (#163).

Also includes:
- CHANGELOG updated with all 10 post-RC1 commits
- Setup wizard fmt.Printf replaced with zap structured logging
- technical-debt.md updated with 6 newly completed items
- Scenario binary format documented (docs/scenario-format.md)
- Tests: alliance nil-guard (#171), handler dispatch table,
  migrations (sorted/SQL/baseline), setup wizard (10 tests),
  protbot protocol sign/entrance/channel (23 tests)
2026-03-05 16:39:15 +01:00
Houmgaor
10ac803a45 fix(channelserver): correct ecdMagic constant byte order (#174)
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.
2026-03-03 18:23:45 +01:00
Houmgaor
8717fb9b55 fix(guild): add nil guards in cancel and answer scout handlers
GetByID returns (nil, nil) for deleted guilds, and AnswerScout can
return nil result on ErrApplicationMissing, both leading to nil
dereferences.
2026-03-03 18:04:33 +01:00
Houmgaor
8e79fe6834 fix(guild): fix variable shadowing causing nil panic in scout list (#171)
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.
2026-03-03 18:01:20 +01:00
Houmgaor
5b631d1704 perf(channelserver): cache rengoku_data.bin at startup
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.
2026-03-02 20:12:39 +01:00
Houmgaor
aee53534a2 fix(guild): add nil guards for alliance guild lookups (#171)
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.
2026-03-02 19:43:11 +01:00
Houmgaor
07a587213d fix(channelserver): remove false race in PacketDuringLogout test
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.
2026-03-01 18:56:52 +01:00
Houmgaor
6143902f39 test(channelserver): add tests for logoutPlayer, saveAllCharacterData, and transit message
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
2026-03-01 18:41:59 +01:00
Houmgaor
9da3af6087 Merge pull request #170 from sasospanner/patch-1
Update go.mod
2026-02-28 21:55:54 +01:00
sasospanner
611425da1c Update go.mod
added minor version otherwise go would throw an error similar to
go: download go1.25 for linux/amd64: toolchain not available
2026-02-28 13:45:29 -05:00
Houmgaor
9a5a8dfb36 fix(migrations): add IF NOT EXISTS guard to alliance recruiting column
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.
2026-02-28 19:26:21 +01:00
Houmgaor
5106b905de docs: update changelog for v9.3.0-rc1 release
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.
2026-02-28 19:18:11 +01:00
Houmgaor
bb16306f91 test(migrations): update expected counts after adding migrations 0004-0005
Test assertions were still expecting 3 total migrations from when only
0001-0003 existed. Updated to reflect 5 migrations (0001-0005).
v9.3.0-rc1
2026-02-28 18:02:36 +01:00
Houmgaor
fa09e4a39c fix(migrations): drop unused data column from distribution table (#169)
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.
2026-02-27 18:19:57 +01:00
Houmgaor
21f9a79b62 fix(channelserver): correct session handler retail mismatches (#167)
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]
2026-02-27 17:29:32 +01:00
Houmgaor
649eebe67c docs: log bookshelf data pointer fix in changelog and technical debt tracker 2026-02-27 16:53:35 +01:00
Houmgaor
4c47c8e18f fix(channelserver): correct bookshelf save data pointers for non-ZZ (#164)
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).
2026-02-27 16:46:32 +01:00
Houmgaor
7911d84d48 docs: log alliance application toggle fix in changelog 2026-02-27 15:02:30 +01:00
Houmgaor
d6938f2a27 fix(guild): implement alliance application toggle (#166)
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.
2026-02-27 14:59:18 +01:00
Houmgaor
fba8c2413c docs: link remaining TODOs to GitHub issues in technical debt tracker
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.
2026-02-27 13:46:12 +01:00
Houmgaor
7f5d30e2f5 fix: resolve code scanning findings in commands and wizard
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.
2026-02-27 13:45:56 +01:00
Houmgaor
7e24bbc087 ci: serialize test packages to fix shared database race condition
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.
2026-02-27 13:28:45 +01:00
Houmgaor
31266fcb21 test(entranceserver): push coverage from 56.1% to 82.2%
Add 18 tests covering makeSv2Resp (ZZ, G3.2/SVR, MezFes/Return
filtering, debug logging), encodeServerInfo branches (non-local IP,
empty IP fallback, GG/G1-G5 client modes, ProxyPort, type filtering),
makeUsrResp debug logging, sanitizeAddr, and startEntranceCapture
(disabled, entrance-disabled, enabled, default output dir).
2026-02-27 13:19:33 +01:00
Houmgaor
35d920f5da docs: mark repo test coverage as complete in technical debt tracker
The 17 previously-untested repo files all have test files now.
2026-02-27 13:12:43 +01:00
Houmgaor
156b5c53f7 test(signserver): push coverage from 62.9% to 70.3%
Add handlePacket dispatch tests for all switch cases (DSGN, SIGN,
DLTSKEYSIGN, PS4SGN, PS3SGN, VITASGN, WIIUSGN, COGLNK, VITACOGLNK,
DELETE). Add makeSignResponse branch tests covering PSN client PSNID
field, CapLink key/host paths, MezFes minigame switch, non-localhost
remote addr, and PSN token registration. Add startSignCapture
enabled-path tests with temp dir and default output dir.
2026-02-27 13:07:12 +01:00
Houmgaor
3ad2836088 feat(api): add DELETE /v2/characters/{id} route, v2 test coverage, and OpenAPI spec
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.
2026-02-27 12:58:31 +01:00
Houmgaor
bcfdf48dad test(signserver): add session handler and utility coverage tests
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%.
2026-02-27 12:54:13 +01:00
Houmgaor
7ff26f4980 feat(api): add v2 routes, auth middleware, structured errors, and server status endpoint
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
2026-02-27 12:46:23 +01:00
Houmgaor
9f43940a44 docs: add pre-commit checks section to CLAUDE.md
Enforce gofmt and golangci-lint before every commit to catch
formatting and lint issues locally instead of waiting for CI.
2026-02-27 12:37:48 +01:00
Houmgaor
d38fef08bb refactor(discordbot): introduce Session interface for testability
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%.
2026-02-27 11:45:20 +01:00
Houmgaor
4e8c4b4e92 fix(channelserver): handle silently discarded errors across handlers
Replace ~17 instances of '_ =' / '_ :=' with proper error checks that
log warnings or send fail ACKs. Affected handlers: cafe, distitem, data,
guild, guild_board, guild_cooking, guild_scout, house, mercenary, misc,
and rengoku. Also resolves all pre-existing lint issues: unchecked
bf.Seek in tests, unused filtered slice in svc_festa, unused mock
fields, and unused signserver test helper.
2026-02-27 11:33:25 +01:00
Houmgaor
4b24489ebe test(channelserver): add handler coverage tests for scout, house, items, rengoku, tower 2026-02-27 11:33:13 +01:00
Houmgaor
74798fc8b3 fix(channelserver): return error from Save() to prevent misleading success logs
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.
2026-02-27 11:21:37 +01:00
Houmgaor
178a008e25 fix(shop): write gacha header for G1+ clients, not just GG+
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.
2026-02-26 23:53:35 +01:00
Houmgaor
d0837e779c refactor(channelserver): consolidate tests into matching source test files
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.
2026-02-26 23:41:44 +01:00