fix(go): repair migration wire contracts and runtime lifecycle; document incomplete port

This commit is contained in:
CAtlas0784 committed 2026-10-29 15:35:35 +07:00
1 parent e43a310617
commit 32e47014b5
35 files changed
+16826 -438

No files matched your search

+47 -22
View File
@@ -1,7 +1,6 @@
package protocol
import (
"encoding/binary"
"encoding/json"
"fmt"
"os"
@@ -25,9 +24,9 @@ func EncodeServerTime(clientUnixTime float64) []byte {
// 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
w.I32(0) // patchVersion
w.U8(0xFF) // list marker
w.Int7(0) // empty list
return w.Bytes()
}
@@ -40,7 +39,7 @@ func EncodeEmptyList() []byte {
}
// EncodeCheckAccount encodes CheckAccountResult.
func EncodeCheckAccount(accountId, userName, token string, aid, serverId int, pid uint64) []byte {
func EncodeCheckAccount(accountId, userName, token, displayName string, aid, serverId int, pid uint64) []byte {
loginData := map[string]interface{}{
"code": 200,
"subcode": 0,
@@ -52,7 +51,7 @@ func EncodeCheckAccount(accountId, userName, token string, aid, serverId int, pi
"pid": pid,
"player_id": fmt.Sprintf("%d", pid),
"role_id": fmt.Sprintf("%d", pid),
"role_name": "辰宿",
"role_name": displayName,
"server_id": fmt.Sprintf("%d", serverId),
"host_id": serverId,
"username": userName,
@@ -144,10 +143,10 @@ func EncodeEnterScene(pid uint64, raidId uint32, instanceId uint64, universeId u
// 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)
// 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
@@ -230,7 +229,7 @@ func EncodeRaidBattleUnitSpirit(unitId uint64, templateId uint32, posX, posY, po
w.U8(0).U32(0)
// GroundData (MoveActionGroundData Complex 0xFF)
w.U8(0xFF).U32(0).U64(0).U32(0)
w.U8(0xFF).U8(0).U64(0).U8(0)
// LocalPos (UXVector3)
w.F32(0).F32(0).F32(0)
@@ -244,17 +243,43 @@ func EncodePlayerInfo(templatePath string, aid int, pid uint64, accountId, name
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))
// 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")
}
// Patch Pid at offset 8 (8 bytes)
if len(res) > 16 {
binary.LittleEndian.PutUint64(res[8:16], pid)
mark, err = r.U8()
if err != nil || mark != 0xff {
return nil, fmt.Errorf("invalid Config buffer marker")
}
return res, nil
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
}
File diff suppressed because it is too large. Load diff
+12 -3
View File
@@ -24,7 +24,7 @@ func ReadFrame(r io.Reader, cryptor *chacha8.UxChaCha8) (*Frame, error) {
}
payloadLength := int(binary.LittleEndian.Uint32(head[0:4]))
if payloadLength < 0 || payloadLength > 16*1024*1024 {
if payloadLength < 0 || payloadLength > 8*1024*1024 {
return nil, errors.New("invalid frame payload length after decrypt")
}
@@ -54,8 +54,17 @@ func WriteFrame(w io.Writer, mode byte, payload []byte, cryptor *chacha8.UxChaCh
cryptor.XorInPlace(buf)
}
_, err := w.Write(buf)
return err
for len(buf) > 0 {
n, err := w.Write(buf)
if err != nil {
return err
}
if n <= 0 || n > len(buf) {
return io.ErrShortWrite
}
buf = buf[n:]
}
return nil
}
func ParseRpcPacket(payload []byte) RpcPacket {
+3
View File
@@ -20,6 +20,9 @@ func HandleHandshake(conn net.Conn) (*chacha8.UxChaCha8, *chacha8.UxChaCha8, err
if err != nil {
return nil, nil, err
}
if frame.Mode != 0x01 {
return nil, nil, errors.New("invalid handshake frame mode")
}
if len(frame.Payload) < 20 {
return nil, nil, errors.New("client handshake too short")
}
+80 -95
View File
@@ -21,124 +21,109 @@ const (
MethodDebugRequestEnterGame uint32 = 34478273
MethodAskCloseConnectionToGate uint32 = 52140059
MethodGetServerTime uint32 = 52951195
MethodSendServerTime uint32 = 52296181
MethodGateLogin uint32 = 52023760
MethodSyncPlayerGameServerInfo uint32 = 52737039
MethodSendServerTime uint32 = 53477227
MethodGateLogin uint32 = 52023760 // Legacy name: Login
MethodSyncPlayerGameServerInfo uint32 = 154693736
MethodSyncGameSwitchToClient_3 uint32 = 53940944
MethodQuerySkey uint32 = 52367757
MethodReportNetworkQuality uint32 = 52623348
MethodDavinciCode uint32 = 52458997
MethodGetFriendApplicationListToMe uint32 = 52123985
MethodGetAllChatGroupLatestMessage uint32 = 52345719
MethodGetSimplePlayerInfoByPidList uint32 = 52781934
MethodQuerySkey uint32 = 153090217
MethodReportNetworkQuality uint32 = 52443839
MethodDavinciCode uint32 = 153804824
MethodGetFriendApplicationListToMe uint32 = 153718831
MethodGetAllChatGroupLatestMessage uint32 = 153934570
MethodGetSimplePlayerInfoByPidList uint32 = 153582555
// 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
MethodLoginGame uint32 = 63142082
MethodAskEnterWorld uint32 = 63142082
MethodSendServerTimeGame uint32 = 64114176
MethodSyncPlayerInfo uint32 = 64682159
MethodSyncEnterScene uint32 = 64406364
MethodAskLoadSceneCompleted uint32 = 67933251
MethodAskLoadingFinished uint32 = 67722670
MethodSyncLogicAgentEnter uint32 = 68719890
MethodSyncLogicAgentLeave uint32 = 68281244
MethodSyncManagedLogicAgent uint32 = 68771737
MethodSyncRaidBattleUnitSpirit uint32 = 68984102
MethodSyncPlayerCurrentSpirit uint32 = 68830687
MethodSyncSceneLoadCompleted uint32 = 68772583
MethodSyncGamePause uint32 = 68031250
MethodSyncPlayerLoadRate uint32 = 68547190
MethodAskLoadedInSameScene uint32 = 67741470
MethodSyncUnitPositionAndFacing uint32 = 68030312
MethodSyncSceneFogMapAllUnlock uint32 = 64915741
MethodSyncRunGmUtils uint32 = 66042045
MethodSyncAllSpiritCombatPower uint32 = 64826316
MethodSyncSwitchSpiritInSitu uint32 = 68412586
// Movement
MethodAskMoveStart uint32 = 68412852
MethodAskMoveStop uint32 = 68412853
MethodAskUpdatePlayerPosition uint32 = 68058291
// Character Switching
MethodAskPreSwitchSpirit uint32 = 68415783
MethodAskSwitchSpirit uint32 = 68415784
MethodAskSwitchSpirit uint32 = 67482531
// Teleportation & Scene
MethodAskTeleport uint32 = 67891230
MethodReportPreTeleportFinish uint32 = 67891231
MethodReportPostTeleportFinish uint32 = 67891232
MethodAskEnterRaidByMapEntrance uint32 = 67891233
MethodAskTeleportToScene uint32 = 67891234
MethodSwitchSceneDirect uint32 = 67891235
MethodAskTeleport uint32 = 63175104
MethodReportPreTeleportFinish uint32 = 63032762
MethodReportPostTeleportFinish uint32 = 63682240
MethodAskEnterRaidByMapEntrance uint32 = 63510026
// Vehicles
MethodAskSummonVehicle uint32 = 68221895
MethodAskDriveVehicle uint32 = 68221893
MethodAskExitVehicle uint32 = 68221894
MethodAskVehicleMove uint32 = 68221896
MethodChangeVehicleDoorState uint32 = 68221897
MethodAskVehicleSendGamePlaySignal uint32 = 68221898
MethodSyncPlayerVehicleInfo uint32 = 68221926
MethodSyncAetherVehicleInit uint32 = 68221958
MethodAskSummonVehicle uint32 = 67012447
MethodAskVehicleMove uint32 = 67743393
MethodChangeVehicleDoorState uint32 = 67018217
MethodAskVehicleSendGamePlaySignal uint32 = 63816400
// Police & Wanted
MethodAskSetWantedLevel uint32 = 67600001
MethodAskPoliceArrest uint32 = 67600002
MethodReportPoliceArrestFinish uint32 = 67600003
MethodAskPayFine uint32 = 67600004
MethodAskReleaseFromPrison uint32 = 67600005
// Dialog & NPC & Crowd
MethodAskStartDialog uint32 = 67400001
MethodAskDoDialogAction uint32 = 67400002
MethodAskForceFinishDialog uint32 = 67400003
MethodGetCharacterRandomDialog uint32 = 67400004
MethodAskAgentStartNpcSound uint32 = 67400005
MethodAskDoNpcAction uint32 = 67400006
MethodAskSetCrowdDensity uint32 = 67400007
MethodAskPhoneAddCallRecord uint32 = 67400010
MethodAskPhoneContactOptionAction uint32 = 67400011
MethodAskSummonGangMember uint32 = 67400020
MethodAskDestroyAllGangMember uint32 = 67400021
MethodAskStartDialog uint32 = 63754798
MethodAskDoDialogAction uint32 = 63723634
MethodAskForceFinishDialog uint32 = 63010951
MethodGetCharacterRandomDialog uint32 = 63175540
MethodAskAgentStartNpcSound uint32 = 67003057
MethodAskDoNpcAction uint32 = 67257034
MethodAskPhoneAddCallRecord uint32 = 63487875
MethodAskPhoneContactOptionAction uint32 = 63918677
MethodAskSummonGangMember uint32 = 63745420
MethodAskDestroyAllGangMember uint32 = 63243016
// Combat & Skills
MethodAskPlayerAttack uint32 = 68412884
MethodAskPlayerCastSkill uint32 = 68412916
MethodAskUnlockFightStyle uint32 = 68412948
MethodSyncChangeSpirit uint32 = 68582802
MethodGmWorldRefreshEnemyGroup uint32 = 67500001
MethodReportUnitHitFly uint32 = 67500002
MethodReportUnitHitFlyEnd uint32 = 67500003
MethodReportUnitFallGround uint32 = 67500004
MethodReportUnitFallGroundEnd uint32 = 67500005
MethodAskControlPowerHoldEnemyUp uint32 = 67500006
MethodAskControlPowerHoldEnemyFallGrou uint32 = 67500007
MethodGmWorldRefreshEnemyGroup uint32 = 69676618
MethodReportUnitHitFly uint32 = 67430580
MethodReportUnitHitFlyEnd uint32 = 67015647
MethodReportUnitFallGround uint32 = 67719880
MethodReportUnitFallGroundEnd uint32 = 67516089
MethodAskControlPowerHoldEnemyUp uint32 = 67802701
MethodAskControlPowerHoldEnemyFallGrou uint32 = 67043294
// Mini-games
MethodAskChineseChessMove uint32 = 67700001
MethodAskMahjongAction uint32 = 67700002
MethodEnterBalloonZone uint32 = 67700003
MethodLeaveBalloon uint32 = 67700004
MethodAskBreakDestructibleObject uint32 = 67700005
MethodEnterBowlingZone uint32 = 67700006
MethodAskClawBuyTicket uint32 = 67700007
MethodAskClawSettlement uint32 = 67700008
MethodAskBuyMiniGameTicket uint32 = 67700009
MethodEnterBalloonZone uint32 = 67417935
MethodLeaveBalloon uint32 = 67075062
MethodAskBreakDestructibleObject uint32 = 67463592
MethodEnterBowlingZone uint32 = 67832701
MethodAskClawBuyTicket uint32 = 63690710
MethodAskClawSettlement uint32 = 63451209
MethodAskBuyMiniGameTicket uint32 = 63606259
// Racing
MethodAskAcceptAndSetCurrentTask uint32 = 63961538
MethodAskSetRacingCompetitionVehicle uint32 = 63764162
MethodAskSetTaskCounterValue uint32 = 63753212
MethodAskAcceptAndSetCurrentTask uint32 = 63961538
MethodAskSetRacingCompetitionVehicle uint32 = 63764162
MethodAskSetTaskCounterValue uint32 = 63753212
// Fashion & Wardrobe
MethodAskSetSpiritFashionsWithSource uint32 = 67800001
MethodAskApplyFashionColoringSchemeInfos uint32 = 67800002
MethodAskSetWeaponSkins uint32 = 67800003
MethodAskSyncWeaponSkinToSpirits uint32 = 67800004
MethodAskSetSpiritFashionsWithSource uint32 = 63167148
MethodAskApplyFashionColoringSchemeInfos uint32 = 63045354
MethodAskSetWeaponSkins uint32 = 63900850
MethodAskSyncWeaponSkinToSpirits uint32 = 63623831
// Weather & Fog
MethodGmUnlockAllFogMap uint32 = 67900001
MethodGmSetAmbianceEnabled uint32 = 67900002
MethodGmUnlockAllFogMap uint32 = 65914419
MethodGmSetAmbianceEnabled uint32 = 69166506
)
const (
MethodRequestPlayerStartHangup uint32 = 63842476
MethodRequestPlayerStopHangup uint32 = 63910160
MethodAskReportLogicAgentSyncData uint32 = 67543012
)
const MethodAskUnitMoveActionSimple uint32 = 67158757
File diff suppressed because it is too large. Load diff
+10 -2
View File
@@ -27,6 +27,13 @@ var (
isRegistryLoaded bool
)
func init() {
for name, id := range canonicalMethodIDs {
nameToIdMap[name] = id
idToNameMap[id] = name
}
}
// LoadMethodCatalog loads the method catalog from a JSON file (e.g. data/client_data/MethodIds.json).
func LoadMethodCatalog(path string) error {
data, err := os.ReadFile(path)
@@ -72,10 +79,11 @@ func GetMethodID(name string) (uint32, bool) {
return id, ok
}
// MethodGetMethodIDDefault returns the numeric ID for a method name or a fallback.
// MethodGetMethodIDDefault resolves a verified name. Legacy callers still pass
// a fallback, but guessed IDs must never be sent to an unrelated client handler.
func MethodGetMethodIDDefault(name string, fallback uint32) uint32 {
if id, ok := GetMethodID(name); ok {
return id
}
return fallback
return 0
}
+197
View File
@@ -0,0 +1,197 @@
package protocol
import (
"bytes"
"encoding/binary"
"encoding/hex"
"encoding/json"
"go/ast"
"go/parser"
"go/token"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
)
func TestActiveMethodIDsMatchLegacyContracts(t *testing.T) {
data, err := os.ReadFile("testdata/legacy_method_ids.json")
if err != nil {
t.Fatal(err)
}
var expected map[string]uint32
if err := json.Unmarshal(data, &expected); err != nil {
t.Fatal(err)
}
aliases := map[string]string{"GateLogin": "Login", "AskEnterWorld": "LoginGame", "SyncSwitchSpiritInSitu": "SyncPlayerSwitchSpiritInSitu"}
f, err := parser.ParseFile(token.NewFileSet(), "method_id.go", nil, 0)
if err != nil {
t.Fatal(err)
}
ast.Inspect(f, func(n ast.Node) bool {
v, ok := n.(*ast.ValueSpec)
if !ok {
return true
}
for i, id := range v.Names {
name := strings.TrimPrefix(id.Name, "Method")
if alias, ok := aliases[name]; ok {
name = alias
}
lit, ok := v.Values[i].(*ast.BasicLit)
if !ok {
t.Fatalf("nonliteral method %s", name)
}
value, err := strconv.ParseUint(lit.Value, 10, 32)
want, exists := expected[name]
if err != nil || !exists || uint32(value) != want {
t.Errorf("%s = %d, legacy = %d (exists=%v)", name, value, want, exists)
}
}
return true
})
}
func TestDefaultReturnContracts(t *testing.T) {
if len(defaultReturnBodies) != 2356 {
t.Fatalf("missing return contracts: %d", len(defaultReturnBodies))
}
// Independent examples from the original Lua-reader catalog, not round trips.
for _, tc := range []struct {
id uint32
hex string
}{
{34700853, ""}, {34022227, "ff01"}, {13224129, "00000000"},
{12279476, "01"}, {34383517, "0000000000000000"}, {34601301, "00000000ff01"},
} {
body, ok := DefaultReturnBody(tc.id)
if !ok || hex.EncodeToString(body) != tc.hex {
t.Errorf("%d: %x, exists=%v", tc.id, body, ok)
}
}
if _, ok := DefaultReturnBody(0xffffffff); ok {
t.Fatal("unknown contract accepted")
}
a, _ := DefaultReturnBody(34022227)
a[0] = 0
b, _ := DefaultReturnBody(34022227)
if b[0] != 0xff {
t.Fatal("shared body mutated")
}
}
func TestUnknownMethodCannotUseGuessedID(t *testing.T) {
if id := MethodGetMethodIDDefault("InventedMethod", 68030312); id != 0 {
t.Fatalf("guessed id accepted: %d", id)
}
if id := MethodGetMethodIDDefault("SyncEnterScene", 1); id != 64406364 {
t.Fatalf("wrong canonical id: %d", id)
}
}
func TestEnterSceneWireLayout(t *testing.T) {
b := EncodeEnterScene(100, 23300888, 20001222, 76000888, 9, 15020967, 1, 2, 3, 4)
// Offsets derived from C# EnterSceneInfo, SpiritInitData and ServerSimpleGridInfo.
// LoadingTypeInfo uses TypeMark=1, byte Type and an Int32 member count.
if len(b) != 146 {
t.Fatalf("EnterSceneInfo size = %d, want 146", len(b))
}
if got := hex.EncodeToString(b[103:111]); got != "0100ff0000000000" {
t.Fatalf("LoadingTypeInfo: %s", got)
}
// LinkSimpleInfo: mark, ulong id, three bytes, uint event, uint time, ulong host.
if got := hex.EncodeToString(b[111:139]); got != "01000000000000000000010000000000000000000000000000000000" {
t.Fatalf("LinkSimpleInfo: %s", got)
}
if got := hex.EncodeToString(b[139:]); got != "ff01000000ff01" {
t.Fatalf("scene tail: %s", got)
}
}
func TestPlayerInfoPatchesVariableLengthLogin(t *testing.T) {
tail := []byte{10, 0, 0, 0, 1, 0xff, 0, 0, 0, 0, 0xff}
// Nonempty config buffer exercises dynamic offsets rather than fixed offsets 4/8.
original := NewUxWriter().U8(0xff).U8(0xff).Int7(3).Raw([]byte{7, 8, 9}).U8(0xff).
I32(1).U64(2).UxString("old").UxString("old name").Raw(tail).Bytes()
path := filepath.Join(t.TempDir(), "player.bin")
if err := os.WriteFile(path, original, 0600); err != nil {
t.Fatal(err)
}
b, err := EncodePlayerInfo(path, 5944, 100, "longer-account", "辰宿")
if err != nil {
t.Fatal(err)
}
r := NewUxReader(b)
_, _ = r.Bytes(7)
aid, _ := r.I32()
pid, _ := r.U64()
account, _ := r.UxString()
name, _ := r.UxString()
if aid != 5944 || pid != 100 || account != "longer-account" || name != "辰宿" {
t.Fatalf("login fields: %d %d %s %s", aid, pid, account, name)
}
if !bytes.Equal(b[r.Position():], tail) {
t.Fatal("unrelated player data changed")
}
for _, bad := range [][]byte{nil, {0}, {0xff, 0xff, 1, 0xff, 0}} {
if err := os.WriteFile(path, bad, 0600); err != nil {
t.Fatal(err)
}
if _, err := EncodePlayerInfo(path, 1, 2, "", ""); err == nil {
t.Fatalf("accepted malformed template: %x", bad)
}
}
}
type shortWriter struct {
bytes.Buffer
limit int
}
func (w *shortWriter) Write(b []byte) (int, error) {
if len(b) > w.limit {
b = b[:w.limit]
}
return w.Buffer.Write(b)
}
type zeroWriter struct{}
func (zeroWriter) Write([]byte) (int, error) { return 0, nil }
func TestWriteFrameHandlesShortWrites(t *testing.T) {
w := &shortWriter{limit: 2}
if err := WriteFrame(w, 9, []byte{1, 2, 3}, nil); err != nil {
t.Fatal(err)
}
if hex.EncodeToString(w.Bytes()) != "0300000009010203" {
t.Fatalf("truncated frame: %x", w.Bytes())
}
if err := WriteFrame(zeroWriter{}, 9, nil, nil); err != io.ErrShortWrite {
t.Fatalf("zero writer: %v", err)
}
}
func TestVarIntRejectsOverflow(t *testing.T) {
for _, b := range [][]byte{{0x80}, {0xff, 0xff, 0xff, 0xff, 0x08}, {0x80, 0x80, 0x80, 0x80, 0x80, 0}} {
if _, err := NewUxReader(b).UxString(); err == nil {
t.Fatalf("overflow accepted: %x", b)
}
}
if n, err := NewUxReader([]byte{0xff, 0xff, 0xff, 0xff, 0x07}).Raw7(); err != nil || n != 2147483647 {
t.Fatalf("max Int32: %d, %v", n, err)
}
}
func TestGroundDataUsesByteFields(t *testing.T) {
b := EncodeRaidBattleUnitSpirit(9, 15020967, 1, 2, 3, 4, 100)
// GroundData is the last field: marker + byte + ulong + byte + Vector3.
if len(b) < 23 || b[len(b)-23] != 0xff {
t.Fatalf("invalid GroundData tail: %x", b)
}
if binary.LittleEndian.Uint64(b[len(b)-21:]) != 0 {
t.Fatal("nonzero ground id")
}
}
File diff suppressed because it is too large. Load diff
+14 -10
View File
@@ -103,18 +103,22 @@ func (r *UxReader) F64() (float64, error) {
}
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
// UX lengths/counts are nonnegative Int32 values, at most five bytes.
var result uint32
for shift := uint(0); shift < 35; shift += 7 {
b, err := r.U8()
if err != nil {
return 0, errors.New("unterminated varint")
}
if shift == 28 && b > 7 {
return 0, errors.New("varint exceeds Int32")
}
result |= uint32(b&0x7f) << shift
if b&0x80 == 0 {
return int(result), nil
}
shift += 7
}
return result, errors.New("unterminated varint")
return 0, errors.New("varint exceeds Int32")
}
func (r *UxReader) Int7() (int, error) {