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.
This commit is contained in:
Houmgaor
2026-02-20 21:06:16 +01:00
parent d5c44b5557
commit d456bd23e0
13 changed files with 104 additions and 42 deletions

View File

@@ -37,7 +37,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
if pkt.BroadcastType == BroadcastTypeStage && pkt.MessageType == BinaryMessageTypeData && len(pkt.RawDataPayload) == 0x10 { if pkt.BroadcastType == BroadcastTypeStage && pkt.MessageType == BinaryMessageTypeData && len(pkt.RawDataPayload) == 0x10 {
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 { if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
var timer bool 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)) s.logger.Error("Failed to get timer setting", zap.Error(err))
} }
if timer { if timer {

View File

@@ -132,10 +132,10 @@ func parseChatCommand(s *Session, command string) {
case commands["Timer"].Prefix: case commands["Timer"].Prefix:
if commands["Timer"].Enabled || s.isOp() { if commands["Timer"].Enabled || s.isOp() {
var state bool 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)) 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)) s.logger.Error("Failed to update timer setting", zap.Error(err))
} }
if state { if state {
@@ -154,7 +154,7 @@ func parseChatCommand(s *Session, command string) {
s.logger.Error("Failed to check PSN ID existence", zap.Error(err)) s.logger.Error("Failed to check PSN ID existence", zap.Error(err))
} }
if exists == 0 { 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 { if err == nil {
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1])) 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 commands["Rights"].Enabled || s.isOp() {
if len(args) > 1 { if len(args) > 1 {
v, _ := strconv.Atoi(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 { if err == nil {
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v)) sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v))
} else { } else {
@@ -293,9 +293,9 @@ func parseChatCommand(s *Session, command string) {
delta = uint32(math.Pow(2, float64(course.ID))) delta = uint32(math.Pow(2, float64(course.ID)))
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, course.Aliases()[0])) 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 == 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)) 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: case commands["Discord"].Prefix:
if commands["Discord"].Enabled || s.isOp() { if commands["Discord"].Enabled || s.isOp() {
var _token string 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 { if err != nil {
randToken := make([]byte, 4) randToken := make([]byte, 4)
_, _ = rand.Read(randToken) _, _ = rand.Read(randToken)
_token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:]) _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)) s.logger.Error("Failed to update discord token", zap.Error(err))
} }
} }

View File

@@ -172,15 +172,15 @@ func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
case 17: case 17:
_ = addPointNetcafe(s, int(item.Quantity)) _ = addPointNetcafe(s, int(item.Quantity))
case 19: 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)) s.logger.Error("Failed to update gacha premium", zap.Error(err))
} }
case 20: 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)) s.logger.Error("Failed to update gacha trial", zap.Error(err))
} }
case 21: 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)) s.logger.Error("Failed to update frontier points", zap.Error(err))
} }
case 23: case 23:

View File

@@ -55,7 +55,7 @@ func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGachaPoint) pkt := p.(*mhfpacket.MsgMhfGetGachaPoint)
var fp, gp, gt uint32 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, &gt) _ = 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, &gt)
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
resp.WriteUint32(gp) resp.WriteUint32(gp)
resp.WriteUint32(gt) resp.WriteUint32(gt)
@@ -66,12 +66,12 @@ func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUseGachaPoint) pkt := p.(*mhfpacket.MsgMhfUseGachaPoint)
if pkt.TrialCoins > 0 { 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)) s.logger.Error("Failed to deduct gacha trial coins", zap.Error(err))
} }
} }
if pkt.PremiumCoins > 0 { 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)) 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) { func spendGachaCoin(s *Session, quantity uint16) {
var gt 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(&gt) _ = s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users WHERE id=$1`, s.userID).Scan(&gt)
if quantity <= gt { 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)) s.logger.Error("Failed to deduct gacha trial coins", zap.Error(err))
} }
} else { } 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)) 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: case 20:
spendGachaCoin(s, itemNumber) spendGachaCoin(s, itemNumber)
case 21: 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)) 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)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return 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)) 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 { if _, err := s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID); err != nil {

View File

@@ -56,7 +56,11 @@ func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) {
s.logger.Error("Failed to update guild meal", zap.Error(err)) s.logger.Error("Failed to update guild meal", zap.Error(err))
} }
} else { } 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 := byteframe.NewByteFrame()
bf.WriteUint16(1) bf.WriteUint16(1)

View File

@@ -98,7 +98,7 @@ func saveCharacterData(s *Session, ackHandle uint32, column string, data []byte,
func updateRights(s *Session) { func updateRights(s *Session) {
rightsInt := uint32(2) 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) s.courses, rightsInt = mhfcourse.GetCourseStruct(rightsInt, s.server.erupeConfig.DefaultCourses)
update := &mhfpacket.MsgSysUpdateRight{ update := &mhfpacket.MsgSysUpdateRight{
ClientRespAckHandle: 0, ClientRespAckHandle: 0,

View File

@@ -160,10 +160,12 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadHouse) pkt := p.(*mhfpacket.MsgMhfLoadHouse)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
var state uint8 state := uint8(2) // Default to password-protected if DB fails
var password string 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 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) `, 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.Destination != 9 && len(pkt.Password) > 0 && pkt.CheckPass {
if pkt.Password != password { if pkt.Password != password {

View File

@@ -192,7 +192,7 @@ func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {}
func userGetItems(s *Session) []mhfitem.MHFItemStack { func userGetItems(s *Session) []mhfitem.MHFItemStack {
var data []byte var data []byte
var items []mhfitem.MHFItemStack 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 { if len(data) > 0 {
box := byteframe.NewByteFrameFromBytes(data) box := byteframe.NewByteFrameFromBytes(data)
numStacks := box.ReadUint16() numStacks := box.ReadUint16()
@@ -215,7 +215,7 @@ func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateUnionItem) pkt := p.(*mhfpacket.MsgMhfUpdateUnionItem)
newStacks := mhfitem.DiffItemStacks(userGetItems(s), pkt.UpdatedItems) 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)) s.logger.Error("Failed to update union item box", zap.Error(err))
} }
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
@@ -270,10 +270,18 @@ func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
var total, redeemed uint16 var total, redeemed uint16
var tktStack mhfitem.MHFItemStack var tktStack mhfitem.MHFItemStack
if pkt.ExchangeType == 10 { // Yearly Sub Ex 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} tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 2210}, Quantity: 1}
} else { } 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" { if pkt.StampType == "hl" {
tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 1630}, Quantity: 5} tktStack = mhfitem.MHFItemStack{Item: mhfitem.MHFItem{ItemID: 1630}, Quantity: 5}
} else { } else {
@@ -325,7 +333,11 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
} }
var stamps, rewardTier, rewardUnk uint16 var stamps, rewardTier, rewardUnk uint16
reward := mhfitem.MHFItemStack{Item: mhfitem.MHFItem{}} 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 - pkt.Stamps)
bf.WriteUint16(stamps) bf.WriteUint16(stamps)

View File

@@ -163,12 +163,18 @@ func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCreateMercenary) pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
bf := byteframe.NewByteFrame()
var nextID uint32 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 { 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)) s.logger.Error("Failed to set rasta ID", zap.Error(err))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
} }
bf := byteframe.NewByteFrame()
bf.WriteUint32(nextID) bf.WriteUint32(nextID)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -315,7 +321,11 @@ func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
dataLen := bf.ReadUint32() dataLen := bf.ReadUint32()
catID := bf.ReadUint32() catID := bf.ReadUint32()
if catID == 0 { 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() exists := bf.ReadBool()
data := bf.ReadBytes(uint(dataLen) - 5) data := bf.ReadBytes(uint(dataLen) - 5)

View File

@@ -71,6 +71,12 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
s.token = pkt.LoginTokenString s.token = pkt.LoginTokenString
s.Unlock() 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 := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // Unix timestamp bf.WriteUint32(uint32(TimeAdjusted().Unix())) // Unix timestamp
@@ -95,7 +101,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
return 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 { if err != nil {
s.logger.Error("Failed to update last character", zap.Error(err)) s.logger.Error("Failed to update last character", zap.Error(err))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
@@ -232,7 +238,9 @@ func logoutPlayer(s *Session) {
var rpGained int var rpGained int
if s.charID != 0 { 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) sessionTime = int(TimeAdjusted().Unix()) - int(s.sessionStart)
timePlayed += sessionTime timePlayed += sessionTime

View File

@@ -263,9 +263,17 @@ func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item) pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
var balance uint32 var balance uint32
var itemValue, quantity int 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 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 := byteframe.NewByteFrame()
bf.WriteUint32(balance) bf.WriteUint32(balance)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
@@ -275,9 +283,17 @@ func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint) pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
var balance uint32 var balance uint32
var itemValue, quantity int 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 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 := byteframe.NewByteFrame()
bf.WriteUint32(balance) bf.WriteUint32(balance)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())

View File

@@ -306,10 +306,15 @@ func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
data = append(data, bf) data = append(data, bf)
} }
case 4: 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 _ = 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) `, 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 { 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 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 { if pkt.Op == 2 {
var page, requirement, donated int 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++ { for i := 0; i < (page*3)+1; i++ {
requirement += int(tenrouiraiData[i].Cost) requirement += int(tenrouiraiData[i].Cost)

View File

@@ -45,6 +45,7 @@ type Session struct {
stagePass string // Temporary storage stagePass string // Temporary storage
prevGuildID uint32 // Stores the last GuildID used in InfoGuild prevGuildID uint32 // Stores the last GuildID used in InfoGuild
charID uint32 charID uint32
userID uint32
logKey []byte logKey []byte
sessionStart int64 sessionStart int64
courses []mhfcourse.Course courses []mhfcourse.Course
@@ -352,7 +353,7 @@ func (s *Session) GetSemaphoreID() uint32 {
func (s *Session) isOp() bool { func (s *Session) isOp() bool {
var op 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 { if err == nil && op {
return true return true
} }