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() w.I32(0) // patchVersion w.U8(0xFF) // list marker w.Int7(0) // empty list return w.Bytes() } // EncodeEmptyList encodes an empty List. func EncodeEmptyList() []byte { w := NewUxWriter() w.U8(0xFF) w.Int7(0) return w.Bytes() } // EncodeCheckAccount encodes CheckAccountResult. func EncodeCheckAccount(accountId, userName, token, displayName 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": displayName, "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 (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) // 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).U8(0).U64(0).U8(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 } // 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") } mark, err = r.U8() if err != nil || mark != 0xff { return nil, fmt.Errorf("invalid Config buffer marker") } 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 }