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.
This commit is contained in:
Houmgaor
2026-02-27 11:33:25 +01:00
parent 4b24489ebe
commit 4e8c4b4e92
15 changed files with 236 additions and 37 deletions

View File

@@ -51,7 +51,9 @@ func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
var bondBonus, bonusQuests, dailyQuests uint32
bf := byteframe.NewByteFrame()
if midday.After(dailyTime) {
_ = addPointNetcafe(s, 5)
if err := addPointNetcafe(s, 5); err != nil {
s.logger.Error("Failed to add daily netcafe points", zap.Error(err))
}
bondBonus = 5 // Bond point bonus quests
bonusQuests = s.server.erupeConfig.GameplayOptions.BonusQuestAllowance
dailyQuests = s.server.erupeConfig.GameplayOptions.DailyQuestAllowance
@@ -167,7 +169,9 @@ func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket
itemType, quantity, err := s.server.cafeRepo.GetBonusItem(cbID)
if err == nil {
if itemType == 17 {
_ = addPointNetcafe(s, int(quantity))
if err := addPointNetcafe(s, int(quantity)); err != nil {
s.logger.Error("Failed to add cafe bonus netcafe points", zap.Error(err))
}
}
}
if err := s.server.cafeRepo.AcceptBonus(cbID, s.charID); err != nil {
@@ -185,6 +189,7 @@ func addPointNetcafe(s *Session, p int) error {
points = min(points+p, s.server.erupeConfig.GameplayOptions.MaximumNP)
if err := s.server.charRepo.SaveInt(s.charID, "netcafe_points", points); err != nil {
s.logger.Error("Failed to update netcafe points", zap.Error(err))
return fmt.Errorf("save netcafe points: %w", err)
}
return nil
}

View File

@@ -176,9 +176,13 @@ func dumpSaveData(s *Session, data []byte, suffix string) {
func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoaddata)
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin")); err == nil {
data, _ := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin"))
doAckBufSucceed(s, pkt.AckHandle, data)
return
data, readErr := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "save_override.bin"))
if readErr != nil {
s.logger.Error("Failed to read save_override.bin", zap.Error(readErr))
} else {
doAckBufSucceed(s, pkt.AckHandle, data)
return
}
}
data, err := s.server.charRepo.LoadColumn(s.charID, "savedata")

View File

@@ -139,7 +139,9 @@ func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
for _, item := range distItems {
switch item.ItemType {
case 17:
_ = addPointNetcafe(s, int(item.Quantity))
if err := addPointNetcafe(s, int(item.Quantity)); err != nil {
s.logger.Error("Failed to add dist item netcafe points", zap.Error(err))
}
case 19:
if err := s.server.userRepo.AddPremiumCoins(s.userID, item.Quantity); err != nil {
s.logger.Error("Failed to update gacha premium", zap.Error(err))

View File

@@ -87,7 +87,10 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
}
if guild != nil {
isApplicant, _ := s.server.guildRepo.HasApplication(guild.ID, s.charID)
isApplicant, appErr := s.server.guildRepo.HasApplication(guild.ID, s.charID)
if appErr != nil {
s.logger.Warn("Failed to check guild application status", zap.Error(appErr))
}
if isApplicant {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
return
@@ -229,7 +232,12 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(guild.MemberCount))
members, _ := s.server.guildRepo.GetMembers(guild.ID, false)
members, err := s.server.guildRepo.GetMembers(guild.ID, false)
if err != nil {
s.logger.Error("Failed to get guild members for manage right", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
for _, member := range members {
bf.WriteUint32(member.CharID)
bf.WriteBool(member.Recruiter)

View File

@@ -23,7 +23,12 @@ type MessageBoardPost struct {
func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildMessageBoard)
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
guild, err := s.server.guildRepo.GetByCharID(s.charID)
if err != nil {
s.logger.Error("Failed to get guild for message board", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
if pkt.BoardType == 1 {
pkt.MaxPosts = 4
}
@@ -107,7 +112,10 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
case 5: // Check for new messages
timeChecked, err := s.server.charRepo.ReadGuildPostChecked(s.charID)
if err == nil {
newPosts, _ := s.server.guildRepo.CountNewPosts(guild.ID, timeChecked)
newPosts, countErr := s.server.guildRepo.CountNewPosts(guild.ID, timeChecked)
if countErr != nil {
s.logger.Warn("Failed to count new guild posts", zap.Error(countErr))
}
if newPosts > 0 {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
return

View File

@@ -18,7 +18,14 @@ type GuildMeal struct {
func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadGuildCooking)
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
guild, err := s.server.guildRepo.GetByCharID(s.charID)
if err != nil || guild == nil {
if err != nil {
s.logger.Error("Failed to get guild for cooking", zap.Error(err))
}
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
return
}
allMeals, err := s.server.guildRepo.ListMeals(guild.ID)
if err != nil {
s.logger.Error("Failed to get guild meals from db", zap.Error(err))
@@ -44,7 +51,14 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegistGuildCooking)
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
guild, err := s.server.guildRepo.GetByCharID(s.charID)
if err != nil || guild == nil {
if err != nil {
s.logger.Error("Failed to get guild for cooking registration", zap.Error(err))
}
doAckBufFail(s, pkt.AckHandle, nil)
return
}
startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.ClanMealDuration-3600) * time.Second)
if pkt.OverwriteID != 0 {
if err := s.server.guildRepo.UpdateMeal(pkt.OverwriteID, uint32(pkt.MealID), uint32(pkt.Success), startTime); err != nil {

View File

@@ -630,7 +630,7 @@ func TestHandleRenamePugi_Pugi1(t *testing.T) {
nameBytes := stringsupport.UTF8ToSJIS("TestPugi")
bf.WriteBytes(nameBytes)
bf.WriteUint8(0) // null terminator
bf.Seek(0, 0)
_, _ = bf.Seek(0, 0)
handleRenamePugi(s, bf, guild, 1)
if guild.PugiName1 != "TestPugi" {
@@ -648,7 +648,7 @@ func TestHandleRenamePugi_Pugi2(t *testing.T) {
nameBytes := stringsupport.UTF8ToSJIS("Pugi2")
bf.WriteBytes(nameBytes)
bf.WriteUint8(0)
bf.Seek(0, 0)
_, _ = bf.Seek(0, 0)
handleRenamePugi(s, bf, guild, 2)
if guild.PugiName2 != "Pugi2" {
@@ -666,7 +666,7 @@ func TestHandleRenamePugi_Pugi3Default(t *testing.T) {
nameBytes := stringsupport.UTF8ToSJIS("Pugi3")
bf.WriteBytes(nameBytes)
bf.WriteUint8(0)
bf.Seek(0, 0)
_, _ = bf.Seek(0, 0)
handleRenamePugi(s, bf, guild, 3)
if guild.PugiName3 != "Pugi3" {
@@ -720,3 +720,142 @@ func TestHandleAvoidLeadershipUpdate_GetMembershipError(t *testing.T) {
handleAvoidLeadershipUpdate(s, pkt, true)
<-s.sendPackets
}
func TestHandleAvoidLeadershipUpdate_SaveError(t *testing.T) {
srv := createMockServer()
membership := &GuildMember{CharID: 100}
srv.guildRepo = &mockGuildRepo{membership: membership, saveMemberErr: errNotFound}
s := createMockSession(100, srv)
pkt := &mhfpacket.MsgMhfOperateGuild{AckHandle: 1}
handleAvoidLeadershipUpdate(s, pkt, true)
<-s.sendPackets
}
// --- mapMemberAction tests ---
func TestMapMemberAction(t *testing.T) {
tests := []struct {
proto uint8
want GuildMemberAction
ok bool
}{
{mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT, GuildMemberActionAccept, true},
{mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT, GuildMemberActionReject, true},
{mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK, GuildMemberActionKick, true},
{255, 0, false},
}
for _, tt := range tests {
action, ok := mapMemberAction(tt.proto)
if ok != tt.ok {
t.Errorf("mapMemberAction(%d) ok = %v, want %v", tt.proto, ok, tt.ok)
}
if action != tt.want {
t.Errorf("mapMemberAction(%d) = %d, want %d", tt.proto, action, tt.want)
}
}
}
func TestOperateGuildMember_UnknownAction(t *testing.T) {
server := createMockServer()
guildMock := &mockGuildRepo{
membership: &GuildMember{GuildID: 10, CharID: 1, IsLeader: true, OrderIndex: 1},
}
guildMock.guild = &Guild{ID: 10, Name: "TestGuild"}
guildMock.guild.LeaderCharID = 1
server.guildRepo = guildMock
server.mailRepo = &mockMailRepo{}
ensureGuildService(server)
session := createMockSession(1, server)
pkt := &mhfpacket.MsgMhfOperateGuildMember{
AckHandle: 100,
GuildID: 10,
CharID: 42,
Action: 255, // unknown action
}
handleMsgMhfOperateGuildMember(session, pkt)
select {
case <-session.sendPackets:
default:
t.Error("No response packet queued")
}
}
// --- handleDonateRP tests ---
func TestDonateRP_Type0_RankRP(t *testing.T) {
server := createMockServer()
charMock := newMockCharacterRepo()
// Build minimal save data that GetCharacterSaveData can parse
charMock.loadSaveDataID = 1
charMock.loadSaveDataData = nil // triggers new char path
charMock.loadSaveDataNew = true
charMock.loadSaveDataName = "TestChar"
server.charRepo = charMock
guildMock := &mockGuildRepo{}
guildMock.guild = &Guild{ID: 10}
server.guildRepo = guildMock
session := createMockSession(1, server)
result := handleDonateRP(session, 5, guildMock.guild, 0)
if len(result) != 4 {
t.Errorf("Expected 4 bytes, got %d", len(result))
}
}
func TestDonateRP_Type1_EventRP(t *testing.T) {
server := createMockServer()
charMock := newMockCharacterRepo()
charMock.loadSaveDataID = 1
charMock.loadSaveDataData = nil
charMock.loadSaveDataNew = true
charMock.loadSaveDataName = "TestChar"
server.charRepo = charMock
guildMock := &mockGuildRepo{}
guildMock.guild = &Guild{ID: 10}
server.guildRepo = guildMock
session := createMockSession(1, server)
result := handleDonateRP(session, 10, guildMock.guild, 1)
if len(result) != 4 {
t.Errorf("Expected 4 bytes, got %d", len(result))
}
}
func TestDonateRP_Type2_RoomRP_NoReset(t *testing.T) {
server := createMockServer()
charMock := newMockCharacterRepo()
charMock.loadSaveDataID = 1
charMock.loadSaveDataData = nil
charMock.loadSaveDataNew = true
charMock.loadSaveDataName = "TestChar"
server.charRepo = charMock
guildMock := &mockGuildRepo{}
guildMock.guild = &Guild{ID: 10}
server.guildRepo = guildMock
session := createMockSession(1, server)
result := handleDonateRP(session, 5, guildMock.guild, 2) // 0+5 < 30, no reset
if len(result) != 4 {
t.Errorf("Expected 4 bytes, got %d", len(result))
}
}
func TestDonateRP_SaveDataError(t *testing.T) {
server := createMockServer()
charMock := newMockCharacterRepo()
charMock.loadSaveDataErr = errNotFound
server.charRepo = charMock
guildMock := &mockGuildRepo{}
guildMock.guild = &Guild{ID: 10}
server.guildRepo = guildMock
session := createMockSession(1, server)
result := handleDonateRP(session, 5, guildMock.guild, 0)
if len(result) != 4 {
t.Errorf("Expected 4 bytes on error, got %d", len(result))
}
}

View File

@@ -101,7 +101,10 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildScoutList)
guildInfo, _ := s.server.guildRepo.GetByCharID(s.charID)
guildInfo, err := s.server.guildRepo.GetByCharID(s.charID)
if err != nil {
s.logger.Warn("Failed to get guild for scout list", zap.Error(err))
}
if guildInfo == nil && s.prevGuildID == 0 {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))

View File

@@ -43,7 +43,10 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
var houses []HouseData
switch pkt.Method {
case 1:
friendsList, _ := s.server.charRepo.ReadString(s.charID, "friends")
friendsList, flErr := s.server.charRepo.ReadString(s.charID, "friends")
if flErr != nil {
s.logger.Warn("Failed to read friends list for house enumeration", zap.Error(flErr))
}
cids := stringsupport.CSVElems(friendsList)
for _, cid := range cids {
house, err := s.server.houseRepo.GetHouseByCharID(uint32(cid))
@@ -134,7 +137,10 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
// Friends list verification
if state == 3 || state == 5 {
friendsList, _ := s.server.charRepo.ReadString(pkt.CharID, "friends")
friendsList, flErr := s.server.charRepo.ReadString(pkt.CharID, "friends")
if flErr != nil {
s.logger.Warn("Failed to read friends list for house access check", zap.Error(flErr))
}
cids := stringsupport.CSVElems(friendsList)
for _, cid := range cids {
if uint32(cid) == s.charID {
@@ -148,7 +154,10 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
if state > 3 {
ownGuild, err := s.server.guildRepo.GetByCharID(s.charID)
if err == nil && ownGuild != nil {
isApplicant, _ := s.server.guildRepo.HasApplication(ownGuild.ID, s.charID)
isApplicant, appErr := s.server.guildRepo.HasApplication(ownGuild.ID, s.charID)
if appErr != nil {
s.logger.Warn("Failed to check guild application for house access", zap.Error(appErr))
}
othersGuild, err := s.server.guildRepo.GetByCharID(pkt.CharID)
if err == nil && othersGuild != nil {
if othersGuild.ID == ownGuild.ID && !isApplicant {

View File

@@ -208,7 +208,10 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
bf := byteframe.NewByteFrame()
pactID, _ := readCharacterInt(s, "pact_id")
pactID, pactErr := readCharacterInt(s, "pact_id")
if pactErr != nil {
s.logger.Warn("Failed to read pact_id", zap.Error(pactErr))
}
var cid uint32
var name string
if pactID > 0 {
@@ -243,8 +246,14 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
}
if pkt.Op != 1 && pkt.Op != 4 {
data, _ := s.server.charRepo.LoadColumn(s.charID, "savemercenary")
gcp, _ := readCharacterInt(s, "gcp")
data, dataErr := s.server.charRepo.LoadColumn(s.charID, "savemercenary")
if dataErr != nil {
s.logger.Warn("Failed to load savemercenary", zap.Error(dataErr))
}
gcp, gcpErr := readCharacterInt(s, "gcp")
if gcpErr != nil {
s.logger.Warn("Failed to read gcp", zap.Error(gcpErr))
}
if len(data) == 0 {
bf.WriteBool(false)
@@ -261,7 +270,10 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
data, _ := s.server.charRepo.LoadColumn(pkt.CharID, "savemercenary")
data, err := s.server.charRepo.LoadColumn(pkt.CharID, "savemercenary")
if err != nil {
s.logger.Warn("Failed to load savemercenary for other character", zap.Error(err))
}
resp := byteframe.NewByteFrame()
if len(data) == 0 {
resp.WriteBool(false)

View File

@@ -13,7 +13,10 @@ import (
func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEtcPoints)
dailyTime, _ := s.server.charRepo.ReadTime(s.charID, "daily_time", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
dailyTime, err := s.server.charRepo.ReadTime(s.charID, "daily_time", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
if err != nil {
s.logger.Warn("Failed to read daily_time for etc points", zap.Error(err))
}
if TimeAdjusted().After(dailyTime) {
if err := s.server.charRepo.ResetDailyQuests(s.charID); err != nil {
s.logger.Error("Failed to reset daily quests", zap.Error(err))

View File

@@ -199,7 +199,10 @@ type RengokuScore struct {
func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking)
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
guild, guildErr := s.server.guildRepo.GetByCharID(s.charID)
if guildErr != nil {
s.logger.Warn("Failed to get guild for rengoku ranking", zap.Error(guildErr))
}
var isApplicant bool
if guild != nil {
var appErr error

View File

@@ -810,8 +810,6 @@ type mockGachaRepo struct {
allEntriesErr error
weightDivisor float64
// FrontierPoints from gacha
addFPErr error
}
func (m *mockGachaRepo) GetEntryForTransaction(_ uint32, _ uint8) (uint8, uint16, int, error) {

View File

@@ -46,12 +46,11 @@ func (svc *FestaService) EnsureActiveEvent(currentStart uint32, now time.Time, n
// SubmitSouls filters out zero-value soul entries and records the remaining
// submissions for the character. Returns nil if all entries are zero.
func (svc *FestaService) SubmitSouls(charID, guildID uint32, souls []uint16) error {
var filtered []uint16
hasNonZero := false
for _, s := range souls {
filtered = append(filtered, s)
if s != 0 {
hasNonZero = true
break
}
}
if !hasNonZero {

View File

@@ -253,11 +253,3 @@ func (m *mockSignSessionRepo) GetPSNIDByToken(token string) (string, error) {
return m.psnIDByToken, m.psnIDByTokenErr
}
// newTestServer creates a Server with mock repos for testing.
func newTestServer(userRepo SignUserRepo, charRepo SignCharacterRepo, sessionRepo SignSessionRepo) *Server {
return &Server{
userRepo: userRepo,
charRepo: charRepo,
sessionRepo: sessionRepo,
}
}