feat(go): Port full Ananta private server to Go architecture
This commit is contained in:
1 parent
ca2e80f9b7
commit
c73d744649
28 files changed
+3585
-57
No files matched your search
@@ -0,0 +1,260 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"ananta-ps/pkg/crypto/chacha8"
|
||||
)
|
||||
|
||||
type Frame struct {
|
||||
Mode byte
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
func ReadFrame(r io.Reader, cryptor *chacha8.UxChaCha8) (*Frame, error) {
|
||||
head := make([]byte, 5)
|
||||
if _, err := io.ReadFull(r, head); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cryptor != nil {
|
||||
cryptor.XorInPlace(head)
|
||||
}
|
||||
|
||||
payloadLength := int(binary.LittleEndian.Uint32(head[0:4]))
|
||||
if payloadLength < 0 || payloadLength > 16*1024*1024 {
|
||||
return nil, errors.New("invalid frame payload length after decrypt")
|
||||
}
|
||||
|
||||
payload := make([]byte, payloadLength)
|
||||
if payloadLength > 0 {
|
||||
if _, err := io.ReadFull(r, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cryptor != nil {
|
||||
cryptor.XorInPlace(payload)
|
||||
}
|
||||
}
|
||||
|
||||
return &Frame{
|
||||
Mode: head[4],
|
||||
Payload: payload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func WriteFrame(w io.Writer, mode byte, payload []byte, cryptor *chacha8.UxChaCha8) error {
|
||||
buf := make([]byte, 5+len(payload))
|
||||
binary.LittleEndian.PutUint32(buf[0:4], uint32(len(payload)))
|
||||
buf[4] = mode
|
||||
copy(buf[5:], payload)
|
||||
|
||||
if cryptor != nil {
|
||||
cryptor.XorInPlace(buf)
|
||||
}
|
||||
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func ParseRpcPacket(payload []byte) RpcPacket {
|
||||
if len(payload) < 5 {
|
||||
return RpcPacket{Kind: PacketKindUnknown, Body: payload}
|
||||
}
|
||||
|
||||
tag := payload[0]
|
||||
methodID := binary.LittleEndian.Uint32(payload[1:5])
|
||||
|
||||
switch tag {
|
||||
case 0x03: // Notify
|
||||
return RpcPacket{
|
||||
Kind: PacketKindNotify,
|
||||
MethodID: methodID,
|
||||
Body: payload[5:],
|
||||
}
|
||||
case 0x01: // Invoke
|
||||
if len(payload) >= 9 {
|
||||
invokeID := int32(binary.LittleEndian.Uint32(payload[5:9]))
|
||||
return RpcPacket{
|
||||
Kind: PacketKindInvoke,
|
||||
MethodID: methodID,
|
||||
InvokeID: invokeID,
|
||||
Body: payload[9:],
|
||||
}
|
||||
}
|
||||
case 0x02: // Return
|
||||
if len(payload) >= 13 {
|
||||
invokeID := int32(binary.LittleEndian.Uint32(payload[5:9]))
|
||||
errCode := int32(binary.LittleEndian.Uint32(payload[9:13]))
|
||||
return RpcPacket{
|
||||
Kind: PacketKindReturn,
|
||||
MethodID: methodID,
|
||||
InvokeID: invokeID,
|
||||
Error: errCode,
|
||||
Body: payload[13:],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RpcPacket{
|
||||
Kind: PacketKindUnknown,
|
||||
MethodID: methodID,
|
||||
Body: payload,
|
||||
}
|
||||
}
|
||||
|
||||
func BuildNotify(methodID uint32, body []byte) []byte {
|
||||
buf := make([]byte, 5+len(body))
|
||||
buf[0] = 0x03
|
||||
binary.LittleEndian.PutUint32(buf[1:5], methodID)
|
||||
copy(buf[5:], body)
|
||||
return buf
|
||||
}
|
||||
|
||||
func BuildInvoke(methodID uint32, invokeID int32, body []byte) []byte {
|
||||
buf := make([]byte, 9+len(body))
|
||||
buf[0] = 0x01
|
||||
binary.LittleEndian.PutUint32(buf[1:5], methodID)
|
||||
binary.LittleEndian.PutUint32(buf[5:9], uint32(invokeID))
|
||||
copy(buf[9:], body)
|
||||
return buf
|
||||
}
|
||||
|
||||
func BuildReturn(methodID uint32, invokeID int32, errCode int32, body []byte) []byte {
|
||||
buf := make([]byte, 13+len(body))
|
||||
buf[0] = 0x02
|
||||
binary.LittleEndian.PutUint32(buf[1:5], methodID)
|
||||
binary.LittleEndian.PutUint32(buf[5:9], uint32(invokeID))
|
||||
binary.LittleEndian.PutUint32(buf[9:13], uint32(errCode))
|
||||
copy(buf[13:], body)
|
||||
return buf
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package protocol
|
||||
|
||||
// GameSwitchEntry represents a single switch configuration entry.
|
||||
type GameSwitchEntry struct {
|
||||
Name string
|
||||
Value bool
|
||||
}
|
||||
|
||||
// GameSwitchCatalog contains all 203 UX.GameSwitch flags.
|
||||
var GameSwitchCatalog = []GameSwitchEntry{
|
||||
{"EnableRaidTimeFix", true},
|
||||
{"EnableClientZoneClosure", true},
|
||||
{"UseEditorSectorTileInfoJsonPath1", true},
|
||||
{"EnableNavMultiverseChain", true},
|
||||
{"EnableRecycleItem", true},
|
||||
{"EnableComposeItem", true},
|
||||
{"DisableEditXingZheDangAn", false},
|
||||
{"EnableUpdateSignature", true},
|
||||
{"EnablePartialAntiAddiction", false},
|
||||
{"EnableAntiAddiction", false},
|
||||
{"EnableRealNameBeforeCreateRole", false},
|
||||
{"EnableExchangeGold", true},
|
||||
{"EnableNpcShop", true},
|
||||
{"EnableFashionStore", true},
|
||||
{"Enable4SStore", true},
|
||||
{"EnableCreateRole", true},
|
||||
{"EnableSeasonalBattlePass", true},
|
||||
{"EnableGamePlayBattlePass", true},
|
||||
{"EnableGachaSystem", true},
|
||||
{"EnableRadiantChest", true},
|
||||
{"EnableCloset", true},
|
||||
{"EnableGachaMilestone", true},
|
||||
{"EnableHeartbeatTimeoutCheckDebug", false},
|
||||
{"EnableHeartbeatTimeoutCheckRelease", true},
|
||||
{"EnableLoginReconnect", true},
|
||||
{"EnableLoginOnlineAndDetachMatchLink", true},
|
||||
{"AutoSwitchTextModeWithAllPrefix", true},
|
||||
{"ChatLimitRegion", true},
|
||||
{"EnableMahjong", true},
|
||||
{"EnableMahjongFriend", true},
|
||||
{"EnableMahjongWithNpc", true},
|
||||
{"EnableMahjongPve", true},
|
||||
{"EnableChangePersonalZoneHeadInfo", true},
|
||||
{"EnableSpiritAddExp", true},
|
||||
{"EnableSpiritAbilityAddExp", true},
|
||||
{"EnableSpiritDomainLevelUp", true},
|
||||
{"EnableForeignIpSns", true},
|
||||
{"EnableSharePhoto_Netease", true},
|
||||
{"EnableSharePhoto_Oppo", true},
|
||||
{"EnableSharePhoto_Huawei", true},
|
||||
{"EnableSharePhoto_NearmeVivo", true},
|
||||
{"EnableSharePhoto_XiaomiApp", true},
|
||||
{"EnableSharePhoto_CoolpadSdk", true},
|
||||
{"EnableSharePhoto_Flyme", true},
|
||||
{"EnableSharePhoto_LenovoOpen", true},
|
||||
{"EnableSharePhoto_Gionee", true},
|
||||
{"EnableSharePhoto_UcPlatform", true},
|
||||
{"EnableSharePhoto_360Assistant", true},
|
||||
{"EnableSharePhoto_Baidu", true},
|
||||
{"EnableSharePhoto_BilibiliSdk", true},
|
||||
{"EnableSharePhoto_Dtkj", true},
|
||||
{"EnableSharePhoto_AppStore", true},
|
||||
{"EnableSharePhoto_TapTapDev", true},
|
||||
{"EnableSharePhoto_TapTapDis", true},
|
||||
{"EnableSharePhoto_HykbDev", true},
|
||||
{"EnableSharePhoto_HykbDis", true},
|
||||
{"EnableFightSpiritSkillLevelUp", true},
|
||||
{"EnableSignIn", true},
|
||||
{"EnableSaveCardGroupName", true},
|
||||
{"EnableAetherNpcFixedSpawnPointRule", true},
|
||||
{"EnableNpcUseBelonging", true},
|
||||
{"EnableVehicleNpcDefaultPlayPoi1", true},
|
||||
{"EnableAgentInteractForbidFromServer1", true},
|
||||
{"EnableTeleportOnCrossBoundary", true},
|
||||
{"EnableAgentCompanionAIIdleChat", true},
|
||||
{"EnableAgentPlayPoiCommand", true},
|
||||
{"EnableNpcGateway", true},
|
||||
{"EnableNpcDensityMigration1", true},
|
||||
{"EnableLink", true},
|
||||
{"EnableLinkPlanningBoardUnlockCheck", true},
|
||||
{"ProfilerAetherVehicle", true},
|
||||
{"ProfilerStaticVehicle", true},
|
||||
{"ProfilerAetherVehicleGO", true},
|
||||
{"EnableRaidDetachLocalCleanup", true},
|
||||
{"ProfilerIntersection", true},
|
||||
{"ProfilerIntersectionRender", true},
|
||||
{"EnablePoliceJobStoryLegacy", true},
|
||||
{"EnableGroupAIStory", true},
|
||||
{"EnableGroupAIDebug", true},
|
||||
{"EnableNewProgress", true},
|
||||
{"EnableDGTemporaryResource", true},
|
||||
{"ForbidStimEvent", false},
|
||||
{"EnableZoneGraphUndirectLane", true},
|
||||
{"EnableNPCNormalMoveBeta", true},
|
||||
{"EnableIndoor", true},
|
||||
{"EnableVehicleNpcBoardingSync1", true},
|
||||
{"EnableAetherNpcTypeAgnosticSync", true},
|
||||
{"EnableVehicleBoardingUnifiedSync1", true},
|
||||
{"EnableLinkTest", true},
|
||||
{"EnableSpawnSpoonBlockInteractionCheck", true},
|
||||
{"EnableVehicleOverrideSync", true},
|
||||
{"EnableVehicleDefaultSuitExpand", true},
|
||||
{"EnableECSResponse", true},
|
||||
{"DebugSyncVehicleDangerZoneToClient", false},
|
||||
{"DebugHiddenArea", false},
|
||||
{"DebugAetherVehicle", false},
|
||||
{"DebugAetherIntersection", false},
|
||||
{"DebugAetherVehicleLane", false},
|
||||
{"DebugAssetTagPreview", false},
|
||||
{"DebugLogicVehicle", false},
|
||||
{"EnableDropRequireReliableRpcOnClient", true},
|
||||
{"ProfilerRandomEvent", true},
|
||||
{"ProfilerDestructible", true},
|
||||
{"ProfilerGadget", true},
|
||||
{"ProfilerDynamicGo", true},
|
||||
{"ProfilerSpoonAgent", true},
|
||||
{"ProfilerWildEnemy", true},
|
||||
{"ProfilerEnableMetro", true},
|
||||
{"BlockMetroForChongxiao", false},
|
||||
{"MetroCrouchSwitch", true},
|
||||
{"ProfilerEnableNpcGo", true},
|
||||
{"DisableBartender", false},
|
||||
{"DisableScientistProduceItem", false},
|
||||
{"DisableScientistBreakdownItem", false},
|
||||
{"DisableScientistTakeProducedItem", false},
|
||||
{"DisableScientistFactorMachine", false},
|
||||
{"DisableScientistEnchant", false},
|
||||
{"DisableItemExchange", false},
|
||||
{"EnableFallingDownOnDeath2", true},
|
||||
{"EnableBehaveLegacy", true},
|
||||
{"EnableBeggerStory", true},
|
||||
{"EnableGPUScene", true},
|
||||
{"EnableFavorNpcBTree", true},
|
||||
{"EnableFavorNpcCommute", true},
|
||||
{"EnableSwitchSpiritToTempFavorNpc1", true},
|
||||
{"SkipSwitchOutExitTransition", true},
|
||||
{"EnableGenderTransform", true},
|
||||
{"EnableIndoorAbilityLimit", true},
|
||||
{"EnableRemoveAbilityLimitOldBuffHandlers", true},
|
||||
{"EnableSpoonNpc", true},
|
||||
{"EnableSearchRaidSpoonNpc", true},
|
||||
{"EnableParty", true},
|
||||
{"EnableRanking", true},
|
||||
{"EnableJanitor", true},
|
||||
{"EnableBeggarAiPainting", true},
|
||||
{"EnableNewBag", true},
|
||||
{"EnableNotices", true},
|
||||
{"EnableAkashicSystem", true},
|
||||
{"EnableProfile", true},
|
||||
{"EnableRadioStation", true},
|
||||
{"EnableCityPedia", true},
|
||||
{"EnableEonBug", true},
|
||||
{"EnableTutorial", true},
|
||||
{"EnableVehicleRequisitionSystemUnlock1", true},
|
||||
{"EnableTestProcess", true},
|
||||
{"EnableForceStopNaN", true},
|
||||
{"EnableOtlpLogger", true},
|
||||
{"EnableResourceLoadCollect", true},
|
||||
{"EnableHUDRecommend", true},
|
||||
{"EnableAttachmentSystem", true},
|
||||
{"EnableFishing", true},
|
||||
{"EnablePSOCollect", true},
|
||||
{"KeepBehaviorSchedulerWhenSwitch", true},
|
||||
{"KeepBehaviorSchedulerWhenSwitch2", true},
|
||||
{"OpenNewSettleColliderClip2", true},
|
||||
{"OpenNewSettleEffectClip2", true},
|
||||
{"OpenNewSettleCreation2", true},
|
||||
{"UseCoreLibRouteFinder", true},
|
||||
{"EnableScratchServerReward", true},
|
||||
{"DisableYanJieAIReply", false},
|
||||
{"EnableCompetitionSeason", true},
|
||||
{"EnableActivity", true},
|
||||
{"EnableChangeNameByItem", true},
|
||||
{"EnableThisYearChat", true},
|
||||
{"EnableSpecialTimeUserChat", true},
|
||||
{"EnableBuzzCenter", true},
|
||||
{"EnableCheckIn", true},
|
||||
{"EnableDossier", true},
|
||||
{"EnableScope", true},
|
||||
{"EnableFriends", true},
|
||||
{"EnableClub", true},
|
||||
{"EnableBBChat", true},
|
||||
{"EnableMail", true},
|
||||
{"EnableCustom", true},
|
||||
{"EnableBubble", true},
|
||||
{"EnablePhoto", true},
|
||||
{"EnableCatExpress", true},
|
||||
{"EnableDutyTerminal", true},
|
||||
{"EnableMall", true},
|
||||
{"EnableMallRecommend", true},
|
||||
{"EnableMallDirectSale", true},
|
||||
{"EnableMallBundle", true},
|
||||
{"EnableCharge", true},
|
||||
{"EnableSpiritTalent", true},
|
||||
{"EnableJobTalent", true},
|
||||
{"EnableGameplayTalent", true},
|
||||
{"EnableAchievement", true},
|
||||
{"EnablePhone", true},
|
||||
{"EnableTrade", true},
|
||||
{"EnableGift", true},
|
||||
{"EnableGiftFriendTimeCheck", true},
|
||||
{"EnableTime", true},
|
||||
{"DisableOptGoMode", false},
|
||||
{"DisableMonsterOptGoMode", false},
|
||||
{"EnablePedConvertWorthyCheck", true},
|
||||
{"EnableLifeScheduleNpcAlwaysGo", true},
|
||||
{"EnableAnimatorLODOtherPlayer", true},
|
||||
{"EnableSpecialAnimateCullingMode", true},
|
||||
{"EnableAnimatorIncRebind", true},
|
||||
{"SimplifyKinematicInVehicle", true},
|
||||
{"EnableInteractionAction", true},
|
||||
{"EnableCollectionBook", true},
|
||||
{"EnableMonthlyPass", true},
|
||||
}
|
||||
|
||||
// BuildGameSwitchPayload encodes the game switches as required by SyncGameSwitchToClient_3.
|
||||
// Format:
|
||||
// [Int7 count]
|
||||
// for each: [UxString key] [TypeTagBool (0x02)] [Bool (1 byte)]
|
||||
func BuildGameSwitchPayload() []byte {
|
||||
w := NewUxWriter()
|
||||
w.Int7(len(GameSwitchCatalog))
|
||||
for _, entry := range GameSwitchCatalog {
|
||||
w.UxString(entry.Name)
|
||||
w.U8(2) // TypeTagBool
|
||||
w.Bool(entry.Value)
|
||||
}
|
||||
return w.Bytes()
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"ananta-ps/pkg/crypto/chacha8"
|
||||
)
|
||||
|
||||
// HandleHandshake processes client C2S handshake and performs S2C key exchange.
|
||||
// It returns read and write ciphers initialized with negotiated ChaCha8 keys.
|
||||
func HandleHandshake(conn net.Conn) (*chacha8.UxChaCha8, *chacha8.UxChaCha8, error) {
|
||||
// Read frame 0 (mode 0x01 plaintext)
|
||||
frame, err := ReadFrame(conn, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(frame.Payload) < 20 {
|
||||
return nil, nil, errors.New("client handshake too short")
|
||||
}
|
||||
|
||||
aesKey := frame.Payload[4:20] // 16-byte key
|
||||
|
||||
clientWriteKey := make([]byte, 32)
|
||||
clientReadKey := make([]byte, 32)
|
||||
streamNonce := make([]byte, 12)
|
||||
aesNonce := make([]byte, 12)
|
||||
|
||||
if _, err := io.ReadFull(rand.Reader, clientWriteKey); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := io.ReadFull(rand.Reader, clientReadKey); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := io.ReadFull(rand.Reader, streamNonce); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, err := io.ReadFull(rand.Reader, aesNonce); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
chaChaHead := make([]byte, 76)
|
||||
copy(chaChaHead[0:32], clientWriteKey)
|
||||
copy(chaChaHead[32:64], clientReadKey)
|
||||
copy(chaChaHead[64:76], streamNonce)
|
||||
|
||||
block, err := aes.NewCipher(aesKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
aesGcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
encrypted := aesGcm.Seal(nil, aesNonce, chaChaHead, nil) // 76 + 16 = 92 bytes
|
||||
|
||||
payload := make([]byte, 368)
|
||||
binary.LittleEndian.PutUint32(payload[0:4], 1)
|
||||
binary.LittleEndian.PutUint32(payload[4:8], 10)
|
||||
copy(payload[8:20], aesNonce)
|
||||
copy(payload[20:96], encrypted[0:76])
|
||||
copy(payload[96:112], encrypted[76:92])
|
||||
|
||||
// Send handshake reply in plaintext with mode 0x01
|
||||
if err := WriteFrame(conn, 0x01, payload, nil); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
readCryptor, err := chacha8.New(clientWriteKey, streamNonce)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
writeCryptor, err := chacha8.New(clientReadKey, streamNonce)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return readCryptor, writeCryptor, nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package protocol
|
||||
|
||||
// NetEase UX-RPC Method IDs for Ananta CBT (Build 4229938).
|
||||
const (
|
||||
// Login / Gate
|
||||
MethodCheckVersion uint32 = 34700853
|
||||
MethodHasOnlinePlayer uint32 = 34822236
|
||||
MethodAskNewHotFixPatchLogin uint32 = 34692559
|
||||
MethodRequestPatchesCheckDataFromLogin uint32 = 34601301
|
||||
MethodRequestPatchesFromLogin uint32 = 34022227
|
||||
MethodAskUniSdkShareToken_Login uint32 = 34110053
|
||||
MethodAskUniSdkShareToken_Gate uint32 = 52152686
|
||||
MethodRequestFpPassToken uint32 = 34828770
|
||||
MethodCheckAccount uint32 = 34339919
|
||||
MethodCheckAccountPassBy uint32 = 34163006
|
||||
MethodCheckAccountOpenId uint32 = 34381554
|
||||
MethodTryLogin uint32 = 34529582
|
||||
MethodSyncRoleList uint32 = 35167428
|
||||
MethodRequestCreateRoleEx uint32 = 34383517
|
||||
MethodRequestEnterGame uint32 = 34566515
|
||||
MethodDebugRequestEnterGame uint32 = 34478273
|
||||
MethodAskCloseConnectionToGate uint32 = 52140059
|
||||
MethodGetServerTime uint32 = 52951195
|
||||
MethodSendServerTime uint32 = 52296181
|
||||
MethodGateLogin uint32 = 52023760
|
||||
MethodSyncPlayerGameServerInfo uint32 = 52737039
|
||||
MethodSyncGameSwitchToClient_3 uint32 = 53940944
|
||||
MethodQuerySkey uint32 = 52367757
|
||||
MethodReportNetworkQuality uint32 = 52623348
|
||||
MethodDavinciCode uint32 = 52458997
|
||||
MethodGetFriendApplicationListToMe uint32 = 52123985
|
||||
MethodGetAllChatGroupLatestMessage uint32 = 52345719
|
||||
MethodGetSimplePlayerInfoByPidList uint32 = 52781934
|
||||
|
||||
// Game / World
|
||||
MethodLoginGame uint32 = 63142082
|
||||
MethodAskEnterWorld uint32 = 63142082
|
||||
MethodSendServerTimeGame uint32 = 63853503
|
||||
MethodSyncPlayerInfo uint32 = 63200762
|
||||
MethodSyncEnterScene uint32 = 67711467
|
||||
MethodAskLoadSceneCompleted uint32 = 66879899
|
||||
MethodAskLoadingFinished uint32 = 67017252
|
||||
MethodSyncLogicAgentEnter uint32 = 68057283
|
||||
MethodSyncLogicAgentLeave uint32 = 68057315
|
||||
MethodSyncManagedLogicAgent uint32 = 68611146
|
||||
MethodSyncRaidBattleUnitSpirit uint32 = 68953151
|
||||
MethodSyncPlayerCurrentSpirit uint32 = 68582738
|
||||
MethodSyncSceneLoadCompleted uint32 = 67610360
|
||||
MethodSyncGamePause uint32 = 67039327
|
||||
MethodSyncPlayerLoadRate uint32 = 68383503
|
||||
MethodAskLoadedInSameScene uint32 = 68838634
|
||||
MethodSyncUnitPositionAndFacing uint32 = 68664188
|
||||
MethodSyncPlayerPositionAndFacing uint32 = 68702330
|
||||
MethodSyncSceneFogMapAllUnlock uint32 = 68677150
|
||||
MethodSyncRunGmUtils uint32 = 66042045
|
||||
MethodSyncAllSpiritCombatPower uint32 = 68798150
|
||||
MethodSyncSwitchSpiritInSitu uint32 = 68582770
|
||||
|
||||
// Movement
|
||||
MethodAskMoveStart uint32 = 68412852
|
||||
MethodAskMoveStop uint32 = 68412853
|
||||
MethodAskUpdatePlayerPosition uint32 = 68058291
|
||||
|
||||
// Combat & Skills
|
||||
MethodAskPlayerAttack uint32 = 68412884
|
||||
MethodAskPlayerCastSkill uint32 = 68412916
|
||||
MethodAskUnlockFightStyle uint32 = 68412948
|
||||
MethodSyncChangeSpirit uint32 = 68582802
|
||||
MethodAskPreSwitchSpirit uint32 = 68415783
|
||||
MethodAskSwitchSpirit uint32 = 68415784
|
||||
|
||||
// Vehicles
|
||||
MethodAskSummonVehicle uint32 = 68221895
|
||||
MethodAskDriveVehicle uint32 = 68221893
|
||||
MethodAskExitVehicle uint32 = 68221894
|
||||
MethodSyncPlayerVehicleInfo uint32 = 68221926
|
||||
MethodSyncAetherVehicleInit uint32 = 68221958
|
||||
|
||||
// Teleportation & Scene
|
||||
MethodAskTeleportToScene uint32 = 67891234
|
||||
MethodSwitchSceneDirect uint32 = 67891235
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
package protocol
|
||||
|
||||
type RpcPacketKind byte
|
||||
|
||||
const (
|
||||
PacketKindUnknown RpcPacketKind = 0x00
|
||||
PacketKindInvoke RpcPacketKind = 0x01
|
||||
PacketKindReturn RpcPacketKind = 0x02
|
||||
PacketKindNotify RpcPacketKind = 0x03
|
||||
)
|
||||
|
||||
type RpcPacket struct {
|
||||
Kind RpcPacketKind
|
||||
MethodID uint32
|
||||
InvokeID int32
|
||||
Error int32
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (p RpcPacket) IsInvoke() bool { return p.Kind == PacketKindInvoke }
|
||||
func (p RpcPacket) IsNotify() bool { return p.Kind == PacketKindNotify }
|
||||
func (p RpcPacket) IsReturn() bool { return p.Kind == PacketKindReturn }
|
||||
@@ -0,0 +1,108 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUxSerializationRoundtrip(t *testing.T) {
|
||||
w := NewUxWriter()
|
||||
w.U8(42).
|
||||
Bool(true).
|
||||
I16(-1234).
|
||||
U16(5678).
|
||||
I32(-999999).
|
||||
U32(12345678).
|
||||
I64(-9876543210).
|
||||
U64(18446744073709551615).
|
||||
F32(3.14159).
|
||||
F64(2.718281828459).
|
||||
Int7(10).
|
||||
Raw7(25).
|
||||
UxString("Hello Ananta Go Protocol")
|
||||
|
||||
data := w.Bytes()
|
||||
r := NewUxReader(data)
|
||||
|
||||
u8, _ := r.U8()
|
||||
if u8 != 42 {
|
||||
t.Errorf("U8 mismatch: %v", u8)
|
||||
}
|
||||
|
||||
b, _ := r.Bool()
|
||||
if !b {
|
||||
t.Errorf("Bool mismatch: %v", b)
|
||||
}
|
||||
|
||||
i16, _ := r.I16()
|
||||
if i16 != -1234 {
|
||||
t.Errorf("I16 mismatch: %v", i16)
|
||||
}
|
||||
|
||||
u16, _ := r.U16()
|
||||
if u16 != 5678 {
|
||||
t.Errorf("U16 mismatch: %v", u16)
|
||||
}
|
||||
|
||||
i32, _ := r.I32()
|
||||
if i32 != -999999 {
|
||||
t.Errorf("I32 mismatch: %v", i32)
|
||||
}
|
||||
|
||||
u32, _ := r.U32()
|
||||
if u32 != 12345678 {
|
||||
t.Errorf("U32 mismatch: %v", u32)
|
||||
}
|
||||
|
||||
i64, _ := r.I64()
|
||||
if i64 != -9876543210 {
|
||||
t.Errorf("I64 mismatch: %v", i64)
|
||||
}
|
||||
|
||||
u64, _ := r.U64()
|
||||
if u64 != 18446744073709551615 {
|
||||
t.Errorf("U64 mismatch: %v", u64)
|
||||
}
|
||||
|
||||
f32, _ := r.F32()
|
||||
if f32 < 3.14 || f32 > 3.15 {
|
||||
t.Errorf("F32 mismatch: %v", f32)
|
||||
}
|
||||
|
||||
f64, _ := r.F64()
|
||||
if f64 < 2.71 || f64 > 2.72 {
|
||||
t.Errorf("F64 mismatch: %v", f64)
|
||||
}
|
||||
|
||||
int7, _ := r.Int7()
|
||||
if int7 != 10 {
|
||||
t.Errorf("Int7 mismatch: got %v, want 10", int7)
|
||||
}
|
||||
|
||||
raw7, _ := r.Raw7()
|
||||
if raw7 != 25 {
|
||||
t.Errorf("Raw7 mismatch: got %v, want 25", raw7)
|
||||
}
|
||||
|
||||
str, _ := r.UxString()
|
||||
if str != "Hello Ananta Go Protocol" {
|
||||
t.Errorf("UxString mismatch: got %v", str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRpcPacketRoundtrip(t *testing.T) {
|
||||
notify := BuildNotify(MethodCheckVersion, []byte{0x01, 0x02, 0x03})
|
||||
p := ParseRpcPacket(notify)
|
||||
if !p.IsNotify() || p.MethodID != MethodCheckVersion {
|
||||
t.Errorf("Notify packet mismatch: %+v", p)
|
||||
}
|
||||
if !bytes.Equal(p.Body, []byte{0x01, 0x02, 0x03}) {
|
||||
t.Errorf("Body mismatch: %v", p.Body)
|
||||
}
|
||||
|
||||
ret := BuildReturn(MethodAskEnterWorld, 100, 0, []byte("OK"))
|
||||
p2 := ParseRpcPacket(ret)
|
||||
if !p2.IsReturn() || p2.MethodID != MethodAskEnterWorld || p2.InvokeID != 100 || p2.Error != 0 {
|
||||
t.Errorf("Return packet mismatch: %+v", p2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
)
|
||||
|
||||
type UxReader struct {
|
||||
data []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
func NewUxReader(data []byte) *UxReader {
|
||||
return &UxReader{data: data, pos: 0}
|
||||
}
|
||||
|
||||
func (r *UxReader) Position() int { return r.pos }
|
||||
func (r *UxReader) Remaining() int { return len(r.data) - r.pos }
|
||||
|
||||
func (r *UxReader) U8() (byte, error) {
|
||||
if r.pos >= len(r.data) {
|
||||
return 0, errors.New("EOF")
|
||||
}
|
||||
b := r.data[r.pos]
|
||||
r.pos++
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) Bool() (bool, error) {
|
||||
b, err := r.U8()
|
||||
return b != 0, err
|
||||
}
|
||||
|
||||
func (r *UxReader) I16() (int16, error) {
|
||||
if r.Remaining() < 2 {
|
||||
return 0, errors.New("EOF")
|
||||
}
|
||||
v := int16(binary.LittleEndian.Uint16(r.data[r.pos : r.pos+2]))
|
||||
r.pos += 2
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) U16() (uint16, error) {
|
||||
if r.Remaining() < 2 {
|
||||
return 0, errors.New("EOF")
|
||||
}
|
||||
v := binary.LittleEndian.Uint16(r.data[r.pos : r.pos+2])
|
||||
r.pos += 2
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) I32() (int32, error) {
|
||||
if r.Remaining() < 4 {
|
||||
return 0, errors.New("EOF")
|
||||
}
|
||||
v := int32(binary.LittleEndian.Uint32(r.data[r.pos : r.pos+4]))
|
||||
r.pos += 4
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) U32() (uint32, error) {
|
||||
if r.Remaining() < 4 {
|
||||
return 0, errors.New("EOF")
|
||||
}
|
||||
v := binary.LittleEndian.Uint32(r.data[r.pos : r.pos+4])
|
||||
r.pos += 4
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) I64() (int64, error) {
|
||||
if r.Remaining() < 8 {
|
||||
return 0, errors.New("EOF")
|
||||
}
|
||||
v := int64(binary.LittleEndian.Uint64(r.data[r.pos : r.pos+8]))
|
||||
r.pos += 8
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) U64() (ulong uint64, err error) {
|
||||
if r.Remaining() < 8 {
|
||||
return 0, errors.New("EOF")
|
||||
}
|
||||
v := binary.LittleEndian.Uint64(r.data[r.pos : r.pos+8])
|
||||
r.pos += 8
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) F32() (float32, error) {
|
||||
bits, err := r.U32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return math.Float32frombits(bits), nil
|
||||
}
|
||||
|
||||
func (r *UxReader) F64() (float64, error) {
|
||||
bits, err := r.U64()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return math.Float64frombits(bits), nil
|
||||
}
|
||||
|
||||
func (r *UxReader) ReadVarInt() (int, error) {
|
||||
shift := 0
|
||||
result := 0
|
||||
for r.pos < len(r.data) {
|
||||
b := r.data[r.pos]
|
||||
r.pos++
|
||||
result |= int(b&0x7F) << shift
|
||||
if (b & 0x80) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
shift += 7
|
||||
}
|
||||
return result, errors.New("unterminated varint")
|
||||
}
|
||||
|
||||
func (r *UxReader) Int7() (int, error) {
|
||||
v, err := r.ReadVarInt()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v < 1 {
|
||||
return 0, nil
|
||||
}
|
||||
return v - 1, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) Raw7() (int, error) {
|
||||
return r.ReadVarInt()
|
||||
}
|
||||
|
||||
func (r *UxReader) UxString() (string, error) {
|
||||
lenPlus, err := r.ReadVarInt()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if lenPlus == 0 {
|
||||
return "", nil
|
||||
}
|
||||
length := lenPlus - 1
|
||||
if length > r.Remaining() {
|
||||
return "", errors.New("string length exceeds buffer")
|
||||
}
|
||||
str := string(r.data[r.pos : r.pos+length])
|
||||
r.pos += length
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (r *UxReader) Bytes(count int) ([]byte, error) {
|
||||
if count < 0 || count > r.Remaining() {
|
||||
return nil, errors.New("requested count exceeds buffer")
|
||||
}
|
||||
res := make([]byte, count)
|
||||
copy(res, r.data[r.pos:r.pos+count])
|
||||
r.pos += count
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
)
|
||||
|
||||
type UxWriter struct {
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func NewUxWriter() *UxWriter {
|
||||
return &UxWriter{}
|
||||
}
|
||||
|
||||
func (w *UxWriter) U8(val byte) *UxWriter {
|
||||
w.buf.WriteByte(val)
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) Bool(val bool) *UxWriter {
|
||||
if val {
|
||||
w.buf.WriteByte(1)
|
||||
} else {
|
||||
w.buf.WriteByte(0)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) I16(val int16) *UxWriter {
|
||||
var b [2]byte
|
||||
binary.LittleEndian.PutUint16(b[:], uint16(val))
|
||||
w.buf.Write(b[:])
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) U16(val uint16) *UxWriter {
|
||||
var b [2]byte
|
||||
binary.LittleEndian.PutUint16(b[:], val)
|
||||
w.buf.Write(b[:])
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) I32(val int32) *UxWriter {
|
||||
var b [4]byte
|
||||
binary.LittleEndian.PutUint32(b[:], uint32(val))
|
||||
w.buf.Write(b[:])
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) U32(val uint32) *UxWriter {
|
||||
var b [4]byte
|
||||
binary.LittleEndian.PutUint32(b[:], val)
|
||||
w.buf.Write(b[:])
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) I64(val int64) *UxWriter {
|
||||
var b [8]byte
|
||||
binary.LittleEndian.PutUint64(b[:], uint64(val))
|
||||
w.buf.Write(b[:])
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) U64(val uint64) *UxWriter {
|
||||
var b [8]byte
|
||||
binary.LittleEndian.PutUint64(b[:], val)
|
||||
w.buf.Write(b[:])
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) F32(val float32) *UxWriter {
|
||||
return w.U32(math.Float32bits(val))
|
||||
}
|
||||
|
||||
func (w *UxWriter) F64(val float64) *UxWriter {
|
||||
return w.U64(math.Float64bits(val))
|
||||
}
|
||||
|
||||
func (w *UxWriter) WriteVarInt(val int) *UxWriter {
|
||||
v := uint32(val)
|
||||
for v >= 0x80 {
|
||||
w.buf.WriteByte(byte(v | 0x80))
|
||||
v >>= 7
|
||||
}
|
||||
w.buf.WriteByte(byte(v))
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) Int7(val int) *UxWriter {
|
||||
if val < 0 {
|
||||
val = 0
|
||||
}
|
||||
return w.WriteVarInt(val + 1)
|
||||
}
|
||||
|
||||
func (w *UxWriter) Raw7(val int) *UxWriter {
|
||||
if val < 0 {
|
||||
val = 0
|
||||
}
|
||||
return w.WriteVarInt(val)
|
||||
}
|
||||
|
||||
func (w *UxWriter) UxString(str string) *UxWriter {
|
||||
utf8 := []byte(str)
|
||||
w.WriteVarInt(len(utf8) + 1)
|
||||
w.buf.Write(utf8)
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) UxStringNullable(str *string) *UxWriter {
|
||||
if str == nil {
|
||||
w.buf.WriteByte(0x00)
|
||||
return w
|
||||
}
|
||||
return w.UxString(*str)
|
||||
}
|
||||
|
||||
func (w *UxWriter) Raw(b []byte) *UxWriter {
|
||||
w.buf.Write(b)
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *UxWriter) Bytes() []byte {
|
||||
return w.buf.Bytes()
|
||||
}
|
||||
Reference in new issue
Block a user