Brings 53 develop commits (i18n, Diva, campaign, guild invites, save
transfer, return/rookie guilds, hunting tournament, JSON quest/scenario
loaders, Ghidra-derived user binary parsing, and misc fixes) onto main
now that 9.3.2 has been tagged and released.
Resolves two overlap zones:
1. Migration number collision. Main shipped 0010_fix_zero_rasta_id and
0011_fix_stale_boost_time in 9.3.2; develop had independently
numbered 0010_campaign..0015_tournament. The migration runner keys
applied versions by integer, so coexisting files with the same
numeric prefix would silently skip each other. Develop's files have
been renumbered to 0016..0021, leaving main's 0010/0011 intact. A
schema_version rename script is required on any server that had
already applied the old develop numbers (only frontier.mogapedia.fr
at the time of this merge).
2. CHANGELOG.md. Develop's in-progress feature entries move into
[Unreleased] with updated migration references; the [9.3.2] section
is preserved verbatim.
main.go version string bumped to 9.4.0-dev to mark the new cycle.
Full test suite (go test -race ./...) passes.
Adds non-destructive test scenarios for the #187 boost-time fix and
the #175 / gacha-logging changes so regressions in those paths can be
caught without a full game client.
- boost: queries GET_BOOST_TIME_LIMIT, GET_BOOST_RIGHT, and
GET_KEEP_LOGIN_BOOST_STATUS, flagging a zero boost_limit and all-zero
login boost entries as the expected DisableBoostTime/DisableLoginBoost
state.
- gacha: snapshots GET_GACHA_POINT and RECEIVE_GACHA_ITEM (freeze=true,
so temp storage is not cleared), with an opt-in --roll flag that
exercises PLAY_NORMAL_GACHA end-to-end. Detects the post-#175
single-byte validation-failure ACK.
Adds two complementary paths for transferring character save data between
Erupe instances without breaking the SHA-256 integrity check system:
- `cmd/saveutil/`: admin CLI with `import`, `export`, `grant-import`, and
`revoke-import` subcommands. Direct DB access; no server running required.
- `POST /v2/characters/{id}/import`: player-facing API endpoint gated behind
a one-time token issued by `saveutil grant-import` (default TTL 24 h).
Token is validated and consumed atomically to prevent TOCTOU races.
- Migration `0013_save_transfer`: `savedata_import_token` and
`savedata_import_token_expiry` columns on `characters` table.
- Both paths decompress incoming savedata and recompute the SHA-256 hash
server-side, so the integrity check remains valid after import.
- README documents both methods and the per-character hash-reset workaround.
Closes#183.
RE'd putDisplayed_achievement from ZZ client DLL via Ghidra: the packet
sends opcode + 1 zero byte with no achievement ID, acting as a blanket
"I saw everything" signal.
Server changes:
- Track per-character last-displayed levels in new displayed_levels
column (migration 0008)
- GetAchievement compares current vs displayed levels per entry
- DisplayedAchievement snapshots current levels to clear notifications
- Repo, service, mock, and 3 new service tests
Protbot changes:
- New --action achievement: fetches achievements, shows rank-up markers,
sends DISPLAYED_ACHIEVEMENT, re-fetches to verify notifications clear
- Packet builders for GET/ADD/DISPLAYED_ACHIEVEMENT
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
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.
golangci-lint's errcheck rule requires explicit handling of error
return values from Close, Write, and Logout calls. Use blank
identifier assignment for cleanup paths where errors are
intentionally discarded.
The protbot sent "DSGN:\x00" as the sign request type, but the server
strips the last 3 characters as a version suffix. Send "DSGN:041"
(ZZ client mode 41) to match the real client format.
The entrance channel entry parser read 14 bytes for remaining fields
but the server writes 18 bytes (9 uint16, not 7), causing a panic
when parsing the server list.
The channel server panicked on disconnect when a session had no
decompressed save data (e.g. protbot or early client disconnect).
Guard Save() against nil decompSave.
Also fix docker-compose volume mount for Postgres 18 which changed
its data directory layout.
Copy MHBridge into the Erupe module as cmd/protbot/ so it can be
built, tested, and maintained alongside the server. The bot
implements the full sign → entrance → channel login flow and
supports lobby entry, chat, session setup, and quest enumeration.
The conn/ package keeps its own Blowfish crypto primitives to avoid
importing erupe-ce/config (which requires a config file at init).