Files
bl/logic/service/player/pack.go
xinian c021b40fbe
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat: 增强踢人逻辑与BOSS脚本支持
优化踢人超时处理和僵尸连接清理,支持BOSS动作脚本并增加测试,修复事件匹配与战斗循环中的并发问题。
2026-04-05 21:59:22 +08:00

352 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package player
import (
"blazing/common/socket/errorcode"
"blazing/cool"
"blazing/logic/service/common"
"encoding/binary"
"encoding/hex"
"sync"
"sync/atomic"
"context"
"bytes"
"fmt"
"reflect"
"github.com/bruceshao/lockfree"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/gogf/gf/v2/os/glog"
"github.com/lunixbochs/struc"
"github.com/panjf2000/gnet/v2"
)
// getUnderlyingValue 递归解析reflect.Value解包指针、interface{}到底层具体类型
func getUnderlyingValue(val reflect.Value) (reflect.Value, error) {
for {
switch val.Kind() {
// 解包指针:获取指针指向的值
case reflect.Ptr:
if val.IsNil() {
return reflect.Value{}, nil
}
val = val.Elem()
// 解包interface{}:获取接口包裹的动态值
case reflect.Interface:
if val.IsNil() {
return reflect.Value{}, nil
}
val = val.Elem()
// 非指针/接口类型,终止递归
default:
return val, nil
}
}
}
// XORDecryptU 原地执行异或解密,避免额外分配和拷贝。
func XORDecryptU(encryptedData []byte, key uint32) []byte {
if len(encryptedData) == 0 {
return []byte{}
}
var keyBytes [4]byte
binary.BigEndian.PutUint32(keyBytes[:], key)
keyLen := len(keyBytes)
for i, b := range encryptedData {
encryptedData[i] = b ^ keyBytes[i%keyLen]
}
return encryptedData
}
var packetDataPoolSizes = [...]int{64, 128, 256, 512, 1024, 2048, 4096}
var packetDataPools = func() []sync.Pool {
pools := make([]sync.Pool, len(packetDataPoolSizes))
for i, size := range packetDataPoolSizes {
size := size
pools[i].New = func() any {
return make([]byte, size)
}
}
return pools
}()
func getPacketData(size int) []byte {
if size <= 0 {
return nil
}
for i, poolSize := range packetDataPoolSizes {
if size <= poolSize {
return packetDataPools[i].Get().([]byte)[:size]
}
}
return make([]byte, size)
}
func putPacketData(buf []byte) {
if buf == nil {
return
}
for i, poolSize := range packetDataPoolSizes {
if cap(buf) == poolSize {
packetDataPools[i].Put(buf[:poolSize])
return
}
}
}
func (h *ClientData) PushEvent(v []byte, submit func(task func()) error) {
if h == nil || h.IsClosed() {
return
}
var header common.TomeeHeader
header.Len = binary.BigEndian.Uint32(v[0:4])
header.CMD = binary.BigEndian.Uint32(v[5:9])
header.UserID = binary.BigEndian.Uint32(v[9:13])
if dataLen := len(v) - 17; dataLen > 0 {
header.Data = getPacketData(dataLen)
copy(header.Data, v[17:])
}
err := submit(func() {
if h.IsClosed() || h.LF == nil || !h.LF.Running() {
putPacketData(header.Data)
return
}
if err := h.LF.Producer().Write(header); err != nil {
putPacketData(header.Data)
}
})
if err != nil {
putPacketData(header.Data)
}
}
// 重写
// 遍历结构体方法并执行RECV_cmd
func (h *ClientData) OnEvent(data common.TomeeHeader) {
defer func() {
if err := recover(); err != nil { // 恢复 panicerr 为 panic 错误值
// 1. 打印错误信息
if h.Player != nil {
if h.Player.Info != nil {
cool.Logger.Error(context.TODO(), "panic 错误:", cool.Config.ServerInfo.OnlineID, h.Player.Info.UserID, err)
} else {
cool.Logger.Error(context.TODO(), "panic 错误:", cool.Config.ServerInfo.OnlineID, err)
}
} else {
cool.Logger.Error(context.TODO(), "panic 错误:", err)
}
}
}()
if data.CMD > 1001 {
if h.Player == nil {
fmt.Println(data.UserID, "账号未注册")
return
}
if h.Player.Info == nil {
fmt.Println(data.UserID, "未创建角色")
return
}
if data.Data != nil {
defer putPacketData(data.Data)
data.Res = XORDecryptU(data.Data, h.Player.Hash)
}
} else if data.Data != nil {
defer putPacketData(data.Data)
data.Res = data.Data
}
if cool.Config.ServerInfo.IsDebug != 0 {
fmt.Println("接收数据", data.UserID, data.CMD)
}
if data.UserID == 0 {
return
}
cmdlister, ok := cool.CmdCache[data.CMD]
if !ok {
// glog.Debug(context.Background(), data.UserID, data.CMD, "cmd未注册")
return //TODO 待实现cmd未注册
}
params := []reflect.Value{}
//funct := cmdlister.Type().NumIn()
// 如果需要可设置的变量(用于修改值),创建指针并解引用
ptrValue := reflect.New(cmdlister.Req)
// fmt.Println(tt1)
if data.Res != nil {
tt1 := ptrValue.Elem().Addr().Interface()
err := struc.Unpack(bytes.NewBuffer(data.Res), tt1)
if err != nil {
cool.Logger.Error(context.Background(), data.UserID, data.CMD, "解包失败,", err, hex.EncodeToString(data.Res))
//fmt.Println(data.UserID, data.CMD, "解包失败,", hex.EncodeToString(data.Data))
data.Result = uint32(errorcode.ErrorCodes.ErrSystemProcessingError)
h.SendPack(data.Pack(nil))
return
}
}
ptrValue1 := ptrValue.Elem().Addr()
// 设置 Name 字段
nameField := ptrValue.Elem().Field(0) //首个为header
nameField.Set(reflect.ValueOf(data))
if data.CMD > 1001 { //if cmdlister.Type().In(1) == reflect.TypeOf(&Player{}) {
//t := GetPlayer(c, data.UserID)
// fmt.Println(data.CMD, "接收 变量的地址 ", &t.Info, t.Info.UserID)
params = append(params, ptrValue1, reflect.ValueOf(h.Player))
} else {
params = append(params, ptrValue1, reflect.ValueOf(h.Conn))
}
ret := cmdlister.Func.Call(params)
if len(ret) <= 0 { //如果判断没有参数,那就说明这个包没有返回参数
return
}
aa, ok := ret[1].Interface().(errorcode.ErrorCode) //判断错误
data.Result = uint32(aa)
if aa == -1 {
return
}
if ok && aa != 0 { //这里实现回复错误包
glog.Info(context.Background(), data.UserID, data.CMD, aa.Code())
h.SendPack(data.Pack(nil))
return
}
t1 := data.Pack(ret[0].Interface())
h.SendPack(t1)
}
type ClientData struct {
IsCrossDomain sync.Once //是否跨域过
Player *Player //客户实体
ERROR_CONNUT int
Wsmsg *WsCodec
Conn gnet.Conn
LF *lockfree.Lockfree[common.TomeeHeader]
closed int32
}
func (p *ClientData) IsClosed() bool {
return atomic.LoadInt32(&p.closed) == 1
}
func (p *ClientData) Close() {
if !atomic.CompareAndSwapInt32(&p.closed, 0, 1) {
return
}
if p.LF != nil && p.LF.Running() {
_ = p.LF.Close()
}
}
func (p *ClientData) GetPlayer(userid uint32) *Player { //TODO 这里待优化,可能存在内存泄漏问题
if p.Player == nil {
p.Player = NewPlayer(p.Conn)
}
_, ok := Mainplayer.LoadOrStore(userid, p)
if !ok {
p.Player = NewPlayer(p.Conn)
}
return p.Player
// return nil
}
func NewClientData(c gnet.Conn) *ClientData {
cd := &ClientData{
Conn: c,
Wsmsg: &WsCodec{},
}
cd.LF = lockfree.NewLockfree(
8,
cd,
lockfree.NewConditionBlockStrategy(),
)
if err := cd.LF.Start(); err != nil {
panic(err)
}
return cd
}
// XORDecrypt 异或解密函数密钥改为uint32版本
// 核心逻辑将uint32密钥拆分为4字节数组大端序适配AS3二进制处理习惯循环与加密数据异或
// 参数:
//
// encryptedData - 待解密的字节数组
// key - 32位无符号整数密钥替代原字符串密钥
//
// 返回值:解密后的字节数组
func XORDecrypt(encryptedData []byte, keyStr string) []byte {
if len(encryptedData) == 0 || keyStr == "" {
return []byte{}
}
// 1. 将密钥字符串转换为UTF-8字节数组对应AS3的writeUTFBytes(_arg_2)
keyBytes := []byte(keyStr) // Go中string转[]byte默认是UTF-8编码与AS3的writeUTFBytes一致
keyLen := len(keyBytes)
if keyLen == 0 {
return encryptedData // 空密钥不加密,直接返回
}
// 2. 执行异或操作(与加密逻辑一致,异或两次还原数据)
decrypted := make([]byte, len(encryptedData))
for i, b := range encryptedData {
// 循环复用密钥字节(索引取模)
keyIndex := i % keyLen
decrypted[i] = b ^ keyBytes[keyIndex]
}
return decrypted
}
func (p *ClientData) SendPack(b []byte) error {
if p.Wsmsg == nil {
return fmt.Errorf("ws空")
}
if p.Wsmsg.Upgraded {
// This is the echo server
wsutil.WriteServerMessage(p.Conn, ws.OpBinary, b)
} else {
p.Conn.AsyncWrite(b, nil)
}
return nil
}