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. 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 }