From 84e72f7d35a281058a348f6f5fdb5b72f3895807 Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Mon, 6 Apr 2026 16:16:05 +0200 Subject: [PATCH] fix(handlers): honor DisableLoginBoost and DisableBoostTime fully (#187) GetBoostTimeLimit and GetBoostRight now respect DisableBoostTime, and UseKeepLoginBoost now respects DisableLoginBoost. Also fix a latent zero-time.Time wraparound in GetBoostTimeLimit that caused the "Boost Time" overlay to appear on fresh characters regardless of config, since time.Time{}.Unix() cast to uint32 yields a large value the client interprets as an active timestamp. --- CHANGELOG.md | 1 + server/channelserver/handlers_cafe.go | 8 +++++++- server/channelserver/handlers_event.go | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cebd7e764..2cb0908d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed `DisableLoginBoost` and `DisableBoostTime` config flags not fully honored ([#187](https://github.com/Mezeporta/Erupe/issues/187)): `GetBoostTimeLimit`/`GetBoostRight` now respect `DisableBoostTime` and `UseKeepLoginBoost` now respects `DisableLoginBoost`. Also fixes a zero-`time.Time` wraparound in `GetBoostTimeLimit` that made the "Boost Time" overlay appear on fresh characters. - 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. - Fixed player softlock when buying items at the forge: `MSG_CA_EXCHANGE_ITEM` `Parse()` was returning `NOT IMPLEMENTED`, causing the dispatch loop to drop the packet without sending an ACK. Now parses the `AckHandle` and responds with `doAckBufFail` so the client's error branch exits cleanly. - Fixed player softlock on N-points (Hunting Road) interactions: same root cause for `MSG_MHF_USE_UD_SHOP_COIN` — `Parse()` now reads the `AckHandle` and responds with `doAckBufFail`. diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index 2f2ff4fb8..4197f7d99 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -219,7 +219,9 @@ func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit) bf := byteframe.NewByteFrame() boostLimit, err := s.server.charRepo.ReadTime(s.charID, "boost_time", time.Time{}) - if err != nil { + // Return 0 when disabled, on read error, or when boost_time is unset + // (zero time.Time.Unix() wraps to a large uint32 the client interprets as active). + if err != nil || s.server.erupeConfig.GameplayOptions.DisableBoostTime || boostLimit.IsZero() { bf.WriteUint32(0) } else { bf.WriteUint32(uint32(boostLimit.Unix())) @@ -230,6 +232,10 @@ func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetBoostRight) + if s.server.erupeConfig.GameplayOptions.DisableBoostTime { + doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return + } boostLimit, err := s.server.charRepo.ReadTime(s.charID, "boost_time", time.Time{}) if err != nil { doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) diff --git a/server/channelserver/handlers_event.go b/server/channelserver/handlers_event.go index 2fb746105..125c6db09 100644 --- a/server/channelserver/handlers_event.go +++ b/server/channelserver/handlers_event.go @@ -195,6 +195,10 @@ func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUseKeepLoginBoost) + if s.server.erupeConfig.GameplayOptions.DisableLoginBoost { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 5)) + return + } var expiration time.Time bf := byteframe.NewByteFrame() bf.WriteUint8(0)