Files
Erupe/server/channelserver/handlers_cast_binary.go
2024-10-15 04:46:11 +01:00

217 lines
5.7 KiB
Go

package channelserver
import (
"erupe-ce/config"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"erupe-ce/utils/byteframe"
"erupe-ce/utils/logger"
"erupe-ce/utils/token"
"sync"
"fmt"
"math"
"strings"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
// MSG_SYS_CAST[ED]_BINARY types enum
const (
BinaryMessageTypeState = 0
BinaryMessageTypeChat = 1
BinaryMessageTypeQuest = 2
BinaryMessageTypeData = 3
BinaryMessageTypeMailNotify = 4
BinaryMessageTypeEmote = 6
)
// MSG_SYS_CAST[ED]_BINARY broadcast types enum
const (
BroadcastTypeTargeted = 0x01
BroadcastTypeStage = 0x03
BroadcastTypeServer = 0x06
BroadcastTypeWorld = 0x0a
)
var (
commands map[string]config.Command
once sync.Once // Ensures that initialization happens only once
commandsMu sync.Mutex // Mutex to ensure thread safety for commands map
)
func (server *ChannelServer) initCommands() {
once.Do(func() {
commands = make(map[string]config.Command)
commandLogger := logger.Get().Named("command")
cmds := config.GetConfig().Commands
for cmd, conf := range cmds {
if conf.Enabled {
commandLogger.Info(fmt.Sprintf("%s: Enabled", cmd))
} else {
commandLogger.Info(fmt.Sprintf("%s: Disabled", cmd))
}
}
})
}
func sendServerChatMessage(s *Session, message string) {
// Make the inside of the casted binary
bf := byteframe.NewByteFrame()
bf.SetLE()
msgBinChat := &binpacket.MsgBinChat{
Unk0: 0,
Type: 5,
Flags: 0x80,
Message: message,
SenderName: "Erupe",
}
msgBinChat.Build(bf)
castedBin := &mhfpacket.MsgSysCastedBinary{
CharID: 0,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}
s.QueueSendMHF(castedBin)
}
func handleMsgSysCastBinary(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCastBinary)
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 {
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
var timer bool
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)
if timer {
_ = tmp.ReadBytes(9)
tmp.SetLE()
frame := tmp.ReadUint32()
s.sendMessage(t("timer", v{
"hours": fmt.Sprintf("%d", int(frame/30/60/60)),
"minutes": fmt.Sprintf("%d", frame/30/60),
"seconds": fmt.Sprintf("%d", frame/30%60),
"milliseconds": fmt.Sprintf("%d", int(math.Round(float64(frame%30*100)/3))),
"frames": fmt.Sprintf("%d", frame),
}))
}
}
}
if config.GetConfig().DebugOptions.QuestTools {
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 {
// This is only correct most of the time
tmp.ReadBytes(20)
tmp.SetLE()
x := tmp.ReadFloat32()
y := tmp.ReadFloat32()
z := tmp.ReadFloat32()
s.Logger.Debug("Coord", zap.Float32s("XYZ", []float32{x, y, z}))
}
}
// Parse out the real casted binary payload
var msgBinTargeted *binpacket.MsgBinTargeted
var message, author string
var returnToSender bool
if pkt.MessageType == BinaryMessageTypeChat {
tmp.SetLE()
tmp.Seek(8, 0)
message = string(tmp.ReadNullTerminatedBytes())
author = string(tmp.ReadNullTerminatedBytes())
}
// Customise payload
realPayload := pkt.RawDataPayload
if pkt.BroadcastType == BroadcastTypeTargeted {
tmp.SetBE()
tmp.Seek(0, 0)
msgBinTargeted = &binpacket.MsgBinTargeted{}
err := msgBinTargeted.Parse(tmp)
if err != nil {
s.Logger.Warn("Failed to parse targeted cast binary")
return
}
realPayload = msgBinTargeted.RawDataPayload
} else if pkt.MessageType == BinaryMessageTypeChat {
if message == "@dice" {
returnToSender = true
m := binpacket.MsgBinChat{
Type: BinaryMessageTypeChat,
Flags: 4,
Message: fmt.Sprintf(`%d`, token.RNG.Intn(100)+1),
SenderName: author,
}
bf := byteframe.NewByteFrame()
bf.SetLE()
m.Build(bf)
realPayload = bf.Data()
} else {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
bf.SetLE()
chatMessage := &binpacket.MsgBinChat{}
chatMessage.Parse(bf)
if strings.HasPrefix(chatMessage.Message, config.GetConfig().CommandPrefix) {
err := executeCommand(s, chatMessage.Message)
if err != nil {
s.Logger.Error(fmt.Sprintf("Failed to execute command: %s", err))
}
return
}
if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld {
s.Server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
}
}
}
// Make the response to forward to the other client(s).
resp := &mhfpacket.MsgSysCastedBinary{
CharID: s.CharID,
BroadcastType: pkt.BroadcastType, // (The client never uses Type0 upon receiving)
MessageType: pkt.MessageType,
RawDataPayload: realPayload,
}
// Send to the proper recipients.
switch pkt.BroadcastType {
case BroadcastTypeWorld:
s.Server.WorldcastMHF(resp, s, nil)
case BroadcastTypeStage:
if returnToSender {
s.stage.BroadcastMHF(resp, nil)
} else {
s.stage.BroadcastMHF(resp, s)
}
case BroadcastTypeServer:
if pkt.MessageType == 1 {
raviSema := s.Server.getRaviSemaphore()
if raviSema != nil {
raviSema.BroadcastMHF(resp, s)
}
} else {
s.Server.BroadcastMHF(resp, s)
}
case BroadcastTypeTargeted:
for _, targetID := range (*msgBinTargeted).TargetCharIDs {
char := s.Server.FindSessionByCharID(targetID)
if char != nil {
char.QueueSendMHF(resp)
}
}
default:
s.Lock()
haveStage := s.stage != nil
if haveStage {
s.stage.BroadcastMHF(resp, s)
}
s.Unlock()
}
}
func handleMsgSysCastedBinary(s *Session, db *sqlx.DB, p mhfpacket.MHFPacket) {}