Files

198 lines
5.8 KiB
Go

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")
}
}