mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-28 10:32:55 +01:00
feat(protbot): add headless MHF protocol bot as cmd/protbot
Copy MHBridge into the Erupe module as cmd/protbot/ so it can be built, tested, and maintained alongside the server. The bot implements the full sign → entrance → channel login flow and supports lobby entry, chat, session setup, and quest enumeration. The conn/ package keeps its own Blowfish crypto primitives to avoid importing erupe-ce/config (which requires a config file at init).
This commit is contained in:
74
cmd/protbot/scenario/chat.go
Normal file
74
cmd/protbot/scenario/chat.go
Normal file
@@ -0,0 +1,74 @@
|
||||
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.SJISToUTF8(pbf.ReadNullTerminatedBytes())
|
||||
sender := stringsupport.SJISToUTF8(pbf.ReadNullTerminatedBytes())
|
||||
|
||||
cb(ChatMessage{
|
||||
ChatType: chatType,
|
||||
SenderName: sender,
|
||||
Message: msg,
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user