From 72088db4ff3d55b5a6cc6f42e690e1c6970f97ab Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Mon, 23 Mar 2026 22:00:06 +0100 Subject: [PATCH] fix(savedata): write playtime back to binary blob on save updateSaveDataWithStruct only wrote RP and KQF into the blob, leaving the playtime field stale. On each reconnect, GetCharacterSaveData read the old in-game counter from the blob and reset s.playtime to it, rolling back all progress accumulated during the previous session. Playtime is now persisted into the blob alongside RP, using the same S6 mode guard as the read path in updateStructWithSaveData. --- CHANGELOG.md | 4 ++++ server/channelserver/model_character.go | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bda44230..a2c370aa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fixed playtime regression across sessions: `updateSaveDataWithStruct` now writes the accumulated playtime back into the binary save blob, preventing each reconnect from loading a stale in-game counter and rolling back progress. + ## [9.3.1] - 2026-03-23 ### Added diff --git a/server/channelserver/model_character.go b/server/channelserver/model_character.go index ca7c7e91c..488c6fbf5 100644 --- a/server/channelserver/model_character.go +++ b/server/channelserver/model_character.go @@ -148,6 +148,11 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() { if save.Mode >= cfg.F4 { copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+saveFieldRP], rpBytes) } + if save.Mode >= cfg.S6 { + playtimeBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(playtimeBytes, save.Playtime) + copy(save.decompSave[save.Pointers[pPlaytime]:save.Pointers[pPlaytime]+saveFieldPlaytime], playtimeBytes) + } if save.Mode >= cfg.G10 { copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+saveFieldKQF], save.KQF) }