261 lines
7.0 KiB
Go
261 lines
7.0 KiB
Go
package protocol
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"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()
|
|
w.I32(0) // patchVersion
|
|
w.U8(0xFF) // list marker
|
|
w.Int7(0) // empty list
|
|
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.
|
|
func EncodeCheckAccount(accountId, userName, token string, aid, serverId int, pid uint64) []byte {
|
|
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),
|
|
"role_name": "辰宿",
|
|
"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
|
|
// LoadingType (Complex 0xFF: Type, Members list, Ripple)
|
|
w.U8(0xFF).U32(0).U8(0xFF).Int7(0).Bool(false)
|
|
// LinkSimpleInfo (Complex 0xFF: Id, Mode, DeviceLevel, Tag, PublicEventId, LastPublicEventTime, HostPid)
|
|
w.U8(0xFF).U32(0).U32(0).U32(1).U32(0).U32(0).U64(0).U64(0)
|
|
// 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)
|
|
w.U8(0xFF).U32(0).U64(0).U32(0)
|
|
// 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
|
|
}
|
|
|
|
res := make([]byte, len(data))
|
|
copy(res, data)
|
|
|
|
// Patch Aid at offset 4 (4 bytes)
|
|
if len(res) > 8 {
|
|
binary.LittleEndian.PutUint32(res[4:8], uint32(aid))
|
|
}
|
|
// Patch Pid at offset 8 (8 bytes)
|
|
if len(res) > 16 {
|
|
binary.LittleEndian.PutUint64(res[8:16], pid)
|
|
}
|
|
|
|
return res, nil
|
|
}
|