feat(savedata): parse zenny/gzenny/CP from ZZ save blob

Adds read-only parsing for three scalar fields in the ZZ character save
blob: zenny (0xB0), gzenny (0x1FF64) and caravan points (0x212E4). Also
registers an offset for current_equip (0x1F604); extraction deferred
until its length is reverse-engineered. Offsets sourced from
Chakratos/mhf-save-manager and validated against a live HR999 blob.

Scope is intentionally ZZ-only: mhf-save-manager's F5 and G1-G5.2 maps
are not validated against live data, and the dormant pPlaytime vs
item_pouch collision in those versions is not resolved yet. Non-ZZ
modes leave the new pointers unmapped, and the read path is guarded by
`ok && off > 0 && off+size <= len(blob)` so unverified versions cannot
accidentally read from the blob.

Tests cover positive-path roundtrip (including live kirito blob),
regression guards for existing fields, non-ZZ isolation, new-character
skip, and bounds safety against truncated blobs.
This commit is contained in:
Houmgaor
2026-04-17 23:04:30 +02:00
parent 538724e6c9
commit 47277c712d
4 changed files with 312 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Parse zenny, gzenny and caravan points (CP) from the ZZ character save blob (offsets 0xB0, 0x1FF64, 0x212E4 — sourced from Chakratos/mhf-save-manager, validated against a live HR999 save). Exposed as `CharacterSaveData.Zenny/GZenny/CP` alongside the existing `current_equip` pointer; read-only for now. Pre-ZZ modes remain unmapped to avoid corrupting unverified layouts.
- Chinese (`zh`) language strings for chat commands, guild mails, cafe/timer broadcasts and prayer beads. Note: Shift-JIS wire encoding only covers characters shared with Japanese — simplified-only glyphs may fail to encode.
- Server-side multi-language support ([#188](https://github.com/Mezeporta/Erupe/issues/188)): each player picks their own language with `!lang <en|jp|fr|es|zh>`, persisted per user (migration `0022_user_language`) and loaded on login. Chat replies, guild invite mails, and cafe/timer broadcasts are served in that language via `Session.I18n()`. Quest and scenario JSON text fields now accept either a plain string (unchanged) or a `{"jp":"...","en":"...","fr":"..."}` map; the compiler resolves per session and the quest cache is keyed by `(questID, lang)`. Existing single-language JSONs and `.bin` round-trips remain byte-identical. Shift-JIS wire encoding still applies (ASCII/kana/CJK only). Raviente world-wide broadcasts stay on the server default since they have no single session.