From d32e77efbab23f0ed36cf5ab81b4ce5212792f6a Mon Sep 17 00:00:00 2001 From: Houmgaor Date: Fri, 20 Feb 2026 19:11:41 +0100 Subject: [PATCH] refactor: replace panic calls with structured error handling Replace ~25 panic() calls in non-fatal code paths with proper s.logger.Error + return patterns. Panics in handler code crashed goroutines (caught by defer/recover but still disruptive) instead of failing gracefully. Key changes: - SJISToUTF8 now returns (string, error); all 30+ callers updated - Handler DB/IO panics replaced with log + return/ack fail - Unhandled switch-case panics replaced with logger.Error - Sign server Accept() panic replaced with log + continue - Dead unreachable panic in guild_model.go removed - deltacomp patch error logs and returns partial data Panics intentionally kept: ByteFrame sentinel, unimplemented packet stubs, os.Exit in main.go. --- cmd/protbot/scenario/chat.go | 4 +-- common/stringsupport/string_convert.go | 6 ++-- common/stringsupport/string_convert_test.go | 11 ++++--- network/binpacket/msg_bin_chat.go | 4 +-- .../mhfpacket/msg_mhf_apply_bbs_article.go | 6 ++-- network/mhfpacket/msg_mhf_create_guild.go | 2 +- network/mhfpacket/msg_mhf_create_joint.go | 2 +- network/mhfpacket/msg_mhf_enumerate_house.go | 2 +- network/mhfpacket/msg_mhf_load_house.go | 2 +- .../mhfpacket/msg_mhf_operate_warehouse.go | 2 +- network/mhfpacket/msg_mhf_send_mail.go | 4 +-- .../msg_mhf_update_guild_message_board.go | 8 ++--- network/mhfpacket/msg_mhf_update_house.go | 2 +- .../compression/deltacomp/deltacomp.go | 3 +- server/channelserver/guild_model.go | 4 --- server/channelserver/handlers_cafe.go | 3 +- server/channelserver/handlers_data.go | 13 ++------ server/channelserver/handlers_festa.go | 10 +----- server/channelserver/handlers_guild.go | 17 +++++++--- .../channelserver/handlers_guild_alliance.go | 2 +- server/channelserver/handlers_guild_info.go | 12 ++++--- server/channelserver/handlers_guild_ops.go | 6 ++-- server/channelserver/handlers_guild_scout.go | 31 ++++++++++++------ server/channelserver/handlers_helpers.go | 32 +++++++++++++++++++ server/channelserver/handlers_mercenary.go | 28 ++-------------- server/channelserver/handlers_plate.go | 22 ++----------- server/channelserver/handlers_rengoku.go | 4 ++- server/channelserver/handlers_session.go | 17 ++++++---- server/channelserver/model_character.go | 2 +- server/signserver/session.go | 7 ++-- server/signserver/sign_server.go | 3 +- 31 files changed, 141 insertions(+), 130 deletions(-) diff --git a/cmd/protbot/scenario/chat.go b/cmd/protbot/scenario/chat.go index 61394dc4b..8e5ba64fb 100644 --- a/cmd/protbot/scenario/chat.go +++ b/cmd/protbot/scenario/chat.go @@ -62,8 +62,8 @@ func ListenChat(ch *protocol.ChannelConn, cb ChatCallback) { _ = pbf.ReadUint16() // flags _ = pbf.ReadUint16() // senderNameLen _ = pbf.ReadUint16() // messageLen - msg := stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes()) - sender := stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes()) + msg, _ := stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes()) + sender, _ := stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes()) cb(ChatMessage{ ChatType: chatType, diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go index 79e2a8459..41a3dbeb0 100644 --- a/common/stringsupport/string_convert.go +++ b/common/stringsupport/string_convert.go @@ -31,13 +31,13 @@ func UTF8ToSJIS(x string) []byte { } // SJISToUTF8 decodes Shift-JIS bytes to a UTF-8 string. -func SJISToUTF8(b []byte) string { +func SJISToUTF8(b []byte) (string, error) { d := japanese.ShiftJIS.NewDecoder() result, err := io.ReadAll(transform.NewReader(bytes.NewReader(b), d)) if err != nil { - panic(err) + return "", fmt.Errorf("ShiftJIS decode: %w", err) } - return string(result) + return string(result), nil } // ToNGWord converts a UTF-8 string into a slice of uint16 values in the diff --git a/common/stringsupport/string_convert_test.go b/common/stringsupport/string_convert_test.go index 69a93fdea..b90582024 100644 --- a/common/stringsupport/string_convert_test.go +++ b/common/stringsupport/string_convert_test.go @@ -32,7 +32,10 @@ func TestUTF8ToSJIS(t *testing.T) { func TestSJISToUTF8(t *testing.T) { // Test ASCII characters (which are the same in SJIS and UTF-8) asciiBytes := []byte("Hello World") - result := SJISToUTF8(asciiBytes) + result, err := SJISToUTF8(asciiBytes) + if err != nil { + t.Fatalf("SJISToUTF8() unexpected error: %v", err) + } if result != "Hello World" { t.Errorf("SJISToUTF8() = %q, want %q", result, "Hello World") } @@ -42,7 +45,7 @@ func TestUTF8ToSJIS_RoundTrip(t *testing.T) { // Test round-trip conversion for ASCII original := "Hello World 123" sjis := UTF8ToSJIS(original) - back := SJISToUTF8(sjis) + back, _ := SJISToUTF8(sjis) if back != original { t.Errorf("Round-trip failed: got %q, want %q", back, original) @@ -509,7 +512,7 @@ func TestUTF8ToSJIS_PreservesValidContent(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sjis := UTF8ToSJIS(tt.input) - roundTripped := SJISToUTF8(sjis) + roundTripped, _ := SJISToUTF8(sjis) if roundTripped != tt.expected { t.Errorf("UTF8ToSJIS(%q) round-tripped to %q, want %q", tt.input, roundTripped, tt.expected) } @@ -544,7 +547,7 @@ func BenchmarkSJISToUTF8(b *testing.B) { text := []byte("Hello World") b.ResetTimer() for i := 0; i < b.N; i++ { - _ = SJISToUTF8(text) + _, _ = SJISToUTF8(text) } } diff --git a/network/binpacket/msg_bin_chat.go b/network/binpacket/msg_bin_chat.go index 6938bd046..ebd1636cc 100644 --- a/network/binpacket/msg_bin_chat.go +++ b/network/binpacket/msg_bin_chat.go @@ -40,8 +40,8 @@ func (m *MsgBinChat) Parse(bf *byteframe.ByteFrame) error { m.Flags = bf.ReadUint16() _ = bf.ReadUint16() // lenSenderName _ = bf.ReadUint16() // lenMessage - m.Message = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - m.SenderName = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Message, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.SenderName, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_mhf_apply_bbs_article.go b/network/mhfpacket/msg_mhf_apply_bbs_article.go index d2b9c803b..4a83e36d3 100644 --- a/network/mhfpacket/msg_mhf_apply_bbs_article.go +++ b/network/mhfpacket/msg_mhf_apply_bbs_article.go @@ -30,9 +30,9 @@ func (m *MsgMhfApplyBbsArticle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl m.AckHandle = bf.ReadUint32() m.Unk0 = bf.ReadUint32() m.Unk1 = bf.ReadBytes(16) - m.Name = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(32))) - m.Title = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(128))) - m.Description = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(256))) + m.Name, _ = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(32))) + m.Title, _ = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(128))) + m.Description, _ = stringsupport.SJISToUTF8(bfutil.UpToNull(bf.ReadBytes(256))) return nil } diff --git a/network/mhfpacket/msg_mhf_create_guild.go b/network/mhfpacket/msg_mhf_create_guild.go index e82f7157e..a3739a184 100644 --- a/network/mhfpacket/msg_mhf_create_guild.go +++ b/network/mhfpacket/msg_mhf_create_guild.go @@ -25,7 +25,7 @@ func (m *MsgMhfCreateGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client m.AckHandle = bf.ReadUint32() bf.ReadUint16() // Zeroed bf.ReadUint16() // Name length - m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Name, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_mhf_create_joint.go b/network/mhfpacket/msg_mhf_create_joint.go index 5a9a9f5fd..303571548 100644 --- a/network/mhfpacket/msg_mhf_create_joint.go +++ b/network/mhfpacket/msg_mhf_create_joint.go @@ -27,7 +27,7 @@ func (m *MsgMhfCreateJoint) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client m.GuildID = bf.ReadUint32() bf.ReadUint16() // Zeroed bf.ReadUint16() // Name length - m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Name, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_mhf_enumerate_house.go b/network/mhfpacket/msg_mhf_enumerate_house.go index 41f57323a..fa3cc30cc 100644 --- a/network/mhfpacket/msg_mhf_enumerate_house.go +++ b/network/mhfpacket/msg_mhf_enumerate_house.go @@ -30,7 +30,7 @@ func (m *MsgMhfEnumerateHouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli bf.ReadUint16() // Zeroed lenName := bf.ReadUint8() if lenName > 0 { - m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Name, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) } return nil } diff --git a/network/mhfpacket/msg_mhf_load_house.go b/network/mhfpacket/msg_mhf_load_house.go index 138c8af22..029307a18 100644 --- a/network/mhfpacket/msg_mhf_load_house.go +++ b/network/mhfpacket/msg_mhf_load_house.go @@ -32,7 +32,7 @@ func (m *MsgMhfLoadHouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCo m.CheckPass = bf.ReadBool() bf.ReadUint16() // Zeroed bf.ReadUint8() // Password length - m.Password = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Password, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_mhf_operate_warehouse.go b/network/mhfpacket/msg_mhf_operate_warehouse.go index 0ea57e6c6..df9222742 100644 --- a/network/mhfpacket/msg_mhf_operate_warehouse.go +++ b/network/mhfpacket/msg_mhf_operate_warehouse.go @@ -32,7 +32,7 @@ func (m *MsgMhfOperateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.C lenName := bf.ReadUint8() bf.ReadUint16() // Zeroed if lenName > 0 { - m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Name, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) } return nil } diff --git a/network/mhfpacket/msg_mhf_send_mail.go b/network/mhfpacket/msg_mhf_send_mail.go index 2a21ef93b..e79384b6b 100644 --- a/network/mhfpacket/msg_mhf_send_mail.go +++ b/network/mhfpacket/msg_mhf_send_mail.go @@ -35,8 +35,8 @@ func (m *MsgMhfSendMail) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientCon bf.ReadUint16() // Zeroed m.Quantity = bf.ReadUint16() m.ItemID = bf.ReadUint16() - m.Subject = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - m.Body = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Subject, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Body, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/network/mhfpacket/msg_mhf_update_guild_message_board.go b/network/mhfpacket/msg_mhf_update_guild_message_board.go index 94316cc52..fb1913aef 100644 --- a/network/mhfpacket/msg_mhf_update_guild_message_board.go +++ b/network/mhfpacket/msg_mhf_update_guild_message_board.go @@ -38,8 +38,8 @@ func (m *MsgMhfUpdateGuildMessageBoard) Parse(bf *byteframe.ByteFrame, ctx *clie m.StampID = bf.ReadUint32() m.TitleLength = bf.ReadUint32() m.BodyLength = bf.ReadUint32() - m.Title = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.TitleLength))) - m.Body = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.BodyLength))) + m.Title, _ = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.TitleLength))) + m.Body, _ = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.BodyLength))) case 1: m.PostID = bf.ReadUint32() case 2: @@ -47,8 +47,8 @@ func (m *MsgMhfUpdateGuildMessageBoard) Parse(bf *byteframe.ByteFrame, ctx *clie bf.ReadBytes(8) m.TitleLength = bf.ReadUint32() m.BodyLength = bf.ReadUint32() - m.Title = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.TitleLength))) - m.Body = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.BodyLength))) + m.Title, _ = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.TitleLength))) + m.Body, _ = stringsupport.SJISToUTF8(bf.ReadBytes(uint(m.BodyLength))) case 3: m.PostID = bf.ReadUint32() bf.ReadBytes(8) diff --git a/network/mhfpacket/msg_mhf_update_house.go b/network/mhfpacket/msg_mhf_update_house.go index 0f7e77c21..c70e4633d 100644 --- a/network/mhfpacket/msg_mhf_update_house.go +++ b/network/mhfpacket/msg_mhf_update_house.go @@ -30,7 +30,7 @@ func (m *MsgMhfUpdateHouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client bf.ReadUint8() // Zeroed bf.ReadUint8() // Zeroed bf.ReadUint8() // Password length - m.Password = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + m.Password, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) return nil } diff --git a/server/channelserver/compression/deltacomp/deltacomp.go b/server/channelserver/compression/deltacomp/deltacomp.go index 786371c23..4f441af9e 100644 --- a/server/channelserver/compression/deltacomp/deltacomp.go +++ b/server/channelserver/compression/deltacomp/deltacomp.go @@ -92,7 +92,8 @@ func ApplyDataDiff(diff []byte, baseData []byte) []byte { for i := 0; i < differentCount; i++ { b, err := checkReadUint8(patch) if err != nil { - panic("Invalid or misunderstood patch format!") + zap.L().Error("Invalid or misunderstood patch format", zap.Int("dataOffset", dataOffset)) + return baseCopy } baseCopy[dataOffset+i] = b diff --git a/server/channelserver/guild_model.go b/server/channelserver/guild_model.go index 7d33b657d..073fe651d 100644 --- a/server/channelserver/guild_model.go +++ b/server/channelserver/guild_model.go @@ -484,10 +484,6 @@ func CreateGuild(s *Session, guildName string) (int32, error) { return 0, err } - if err != nil { - panic(err) - } - guildResult, err := transaction.Query( "INSERT INTO guilds (name, leader_id) VALUES ($1, $2) RETURNING id", guildName, s.charID, diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go index f99c94124..ba828af18 100644 --- a/server/channelserver/handlers_cafe.go +++ b/server/channelserver/handlers_cafe.go @@ -96,7 +96,8 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) { var cafeTime uint32 err = s.server.db.QueryRow("SELECT cafe_time FROM characters WHERE id = $1", s.charID).Scan(&cafeTime) if err != nil { - panic(err) + s.logger.Error("Failed to get cafe time", zap.Error(err)) + return } if mhfcourse.CourseExists(30, s.courses) { cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 5b85f2d01..ae898f2db 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -178,7 +178,7 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) { s.server.userBinaryPartsLock.Lock() s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 1}] = append(name, []byte{0x00}...) s.server.userBinaryPartsLock.Unlock() - s.Name = stringsupport.SJISToUTF8(name) + s.Name, _ = stringsupport.SJISToUTF8(name) } func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) { @@ -200,16 +200,7 @@ func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadScenarioData) - var scenarioData []byte - bf := byteframe.NewByteFrame() - err := s.server.db.QueryRow("SELECT scenariodata FROM characters WHERE id = $1", s.charID).Scan(&scenarioData) - if err != nil || len(scenarioData) < 10 { - s.logger.Error("Failed to load scenariodata", zap.Error(err)) - bf.WriteBytes(make([]byte, 10)) - } else { - bf.WriteBytes(scenarioData) - } - doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + loadCharacterData(s, pkt.AckHandle, "scenariodata", make([]byte, 10)) } func handleMsgSysAuthData(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index b51277b5c..ec65f3c60 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -17,15 +17,7 @@ import ( func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveMezfesData) - if len(pkt.RawDataPayload) > 4096 { - s.logger.Warn("MezFes payload too large", zap.Int("len", len(pkt.RawDataPayload))) - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return - } - if _, err := s.server.db.Exec(`UPDATE characters SET mezfes=$1 WHERE id=$2`, pkt.RawDataPayload, s.charID); err != nil { - s.logger.Error("Failed to save mezfes data", zap.Error(err)) - } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + saveCharacterData(s, pkt.AckHandle, "mezfes", pkt.RawDataPayload, 4096) } func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index f3fb60260..05fef55ea 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -154,7 +154,8 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { if guild.ID != alliance.ParentGuildID { mems, err := GetGuildMembers(s, alliance.ParentGuildID, false) if err != nil { - panic(err) + s.logger.Error("Failed to get parent guild members for alliance", zap.Error(err)) + return } for _, m := range mems { bf.WriteUint32(m.CharID) @@ -163,7 +164,8 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { if guild.ID != alliance.SubGuild1ID { mems, err := GetGuildMembers(s, alliance.SubGuild1ID, false) if err != nil { - panic(err) + s.logger.Error("Failed to get sub guild 1 members for alliance", zap.Error(err)) + return } for _, m := range mems { bf.WriteUint32(m.CharID) @@ -172,7 +174,8 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { if guild.ID != alliance.SubGuild2ID { mems, err := GetGuildMembers(s, alliance.SubGuild2ID, false) if err != nil { - panic(err) + s.logger.Error("Failed to get sub guild 2 members for alliance", zap.Error(err)) + return } for _, m := range mems { bf.WriteUint32(m.CharID) @@ -267,13 +270,17 @@ func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) { guild, err := GetGuildInfoByID(s, pkt.GuildID) if err != nil { - panic(err) + s.logger.Error("Failed to get guild info for icon update", zap.Error(err)) + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) + return } characterInfo, err := GetCharacterGuildData(s, s.charID) if err != nil { - panic(err) + s.logger.Error("Failed to get character guild data for icon update", zap.Error(err)) + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) + return } if !characterInfo.IsSubLeader() && !characterInfo.IsLeader { diff --git a/server/channelserver/handlers_guild_alliance.go b/server/channelserver/handlers_guild_alliance.go index 5d21c063b..5c9ac4d97 100644 --- a/server/channelserver/handlers_guild_alliance.go +++ b/server/channelserver/handlers_guild_alliance.go @@ -195,8 +195,8 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) } default: + s.logger.Error("unhandled operate joint action", zap.Uint8("action", uint8(pkt.Action))) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - panic(fmt.Sprintf("Unhandled operate joint action '%d'", pkt.Action)) } } diff --git a/server/channelserver/handlers_guild_info.go b/server/channelserver/handlers_guild_info.go index 68551813b..dea46ab29 100644 --- a/server/channelserver/handlers_guild_info.go +++ b/server/channelserver/handlers_guild_info.go @@ -291,14 +291,16 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { } switch pkt.Type { case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME: + searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes()) for _, guild := range tempGuilds { - if strings.Contains(guild.Name, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) { + if strings.Contains(guild.Name, searchName) { guilds = append(guilds, guild) } } case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME: + searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes()) for _, guild := range tempGuilds { - if strings.Contains(guild.LeaderName, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) { + if strings.Contains(guild.LeaderName, searchName) { guilds = append(guilds, guild) } } @@ -371,14 +373,16 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) { } switch pkt.Type { case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME: + searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes()) for _, alliance := range tempAlliances { - if strings.Contains(alliance.Name, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) { + if strings.Contains(alliance.Name, searchName) { alliances = append(alliances, alliance) } } case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME: + searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes()) for _, alliance := range tempAlliances { - if strings.Contains(alliance.ParentGuild.LeaderName, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) { + if strings.Contains(alliance.ParentGuild.LeaderName, searchName) { alliances = append(alliances, alliance) } } diff --git a/server/channelserver/handlers_guild_ops.go b/server/channelserver/handlers_guild_ops.go index dc4086aea..bc3cc968b 100644 --- a/server/channelserver/handlers_guild_ops.go +++ b/server/channelserver/handlers_guild_ops.go @@ -104,7 +104,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) return } - guild.Comment = stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes()) + guild.Comment, _ = stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes()) _ = guild.Save(s) case mhfpacket.OperateGuildUpdateMotto: if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() { @@ -149,7 +149,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint32(balance) default: - panic(fmt.Sprintf("unhandled operate guild action '%d'", pkt.Action)) + s.logger.Error("unhandled operate guild action", zap.Uint8("action", uint8(pkt.Action))) } if len(bf.Data()) > 0 { @@ -160,7 +160,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) { } func handleRenamePugi(s *Session, bf *byteframe.ByteFrame, guild *Guild, num int) { - name := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + name, _ := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) switch num { case 1: guild.PugiName1 = name diff --git a/server/channelserver/handlers_guild_scout.go b/server/channelserver/handlers_guild_scout.go index ba87f1ca5..77aaa1567 100644 --- a/server/channelserver/handlers_guild_scout.go +++ b/server/channelserver/handlers_guild_scout.go @@ -15,8 +15,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) { actorCharGuildData, err := GetCharacterGuildData(s, s.charID) if err != nil { + s.logger.Error("Failed to get character guild data for scout", zap.Error(err)) doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - panic(err) + return } if actorCharGuildData == nil || !actorCharGuildData.CanRecruit() { @@ -27,15 +28,17 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) { guildInfo, err := GetGuildInfoByID(s, actorCharGuildData.GuildID) if err != nil { + s.logger.Error("Failed to get guild info for scout", zap.Error(err)) doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - panic(err) + return } hasApplication, err := guildInfo.HasApplicationForCharID(s, pkt.CharID) if err != nil { + s.logger.Error("Failed to check application for scout", zap.Error(err)) doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - panic(err) + return } if hasApplication { @@ -46,15 +49,18 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) { transaction, err := s.server.db.Begin() if err != nil { - panic(err) + s.logger.Error("Failed to begin transaction for guild scout", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, nil) + return } err = guildInfo.CreateApplication(s, pkt.CharID, GuildApplicationTypeInvited, transaction) if err != nil { rollbackTransaction(s, transaction) + s.logger.Error("Failed to create guild scout application", zap.Error(err)) doAckBufFail(s, pkt.AckHandle, nil) - panic(err) + return } mail := &Mail{ @@ -79,8 +85,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) { err = transaction.Commit() if err != nil { + s.logger.Error("Failed to commit guild scout transaction", zap.Error(err)) doAckBufFail(s, pkt.AckHandle, nil) - panic(err) + return } doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) @@ -92,7 +99,9 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) { guildCharData, err := GetCharacterGuildData(s, s.charID) if err != nil { - panic(err) + s.logger.Error("Failed to get character guild data for cancel scout", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) + return } if guildCharData == nil || !guildCharData.CanRecruit() { @@ -123,7 +132,9 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) { guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID) if err != nil { - panic(err) + s.logger.Error("Failed to get guild info for answer scout", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, nil) + return } app, err := guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited) @@ -255,7 +266,9 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) { _, err = bf.Seek(0, io.SeekStart) if err != nil { - panic(err) + s.logger.Error("Failed to seek in guild scout list buffer", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, nil) + return } bf.WriteUint32(count) diff --git a/server/channelserver/handlers_helpers.go b/server/channelserver/handlers_helpers.go index f2c30121b..e50b14f77 100644 --- a/server/channelserver/handlers_helpers.go +++ b/server/channelserver/handlers_helpers.go @@ -4,6 +4,8 @@ import ( "erupe-ce/common/byteframe" "erupe-ce/common/mhfcourse" "erupe-ce/network/mhfpacket" + + "go.uber.org/zap" ) // Temporary function to just return no results for a MSG_MHF_ENUMERATE* packet @@ -62,6 +64,36 @@ func doAckSimpleFail(s *Session, ackHandle uint32, data []byte) { }) } +// loadCharacterData loads a column from the characters table and sends it as +// a buffered ack response. If the data is empty/nil, defaultData is sent instead. +func loadCharacterData(s *Session, ackHandle uint32, column string, defaultData []byte) { + var data []byte + err := s.server.db.QueryRow("SELECT "+column+" FROM characters WHERE id = $1", s.charID).Scan(&data) + if err != nil { + s.logger.Error("Failed to load "+column, zap.Error(err)) + } + if len(data) == 0 && defaultData != nil { + data = defaultData + } + doAckBufSucceed(s, ackHandle, data) +} + +// saveCharacterData saves data to a column in the characters table with size +// validation, optional save dump, and a simple ack response. +func saveCharacterData(s *Session, ackHandle uint32, column string, data []byte, maxSize int) { + if maxSize > 0 && len(data) > maxSize { + s.logger.Warn("Payload too large for "+column, zap.Int("len", len(data)), zap.Int("max", maxSize)) + doAckSimpleSucceed(s, ackHandle, make([]byte, 4)) + return + } + dumpSaveData(s, data, column) + _, err := s.server.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", data, s.charID) + if err != nil { + s.logger.Error("Failed to save "+column, zap.Error(err)) + } + doAckSimpleSucceed(s, ackHandle, make([]byte, 4)) +} + 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) diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 7d433cae6..d9e4119f2 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -14,28 +14,12 @@ import ( func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadPartner) - var data []byte - err := s.server.db.QueryRow("SELECT partner FROM characters WHERE id = $1", s.charID).Scan(&data) - if len(data) == 0 { - s.logger.Error("Failed to load partner", zap.Error(err)) - data = make([]byte, 9) - } - doAckBufSucceed(s, pkt.AckHandle, data) + loadCharacterData(s, pkt.AckHandle, "partner", make([]byte, 9)) } func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePartner) - if len(pkt.RawDataPayload) > 65536 { - s.logger.Warn("Partner payload too large", zap.Int("len", len(pkt.RawDataPayload))) - doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) - return - } - dumpSaveData(s, pkt.RawDataPayload, "partner") - _, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) - if err != nil { - s.logger.Error("Failed to save partner", zap.Error(err)) - } - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + saveCharacterData(s, pkt.AckHandle, "partner", pkt.RawDataPayload, 65536) } func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) { @@ -311,13 +295,7 @@ func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadOtomoAirou) - var data []byte - err := s.server.db.QueryRow("SELECT otomoairou FROM characters WHERE id = $1", s.charID).Scan(&data) - if len(data) == 0 { - s.logger.Error("Failed to load otomoairou", zap.Error(err)) - data = make([]byte, 10) - } - doAckBufSucceed(s, pkt.AckHandle, data) + loadCharacterData(s, pkt.AckHandle, "otomoairou", make([]byte, 10)) } func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/handlers_plate.go b/server/channelserver/handlers_plate.go index 3199c66a7..8774bd11c 100644 --- a/server/channelserver/handlers_plate.go +++ b/server/channelserver/handlers_plate.go @@ -31,12 +31,7 @@ import ( func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadPlateData) - var data []byte - err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data) - if err != nil { - s.logger.Error("Failed to load platedata", zap.Error(err)) - } - doAckBufSucceed(s, pkt.AckHandle, data) + loadCharacterData(s, pkt.AckHandle, "platedata", nil) } func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { @@ -144,12 +139,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadPlateBox) - var data []byte - err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data) - if err != nil { - s.logger.Error("Failed to load platebox", zap.Error(err)) - } - doAckBufSucceed(s, pkt.AckHandle, data) + loadCharacterData(s, pkt.AckHandle, "platebox", nil) } func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { @@ -223,13 +213,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadPlateMyset) - var data []byte - err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data) - if len(data) == 0 { - s.logger.Error("Failed to load platemyset", zap.Error(err)) - data = make([]byte, 1920) - } - doAckBufSucceed(s, pkt.AckHandle, data) + loadCharacterData(s, pkt.AckHandle, "platemyset", make([]byte, 1920)) } func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/handlers_rengoku.go b/server/channelserver/handlers_rengoku.go index 210e756de..09e2c3322 100644 --- a/server/channelserver/handlers_rengoku.go +++ b/server/channelserver/handlers_rengoku.go @@ -103,7 +103,9 @@ func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) { // a (massively out of date) version resides in the game's /dat/ folder or up to date can be pulled from packets data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin")) if err != nil { - panic(err) + s.logger.Error("Failed to read rengoku_data.bin", zap.Error(err)) + doAckBufFail(s, pkt.AckHandle, nil) + return } doAckBufSucceed(s, pkt.AckHandle, data) } diff --git a/server/channelserver/handlers_session.go b/server/channelserver/handlers_session.go index e52510995..acf9f8418 100644 --- a/server/channelserver/handlers_session.go +++ b/server/channelserver/handlers_session.go @@ -76,22 +76,26 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { _, err := s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID) if err != nil { - panic(err) + s.logger.Error("Failed to update current players", zap.Error(err)) + return } _, err = s.server.db.Exec("UPDATE sign_sessions SET server_id=$1, char_id=$2 WHERE token=$3", s.server.ID, s.charID, s.token) if err != nil { - panic(err) + s.logger.Error("Failed to update sign session", zap.Error(err)) + return } _, err = s.server.db.Exec("UPDATE characters SET last_login=$1 WHERE id=$2", TimeAdjusted().Unix(), s.charID) if err != nil { - panic(err) + s.logger.Error("Failed to update last login", zap.Error(err)) + 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) if err != nil { - panic(err) + s.logger.Error("Failed to update last character", zap.Error(err)) + return } doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) @@ -361,7 +365,8 @@ func handleMsgSysIssueLogkey(s *Session, p mhfpacket.MHFPacket) { logKey := make([]byte, 16) _, err := rand.Read(logKey) if err != nil { - panic(err) + s.logger.Error("Failed to generate log key", zap.Error(err)) + return } // TODO(Andoryuuta): In the offical client, the log key index is off by one, @@ -461,7 +466,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { bf.ReadUint16() // term length maxResults = bf.ReadUint16() bf.ReadUint8() // Unk - term = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + term, _ = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) case 3: _ip := bf.ReadBytes(4) ip = fmt.Sprintf("%d.%d.%d.%d", _ip[3], _ip[2], _ip[1], _ip[0]) diff --git a/server/channelserver/model_character.go b/server/channelserver/model_character.go index f8b2eb5f8..f84d02875 100644 --- a/server/channelserver/model_character.go +++ b/server/channelserver/model_character.go @@ -155,7 +155,7 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() { // This will update the save struct with the values stored in the character save func (save *CharacterSaveData) updateStructWithSaveData() { - save.Name = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[88:100])) + save.Name, _ = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[88:100])) if save.decompSave[save.Pointers[pGender]] == 1 { save.Gender = true } else { diff --git a/server/signserver/session.go b/server/signserver/session.go index 609e2ffa0..9944e12e5 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -157,7 +157,8 @@ func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) { func (s *Session) handlePSNLink(bf *byteframe.ByteFrame) { _ = bf.ReadNullTerminatedBytes() // Client ID - credentials := strings.Split(stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()), "\n") + credStr, _ := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + credentials := strings.Split(credStr, "\n") token := string(bf.ReadNullTerminatedBytes()) uid, resp := s.server.validateLogin(credentials[0], credentials[1]) if resp == SIGN_SUCCESS && uid > 0 { @@ -199,8 +200,8 @@ func (s *Session) handlePSNLink(bf *byteframe.ByteFrame) { } func (s *Session) handleDSGN(bf *byteframe.ByteFrame) { - user := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - pass := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + user, _ := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + pass, _ := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) _ = string(bf.ReadNullTerminatedBytes()) // Unk s.authenticate(user, pass) } diff --git a/server/signserver/sign_server.go b/server/signserver/sign_server.go index 12e4b44b8..4f37cc741 100644 --- a/server/signserver/sign_server.go +++ b/server/signserver/sign_server.go @@ -76,7 +76,8 @@ func (s *Server) acceptClients() { if shutdown { break } else { - panic(err) + s.logger.Warn("Error accepting client", zap.Error(err)) + continue } }