Files
Erupe/cmd/protbot/scenario/chat.go
Houmgaor f640cfee27 fix: log SJIS decoding errors instead of silently discarding them
Add SJISToUTF8Lossy() that wraps SJISToUTF8() and logs decode errors at
slog.Debug level. Replace all 31 call sites across 17 files that previously
discarded the error with `_, _ =`. This makes garbled text from malformed
SJIS client data debuggable without adding noise at default log levels.
2026-02-22 17:01:22 +01:00

75 lines
2.2 KiB
Go

package scenario
import (
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/cmd/protbot/protocol"
)
// ChatMessage holds a parsed incoming chat message.
type ChatMessage struct {
ChatType uint8
SenderName string
Message string
}
// SendChat sends a chat message via MSG_SYS_CAST_BINARY with a MsgBinChat payload.
// broadcastType controls delivery scope: 0x03 = stage, 0x06 = world.
func SendChat(ch *protocol.ChannelConn, broadcastType, chatType uint8, message, senderName string) error {
payload := protocol.BuildChatPayload(chatType, message, senderName)
pkt := protocol.BuildCastBinaryPacket(broadcastType, 1, payload)
fmt.Printf("[chat] Sending chat (type=%d, broadcast=%d): %s\n", chatType, broadcastType, message)
return ch.SendPacket(pkt)
}
// ChatCallback is invoked when a chat message is received.
type ChatCallback func(msg ChatMessage)
// ListenChat registers a handler on MSG_SYS_CASTED_BINARY that parses chat
// messages (messageType=1) and invokes the callback.
func ListenChat(ch *protocol.ChannelConn, cb ChatCallback) {
ch.OnPacket(protocol.MSG_SYS_CASTED_BINARY, func(opcode uint16, data []byte) {
// MSG_SYS_CASTED_BINARY layout from server:
// uint32 unk
// uint8 broadcastType
// uint8 messageType
// uint16 dataSize
// []byte payload
if len(data) < 8 {
return
}
messageType := data[5]
if messageType != 1 { // Only handle chat messages.
return
}
bf := byteframe.NewByteFrameFromBytes(data)
_ = bf.ReadUint32() // unk
_ = bf.ReadUint8() // broadcastType
_ = bf.ReadUint8() // messageType
dataSize := bf.ReadUint16()
if dataSize == 0 {
return
}
payload := bf.ReadBytes(uint(dataSize))
// Parse MsgBinChat inner payload.
pbf := byteframe.NewByteFrameFromBytes(payload)
_ = pbf.ReadUint8() // unk0
chatType := pbf.ReadUint8()
_ = pbf.ReadUint16() // flags
_ = pbf.ReadUint16() // senderNameLen
_ = pbf.ReadUint16() // messageLen
msg := stringsupport.SJISToUTF8Lossy(pbf.ReadNullTerminatedBytes())
sender := stringsupport.SJISToUTF8Lossy(pbf.ReadNullTerminatedBytes())
cb(ChatMessage{
ChatType: chatType,
SenderName: sender,
Message: msg,
})
})
}