From d456bd23e0e78d2accc1ad21e3fc86d5a5e59ee0 Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Fri, 20 Feb 2026 21:06:16 +0100 Subject: [PATCH] fix(channelserver): handle ignored DB errors and cache userID on session Silently ignored DB errors in handlers could cause data loss (frontier point transactions completing without DB writes), reward duplication (stamp exchange granting items on failed UPDATE), and crashes (tower mission page=0 causing index-out-of-bounds). House access state defaulting to 0 on DB failure also bypassed all access controls. HIGH risk fixes: - frontier point buy/sell now fails with ACK on DB error - stamp exchange/stampcard abort on failed UPDATE - guild meal INSERT returns fail ACK instead of orphaned ID 0 - mercenary/airou creation aborts on failed sequence nextval MEDIUM risk fixes: - tower mission page clamped to >= 1 preventing array underflow - tower RP donation returns early on failed guild state read - house state defaults to 2 (password-protected) on DB failure - playtime read failure logged instead of silently resetting RP Also cache userID on Session at login time, eliminating ~25 redundant subqueries of the form WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1) across shop, gacha, command, and distitem handlers. --- server/channelserver/handlers_cast_binary.go | 2 +- server/channelserver/handlers_commands.go | 16 ++++++------- server/channelserver/handlers_distitem.go | 6 ++--- server/channelserver/handlers_gacha.go | 16 ++++++------- .../channelserver/handlers_guild_cooking.go | 6 ++++- server/channelserver/handlers_helpers.go | 2 +- server/channelserver/handlers_house.go | 8 ++++--- server/channelserver/handlers_items.go | 22 +++++++++++++---- server/channelserver/handlers_mercenary.go | 16 ++++++++++--- server/channelserver/handlers_session.go | 12 ++++++++-- server/channelserver/handlers_shop.go | 24 +++++++++++++++---- server/channelserver/handlers_tower.go | 13 ++++++++-- server/channelserver/sys_session.go | 3 ++- 13 files changed, 104 insertions(+), 42 deletions(-) diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index f8d51c015..013580ab8 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -37,7 +37,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { if pkt.BroadcastType == BroadcastTypeStage && pkt.MessageType == BinaryMessageTypeData && len(pkt.RawDataPayload) == 0x10 { if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 { var timer bool - if err := s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&timer); err != nil { + if err := s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users WHERE id=$1`, s.userID).Scan(&timer); err != nil { s.logger.Error("Failed to get timer setting", zap.Error(err)) } if timer { diff --git a/server/channelserver/handlers_commands.go b/server/channelserver/handlers_commands.go index d18927971..5ec5dd3da 100644 --- a/server/channelserver/handlers_commands.go +++ b/server/channelserver/handlers_commands.go @@ -132,10 +132,10 @@ func parseChatCommand(s *Session, command string) { case commands["Timer"].Prefix: if commands["Timer"].Enabled || s.isOp() { var state bool - if err := s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state); err != nil { + if err := s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users WHERE id=$1`, s.userID).Scan(&state); err != nil { s.logger.Error("Failed to get timer state", zap.Error(err)) } - if _, err := s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID); err != nil { + if _, err := s.server.db.Exec(`UPDATE users SET timer=$1 WHERE id=$2`, !state, s.userID); err != nil { s.logger.Error("Failed to update timer setting", zap.Error(err)) } if state { @@ -154,7 +154,7 @@ func parseChatCommand(s *Session, command string) { s.logger.Error("Failed to check PSN ID existence", zap.Error(err)) } if exists == 0 { - _, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID) + _, err := s.server.db.Exec(`UPDATE users SET psn_id=$1 WHERE id=$2`, args[1], s.userID) if err == nil { sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1])) } @@ -256,7 +256,7 @@ func parseChatCommand(s *Session, command string) { if commands["Rights"].Enabled || s.isOp() { if len(args) > 1 { v, _ := strconv.Atoi(args[1]) - _, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID) + _, err := s.server.db.Exec("UPDATE users SET rights=$1 WHERE id=$2", v, s.userID) if err == nil { sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v)) } else { @@ -293,9 +293,9 @@ func parseChatCommand(s *Session, command string) { delta = uint32(math.Pow(2, float64(course.ID))) sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, course.Aliases()[0])) } - err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt) + err := s.server.db.QueryRow("SELECT rights FROM users WHERE id=$1", s.userID).Scan(&rightsInt) if err == nil { - if _, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", rightsInt+delta, s.charID); err != nil { + if _, err := s.server.db.Exec("UPDATE users SET rights=$1 WHERE id=$2", rightsInt+delta, s.userID); err != nil { s.logger.Error("Failed to update user rights", zap.Error(err)) } } @@ -390,12 +390,12 @@ func parseChatCommand(s *Session, command string) { case commands["Discord"].Prefix: if commands["Discord"].Enabled || s.isOp() { var _token string - err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token) + err := s.server.db.QueryRow(`SELECT discord_token FROM users WHERE id=$1`, s.userID).Scan(&_token) if err != nil { randToken := make([]byte, 4) _, _ = rand.Read(randToken) _token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:]) - if _, err := s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID); err != nil { + if _, err := s.server.db.Exec(`UPDATE users SET discord_token = $1 WHERE id=$2`, _token, s.userID); err != nil { s.logger.Error("Failed to update discord token", zap.Error(err)) } } diff --git a/server/channelserver/handlers_distitem.go b/server/channelserver/handlers_distitem.go index 81675997e..6311fce2d 100644 --- a/server/channelserver/handlers_distitem.go +++ b/server/channelserver/handlers_distitem.go @@ -172,15 +172,15 @@ func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) { case 17: _ = addPointNetcafe(s, int(item.Quantity)) case 19: - if _, err := s.server.db.Exec("UPDATE users u SET gacha_premium=gacha_premium+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", item.Quantity, s.charID); err != nil { + if _, err := s.server.db.Exec("UPDATE users SET gacha_premium=gacha_premium+$1 WHERE id=$2", item.Quantity, s.userID); err != nil { s.logger.Error("Failed to update gacha premium", zap.Error(err)) } case 20: - if _, err := s.server.db.Exec("UPDATE users u SET gacha_trial=gacha_trial+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", item.Quantity, s.charID); err != nil { + if _, err := s.server.db.Exec("UPDATE users SET gacha_trial=gacha_trial+$1 WHERE id=$2", item.Quantity, s.userID); err != nil { s.logger.Error("Failed to update gacha trial", zap.Error(err)) } case 21: - if _, err := s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", item.Quantity, s.charID); err != nil { + if _, err := s.server.db.Exec("UPDATE users SET frontier_points=frontier_points+$1 WHERE id=$2", item.Quantity, s.userID); err != nil { s.logger.Error("Failed to update frontier points", zap.Error(err)) } case 23: diff --git a/server/channelserver/handlers_gacha.go b/server/channelserver/handlers_gacha.go index 977f64fdb..a5e7fcd94 100644 --- a/server/channelserver/handlers_gacha.go +++ b/server/channelserver/handlers_gacha.go @@ -55,7 +55,7 @@ func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetGachaPoint) var fp, gp, gt uint32 - _ = s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_premium, 0), COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)", s.charID).Scan(&fp, &gp, >) + _ = s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_premium, 0), COALESCE(gacha_trial, 0) FROM users WHERE id=$1", s.userID).Scan(&fp, &gp, >) resp := byteframe.NewByteFrame() resp.WriteUint32(gp) resp.WriteUint32(gt) @@ -66,12 +66,12 @@ func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUseGachaPoint) if pkt.TrialCoins > 0 { - if _, err := s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.TrialCoins, s.charID); err != nil { + if _, err := s.server.db.Exec(`UPDATE users SET gacha_trial=gacha_trial-$1 WHERE id=$2`, pkt.TrialCoins, s.userID); err != nil { s.logger.Error("Failed to deduct gacha trial coins", zap.Error(err)) } } if pkt.PremiumCoins > 0 { - if _, err := s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.PremiumCoins, s.charID); err != nil { + if _, err := s.server.db.Exec(`UPDATE users SET gacha_premium=gacha_premium-$1 WHERE id=$2`, pkt.PremiumCoins, s.userID); err != nil { s.logger.Error("Failed to deduct gacha premium coins", zap.Error(err)) } } @@ -80,13 +80,13 @@ func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { func spendGachaCoin(s *Session, quantity uint16) { var gt uint16 - _ = s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(>) + _ = s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users WHERE id=$1`, s.userID).Scan(>) if quantity <= gt { - if _, err := s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID); err != nil { + if _, err := s.server.db.Exec(`UPDATE users SET gacha_trial=gacha_trial-$1 WHERE id=$2`, quantity, s.userID); err != nil { s.logger.Error("Failed to deduct gacha trial coins", zap.Error(err)) } } else { - if _, err := s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID); err != nil { + if _, err := s.server.db.Exec(`UPDATE users SET gacha_premium=gacha_premium-$1 WHERE id=$2`, quantity, s.userID); err != nil { s.logger.Error("Failed to deduct gacha premium coins", zap.Error(err)) } } @@ -117,7 +117,7 @@ func transactGacha(s *Session, gachaID uint32, rollID uint8) (int, error) { case 20: spendGachaCoin(s, itemNumber) case 21: - if _, err := s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", itemNumber, s.charID); err != nil { + if _, err := s.server.db.Exec("UPDATE users SET frontier_points=frontier_points-$1 WHERE id=$2", itemNumber, s.userID); err != nil { s.logger.Error("Failed to deduct frontier points for gacha", zap.Error(err)) } } @@ -289,7 +289,7 @@ func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) return } - if _, err := s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.GachaID, pkt.RollType, s.charID); err != nil { + if _, err := s.server.db.Exec("UPDATE users SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE id=$3", pkt.GachaID, pkt.RollType, s.userID); err != nil { s.logger.Error("Failed to award stepup gacha frontier points", zap.Error(err)) } if _, err := s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID); err != nil { diff --git a/server/channelserver/handlers_guild_cooking.go b/server/channelserver/handlers_guild_cooking.go index cfe87fba0..15180984d 100644 --- a/server/channelserver/handlers_guild_cooking.go +++ b/server/channelserver/handlers_guild_cooking.go @@ -56,7 +56,11 @@ func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) { s.logger.Error("Failed to update guild meal", zap.Error(err)) } } else { - _ = s.server.db.QueryRow("INSERT INTO guild_meals (guild_id, meal_id, level, created_at) VALUES ($1, $2, $3, $4) RETURNING id", guild.ID, pkt.MealID, pkt.Success, startTime).Scan(&pkt.OverwriteID) + if err := s.server.db.QueryRow("INSERT INTO guild_meals (guild_id, meal_id, level, created_at) VALUES ($1, $2, $3, $4) RETURNING id", guild.ID, pkt.MealID, pkt.Success, startTime).Scan(&pkt.OverwriteID); err != nil { + s.logger.Error("Failed to insert guild meal", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, nil) + return + } } bf := byteframe.NewByteFrame() bf.WriteUint16(1) diff --git a/server/channelserver/handlers_helpers.go b/server/channelserver/handlers_helpers.go index 043bd4dee..d872c8e7a 100644 --- a/server/channelserver/handlers_helpers.go +++ b/server/channelserver/handlers_helpers.go @@ -98,7 +98,7 @@ func saveCharacterData(s *Session, ackHandle uint32, column string, data []byte, func updateRights(s *Session) { rightsInt := uint32(2) - _ = s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt) + _ = s.server.db.QueryRow("SELECT rights FROM users WHERE id=$1", s.userID).Scan(&rightsInt) s.courses, rightsInt = mhfcourse.GetCourseStruct(rightsInt, s.server.erupeConfig.DefaultCourses) update := &mhfpacket.MsgSysUpdateRight{ ClientRespAckHandle: 0, diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 4006580bb..a13751d89 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -160,10 +160,12 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadHouse) bf := byteframe.NewByteFrame() - var state uint8 + state := uint8(2) // Default to password-protected if DB fails var password string - _ = s.server.db.QueryRow(`SELECT COALESCE(house_state, 2) as house_state, COALESCE(house_password, '') as house_password FROM user_binary WHERE id=$1 - `, pkt.CharID).Scan(&state, &password) + if err := s.server.db.QueryRow(`SELECT COALESCE(house_state, 2) as house_state, COALESCE(house_password, '') as house_password FROM user_binary WHERE id=$1 + `, pkt.CharID).Scan(&state, &password); err != nil { + s.logger.Error("Failed to read house state", zap.Error(err)) + } if pkt.Destination != 9 && len(pkt.Password) > 0 && pkt.CheckPass { if pkt.Password != password { diff --git a/server/channelserver/handlers_items.go b/server/channelserver/handlers_items.go index 2d2bd3d87..d01c5dec5 100644 --- a/server/channelserver/handlers_items.go +++ b/server/channelserver/handlers_items.go @@ -192,7 +192,7 @@ func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {} func userGetItems(s *Session) []mhfitem.MHFItemStack { var data []byte var items []mhfitem.MHFItemStack - _ = s.server.db.QueryRow(`SELECT item_box FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&data) + _ = s.server.db.QueryRow(`SELECT item_box FROM users WHERE id=$1`, s.userID).Scan(&data) if len(data) > 0 { box := byteframe.NewByteFrameFromBytes(data) numStacks := box.ReadUint16() @@ -215,7 +215,7 @@ func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateUnionItem) newStacks := mhfitem.DiffItemStacks(userGetItems(s), pkt.UpdatedItems) - if _, err := s.server.db.Exec(`UPDATE users u SET item_box=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, mhfitem.SerializeWarehouseItems(newStacks), s.charID); err != nil { + if _, err := s.server.db.Exec(`UPDATE users SET item_box=$1 WHERE id=$2`, mhfitem.SerializeWarehouseItems(newStacks), s.userID); err != nil { s.logger.Error("Failed to update union item box", zap.Error(err)) } doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) @@ -270,10 +270,18 @@ func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) { var total, redeemed uint16 var tktStack mhfitem.MHFItemStack if pkt.ExchangeType == 10 { // Yearly Sub Ex - _ = s.server.db.QueryRow("UPDATE stamps SET hl_total=hl_total-48, hl_redeemed=hl_redeemed-48 WHERE character_id=$1 RETURNING hl_total, hl_redeemed", s.charID).Scan(&total, &redeemed) + if err := s.server.db.QueryRow("UPDATE stamps SET hl_total=hl_total-48, hl_redeemed=hl_redeemed-48 WHERE character_id=$1 RETURNING hl_total, hl_redeemed", s.charID).Scan(&total, &redeemed); err != nil { + s.logger.Error("Failed to update yearly stamp exchange", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, nil) + return + } tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 2210}, Quantity: 1} } else { - _ = s.server.db.QueryRow(fmt.Sprintf("UPDATE stamps SET %s_redeemed=%s_redeemed+8 WHERE character_id=$1 RETURNING %s_total, %s_redeemed", pkt.StampType, pkt.StampType, pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed) + if err := s.server.db.QueryRow(fmt.Sprintf("UPDATE stamps SET %s_redeemed=%s_redeemed+8 WHERE character_id=$1 RETURNING %s_total, %s_redeemed", pkt.StampType, pkt.StampType, pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed); err != nil { + s.logger.Error("Failed to update stamp redemption", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, nil) + return + } if pkt.StampType == "hl" { tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 1630}, Quantity: 5} } else { @@ -325,7 +333,11 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { } var stamps, rewardTier, rewardUnk uint16 reward := mhfitem.MHFItemStack{Item: mhfitem.MHFItem{}} - _ = s.server.db.QueryRow(`UPDATE characters SET stampcard = stampcard + $1 WHERE id = $2 RETURNING stampcard`, pkt.Stamps, s.charID).Scan(&stamps) + if err := s.server.db.QueryRow(`UPDATE characters SET stampcard = stampcard + $1 WHERE id = $2 RETURNING stampcard`, pkt.Stamps, s.charID).Scan(&stamps); err != nil { + s.logger.Error("Failed to update stampcard", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, nil) + return + } bf.WriteUint16(stamps - pkt.Stamps) bf.WriteUint16(stamps) diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index f5c455f93..d60acb4e2 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -163,12 +163,18 @@ func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfCreateMercenary) - bf := byteframe.NewByteFrame() var nextID uint32 - _ = s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID) + if err := s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID); err != nil { + s.logger.Error("Failed to get next rasta ID", zap.Error(err)) + doAckSimpleFail(s, pkt.AckHandle, nil) + return + } if _, err := s.server.db.Exec("UPDATE characters SET rasta_id=$1 WHERE id=$2", nextID, s.charID); err != nil { s.logger.Error("Failed to set rasta ID", zap.Error(err)) + doAckSimpleFail(s, pkt.AckHandle, nil) + return } + bf := byteframe.NewByteFrame() bf.WriteUint32(nextID) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) } @@ -315,7 +321,11 @@ func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) { dataLen := bf.ReadUint32() catID := bf.ReadUint32() if catID == 0 { - _ = s.server.db.QueryRow("SELECT nextval('airou_id_seq')").Scan(&catID) + if err := s.server.db.QueryRow("SELECT nextval('airou_id_seq')").Scan(&catID); err != nil { + s.logger.Error("Failed to get next airou ID", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } } exists := bf.ReadBool() data := bf.ReadBytes(uint(dataLen) - 5) diff --git a/server/channelserver/handlers_session.go b/server/channelserver/handlers_session.go index 4a2571800..1aca36db1 100644 --- a/server/channelserver/handlers_session.go +++ b/server/channelserver/handlers_session.go @@ -71,6 +71,12 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { s.token = pkt.LoginTokenString s.Unlock() + if err := s.server.db.QueryRow("SELECT user_id FROM characters WHERE id=$1", s.charID).Scan(&s.userID); err != nil { + s.logger.Error("Failed to resolve user ID for character", zap.Error(err), zap.Uint32("charID", s.charID)) + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) + return + } + bf := byteframe.NewByteFrame() bf.WriteUint32(uint32(TimeAdjusted().Unix())) // Unix timestamp @@ -95,7 +101,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { return } - _, err = s.server.db.Exec("UPDATE users u SET last_character=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)", s.charID) + _, err = s.server.db.Exec("UPDATE users SET last_character=$1 WHERE id=$2", s.charID, s.userID) if err != nil { s.logger.Error("Failed to update last character", zap.Error(err)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) @@ -232,7 +238,9 @@ func logoutPlayer(s *Session) { var rpGained int if s.charID != 0 { - _ = s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed) + if err := s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed); err != nil { + s.logger.Error("Failed to read time_played, RP accrual may be inaccurate", zap.Error(err)) + } sessionTime = int(TimeAdjusted().Unix()) - int(s.sessionStart) timePlayed += sessionTime diff --git a/server/channelserver/handlers_shop.go b/server/channelserver/handlers_shop.go index 5555c53de..72ff9b8b6 100644 --- a/server/channelserver/handlers_shop.go +++ b/server/channelserver/handlers_shop.go @@ -263,9 +263,17 @@ func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item) var balance uint32 var itemValue, quantity int - _ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) + if err := s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue); err != nil { + s.logger.Error("Failed to read fpoint item cost", zap.Error(err)) + doAckSimpleFail(s, pkt.AckHandle, nil) + return + } cost := (int(pkt.Quantity) * quantity) * itemValue - _ = s.server.db.QueryRow("UPDATE users u SET frontier_points=frontier_points::int - $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance) + if err := s.server.db.QueryRow("UPDATE users SET frontier_points=frontier_points::int - $1 WHERE id=$2 RETURNING frontier_points", cost, s.userID).Scan(&balance); err != nil { + s.logger.Error("Failed to deduct frontier points", zap.Error(err)) + doAckSimpleFail(s, pkt.AckHandle, nil) + return + } bf := byteframe.NewByteFrame() bf.WriteUint32(balance) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) @@ -275,9 +283,17 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint) var balance uint32 var itemValue, quantity int - _ = s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue) + if err := s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue); err != nil { + s.logger.Error("Failed to read fpoint item value", zap.Error(err)) + doAckSimpleFail(s, pkt.AckHandle, nil) + return + } cost := (int(pkt.Quantity) / quantity) * itemValue - _ = s.server.db.QueryRow("UPDATE users u SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance) + if err := s.server.db.QueryRow("UPDATE users SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2 RETURNING frontier_points", cost, s.userID).Scan(&balance); err != nil { + s.logger.Error("Failed to credit frontier points", zap.Error(err)) + doAckSimpleFail(s, pkt.AckHandle, nil) + return + } bf := byteframe.NewByteFrame() bf.WriteUint32(balance) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) diff --git a/server/channelserver/handlers_tower.go b/server/channelserver/handlers_tower.go index dec9e4202..ca0e675b6 100644 --- a/server/channelserver/handlers_tower.go +++ b/server/channelserver/handlers_tower.go @@ -306,10 +306,15 @@ func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) { data = append(data, bf) } case 4: - _ = s.server.db.QueryRow(`SELECT tower_mission_page FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&tenrouirai.Progress[0].Page) + if err := s.server.db.QueryRow(`SELECT tower_mission_page FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&tenrouirai.Progress[0].Page); err != nil { + s.logger.Error("Failed to read tower mission page", zap.Error(err)) + } _ = s.server.db.QueryRow(`SELECT SUM(tower_mission_1) AS _, SUM(tower_mission_2) AS _, SUM(tower_mission_3) AS _ FROM guild_characters WHERE guild_id=$1 `, pkt.GuildID).Scan(&tenrouirai.Progress[0].Mission1, &tenrouirai.Progress[0].Mission2, &tenrouirai.Progress[0].Mission3) + if tenrouirai.Progress[0].Page < 1 { + tenrouirai.Progress[0].Page = 1 + } if tenrouirai.Progress[0].Mission1 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-3].Goal { tenrouirai.Progress[0].Mission1 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-3].Goal } @@ -384,7 +389,11 @@ func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) { if pkt.Op == 2 { var page, requirement, donated int - _ = s.server.db.QueryRow(`SELECT tower_mission_page, tower_rp FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&page, &donated) + if err := s.server.db.QueryRow(`SELECT tower_mission_page, tower_rp FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&page, &donated); err != nil { + s.logger.Error("Failed to read guild tower state for donation", zap.Error(err)) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) + return + } for i := 0; i < (page*3)+1; i++ { requirement += int(tenrouiraiData[i].Cost) diff --git a/server/channelserver/sys_session.go b/server/channelserver/sys_session.go index 926947e9f..703672776 100644 --- a/server/channelserver/sys_session.go +++ b/server/channelserver/sys_session.go @@ -45,6 +45,7 @@ type Session struct { stagePass string // Temporary storage prevGuildID uint32 // Stores the last GuildID used in InfoGuild charID uint32 + userID uint32 logKey []byte sessionStart int64 courses []mhfcourse.Course @@ -352,7 +353,7 @@ func (s *Session) GetSemaphoreID() uint32 { func (s *Session) isOp() bool { var op bool - err := s.server.db.QueryRow(`SELECT op FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&op) + err := s.server.db.QueryRow(`SELECT op FROM users WHERE id=$1`, s.userID).Scan(&op) if err == nil && op { return true }