fix(go): repair migration wire contracts and runtime lifecycle; document incomplete port
This commit is contained in:
1 parent
e43a310617
commit
32e47014b5
35 files changed
+16826
-438
No files matched your search
+47
-22
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
+4546
File diff suppressed because it is too large.
Load diff
+14
-10
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user