Files
Erupe/network/mhfpacket/msg_parse_large_test.go
Houmgaor 5f3c843082 refactor(config): eliminate ErupeConfig global variable
Replace the mutable global `_config.ErupeConfig` with dependency
injection across 79 files. Config is now threaded through existing
paths: `ClientContext.RealClientMode` for packet encoding, `s.server.
erupeConfig` for channel handlers, and explicit parameters for utility
functions. This removes hidden coupling, enables test parallelism
without global save/restore, and prevents low-level packages from
reaching up to the config layer.

Key changes:
- Enrich ClientContext with RealClientMode for packet files
- Add mode parameter to CryptConn, mhfitem, mhfcourse functions
- Convert handlers_commands init() to lazy sync.Once initialization
- Delete global var, init(), and helper functions from config.go
- Update all tests to pass config explicitly
2026-02-20 17:07:42 +01:00

882 lines
27 KiB
Go

package mhfpacket
import (
"bytes"
"io"
"testing"
"erupe-ce/common/byteframe"
_config "erupe-ce/config"
"erupe-ce/network/clientctx"
)
// TestParseLargeMsgSysUpdateRightBuild tests Build for MsgSysUpdateRight (no Parse implementation).
func TestParseLargeMsgSysUpdateRightBuild(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: _config.ZZ}
original := &MsgSysUpdateRight{
ClientRespAckHandle: 0x12345678,
Bitfield: 0xDEADBEEF,
Rights: nil,
TokenLength: 0,
}
bf := byteframe.NewByteFrame()
if err := original.Build(bf, ctx); err != nil {
t.Fatalf("Build() error = %v", err)
}
// Verify binary output manually:
// uint32 ClientRespAckHandle + uint32 Bitfield + uint16 Rights count(0) + uint16 padding(0) + ps.Uint16 empty string(uint16(1) + 0x00)
data := bf.Data()
if len(data) < 12 {
t.Fatalf("Build() wrote %d bytes, want at least 12", len(data))
}
_, _ = bf.Seek(0, io.SeekStart)
if bf.ReadUint32() != 0x12345678 {
t.Error("ClientRespAckHandle mismatch")
}
if bf.ReadUint32() != 0xDEADBEEF {
t.Error("Bitfield mismatch")
}
if bf.ReadUint16() != 0 {
t.Error("Rights count should be 0")
}
}
// TestParseLargeMsgMhfOperateWarehouse tests Parse for MsgMhfOperateWarehouse.
func TestParseLargeMsgMhfOperateWarehouse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint8(1) // Operation
bf.WriteUint8(0) // BoxType = item
bf.WriteUint8(2) // BoxIndex
bf.WriteUint8(8) // lenName (unused but read)
bf.WriteUint16(0) // Unk
bf.WriteBytes([]byte("TestBox"))
bf.WriteUint8(0) // null terminator
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateWarehouse{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.Operation != 1 {
t.Errorf("Operation = %d, want 1", pkt.Operation)
}
if pkt.BoxType != 0 {
t.Errorf("BoxType = %d, want 0", pkt.BoxType)
}
if pkt.BoxIndex != 2 {
t.Errorf("BoxIndex = %d, want 2", pkt.BoxIndex)
}
if pkt.Name != "TestBox" {
t.Errorf("Name = %q, want %q", pkt.Name, "TestBox")
}
}
// TestParseLargeMsgMhfOperateWarehouseEquip tests Parse for MsgMhfOperateWarehouse with equip box type.
func TestParseLargeMsgMhfOperateWarehouseEquip(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(42) // AckHandle
bf.WriteUint8(2) // Operation
bf.WriteUint8(1) // BoxType = equip
bf.WriteUint8(0) // BoxIndex
bf.WriteUint8(5) // lenName
bf.WriteUint16(0) // Unk
bf.WriteBytes([]byte("Arms"))
bf.WriteUint8(0) // null terminator
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateWarehouse{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.BoxType != 1 {
t.Errorf("BoxType = %d, want 1", pkt.BoxType)
}
if pkt.Name != "Arms" {
t.Errorf("Name = %q, want %q", pkt.Name, "Arms")
}
}
// TestParseLargeMsgMhfLoadHouse tests Parse for MsgMhfLoadHouse.
func TestParseLargeMsgMhfLoadHouse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
charID uint32
destination uint8
checkPass bool
password string
}{
{"with password", 0xAABBCCDD, 12345, 1, true, "pass123"},
{"no password", 0x11111111, 0, 0, false, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint32(tt.charID)
bf.WriteUint8(tt.destination)
bf.WriteBool(tt.checkPass)
bf.WriteUint16(0) // Unk (hardcoded 0)
bf.WriteUint8(uint8(len(tt.password) + 1)) // Password length
bf.WriteBytes([]byte(tt.password))
bf.WriteUint8(0) // null terminator
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfLoadHouse{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ackHandle)
}
if pkt.CharID != tt.charID {
t.Errorf("CharID = %d, want %d", pkt.CharID, tt.charID)
}
if pkt.Destination != tt.destination {
t.Errorf("Destination = %d, want %d", pkt.Destination, tt.destination)
}
if pkt.CheckPass != tt.checkPass {
t.Errorf("CheckPass = %v, want %v", pkt.CheckPass, tt.checkPass)
}
if pkt.Password != tt.password {
t.Errorf("Password = %q, want %q", pkt.Password, tt.password)
}
})
}
}
// TestParseLargeMsgMhfSendMail tests Parse for MsgMhfSendMail.
func TestParseLargeMsgMhfSendMail(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(99999) // RecipientID
bf.WriteUint16(6) // SubjectLength
bf.WriteUint16(12) // BodyLength
bf.WriteUint32(5) // Quantity
bf.WriteUint16(1001) // ItemID
bf.WriteBytes([]byte("Hello"))
bf.WriteUint8(0) // null terminator for Subject
bf.WriteBytes([]byte("Hello World"))
bf.WriteUint8(0) // null terminator for Body
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSendMail{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.RecipientID != 99999 {
t.Errorf("RecipientID = %d, want 99999", pkt.RecipientID)
}
if pkt.SubjectLength != 6 {
t.Errorf("SubjectLength = %d, want 6", pkt.SubjectLength)
}
if pkt.BodyLength != 12 {
t.Errorf("BodyLength = %d, want 12", pkt.BodyLength)
}
if pkt.Quantity != 5 {
t.Errorf("Quantity = %d, want 5", pkt.Quantity)
}
if pkt.ItemID != 1001 {
t.Errorf("ItemID = %d, want 1001", pkt.ItemID)
}
if pkt.Subject != "Hello" {
t.Errorf("Subject = %q, want %q", pkt.Subject, "Hello")
}
if pkt.Body != "Hello World" {
t.Errorf("Body = %q, want %q", pkt.Body, "Hello World")
}
}
// TestParseLargeMsgMhfApplyBbsArticle tests Parse for MsgMhfApplyBbsArticle.
func TestParseLargeMsgMhfApplyBbsArticle(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xCAFEBABE) // AckHandle
bf.WriteUint32(42) // Unk0
// Unk1: 16 bytes
unk1 := make([]byte, 16)
for i := range unk1 {
unk1[i] = byte(i + 1)
}
bf.WriteBytes(unk1)
// Name: 32 bytes (padded with nulls) - uses bfutil.UpToNull
nameBytes := make([]byte, 32)
copy(nameBytes, "Hunter")
bf.WriteBytes(nameBytes)
// Title: 128 bytes (padded with nulls)
titleBytes := make([]byte, 128)
copy(titleBytes, "My Post Title")
bf.WriteBytes(titleBytes)
// Description: 256 bytes (padded with nulls)
descBytes := make([]byte, 256)
copy(descBytes, "This is a description")
bf.WriteBytes(descBytes)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfApplyBbsArticle{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xCAFEBABE {
t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
}
if pkt.Unk0 != 42 {
t.Errorf("Unk0 = %d, want 42", pkt.Unk0)
}
if !bytes.Equal(pkt.Unk1, unk1) {
t.Error("Unk1 mismatch")
}
if pkt.Name != "Hunter" {
t.Errorf("Name = %q, want %q", pkt.Name, "Hunter")
}
if pkt.Title != "My Post Title" {
t.Errorf("Title = %q, want %q", pkt.Title, "My Post Title")
}
if pkt.Description != "This is a description" {
t.Errorf("Description = %q, want %q", pkt.Description, "This is a description")
}
}
// TestParseLargeMsgMhfChargeFesta tests Parse for MsgMhfChargeFesta.
func TestParseLargeMsgMhfChargeFesta(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x11223344) // AckHandle
bf.WriteUint32(100) // FestaID
bf.WriteUint32(200) // GuildID
bf.WriteUint16(3) // soul count
bf.WriteUint16(10) // soul value 1
bf.WriteUint16(20) // soul value 2
bf.WriteUint16(30) // soul value 3
bf.WriteUint8(0) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfChargeFesta{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x11223344 {
t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
}
if pkt.FestaID != 100 {
t.Errorf("FestaID = %d, want 100", pkt.FestaID)
}
if pkt.GuildID != 200 {
t.Errorf("GuildID = %d, want 200", pkt.GuildID)
}
if len(pkt.Souls) != 3 {
t.Fatalf("Souls len = %d, want 3", len(pkt.Souls))
}
expectedSouls := []uint16{10, 20, 30}
for i, v := range expectedSouls {
if pkt.Souls[i] != v {
t.Errorf("Souls[%d] = %d, want %d", i, pkt.Souls[i], v)
}
}
}
// TestParseLargeMsgMhfChargeFestaZeroSouls tests Parse for MsgMhfChargeFesta with zero soul entries.
func TestParseLargeMsgMhfChargeFestaZeroSouls(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(0) // FestaID
bf.WriteUint32(0) // GuildID
bf.WriteUint16(0) // soul count = 0
bf.WriteUint8(0) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfChargeFesta{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if len(pkt.Souls) != 0 {
t.Errorf("Souls len = %d, want 0", len(pkt.Souls))
}
}
// TestParseLargeMsgMhfOperateJoint tests Parse for MsgMhfOperateJoint.
// Parse reads: uint32 AckHandle, uint32 AllianceID, uint32 GuildID, uint8 Action,
// uint8 dataLen, 4 bytes Data1, dataLen bytes Data2.
func TestParseLargeMsgMhfOperateJoint(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(100) // AllianceID
bf.WriteUint32(200) // GuildID
bf.WriteUint8(0x01) // Action = OPERATE_JOINT_DISBAND
bf.WriteUint8(3) // dataLen = 3
bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC, 0xDD}) // Data1 (always 4 bytes)
bf.WriteBytes([]byte{0x01, 0x02, 0x03}) // Data2 (dataLen bytes)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateJoint{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.AllianceID != 100 {
t.Errorf("AllianceID = %d, want 100", pkt.AllianceID)
}
if pkt.GuildID != 200 {
t.Errorf("GuildID = %d, want 200", pkt.GuildID)
}
if pkt.Action != OPERATE_JOINT_DISBAND {
t.Errorf("Action = %d, want %d", pkt.Action, OPERATE_JOINT_DISBAND)
}
if pkt.Data1 == nil {
t.Fatal("Data1 is nil")
}
if pkt.Data2 == nil {
t.Fatal("Data2 is nil")
}
}
// TestParseLargeMsgMhfOperationInvGuild tests Parse for MsgMhfOperationInvGuild.
func TestParseLargeMsgMhfOperationInvGuild(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint8(1) // Operation
bf.WriteUint8(5) // ActiveHours
bf.WriteUint8(7) // DaysActive
bf.WriteUint8(3) // PlayStyle
bf.WriteUint8(2) // GuildRequest
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperationInvGuild{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.Operation != 1 {
t.Errorf("Operation = %d, want 1", pkt.Operation)
}
if pkt.ActiveHours != 5 {
t.Errorf("ActiveHours = %d, want 5", pkt.ActiveHours)
}
if pkt.DaysActive != 7 {
t.Errorf("DaysActive = %d, want 7", pkt.DaysActive)
}
if pkt.PlayStyle != 3 {
t.Errorf("PlayStyle = %d, want 3", pkt.PlayStyle)
}
if pkt.GuildRequest != 2 {
t.Errorf("GuildRequest = %d, want 2", pkt.GuildRequest)
}
}
// TestParseLargeMsgMhfSaveMercenary tests Parse for MsgMhfSaveMercenary.
func TestParseLargeMsgMhfSaveMercenary(t *testing.T) {
mercData := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xCAFEBABE) // AckHandle
bf.WriteUint32(0) // lenData (skipped)
bf.WriteUint32(5000) // GCP
bf.WriteUint32(42) // PactMercID
bf.WriteUint32(uint32(len(mercData))) // dataSize
bf.WriteUint32(0) // Merc index (skipped)
bf.WriteBytes(mercData)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveMercenary{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xCAFEBABE {
t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
}
if pkt.GCP != 5000 {
t.Errorf("GCP = %d, want 5000", pkt.GCP)
}
if pkt.PactMercID != 42 {
t.Errorf("PactMercID = %d, want 42", pkt.PactMercID)
}
if !bytes.Equal(pkt.MercData, mercData) {
t.Errorf("MercData = %v, want %v", pkt.MercData, mercData)
}
}
// TestParseLargeMsgMhfUpdateHouse tests Parse for MsgMhfUpdateHouse.
func TestParseLargeMsgMhfUpdateHouse(t *testing.T) {
tests := []struct {
name string
state uint8
password string
}{
{"with password", 1, "secret"},
{"empty password", 0, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint8(tt.state) // State
bf.WriteUint8(1) // Unk1
bf.WriteUint16(0) // Unk2
bf.WriteUint8(uint8(len(tt.password) + 1)) // Password length
bf.WriteBytes([]byte(tt.password))
bf.WriteUint8(0) // null terminator
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateHouse{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.State != tt.state {
t.Errorf("State = %d, want %d", pkt.State, tt.state)
}
if pkt.HasPassword != 1 {
t.Errorf("HasPassword = %d, want 1", pkt.HasPassword)
}
if pkt.Password != tt.password {
t.Errorf("Password = %q, want %q", pkt.Password, tt.password)
}
})
}
}
// TestParseLargeMsgSysCreateAcquireSemaphore tests Parse for MsgSysCreateAcquireSemaphore.
func TestParseLargeMsgSysCreateAcquireSemaphore(t *testing.T) {
semID := "stage_001"
semBytes := make([]byte, len(semID)+1) // include space for null if needed
copy(semBytes, semID)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xDEADBEEF) // AckHandle
bf.WriteUint16(100) // Unk0
bf.WriteUint8(4) // PlayerCount
bf.WriteUint8(uint8(len(semBytes))) // SemaphoreIDLength
bf.WriteBytes(semBytes)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateAcquireSemaphore{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xDEADBEEF {
t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
}
if pkt.Unk0 != 100 {
t.Errorf("Unk0 = %d, want 100", pkt.Unk0)
}
if pkt.PlayerCount != 4 {
t.Errorf("PlayerCount = %d, want 4", pkt.PlayerCount)
}
if pkt.SemaphoreID != semID {
t.Errorf("SemaphoreID = %q, want %q", pkt.SemaphoreID, semID)
}
}
// TestParseLargeMsgMhfOperateGuild tests Parse for MsgMhfOperateGuild.
func TestParseLargeMsgMhfOperateGuild(t *testing.T) {
dataPayload := []byte{0x10, 0x20, 0x30, 0x40, 0x50}
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint32(999) // GuildID
bf.WriteUint8(0x09) // Action = OperateGuildUpdateComment
bf.WriteUint8(uint8(len(dataPayload))) // dataLen
bf.WriteBytes([]byte{0x01, 0x02, 0x03, 0x04}) // Data1 (always 4 bytes)
bf.WriteBytes(dataPayload) // Data2 (dataLen bytes)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateGuild{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.GuildID != 999 {
t.Errorf("GuildID = %d, want 999", pkt.GuildID)
}
if pkt.Action != OperateGuildUpdateComment {
t.Errorf("Action = %d, want %d", pkt.Action, OperateGuildUpdateComment)
}
if pkt.Data1 == nil {
t.Fatal("Data1 is nil")
}
if pkt.Data2 == nil {
t.Fatal("Data2 is nil")
}
data2Bytes := pkt.Data2.Data()
if !bytes.Equal(data2Bytes, dataPayload) {
t.Errorf("Data2 = %v, want %v", data2Bytes, dataPayload)
}
}
// TestParseLargeMsgMhfReadBeatLevel tests Parse for MsgMhfReadBeatLevel.
func TestParseLargeMsgMhfReadBeatLevel(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(1) // Unk0
bf.WriteUint32(4) // ValidIDCount
// Write 16 uint32 IDs
ids := [16]uint32{0x74, 0x6B, 0x02, 0x24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
for _, id := range ids {
bf.WriteUint32(id)
}
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfReadBeatLevel{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.Unk0 != 1 {
t.Errorf("Unk0 = %d, want 1", pkt.Unk0)
}
if pkt.ValidIDCount != 4 {
t.Errorf("ValidIDCount = %d, want 4", pkt.ValidIDCount)
}
for i, id := range ids {
if pkt.IDs[i] != id {
t.Errorf("IDs[%d] = 0x%X, want 0x%X", i, pkt.IDs[i], id)
}
}
}
// TestParseLargeMsgSysCreateObject tests Parse for MsgSysCreateObject.
func TestParseLargeMsgSysCreateObject(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
x, y, z float32
unk0 uint32
}{
{"origin", 1, 0.0, 0.0, 0.0, 0},
{"typical", 0x12345678, 1.5, 2.5, 3.5, 42},
{"negative coords", 0xFFFFFFFF, -100.25, 200.75, -300.125, 0xDEADBEEF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteFloat32(tt.x)
bf.WriteFloat32(tt.y)
bf.WriteFloat32(tt.z)
bf.WriteUint32(tt.unk0)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateObject{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ackHandle)
}
if pkt.X != tt.x {
t.Errorf("X = %f, want %f", pkt.X, tt.x)
}
if pkt.Y != tt.y {
t.Errorf("Y = %f, want %f", pkt.Y, tt.y)
}
if pkt.Z != tt.z {
t.Errorf("Z = %f, want %f", pkt.Z, tt.z)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
})
}
}
// TestParseLargeMsgSysLockGlobalSema tests Parse for MsgSysLockGlobalSema.
func TestParseLargeMsgSysLockGlobalSema(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xDEADBEEF) // AckHandle
bf.WriteUint16(8) // UserIDLength
bf.WriteUint16(11) // ServerChannelIDLength
bf.WriteBytes([]byte("user123"))
bf.WriteUint8(0) // null terminator
bf.WriteBytes([]byte("channel_01"))
bf.WriteUint8(0) // null terminator
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysLockGlobalSema{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xDEADBEEF {
t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
}
if pkt.UserIDLength != 8 {
t.Errorf("UserIDLength = %d, want 8", pkt.UserIDLength)
}
if pkt.ServerChannelIDLength != 11 {
t.Errorf("ServerChannelIDLength = %d, want 11", pkt.ServerChannelIDLength)
}
if pkt.UserIDString != "user123" {
t.Errorf("UserIDString = %q, want %q", pkt.UserIDString, "user123")
}
if pkt.ServerChannelIDString != "channel_01" {
t.Errorf("ServerChannelIDString = %q, want %q", pkt.ServerChannelIDString, "channel_01")
}
}
// TestParseLargeMsgMhfCreateJoint tests Parse for MsgMhfCreateJoint.
func TestParseLargeMsgMhfCreateJoint(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xCAFEBABE) // AckHandle
bf.WriteUint32(500) // GuildID
bf.WriteUint32(15) // len (unused)
bf.WriteBytes([]byte("Alliance01"))
bf.WriteUint8(0) // null terminator
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfCreateJoint{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xCAFEBABE {
t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
}
if pkt.GuildID != 500 {
t.Errorf("GuildID = %d, want 500", pkt.GuildID)
}
if pkt.Name != "Alliance01" {
t.Errorf("Name = %q, want %q", pkt.Name, "Alliance01")
}
}
// TestParseLargeMsgMhfGetUdTacticsRemainingPoint tests Parse for MsgMhfGetUdTacticsRemainingPoint.
func TestParseLargeMsgMhfGetUdTacticsRemainingPoint(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(100) // Unk0
bf.WriteUint32(200) // Unk1
bf.WriteUint32(300) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetUdTacticsRemainingPoint{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.Unk0 != 100 {
t.Errorf("Unk0 = %d, want 100", pkt.Unk0)
}
if pkt.Unk1 != 200 {
t.Errorf("Unk1 = %d, want 200", pkt.Unk1)
}
if pkt.Unk2 != 300 {
t.Errorf("Unk2 = %d, want 300", pkt.Unk2)
}
}
// TestParseLargeMsgMhfPostCafeDurationBonusReceived tests Parse for MsgMhfPostCafeDurationBonusReceived.
func TestParseLargeMsgMhfPostCafeDurationBonusReceived(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint32(3) // count
bf.WriteUint32(1001) // CafeBonusID[0]
bf.WriteUint32(1002) // CafeBonusID[1]
bf.WriteUint32(1003) // CafeBonusID[2]
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPostCafeDurationBonusReceived{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if len(pkt.CafeBonusID) != 3 {
t.Fatalf("CafeBonusID len = %d, want 3", len(pkt.CafeBonusID))
}
expected := []uint32{1001, 1002, 1003}
for i, v := range expected {
if pkt.CafeBonusID[i] != v {
t.Errorf("CafeBonusID[%d] = %d, want %d", i, pkt.CafeBonusID[i], v)
}
}
}
// TestParseLargeMsgMhfPostCafeDurationBonusReceivedEmpty tests Parse with zero IDs.
func TestParseLargeMsgMhfPostCafeDurationBonusReceivedEmpty(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(0) // count = 0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPostCafeDurationBonusReceived{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if len(pkt.CafeBonusID) != 0 {
t.Errorf("CafeBonusID len = %d, want 0", len(pkt.CafeBonusID))
}
}
// TestParseLargeMsgMhfRegistGuildAdventureDiva tests Parse for MsgMhfRegistGuildAdventureDiva.
func TestParseLargeMsgMhfRegistGuildAdventureDiva(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(5) // Destination
bf.WriteUint32(1000) // Charge
bf.WriteUint32(42) // CharID (skipped)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfRegistGuildAdventureDiva{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.Destination != 5 {
t.Errorf("Destination = %d, want 5", pkt.Destination)
}
if pkt.Charge != 1000 {
t.Errorf("Charge = %d, want 1000", pkt.Charge)
}
}
// TestParseLargeMsgMhfStateFestaG tests Parse for MsgMhfStateFestaG.
func TestParseLargeMsgMhfStateFestaG(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xDEADBEEF) // AckHandle
bf.WriteUint32(100) // FestaID
bf.WriteUint32(200) // GuildID
bf.WriteUint16(0) // Hardcoded 0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfStateFestaG{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xDEADBEEF {
t.Errorf("AckHandle = 0x%X, want 0xDEADBEEF", pkt.AckHandle)
}
if pkt.FestaID != 100 {
t.Errorf("FestaID = %d, want 100", pkt.FestaID)
}
if pkt.GuildID != 200 {
t.Errorf("GuildID = %d, want 200", pkt.GuildID)
}
}
// TestParseLargeMsgMhfStateFestaU tests Parse for MsgMhfStateFestaU.
func TestParseLargeMsgMhfStateFestaU(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xCAFEBABE) // AckHandle
bf.WriteUint32(300) // FestaID
bf.WriteUint32(400) // GuildID
bf.WriteUint16(0) // Hardcoded 0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfStateFestaU{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xCAFEBABE {
t.Errorf("AckHandle = 0x%X, want 0xCAFEBABE", pkt.AckHandle)
}
if pkt.FestaID != 300 {
t.Errorf("FestaID = %d, want 300", pkt.FestaID)
}
if pkt.GuildID != 400 {
t.Errorf("GuildID = %d, want 400", pkt.GuildID)
}
}
// TestParseLargeMsgSysEnumerateStage tests Parse for MsgSysEnumerateStage.
func TestParseLargeMsgSysEnumerateStage(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x11223344) // AckHandle
bf.WriteUint8(1) // Unk0
bf.WriteUint8(0) // skipped byte
bf.WriteBytes([]byte("quest_"))
bf.WriteUint8(0) // null terminator
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnumerateStage{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x11223344 {
t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
}
if pkt.StagePrefix != "quest_" {
t.Errorf("StagePrefix = %q, want %q", pkt.StagePrefix, "quest_")
}
}
// TestParseLargeMsgSysReserveStage tests Parse for MsgSysReserveStage.
func TestParseLargeMsgSysReserveStage(t *testing.T) {
stageID := "stage_42"
stageBytes := make([]byte, len(stageID)+1) // padded with null at end
copy(stageBytes, stageID)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint8(0x11) // Ready
bf.WriteUint8(uint8(len(stageBytes))) // stageIDLength
bf.WriteBytes(stageBytes)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysReserveStage{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: _config.ZZ}); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.Ready != 0x11 {
t.Errorf("Ready = 0x%X, want 0x11", pkt.Ready)
}
if pkt.StageID != stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, stageID)
}
}