2026-10-29 03:12:00 +07:00
|
|
|
package protocol
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// EncodeServerTime encodes the ServerTime payload.
|
|
|
|
|
// Format (Inline): [F64 clientUnixTime] [F64 serverUnixTime]
|
|
|
|
|
func EncodeServerTime(clientUnixTime float64) []byte {
|
|
|
|
|
now := float64(time.Now().UnixNano()) / 1e9
|
|
|
|
|
if clientUnixTime <= 0 {
|
|
|
|
|
clientUnixTime = now
|
|
|
|
|
}
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.F64(clientUnixTime)
|
|
|
|
|
w.F64(now)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodePatchCheck encodes PatchCheckResult.
|
|
|
|
|
// Format (Inline): [I32 patchVersion (0)] [0xFF marker] [Int7 count (0)]
|
|
|
|
|
func EncodePatchCheck() []byte {
|
|
|
|
|
w := NewUxWriter()
|
2026-10-29 15:35:35 +07:00
|
|
|
w.I32(0) // patchVersion
|
|
|
|
|
w.U8(0xFF) // list marker
|
|
|
|
|
w.Int7(0) // empty list
|
2026-10-29 03:12:00 +07:00
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeEmptyList encodes an empty List<uint>.
|
|
|
|
|
func EncodeEmptyList() []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U8(0xFF)
|
|
|
|
|
w.Int7(0)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeCheckAccount encodes CheckAccountResult.
|
2026-10-29 15:35:35 +07:00
|
|
|
func EncodeCheckAccount(accountId, userName, token, displayName string, aid, serverId int, pid uint64) []byte {
|
2026-10-29 03:12:00 +07:00
|
|
|
loginData := map[string]interface{}{
|
|
|
|
|
"code": 200,
|
|
|
|
|
"subcode": 0,
|
|
|
|
|
"msg": "ok",
|
|
|
|
|
"uid": accountId,
|
|
|
|
|
"sdkuid": accountId,
|
|
|
|
|
"user_id": accountId,
|
|
|
|
|
"aid": aid,
|
|
|
|
|
"pid": pid,
|
|
|
|
|
"player_id": fmt.Sprintf("%d", pid),
|
|
|
|
|
"role_id": fmt.Sprintf("%d", pid),
|
2026-10-29 15:35:35 +07:00
|
|
|
"role_name": displayName,
|
2026-10-29 03:12:00 +07:00
|
|
|
"server_id": fmt.Sprintf("%d", serverId),
|
|
|
|
|
"host_id": serverId,
|
|
|
|
|
"username": userName,
|
|
|
|
|
"account": accountId,
|
|
|
|
|
"platform": "pc",
|
|
|
|
|
"token": token,
|
|
|
|
|
"access_token": token,
|
|
|
|
|
"ext_access_token": token,
|
|
|
|
|
"sessionid": token,
|
|
|
|
|
"login_ticket": token,
|
|
|
|
|
"realname_status": 1,
|
|
|
|
|
"realname_verify_status": 1,
|
|
|
|
|
"mobile_bind_status": 1,
|
|
|
|
|
}
|
|
|
|
|
loginJSON, _ := json.Marshal(loginData)
|
|
|
|
|
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U8(0xFF) // Complex object marker
|
|
|
|
|
w.UxString(string(loginJSON))
|
|
|
|
|
w.UxString(token)
|
|
|
|
|
w.UxString(userName)
|
|
|
|
|
w.I32(int32(aid))
|
|
|
|
|
w.Bool(false) // NeedRealNameTip
|
|
|
|
|
w.Bool(false) // NeedRoleEnter
|
|
|
|
|
w.Bool(true) // RealNameVerified
|
|
|
|
|
w.I32(int32(serverId))
|
|
|
|
|
w.I32(200) // code
|
|
|
|
|
w.I32(0) // subcode
|
|
|
|
|
w.UxString("ok")
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeEnterGame encodes EnterGameResult.
|
|
|
|
|
func EncodeEnterGame(aid int, pid uint64, host string, port int, token string, serverId int, accountId string) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.I32(int32(aid))
|
|
|
|
|
w.U64(pid)
|
|
|
|
|
|
|
|
|
|
// GateServerInfo
|
|
|
|
|
w.U8(0xFF)
|
|
|
|
|
w.I32(int32(aid))
|
|
|
|
|
w.U64(pid)
|
|
|
|
|
w.UxString(host)
|
|
|
|
|
w.I32(int32(port))
|
|
|
|
|
w.UxString(token)
|
|
|
|
|
w.I32(int32(serverId))
|
|
|
|
|
w.UxString(accountId)
|
|
|
|
|
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeGameServerInfo encodes GameServerInfo.
|
|
|
|
|
func EncodeGameServerInfo(host string, port int, gameToken string) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U8(0xFF)
|
|
|
|
|
w.UxString(host)
|
|
|
|
|
w.I32(int32(port))
|
|
|
|
|
w.UxString(gameToken)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeEnterScene encodes EnterSceneInfo.
|
|
|
|
|
func EncodeEnterScene(pid uint64, raidId uint32, instanceId uint64, universeId uint32, unitId uint64, templateId uint32, posX, posY, posZ, facing float32) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U8(0xFF) // [UxContract]
|
|
|
|
|
w.U64(pid)
|
|
|
|
|
w.U32(raidId)
|
|
|
|
|
w.U64(instanceId)
|
|
|
|
|
// UXVector3 Position (Struct)
|
|
|
|
|
w.F32(posX)
|
|
|
|
|
w.F32(posY)
|
|
|
|
|
w.F32(posZ)
|
|
|
|
|
w.F32(facing)
|
|
|
|
|
// SceneSpoonNames (empty list)
|
|
|
|
|
w.U8(0xFF).Int7(0)
|
|
|
|
|
// SceneSpoonMd5s (empty list)
|
|
|
|
|
w.U8(0xFF).Int7(0)
|
|
|
|
|
// Spirits List (count 1, Struct)
|
|
|
|
|
w.U8(0xFF).Int7(1)
|
|
|
|
|
// SpiritInitData (Struct: Id, TemplateId, IsActive, WeaponTemplateId, WeaponSkinId)
|
|
|
|
|
w.U64(unitId).U32(templateId).Bool(true).U32(0).U32(0)
|
|
|
|
|
// GridInfo (Struct: MinX, MinZ, MaxX, MaxZ)
|
|
|
|
|
w.I32(0).I32(0).I32(300000).I32(300000)
|
|
|
|
|
w.U32(0) // MatchGameId
|
|
|
|
|
w.U32(0) // SwitchShowId
|
|
|
|
|
w.Bool(false) // IsSwitchSpiritShow
|
|
|
|
|
w.U32(universeId)
|
|
|
|
|
w.U32(0) // SectorControlId
|
|
|
|
|
// SectorControlIds (empty list)
|
|
|
|
|
w.U8(0xFF).Int7(0)
|
|
|
|
|
w.U32(0) // DynamicLayerId
|
2026-10-29 15:35:35 +07:00
|
|
|
// LoadingType (TypeMark 1: byte Type, Int32 Members list, Ripple)
|
|
|
|
|
w.U8(1).U8(0).U8(0xFF).I32(0).Bool(false)
|
|
|
|
|
// LinkSimpleInfo (TypeMark 1: U64 Id, three bytes, U32 event/time, U64 host)
|
|
|
|
|
w.U8(1).U64(0).U8(0).U8(1).U8(0).U32(0).U32(0).U64(0)
|
2026-10-29 03:12:00 +07:00
|
|
|
// DataLayerIndices (empty list)
|
|
|
|
|
w.U8(0xFF).Int7(0)
|
|
|
|
|
w.Bool(false) // Seamless
|
|
|
|
|
// CollectionRoomLoadData (null complex)
|
|
|
|
|
w.U8(0x00)
|
|
|
|
|
// HouseLoadData (null complex)
|
|
|
|
|
w.U8(0x00)
|
|
|
|
|
// TombIslandIds (empty list)
|
|
|
|
|
w.U8(0xFF).Int7(0)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeLogicAgentEnter encodes SyncLogicAgentEnter.
|
|
|
|
|
func EncodeLogicAgentEnter(unitId uint64) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U64(unitId)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeLogicAgentLeave encodes SyncLogicAgentLeave.
|
|
|
|
|
func EncodeLogicAgentLeave(unitId uint64) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U64(unitId)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeManagedLogicAgent encodes SyncManagedLogicAgent.
|
|
|
|
|
func EncodeManagedLogicAgent(unitId, pid uint64, moveId byte) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U64(unitId).U64(pid).U8(moveId)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodePlayerCurrentSpirit encodes SyncPlayerCurrentSpirit.
|
|
|
|
|
func EncodePlayerCurrentSpirit(pid uint64, templateId uint32, unitId uint64, isAgentSwitch bool) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U64(pid).U32(templateId).U64(unitId).Bool(isAgentSwitch)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeSceneLoadCompleted encodes SyncSceneLoadCompleted.
|
|
|
|
|
func EncodeSceneLoadCompleted(sceneId uint64) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U64(sceneId)
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeRaidBattleUnitSpirit encodes the character projection for world entry and spirit switching.
|
|
|
|
|
func EncodeRaidBattleUnitSpirit(unitId uint64, templateId uint32, posX, posY, posZ, facing float32, pid uint64) []byte {
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.U8(0xFF) // RaidBattleUnitSpirit Complex marker
|
|
|
|
|
w.U8(0xFF) // SpiritWearFashionsInfo Complex marker
|
|
|
|
|
|
|
|
|
|
// WearFashionColoringInfoDict empty dictionary (0xFF marker, count 0)
|
|
|
|
|
w.U8(0xFF).I32(0)
|
|
|
|
|
|
|
|
|
|
// WearFashionInfoList with 5 default fashion items
|
|
|
|
|
w.U8(0xFF).I32(5)
|
|
|
|
|
fashionIds := []uint32{11100051, 11100052, 11100053, 11189102, 11189103}
|
|
|
|
|
for _, fid := range fashionIds {
|
|
|
|
|
w.U8(0xFF).U32(fid)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WearFashionEditInfoList empty list
|
|
|
|
|
w.U8(0xFF).I32(0)
|
|
|
|
|
|
|
|
|
|
// HiddenParts = 0, EditedHiddenParts = 0, Begging = false, IsBot = false
|
|
|
|
|
w.U8(0).U8(0).Bool(false).Bool(false)
|
|
|
|
|
|
|
|
|
|
// Id & TemplateId
|
|
|
|
|
w.U64(unitId).U32(templateId)
|
|
|
|
|
|
|
|
|
|
// Position & Facing
|
|
|
|
|
w.F32(posX).F32(posY).F32(posZ).F32(facing)
|
|
|
|
|
|
|
|
|
|
// OwnerId & ManagedPid
|
|
|
|
|
w.U64(pid).U64(0)
|
|
|
|
|
|
|
|
|
|
// MoveId & NavTags
|
|
|
|
|
w.U8(0).U32(0)
|
|
|
|
|
|
|
|
|
|
// GroundData (MoveActionGroundData Complex 0xFF)
|
2026-10-29 15:35:35 +07:00
|
|
|
w.U8(0xFF).U8(0).U64(0).U8(0)
|
2026-10-29 03:12:00 +07:00
|
|
|
// LocalPos (UXVector3)
|
|
|
|
|
w.F32(0).F32(0).F32(0)
|
|
|
|
|
|
|
|
|
|
return w.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodePlayerInfo loads the baseline PlayerClientInfo and patches dynamic fields.
|
|
|
|
|
func EncodePlayerInfo(templatePath string, aid int, pid uint64, accountId, name string) ([]byte, error) {
|
|
|
|
|
data, err := os.ReadFile(templatePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-29 15:35:35 +07:00
|
|
|
// PlayerClientInfo: complex marker, Config buffer (Int7), InfoLogin marker,
|
|
|
|
|
// Aid, Pid, AccountId, Name. String lengths may change; preserve the rest verbatim.
|
|
|
|
|
r := NewUxReader(data)
|
|
|
|
|
mark, err := r.U8()
|
|
|
|
|
if err != nil || mark != 0xff {
|
|
|
|
|
return nil, fmt.Errorf("invalid PlayerClientInfo marker")
|
2026-10-29 03:12:00 +07:00
|
|
|
}
|
2026-10-29 15:35:35 +07:00
|
|
|
mark, err = r.U8()
|
|
|
|
|
if err != nil || mark != 0xff {
|
|
|
|
|
return nil, fmt.Errorf("invalid Config buffer marker")
|
2026-10-29 03:12:00 +07:00
|
|
|
}
|
2026-10-29 15:35:35 +07:00
|
|
|
n, err := r.Int7()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if _, err = r.Bytes(n); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
mark, err = r.U8()
|
|
|
|
|
if err != nil || mark != 0xff {
|
|
|
|
|
return nil, fmt.Errorf("invalid InfoLogin marker")
|
|
|
|
|
}
|
|
|
|
|
prefixEnd := r.Position()
|
|
|
|
|
if _, err = r.I32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if _, err = r.U64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if _, err = r.UxString(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if _, err = r.UxString(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
w := NewUxWriter()
|
|
|
|
|
w.Raw(data[:prefixEnd]).I32(int32(aid)).U64(pid).UxString(accountId).UxString(name)
|
|
|
|
|
w.Raw(data[r.Position():])
|
|
|
|
|
return w.Bytes(), nil
|
2026-10-29 03:12:00 +07:00
|
|
|
}
|