test: backport remaining test files from v9.2.x-stable

Import 18 network packet test files and 5 server infrastructure test
files, adapted for main branch APIs: fix config import alias (_config),
remove non-existent DevMode field, use global handlerTable instead of
per-server handlers map, and correct validateToken mock expectations
to include both token and tokenID arguments.

Adds go-sqlmock dependency for database mocking in signserver tests.
This commit is contained in:
Houmgaor
2026-02-17 00:09:41 +01:00
parent 4c3810df7e
commit 0bd724f74e
25 changed files with 13785 additions and 0 deletions

View File

@@ -0,0 +1,430 @@
package binpacket
import (
"bytes"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
)
func TestMsgBinTargetedOpcode(t *testing.T) {
m := &MsgBinTargeted{}
if m.Opcode() != network.MSG_SYS_CAST_BINARY {
t.Errorf("MsgBinTargeted.Opcode() = %v, want MSG_SYS_CAST_BINARY", m.Opcode())
}
}
func TestMsgBinTargetedParseEmpty(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(0) // TargetCount = 0
bf.Seek(0, 0)
m := &MsgBinTargeted{}
err := m.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if m.TargetCount != 0 {
t.Errorf("TargetCount = %d, want 0", m.TargetCount)
}
if len(m.TargetCharIDs) != 0 {
t.Errorf("TargetCharIDs len = %d, want 0", len(m.TargetCharIDs))
}
}
func TestMsgBinTargetedParseSingleTarget(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(1) // TargetCount = 1
bf.WriteUint32(0x12345678) // TargetCharID
bf.WriteBytes([]byte{0xDE, 0xAD, 0xBE, 0xEF})
bf.Seek(0, 0)
m := &MsgBinTargeted{}
err := m.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if m.TargetCount != 1 {
t.Errorf("TargetCount = %d, want 1", m.TargetCount)
}
if len(m.TargetCharIDs) != 1 {
t.Errorf("TargetCharIDs len = %d, want 1", len(m.TargetCharIDs))
}
if m.TargetCharIDs[0] != 0x12345678 {
t.Errorf("TargetCharIDs[0] = %x, want 0x12345678", m.TargetCharIDs[0])
}
if !bytes.Equal(m.RawDataPayload, []byte{0xDE, 0xAD, 0xBE, 0xEF}) {
t.Errorf("RawDataPayload = %v, want [0xDE, 0xAD, 0xBE, 0xEF]", m.RawDataPayload)
}
}
func TestMsgBinTargetedParseMultipleTargets(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(3) // TargetCount = 3
bf.WriteUint32(100)
bf.WriteUint32(200)
bf.WriteUint32(300)
bf.WriteBytes([]byte{0x01, 0x02, 0x03})
bf.Seek(0, 0)
m := &MsgBinTargeted{}
err := m.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if m.TargetCount != 3 {
t.Errorf("TargetCount = %d, want 3", m.TargetCount)
}
if len(m.TargetCharIDs) != 3 {
t.Errorf("TargetCharIDs len = %d, want 3", len(m.TargetCharIDs))
}
if m.TargetCharIDs[0] != 100 || m.TargetCharIDs[1] != 200 || m.TargetCharIDs[2] != 300 {
t.Errorf("TargetCharIDs = %v, want [100, 200, 300]", m.TargetCharIDs)
}
}
func TestMsgBinTargetedBuild(t *testing.T) {
m := &MsgBinTargeted{
TargetCount: 2,
TargetCharIDs: []uint32{0x11111111, 0x22222222},
RawDataPayload: []byte{0xAA, 0xBB},
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
expected := []byte{
0x00, 0x02, // TargetCount
0x11, 0x11, 0x11, 0x11, // TargetCharIDs[0]
0x22, 0x22, 0x22, 0x22, // TargetCharIDs[1]
0xAA, 0xBB, // RawDataPayload
}
if !bytes.Equal(bf.Data(), expected) {
t.Errorf("Build() = %v, want %v", bf.Data(), expected)
}
}
func TestMsgBinTargetedRoundTrip(t *testing.T) {
original := &MsgBinTargeted{
TargetCount: 3,
TargetCharIDs: []uint32{1000, 2000, 3000},
RawDataPayload: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, 0)
parsed := &MsgBinTargeted{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.TargetCount != original.TargetCount {
t.Errorf("TargetCount = %d, want %d", parsed.TargetCount, original.TargetCount)
}
if len(parsed.TargetCharIDs) != len(original.TargetCharIDs) {
t.Errorf("TargetCharIDs len = %d, want %d", len(parsed.TargetCharIDs), len(original.TargetCharIDs))
}
for i := range original.TargetCharIDs {
if parsed.TargetCharIDs[i] != original.TargetCharIDs[i] {
t.Errorf("TargetCharIDs[%d] = %d, want %d", i, parsed.TargetCharIDs[i], original.TargetCharIDs[i])
}
}
if !bytes.Equal(parsed.RawDataPayload, original.RawDataPayload) {
t.Errorf("RawDataPayload = %v, want %v", parsed.RawDataPayload, original.RawDataPayload)
}
}
func TestMsgBinMailNotifyOpcode(t *testing.T) {
m := MsgBinMailNotify{}
if m.Opcode() != network.MSG_SYS_CASTED_BINARY {
t.Errorf("MsgBinMailNotify.Opcode() = %v, want MSG_SYS_CASTED_BINARY", m.Opcode())
}
}
func TestMsgBinMailNotifyBuild(t *testing.T) {
m := MsgBinMailNotify{
SenderName: "TestPlayer",
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
data := bf.Data()
// First byte should be 0x01 (Unk)
if data[0] != 0x01 {
t.Errorf("First byte = %x, want 0x01", data[0])
}
// Total length should be 1 (Unk) + 21 (padded name) = 22
if len(data) != 22 {
t.Errorf("Data len = %d, want 22", len(data))
}
}
func TestMsgBinMailNotifyBuildEmptyName(t *testing.T) {
m := MsgBinMailNotify{
SenderName: "",
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if len(bf.Data()) != 22 {
t.Errorf("Data len = %d, want 22", len(bf.Data()))
}
}
func TestMsgBinChatOpcode(t *testing.T) {
m := &MsgBinChat{}
if m.Opcode() != network.MSG_SYS_CAST_BINARY {
t.Errorf("MsgBinChat.Opcode() = %v, want MSG_SYS_CAST_BINARY", m.Opcode())
}
}
func TestMsgBinChatTypes(t *testing.T) {
tests := []struct {
chatType ChatType
value uint8
}{
{ChatTypeStage, 1},
{ChatTypeGuild, 2},
{ChatTypeAlliance, 3},
{ChatTypeParty, 4},
{ChatTypeWhisper, 5},
}
for _, tt := range tests {
if uint8(tt.chatType) != tt.value {
t.Errorf("ChatType %v = %d, want %d", tt.chatType, uint8(tt.chatType), tt.value)
}
}
}
func TestMsgBinChatBuildParse(t *testing.T) {
original := &MsgBinChat{
Unk0: 0x00,
Type: ChatTypeStage,
Flags: 0x0000,
Message: "Hello",
SenderName: "Player",
}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, 0)
parsed := &MsgBinChat{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.Unk0 != original.Unk0 {
t.Errorf("Unk0 = %d, want %d", parsed.Unk0, original.Unk0)
}
if parsed.Type != original.Type {
t.Errorf("Type = %d, want %d", parsed.Type, original.Type)
}
if parsed.Flags != original.Flags {
t.Errorf("Flags = %d, want %d", parsed.Flags, original.Flags)
}
if parsed.Message != original.Message {
t.Errorf("Message = %q, want %q", parsed.Message, original.Message)
}
if parsed.SenderName != original.SenderName {
t.Errorf("SenderName = %q, want %q", parsed.SenderName, original.SenderName)
}
}
func TestMsgBinChatBuildParseJapanese(t *testing.T) {
original := &MsgBinChat{
Unk0: 0x00,
Type: ChatTypeGuild,
Flags: 0x0001,
Message: "こんにちは",
SenderName: "テスト",
}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, 0)
parsed := &MsgBinChat{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if parsed.Message != original.Message {
t.Errorf("Message = %q, want %q", parsed.Message, original.Message)
}
if parsed.SenderName != original.SenderName {
t.Errorf("SenderName = %q, want %q", parsed.SenderName, original.SenderName)
}
}
func TestMsgBinChatBuildParseEmpty(t *testing.T) {
original := &MsgBinChat{
Unk0: 0x00,
Type: ChatTypeParty,
Flags: 0x0000,
Message: "",
SenderName: "",
}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, 0)
parsed := &MsgBinChat{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if parsed.Message != "" {
t.Errorf("Message = %q, want empty", parsed.Message)
}
if parsed.SenderName != "" {
t.Errorf("SenderName = %q, want empty", parsed.SenderName)
}
}
func TestMsgBinChatBuildFormat(t *testing.T) {
m := &MsgBinChat{
Unk0: 0x12,
Type: ChatTypeWhisper,
Flags: 0x3456,
Message: "Hi",
SenderName: "A",
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
data := bf.Data()
// Verify header structure
if data[0] != 0x12 {
t.Errorf("Unk0 = %x, want 0x12", data[0])
}
if data[1] != uint8(ChatTypeWhisper) {
t.Errorf("Type = %x, want %x", data[1], uint8(ChatTypeWhisper))
}
// Flags at bytes 2-3 (big endian)
if data[2] != 0x34 || data[3] != 0x56 {
t.Errorf("Flags = %x%x, want 3456", data[2], data[3])
}
}
func TestMsgBinChatAllTypes(t *testing.T) {
types := []ChatType{
ChatTypeStage,
ChatTypeGuild,
ChatTypeAlliance,
ChatTypeParty,
ChatTypeWhisper,
}
for _, chatType := range types {
t.Run("", func(t *testing.T) {
original := &MsgBinChat{
Type: chatType,
Message: "Test",
SenderName: "Player",
}
bf := byteframe.NewByteFrame()
err := original.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
bf.Seek(0, 0)
parsed := &MsgBinChat{}
err = parsed.Parse(bf)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if parsed.Type != chatType {
t.Errorf("Type = %d, want %d", parsed.Type, chatType)
}
})
}
}
func TestMsgBinMailNotifyParsePanics(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("Parse() should panic with 'implement me'")
}
}()
m := MsgBinMailNotify{}
bf := byteframe.NewByteFrame()
_ = m.Parse(bf)
}
func TestMsgBinMailNotifyBuildLongName(t *testing.T) {
m := MsgBinMailNotify{
SenderName: "ThisIsAVeryLongPlayerNameThatExceeds21Characters",
}
bf := byteframe.NewByteFrame()
err := m.Build(bf)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Data should still be 22 bytes (1 + 21)
if len(bf.Data()) != 22 {
t.Errorf("Data len = %d, want 22", len(bf.Data()))
}
}

View File

@@ -0,0 +1,828 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func TestMHFPacketInterface(t *testing.T) {
// Verify that packets implement the MHFPacket interface
var _ MHFPacket = &MsgSysPing{}
var _ MHFPacket = &MsgSysTime{}
var _ MHFPacket = &MsgSysNop{}
var _ MHFPacket = &MsgSysEnd{}
var _ MHFPacket = &MsgSysLogin{}
var _ MHFPacket = &MsgSysLogout{}
}
func TestFromOpcodeReturnsCorrectType(t *testing.T) {
tests := []struct {
opcode network.PacketID
wantType string
}{
{network.MSG_HEAD, "*mhfpacket.MsgHead"},
{network.MSG_SYS_PING, "*mhfpacket.MsgSysPing"},
{network.MSG_SYS_TIME, "*mhfpacket.MsgSysTime"},
{network.MSG_SYS_NOP, "*mhfpacket.MsgSysNop"},
{network.MSG_SYS_END, "*mhfpacket.MsgSysEnd"},
{network.MSG_SYS_ACK, "*mhfpacket.MsgSysAck"},
{network.MSG_SYS_LOGIN, "*mhfpacket.MsgSysLogin"},
{network.MSG_SYS_LOGOUT, "*mhfpacket.MsgSysLogout"},
{network.MSG_SYS_CREATE_STAGE, "*mhfpacket.MsgSysCreateStage"},
{network.MSG_SYS_ENTER_STAGE, "*mhfpacket.MsgSysEnterStage"},
}
for _, tt := range tests {
t.Run(tt.opcode.String(), func(t *testing.T) {
pkt := FromOpcode(tt.opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", tt.opcode)
return
}
if pkt.Opcode() != tt.opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), tt.opcode)
}
})
}
}
func TestFromOpcodeUnknown(t *testing.T) {
// Test with an invalid opcode
pkt := FromOpcode(network.PacketID(0xFFFF))
if pkt != nil {
t.Error("FromOpcode(0xFFFF) should return nil for unknown opcode")
}
}
func TestMsgSysPingRoundTrip(t *testing.T) {
original := &MsgSysPing{
AckHandle: 0x12345678,
}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, io.SeekStart)
parsed := &MsgSysPing{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.AckHandle != original.AckHandle {
t.Errorf("AckHandle = %d, want %d", parsed.AckHandle, original.AckHandle)
}
}
func TestMsgSysTimeRoundTrip(t *testing.T) {
tests := []struct {
name string
getRemoteTime bool
timestamp uint32
}{
{"no remote time", false, 1577105879},
{"with remote time", true, 1609459200},
{"zero timestamp", false, 0},
{"max timestamp", true, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original := &MsgSysTime{
GetRemoteTime: tt.getRemoteTime,
Timestamp: tt.timestamp,
}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, io.SeekStart)
parsed := &MsgSysTime{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.GetRemoteTime != original.GetRemoteTime {
t.Errorf("GetRemoteTime = %v, want %v", parsed.GetRemoteTime, original.GetRemoteTime)
}
if parsed.Timestamp != original.Timestamp {
t.Errorf("Timestamp = %d, want %d", parsed.Timestamp, original.Timestamp)
}
})
}
}
func TestMsgSysPingOpcode(t *testing.T) {
pkt := &MsgSysPing{}
if pkt.Opcode() != network.MSG_SYS_PING {
t.Errorf("Opcode() = %s, want MSG_SYS_PING", pkt.Opcode())
}
}
func TestMsgSysTimeOpcode(t *testing.T) {
pkt := &MsgSysTime{}
if pkt.Opcode() != network.MSG_SYS_TIME {
t.Errorf("Opcode() = %s, want MSG_SYS_TIME", pkt.Opcode())
}
}
func TestFromOpcodeSystemPackets(t *testing.T) {
// Test all system packet opcodes return non-nil
systemOpcodes := []network.PacketID{
network.MSG_SYS_reserve01,
network.MSG_SYS_reserve02,
network.MSG_SYS_reserve03,
network.MSG_SYS_reserve04,
network.MSG_SYS_reserve05,
network.MSG_SYS_reserve06,
network.MSG_SYS_reserve07,
network.MSG_SYS_ADD_OBJECT,
network.MSG_SYS_DEL_OBJECT,
network.MSG_SYS_DISP_OBJECT,
network.MSG_SYS_HIDE_OBJECT,
network.MSG_SYS_END,
network.MSG_SYS_NOP,
network.MSG_SYS_ACK,
network.MSG_SYS_LOGIN,
network.MSG_SYS_LOGOUT,
network.MSG_SYS_SET_STATUS,
network.MSG_SYS_PING,
network.MSG_SYS_TIME,
}
for _, opcode := range systemOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestFromOpcodeStagePackets(t *testing.T) {
stageOpcodes := []network.PacketID{
network.MSG_SYS_CREATE_STAGE,
network.MSG_SYS_STAGE_DESTRUCT,
network.MSG_SYS_ENTER_STAGE,
network.MSG_SYS_BACK_STAGE,
network.MSG_SYS_MOVE_STAGE,
network.MSG_SYS_LEAVE_STAGE,
network.MSG_SYS_LOCK_STAGE,
network.MSG_SYS_UNLOCK_STAGE,
network.MSG_SYS_RESERVE_STAGE,
network.MSG_SYS_UNRESERVE_STAGE,
network.MSG_SYS_SET_STAGE_PASS,
}
for _, opcode := range stageOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestOpcodeMatches(t *testing.T) {
// Verify that packets return the same opcode they were created from
tests := []network.PacketID{
network.MSG_HEAD,
network.MSG_SYS_PING,
network.MSG_SYS_TIME,
network.MSG_SYS_END,
network.MSG_SYS_NOP,
network.MSG_SYS_ACK,
network.MSG_SYS_LOGIN,
network.MSG_SYS_CREATE_STAGE,
}
for _, opcode := range tests {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Skip("opcode not implemented")
}
if pkt.Opcode() != opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), opcode)
}
})
}
}
func TestParserInterface(t *testing.T) {
// Verify Parser interface works
var p Parser = &MsgSysPing{}
bf := byteframe.NewByteFrame()
bf.WriteUint32(123)
bf.Seek(0, io.SeekStart)
err := p.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Errorf("Parse() error = %v", err)
}
}
func TestBuilderInterface(t *testing.T) {
// Verify Builder interface works
var b Builder = &MsgSysPing{AckHandle: 456}
bf := byteframe.NewByteFrame()
err := b.Build(bf, &clientctx.ClientContext{})
if err != nil {
t.Errorf("Build() error = %v", err)
}
if len(bf.Data()) == 0 {
t.Error("Build() should write data")
}
}
func TestOpcoderInterface(t *testing.T) {
// Verify Opcoder interface works
var o Opcoder = &MsgSysPing{}
opcode := o.Opcode()
if opcode != network.MSG_SYS_PING {
t.Errorf("Opcode() = %s, want MSG_SYS_PING", opcode)
}
}
func TestClientContextNilSafe(t *testing.T) {
// Some packets may need to handle nil ClientContext
pkt := &MsgSysPing{AckHandle: 123}
bf := byteframe.NewByteFrame()
// This should not panic even with nil context (implementation dependent)
// Note: The actual behavior depends on implementation
err := pkt.Build(bf, nil)
if err != nil {
// Error is acceptable if nil context is not supported
t.Logf("Build() with nil context returned error: %v", err)
}
}
func TestMsgSysPingBuildFormat(t *testing.T) {
pkt := &MsgSysPing{AckHandle: 0x12345678}
bf := byteframe.NewByteFrame()
pkt.Build(bf, &clientctx.ClientContext{})
data := bf.Data()
if len(data) != 4 {
t.Errorf("Build() data len = %d, want 4", len(data))
}
// Verify big-endian format (default)
if data[0] != 0x12 || data[1] != 0x34 || data[2] != 0x56 || data[3] != 0x78 {
t.Errorf("Build() data = %x, want 12345678", data)
}
}
func TestMsgSysTimeBuildFormat(t *testing.T) {
pkt := &MsgSysTime{
GetRemoteTime: true,
Timestamp: 0xDEADBEEF,
}
bf := byteframe.NewByteFrame()
pkt.Build(bf, &clientctx.ClientContext{})
data := bf.Data()
if len(data) != 5 {
t.Errorf("Build() data len = %d, want 5 (1 bool + 4 uint32)", len(data))
}
// First byte is bool (1 = true)
if data[0] != 1 {
t.Errorf("GetRemoteTime byte = %d, want 1", data[0])
}
}
func TestMsgSysNop(t *testing.T) {
pkt := FromOpcode(network.MSG_SYS_NOP)
if pkt == nil {
t.Fatal("FromOpcode(MSG_SYS_NOP) returned nil")
}
if pkt.Opcode() != network.MSG_SYS_NOP {
t.Errorf("Opcode() = %s, want MSG_SYS_NOP", pkt.Opcode())
}
}
func TestMsgSysEnd(t *testing.T) {
pkt := FromOpcode(network.MSG_SYS_END)
if pkt == nil {
t.Fatal("FromOpcode(MSG_SYS_END) returned nil")
}
if pkt.Opcode() != network.MSG_SYS_END {
t.Errorf("Opcode() = %s, want MSG_SYS_END", pkt.Opcode())
}
}
func TestMsgHead(t *testing.T) {
pkt := FromOpcode(network.MSG_HEAD)
if pkt == nil {
t.Fatal("FromOpcode(MSG_HEAD) returned nil")
}
if pkt.Opcode() != network.MSG_HEAD {
t.Errorf("Opcode() = %s, want MSG_HEAD", pkt.Opcode())
}
}
func TestMsgSysAck(t *testing.T) {
pkt := FromOpcode(network.MSG_SYS_ACK)
if pkt == nil {
t.Fatal("FromOpcode(MSG_SYS_ACK) returned nil")
}
if pkt.Opcode() != network.MSG_SYS_ACK {
t.Errorf("Opcode() = %s, want MSG_SYS_ACK", pkt.Opcode())
}
}
func TestBinaryPackets(t *testing.T) {
binaryOpcodes := []network.PacketID{
network.MSG_SYS_CAST_BINARY,
network.MSG_SYS_CASTED_BINARY,
network.MSG_SYS_SET_STAGE_BINARY,
network.MSG_SYS_GET_STAGE_BINARY,
network.MSG_SYS_WAIT_STAGE_BINARY,
}
for _, opcode := range binaryOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestEnumeratePackets(t *testing.T) {
enumOpcodes := []network.PacketID{
network.MSG_SYS_ENUMERATE_CLIENT,
network.MSG_SYS_ENUMERATE_STAGE,
}
for _, opcode := range enumOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestSemaphorePackets(t *testing.T) {
semaOpcodes := []network.PacketID{
network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE,
network.MSG_SYS_ACQUIRE_SEMAPHORE,
network.MSG_SYS_RELEASE_SEMAPHORE,
network.MSG_SYS_CHECK_SEMAPHORE,
}
for _, opcode := range semaOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestObjectPackets(t *testing.T) {
objOpcodes := []network.PacketID{
network.MSG_SYS_ADD_OBJECT,
network.MSG_SYS_DEL_OBJECT,
network.MSG_SYS_DISP_OBJECT,
network.MSG_SYS_HIDE_OBJECT,
}
for _, opcode := range objOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestLogPackets(t *testing.T) {
logOpcodes := []network.PacketID{
network.MSG_SYS_TERMINAL_LOG,
network.MSG_SYS_ISSUE_LOGKEY,
network.MSG_SYS_RECORD_LOG,
}
for _, opcode := range logOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestMHFSaveLoad(t *testing.T) {
saveLoadOpcodes := []network.PacketID{
network.MSG_MHF_SAVEDATA,
network.MSG_MHF_LOADDATA,
}
for _, opcode := range saveLoadOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Errorf("FromOpcode(%s) returned nil", opcode)
}
})
}
}
func TestMsgSysCreateStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
wantUnk0 uint8
wantPlayers uint8
wantStageID string
}{
{
name: "simple stage",
data: append([]byte{0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x05}, append([]byte("test"), 0x00)...),
wantHandle: 1,
wantUnk0: 2,
wantPlayers: 4,
wantStageID: "test",
},
{
name: "empty stage ID",
data: []byte{0x12, 0x34, 0x56, 0x78, 0x01, 0x02, 0x00},
wantHandle: 0x12345678,
wantUnk0: 1,
wantPlayers: 2,
wantStageID: "",
},
{
name: "with null terminator",
data: append([]byte{0x00, 0x00, 0x00, 0x0A, 0x01, 0x01, 0x08}, append([]byte("stage01"), 0x00)...),
wantHandle: 10,
wantUnk0: 1,
wantPlayers: 1,
wantStageID: "stage01",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
if pkt.Unk0 != tt.wantUnk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.wantUnk0)
}
if pkt.PlayerCount != tt.wantPlayers {
t.Errorf("PlayerCount = %d, want %d", pkt.PlayerCount, tt.wantPlayers)
}
if pkt.StageID != tt.wantStageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
}
})
}
}
func TestMsgSysEnterStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
wantUnk bool
wantStageID string
}{
{
name: "enter mezeporta",
data: append([]byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x0F}, append([]byte("sl1Ns200p0a0u0"), 0x00)...),
wantHandle: 1,
wantUnk: false,
wantStageID: "sl1Ns200p0a0u0",
},
{
name: "with unk bool set",
data: append([]byte{0xAB, 0xCD, 0xEF, 0x12, 0x01, 0x05}, append([]byte("room1"), 0x00)...),
wantHandle: 0xABCDEF12,
wantUnk: true,
wantStageID: "room1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnterStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
if pkt.Unk != tt.wantUnk {
t.Errorf("Unk = %v, want %v", pkt.Unk, tt.wantUnk)
}
if pkt.StageID != tt.wantStageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
}
})
}
}
func TestMsgSysMoveStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
wantUnkBool uint8
wantStageID string
}{
{
name: "move to quest stage",
data: append([]byte{0x00, 0x00, 0x12, 0x34, 0x00, 0x06}, []byte("quest1")...),
wantHandle: 0x1234,
wantUnkBool: 0,
wantStageID: "quest1",
},
{
name: "with null in string",
data: append([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x08}, append([]byte("stage"), []byte{0x00, 0x00, 0x00}...)...),
wantHandle: 0xFFFFFFFF,
wantUnkBool: 1,
wantStageID: "stage",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysMoveStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
if pkt.UnkBool != tt.wantUnkBool {
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.wantUnkBool)
}
if pkt.StageID != tt.wantStageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
}
})
}
}
func TestMsgSysLockStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
wantStageID string
}{
{
name: "lock stage",
data: append([]byte{0x00, 0x00, 0x00, 0x05, 0x01, 0x01, 0x06}, append([]byte("room01"), 0x00)...),
wantHandle: 5,
wantStageID: "room01",
},
{
name: "different unk values",
data: append([]byte{0x12, 0x34, 0x56, 0x78, 0x02, 0x03, 0x04}, append([]byte("test"), 0x00)...),
wantHandle: 0x12345678,
wantStageID: "test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLockStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
if pkt.StageID != tt.wantStageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.wantStageID)
}
})
}
}
func TestMsgSysUnlockStageRoundTrip(t *testing.T) {
tests := []struct {
name string
unk0 uint16
}{
{"zero value", 0},
{"typical value", 1},
{"max value", 0xFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &clientctx.ClientContext{}
// Build (returns NOT IMPLEMENTED)
original := &MsgSysUnlockStage{}
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err == nil {
t.Fatal("Build() expected NOT IMPLEMENTED error")
}
// Parse should consume a uint16 without error
bf = byteframe.NewByteFrame()
bf.WriteUint16(tt.unk0)
bf.Seek(0, io.SeekStart)
parsed := &MsgSysUnlockStage{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
})
}
}
func TestMsgSysBackStageParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantHandle uint32
}{
{"simple handle", []byte{0x00, 0x00, 0x00, 0x01}, 1},
{"large handle", []byte{0xDE, 0xAD, 0xBE, 0xEF}, 0xDEADBEEF},
{"zero handle", []byte{0x00, 0x00, 0x00, 0x00}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysBackStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.wantHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.wantHandle)
}
})
}
}
func TestMsgSysLogoutParse(t *testing.T) {
tests := []struct {
name string
data []byte
wantUnk0 uint8
}{
{"typical logout", []byte{0x01}, 1},
{"zero value", []byte{0x00}, 0},
{"max value", []byte{0xFF}, 255},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBytes(tt.data)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLogout{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk0 != tt.wantUnk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.wantUnk0)
}
})
}
}
func TestMsgSysLoginParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
charID0 uint32
loginTokenNumber uint32
hardcodedZero0 uint16
requestVersion uint16
charID1 uint32
hardcodedZero1 uint16
tokenStrLen uint16
tokenString string
}{
{
name: "typical login",
ackHandle: 1,
charID0: 12345,
loginTokenNumber: 67890,
hardcodedZero0: 0,
requestVersion: 1,
charID1: 12345,
hardcodedZero1: 0,
tokenStrLen: 0x11,
tokenString: "abc123token",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint32(tt.charID0)
bf.WriteUint32(tt.loginTokenNumber)
bf.WriteUint16(tt.hardcodedZero0)
bf.WriteUint16(tt.requestVersion)
bf.WriteUint32(tt.charID1)
bf.WriteUint16(tt.hardcodedZero1)
bf.WriteUint16(tt.tokenStrLen)
bf.WriteBytes(append([]byte(tt.tokenString), 0x00)) // null terminated
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLogin{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.CharID0 != tt.charID0 {
t.Errorf("CharID0 = %d, want %d", pkt.CharID0, tt.charID0)
}
if pkt.LoginTokenNumber != tt.loginTokenNumber {
t.Errorf("LoginTokenNumber = %d, want %d", pkt.LoginTokenNumber, tt.loginTokenNumber)
}
if pkt.RequestVersion != tt.requestVersion {
t.Errorf("RequestVersion = %d, want %d", pkt.RequestVersion, tt.requestVersion)
}
if pkt.LoginTokenString != tt.tokenString {
t.Errorf("LoginTokenString = %q, want %q", pkt.LoginTokenString, tt.tokenString)
}
})
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
_config "erupe-ce/config"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func init() {
// Initialize ErupeConfig for tests that access it
_config.ErupeConfig = &_config.Config{
RealClientMode: _config.ZZ, // Default to ZZ for tests
}
}
func TestMsgMhfAcquireCafeItemOpcode(t *testing.T) {
pkt := &MsgMhfAcquireCafeItem{}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_CAFE_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_CAFE_ITEM", pkt.Opcode())
}
}
func TestMsgMhfAcquireCafeItemParse(t *testing.T) {
// Test basic parsing with current implementation (always reads uint32 for PointCost)
// Current code: m.PointCost = bf.ReadUint32() (no client mode check)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint16(1) // ItemType
bf.WriteUint16(100) // ItemID
bf.WriteUint16(5) // Quant
bf.WriteUint32(1000) // PointCost (uint32)
bf.WriteUint16(0) // Unk0
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireCafeItem{}
ctx := &clientctx.ClientContext{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.ItemType != 1 {
t.Errorf("ItemType = %d, want 1", pkt.ItemType)
}
if pkt.ItemID != 100 {
t.Errorf("ItemID = %d, want 100", pkt.ItemID)
}
if pkt.Quant != 5 {
t.Errorf("Quant = %d, want 5", pkt.Quant)
}
if pkt.PointCost != 1000 {
t.Errorf("PointCost = %d, want 1000", pkt.PointCost)
}
}
// TestMsgMhfAcquireCafeItemParseUint32PointCost documents the current behavior.
//
// CURRENT BEHAVIOR: Always reads PointCost as uint32.
//
// EXPECTED BEHAVIOR AFTER FIX (commit 3d0114c):
// - G6+: Read PointCost as uint32
// - G1-G5.2: Read PointCost as uint16
//
// This test verifies current uint32 parsing works correctly.
// After the fix is applied, this test should still pass for G6+ clients.
func TestMsgMhfAcquireCafeItemParseUint32PointCost(t *testing.T) {
tests := []struct {
name string
pointCost uint32
wantCost uint32
}{
{"small cost", 100, 100},
{"medium cost", 5000, 5000},
{"large cost exceeding uint16", 70000, 70000},
{"max uint32", 0xFFFFFFFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAAAABBBB) // AckHandle
bf.WriteUint16(1) // ItemType
bf.WriteUint16(200) // ItemID
bf.WriteUint16(10) // Quant
bf.WriteUint32(tt.pointCost)
bf.WriteUint16(0) // Unk0
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireCafeItem{}
ctx := &clientctx.ClientContext{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.PointCost != tt.wantCost {
t.Errorf("PointCost = %d, want %d", pkt.PointCost, tt.wantCost)
}
})
}
}
// TestMsgMhfAcquireCafeItemParseFieldOrder verifies the exact field order in parsing.
// This is important because the fix changes when PointCost is read (uint16 vs uint32).
func TestMsgMhfAcquireCafeItemParseFieldOrder(t *testing.T) {
// Build a packet with known values
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x11223344) // AckHandle (offset 0-3)
bf.WriteUint16(0x5566) // ItemType (offset 4-5)
bf.WriteUint16(0x7788) // ItemID (offset 6-7)
bf.WriteUint16(0x99AA) // Quant (offset 8-9)
bf.WriteUint32(0xBBCCDDEE) // PointCost (offset 10-13)
bf.WriteUint16(0xFF00) // Unk0 (offset 14-15)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireCafeItem{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x11223344 {
t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
}
if pkt.ItemType != 0x5566 {
t.Errorf("ItemType = 0x%X, want 0x5566", pkt.ItemType)
}
if pkt.ItemID != 0x7788 {
t.Errorf("ItemID = 0x%X, want 0x7788", pkt.ItemID)
}
if pkt.Quant != 0x99AA {
t.Errorf("Quant = 0x%X, want 0x99AA", pkt.Quant)
}
if pkt.PointCost != 0xBBCCDDEE {
t.Errorf("PointCost = 0x%X, want 0xBBCCDDEE", pkt.PointCost)
}
if pkt.Unk0 != 0xFF00 {
t.Errorf("Unk0 = 0x%X, want 0xFF00", pkt.Unk0)
}
}
func TestMsgMhfAcquireCafeItemBuildNotImplemented(t *testing.T) {
pkt := &MsgMhfAcquireCafeItem{
AckHandle: 123,
ItemType: 1,
ItemID: 100,
Quant: 5,
PointCost: 1000,
}
bf := byteframe.NewByteFrame()
ctx := &clientctx.ClientContext{}
err := pkt.Build(bf, ctx)
if err == nil {
t.Error("Build() should return error (NOT IMPLEMENTED)")
}
}
func TestMsgMhfAcquireCafeItemFromOpcode(t *testing.T) {
pkt := FromOpcode(network.MSG_MHF_ACQUIRE_CAFE_ITEM)
if pkt == nil {
t.Fatal("FromOpcode(MSG_MHF_ACQUIRE_CAFE_ITEM) returned nil")
}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_CAFE_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_CAFE_ITEM", pkt.Opcode())
}
}

View File

@@ -0,0 +1,263 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func TestAcquirePacketOpcodes(t *testing.T) {
tests := []struct {
name string
pkt MHFPacket
expect network.PacketID
}{
{"MsgMhfAcquireGuildTresure", &MsgMhfAcquireGuildTresure{}, network.MSG_MHF_ACQUIRE_GUILD_TRESURE},
{"MsgMhfAcquireTitle", &MsgMhfAcquireTitle{}, network.MSG_MHF_ACQUIRE_TITLE},
{"MsgMhfAcquireDistItem", &MsgMhfAcquireDistItem{}, network.MSG_MHF_ACQUIRE_DIST_ITEM},
{"MsgMhfAcquireMonthlyItem", &MsgMhfAcquireMonthlyItem{}, network.MSG_MHF_ACQUIRE_MONTHLY_ITEM},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.pkt.Opcode(); got != tt.expect {
t.Errorf("Opcode() = %v, want %v", got, tt.expect)
}
})
}
}
func TestMsgMhfAcquireGuildTresureParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
huntID uint32
unk bool
}{
{"basic acquisition", 1, 12345, false},
{"large hunt ID", 0xABCDEF12, 0xFFFFFFFF, true},
{"zero values", 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.huntID)
bf.WriteBool(tt.unk)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireGuildTresure{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.HuntID != tt.huntID {
t.Errorf("HuntID = %d, want %d", pkt.HuntID, tt.huntID)
}
if pkt.Unk != tt.unk {
t.Errorf("Unk = %v, want %v", pkt.Unk, tt.unk)
}
})
}
}
func TestMsgMhfAcquireTitleParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
titleIDs []uint16
}{
{"acquire title 1", 1, []uint16{1}},
{"acquire titles 100 200", 0x12345678, []uint16{100, 200}},
{"no titles", 0xFFFFFFFF, []uint16{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint16(uint16(len(tt.titleIDs))) // count
bf.WriteUint16(0) // zeroed
for _, id := range tt.titleIDs {
bf.WriteUint16(id)
}
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireTitle{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if len(pkt.TitleIDs) != len(tt.titleIDs) {
t.Errorf("TitleIDs len = %d, want %d", len(pkt.TitleIDs), len(tt.titleIDs))
}
for i, id := range tt.titleIDs {
if i < len(pkt.TitleIDs) && pkt.TitleIDs[i] != id {
t.Errorf("TitleIDs[%d] = %d, want %d", i, pkt.TitleIDs[i], id)
}
}
})
}
}
func TestMsgMhfAcquireDistItemParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
distributionType uint8
distributionID uint32
}{
{"type 0", 1, 0, 12345},
{"type 1", 0xABCD, 1, 67890},
{"max values", 0xFFFFFFFF, 0xFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint8(tt.distributionType)
bf.WriteUint32(tt.distributionID)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireDistItem{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.DistributionType != tt.distributionType {
t.Errorf("DistributionType = %d, want %d", pkt.DistributionType, tt.distributionType)
}
if pkt.DistributionID != tt.distributionID {
t.Errorf("DistributionID = %d, want %d", pkt.DistributionID, tt.distributionID)
}
})
}
}
func TestMsgMhfAcquireMonthlyItemParse(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
unk0 uint8
unk1 uint8
unk2 uint16
unk3 uint32
}{
{"basic", 1, 0, 0, 0, 0},
{"with values", 100, 10, 20, 30, 40},
{"max values", 0xFFFFFFFF, 0xFF, 0xFF, 0xFFFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint8(tt.unk0)
bf.WriteUint8(tt.unk1)
bf.WriteUint16(tt.unk2)
bf.WriteUint32(tt.unk3)
bf.WriteUint32(0) // Zeroed (consumed by Parse)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireMonthlyItem{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
if pkt.Unk1 != tt.unk1 {
t.Errorf("Unk1 = %d, want %d", pkt.Unk1, tt.unk1)
}
if pkt.Unk2 != tt.unk2 {
t.Errorf("Unk2 = %d, want %d", pkt.Unk2, tt.unk2)
}
if pkt.Unk3 != tt.unk3 {
t.Errorf("Unk3 = %d, want %d", pkt.Unk3, tt.unk3)
}
})
}
}
func TestAcquirePacketsFromOpcode(t *testing.T) {
acquireOpcodes := []network.PacketID{
network.MSG_MHF_ACQUIRE_GUILD_TRESURE,
network.MSG_MHF_ACQUIRE_TITLE,
network.MSG_MHF_ACQUIRE_DIST_ITEM,
network.MSG_MHF_ACQUIRE_MONTHLY_ITEM,
}
for _, opcode := range acquireOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Fatalf("FromOpcode(%s) returned nil", opcode)
}
if pkt.Opcode() != opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), opcode)
}
})
}
}
func TestAcquirePacketEdgeCases(t *testing.T) {
t.Run("guild tresure with max hunt ID", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
bf.WriteUint32(0xFFFFFFFF)
bf.WriteBool(true)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireGuildTresure{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.HuntID != 0xFFFFFFFF {
t.Errorf("HuntID = %d, want %d", pkt.HuntID, 0xFFFFFFFF)
}
})
t.Run("dist item with all types", func(t *testing.T) {
for i := uint8(0); i < 5; i++ {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
bf.WriteUint8(i)
bf.WriteUint32(12345)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireDistItem{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v for type %d", err, i)
}
if pkt.DistributionType != i {
t.Errorf("DistributionType = %d, want %d", pkt.DistributionType, i)
}
}
})
}

View File

@@ -0,0 +1,364 @@
package mhfpacket
import (
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
)
func TestMsgMhfUpdateGuacotOpcode_Guacot(t *testing.T) {
pkt := &MsgMhfUpdateGuacot{}
if pkt.Opcode() != network.MSG_MHF_UPDATE_GUACOT {
t.Errorf("Opcode() = %s, want MSG_MHF_UPDATE_GUACOT", pkt.Opcode())
}
}
func TestMsgMhfEnumerateGuacotOpcode_Guacot(t *testing.T) {
pkt := &MsgMhfEnumerateGuacot{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_GUACOT {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_GUACOT", pkt.Opcode())
}
}
func TestMsgMhfUpdateGuacotParse_SingleEntry(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0xAABBCCDD) // AckHandle
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Zeroed
// Goocoo entry
bf.WriteUint32(2) // Index
for i := 0; i < 22; i++ {
bf.WriteInt16(int16(i + 1)) // Data1
}
bf.WriteUint32(100) // Data2[0]
bf.WriteUint32(200) // Data2[1]
bf.WriteUint8(5) // Name length
bf.WriteBytes([]byte("Porky"))
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if pkt.AckHandle != 0xAABBCCDD {
t.Errorf("AckHandle = 0x%X, want 0xAABBCCDD", pkt.AckHandle)
}
if pkt.EntryCount != 1 {
t.Errorf("EntryCount = %d, want 1", pkt.EntryCount)
}
if len(pkt.Goocoos) != 1 {
t.Fatalf("len(Goocoos) = %d, want 1", len(pkt.Goocoos))
}
g := pkt.Goocoos[0]
if g.Index != 2 {
t.Errorf("Index = %d, want 2", g.Index)
}
if len(g.Data1) != 22 {
t.Fatalf("len(Data1) = %d, want 22", len(g.Data1))
}
for i := 0; i < 22; i++ {
if g.Data1[i] != int16(i+1) {
t.Errorf("Data1[%d] = %d, want %d", i, g.Data1[i], i+1)
}
}
if len(g.Data2) != 2 {
t.Fatalf("len(Data2) = %d, want 2", len(g.Data2))
}
if g.Data2[0] != 100 {
t.Errorf("Data2[0] = %d, want 100", g.Data2[0])
}
if g.Data2[1] != 200 {
t.Errorf("Data2[1] = %d, want 200", g.Data2[1])
}
if string(g.Name) != "Porky" {
t.Errorf("Name = %q, want %q", string(g.Name), "Porky")
}
}
func TestMsgMhfUpdateGuacotParse_MultipleEntries(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(3) // EntryCount
bf.WriteUint16(0) // Zeroed
for idx := uint32(0); idx < 3; idx++ {
bf.WriteUint32(idx) // Index
for i := 0; i < 22; i++ {
bf.WriteInt16(int16(idx*100 + uint32(i)))
}
bf.WriteUint32(idx * 10) // Data2[0]
bf.WriteUint32(idx * 20) // Data2[1]
name := []byte("Pog")
bf.WriteUint8(uint8(len(name)))
bf.WriteBytes(name)
}
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if len(pkt.Goocoos) != 3 {
t.Fatalf("len(Goocoos) = %d, want 3", len(pkt.Goocoos))
}
for idx := uint32(0); idx < 3; idx++ {
g := pkt.Goocoos[idx]
if g.Index != idx {
t.Errorf("Goocoos[%d].Index = %d, want %d", idx, g.Index, idx)
}
if g.Data1[0] != int16(idx*100) {
t.Errorf("Goocoos[%d].Data1[0] = %d, want %d", idx, g.Data1[0], idx*100)
}
if g.Data2[0] != idx*10 {
t.Errorf("Goocoos[%d].Data2[0] = %d, want %d", idx, g.Data2[0], idx*10)
}
}
}
func TestMsgMhfUpdateGuacotParse_ZeroEntries(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(42) // AckHandle
bf.WriteUint16(0) // EntryCount
bf.WriteUint16(0) // Zeroed
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if pkt.EntryCount != 0 {
t.Errorf("EntryCount = %d, want 0", pkt.EntryCount)
}
if len(pkt.Goocoos) != 0 {
t.Errorf("len(Goocoos) = %d, want 0", len(pkt.Goocoos))
}
}
func TestMsgMhfUpdateGuacotParse_DeletionEntry(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Zeroed
bf.WriteUint32(0) // Index
// Data1[0] = 0 signals deletion
bf.WriteInt16(0)
for i := 1; i < 22; i++ {
bf.WriteInt16(0)
}
bf.WriteUint32(0) // Data2[0]
bf.WriteUint32(0) // Data2[1]
bf.WriteUint8(0) // Empty name
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
g := pkt.Goocoos[0]
if g.Data1[0] != 0 {
t.Errorf("Data1[0] = %d, want 0 (deletion marker)", g.Data1[0])
}
}
func TestMsgMhfUpdateGuacotParse_EmptyName(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Zeroed
bf.WriteUint32(0) // Index
for i := 0; i < 22; i++ {
bf.WriteInt16(1)
}
bf.WriteUint32(0) // Data2[0]
bf.WriteUint32(0) // Data2[1]
bf.WriteUint8(0) // Empty name
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if len(pkt.Goocoos[0].Name) != 0 {
t.Errorf("Name length = %d, want 0", len(pkt.Goocoos[0].Name))
}
}
func TestMsgMhfEnumerateGuacotParse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(0) // Unk0
bf.WriteUint16(0) // Zeroed
pkt := &MsgMhfEnumerateGuacot{}
bf.Seek(0, 0)
err := pkt.Parse(bf, nil)
if err != nil {
t.Fatalf("Parse() error: %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.Unk0 != 0 {
t.Errorf("Unk0 = %d, want 0", pkt.Unk0)
}
}
func TestMsgMhfUpdateGuacotBuild_NotImplemented(t *testing.T) {
pkt := &MsgMhfUpdateGuacot{}
err := pkt.Build(byteframe.NewByteFrame(), nil)
if err == nil {
t.Error("Build() should return error (not implemented)")
}
}
func TestMsgMhfEnumerateGuacotBuild_NotImplemented(t *testing.T) {
pkt := &MsgMhfEnumerateGuacot{}
err := pkt.Build(byteframe.NewByteFrame(), nil)
if err == nil {
t.Error("Build() should return error (not implemented)")
}
}
func TestGoocooStruct_Data1Size(t *testing.T) {
// Verify 22 int16 entries = 44 bytes of outfit/appearance data
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Zeroed
bf.WriteUint32(0) // Index
for i := 0; i < 22; i++ {
bf.WriteInt16(int16(i * 3))
}
bf.WriteUint32(0xDEAD) // Data2[0]
bf.WriteUint32(0xBEEF) // Data2[1]
bf.WriteUint8(0) // No name
pkt := &MsgMhfUpdateGuacot{}
bf.Seek(0, 0)
_ = pkt.Parse(bf, nil)
g := pkt.Goocoos[0]
// Verify all 22 data slots are correctly read
for i := 0; i < 22; i++ {
expected := int16(i * 3)
if g.Data1[i] != expected {
t.Errorf("Data1[%d] = %d, want %d", i, g.Data1[i], expected)
}
}
if g.Data2[0] != 0xDEAD {
t.Errorf("Data2[0] = 0x%X, want 0xDEAD", g.Data2[0])
}
if g.Data2[1] != 0xBEEF {
t.Errorf("Data2[1] = 0x%X, want 0xBEEF", g.Data2[1])
}
}
func TestGoocooSerialization_Roundtrip(t *testing.T) {
// Simulate what handleMsgMhfUpdateGuacot does when saving to DB
goocoo := Goocoo{
Index: 1,
Data1: make([]int16, 22),
Data2: []uint32{0x1234, 0x5678},
Name: []byte("MyPoogie"),
}
goocoo.Data1[0] = 5 // outfit type (non-zero = exists)
goocoo.Data1[1] = 100 // some appearance data
goocoo.Data1[21] = -50 // test negative int16
// Serialize (matches handler logic)
bf := byteframe.NewByteFrame()
bf.WriteUint32(goocoo.Index)
for i := range goocoo.Data1 {
bf.WriteInt16(goocoo.Data1[i])
}
for i := range goocoo.Data2 {
bf.WriteUint32(goocoo.Data2[i])
}
bf.WriteUint8(uint8(len(goocoo.Name)))
bf.WriteBytes(goocoo.Name)
// Deserialize and verify
data := bf.Data()
rbf := byteframe.NewByteFrameFromBytes(data)
index := rbf.ReadUint32()
if index != 1 {
t.Errorf("index = %d, want 1", index)
}
data1_0 := rbf.ReadInt16()
if data1_0 != 5 {
t.Errorf("data1[0] = %d, want 5", data1_0)
}
data1_1 := rbf.ReadInt16()
if data1_1 != 100 {
t.Errorf("data1[1] = %d, want 100", data1_1)
}
// Skip to data1[21]
for i := 2; i < 21; i++ {
rbf.ReadInt16()
}
data1_21 := rbf.ReadInt16()
if data1_21 != -50 {
t.Errorf("data1[21] = %d, want -50", data1_21)
}
d2_0 := rbf.ReadUint32()
if d2_0 != 0x1234 {
t.Errorf("data2[0] = 0x%X, want 0x1234", d2_0)
}
d2_1 := rbf.ReadUint32()
if d2_1 != 0x5678 {
t.Errorf("data2[1] = 0x%X, want 0x5678", d2_1)
}
nameLen := rbf.ReadUint8()
if nameLen != 8 {
t.Errorf("nameLen = %d, want 8", nameLen)
}
name := rbf.ReadBytes(uint(nameLen))
if string(name) != "MyPoogie" {
t.Errorf("name = %q, want %q", string(name), "MyPoogie")
}
}
func TestGoocooEntrySize(t *testing.T) {
// Each goocoo entry in the packet should be:
// 4 (index) + 22*2 (data1) + 2*4 (data2) + 1 (name len) + N (name)
// = 4 + 44 + 8 + 1 + N = 57 + N bytes
name := []byte("Test")
expectedSize := 4 + 44 + 8 + 1 + len(name)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0) // index
for i := 0; i < 22; i++ {
bf.WriteInt16(0)
}
bf.WriteUint32(0) // data2[0]
bf.WriteUint32(0) // data2[1]
bf.WriteUint8(uint8(len(name))) // name len
bf.WriteBytes(name)
if len(bf.Data()) != expectedSize {
t.Errorf("entry size = %d bytes, want %d bytes (57 + %d name)", len(bf.Data()), expectedSize, len(name))
}
}

View File

@@ -0,0 +1,537 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// TestMsgMhfSavedataParse tests parsing MsgMhfSavedata
func TestMsgMhfSavedataParse(t *testing.T) {
pkt := FromOpcode(network.MSG_MHF_SAVEDATA)
if pkt == nil {
t.Fatal("FromOpcode(MSG_MHF_SAVEDATA) returned nil")
}
if pkt.Opcode() != network.MSG_MHF_SAVEDATA {
t.Errorf("Opcode() = %s, want MSG_MHF_SAVEDATA", pkt.Opcode())
}
}
// TestMsgMhfLoaddataParse tests parsing MsgMhfLoaddata
func TestMsgMhfLoaddataParse(t *testing.T) {
pkt := FromOpcode(network.MSG_MHF_LOADDATA)
if pkt == nil {
t.Fatal("FromOpcode(MSG_MHF_LOADDATA) returned nil")
}
if pkt.Opcode() != network.MSG_MHF_LOADDATA {
t.Errorf("Opcode() = %s, want MSG_MHF_LOADDATA", pkt.Opcode())
}
}
// TestMsgMhfListMemberOpcode tests MsgMhfListMember Opcode
func TestMsgMhfListMemberOpcode(t *testing.T) {
pkt := &MsgMhfListMember{}
if pkt.Opcode() != network.MSG_MHF_LIST_MEMBER {
t.Errorf("Opcode() = %s, want MSG_MHF_LIST_MEMBER", pkt.Opcode())
}
}
// TestMsgMhfOprMemberOpcode tests MsgMhfOprMember Opcode
func TestMsgMhfOprMemberOpcode(t *testing.T) {
pkt := &MsgMhfOprMember{}
if pkt.Opcode() != network.MSG_MHF_OPR_MEMBER {
t.Errorf("Opcode() = %s, want MSG_MHF_OPR_MEMBER", pkt.Opcode())
}
}
// TestMsgMhfEnumerateDistItemOpcode tests MsgMhfEnumerateDistItem Opcode
func TestMsgMhfEnumerateDistItemOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateDistItem{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_DIST_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_DIST_ITEM", pkt.Opcode())
}
}
// TestMsgMhfApplyDistItemOpcode tests MsgMhfApplyDistItem Opcode
func TestMsgMhfApplyDistItemOpcode(t *testing.T) {
pkt := &MsgMhfApplyDistItem{}
if pkt.Opcode() != network.MSG_MHF_APPLY_DIST_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_APPLY_DIST_ITEM", pkt.Opcode())
}
}
// TestMsgMhfAcquireDistItemOpcode tests MsgMhfAcquireDistItem Opcode
func TestMsgMhfAcquireDistItemOpcode(t *testing.T) {
pkt := &MsgMhfAcquireDistItem{}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_DIST_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_DIST_ITEM", pkt.Opcode())
}
}
// TestMsgMhfGetDistDescriptionOpcode tests MsgMhfGetDistDescription Opcode
func TestMsgMhfGetDistDescriptionOpcode(t *testing.T) {
pkt := &MsgMhfGetDistDescription{}
if pkt.Opcode() != network.MSG_MHF_GET_DIST_DESCRIPTION {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_DIST_DESCRIPTION", pkt.Opcode())
}
}
// TestMsgMhfSendMailOpcode tests MsgMhfSendMail Opcode
func TestMsgMhfSendMailOpcode(t *testing.T) {
pkt := &MsgMhfSendMail{}
if pkt.Opcode() != network.MSG_MHF_SEND_MAIL {
t.Errorf("Opcode() = %s, want MSG_MHF_SEND_MAIL", pkt.Opcode())
}
}
// TestMsgMhfReadMailOpcode tests MsgMhfReadMail Opcode
func TestMsgMhfReadMailOpcode(t *testing.T) {
pkt := &MsgMhfReadMail{}
if pkt.Opcode() != network.MSG_MHF_READ_MAIL {
t.Errorf("Opcode() = %s, want MSG_MHF_READ_MAIL", pkt.Opcode())
}
}
// TestMsgMhfListMailOpcode tests MsgMhfListMail Opcode
func TestMsgMhfListMailOpcode(t *testing.T) {
pkt := &MsgMhfListMail{}
if pkt.Opcode() != network.MSG_MHF_LIST_MAIL {
t.Errorf("Opcode() = %s, want MSG_MHF_LIST_MAIL", pkt.Opcode())
}
}
// TestMsgMhfOprtMailOpcode tests MsgMhfOprtMail Opcode
func TestMsgMhfOprtMailOpcode(t *testing.T) {
pkt := &MsgMhfOprtMail{}
if pkt.Opcode() != network.MSG_MHF_OPRT_MAIL {
t.Errorf("Opcode() = %s, want MSG_MHF_OPRT_MAIL", pkt.Opcode())
}
}
// TestMsgMhfLoadFavoriteQuestOpcode tests MsgMhfLoadFavoriteQuest Opcode
func TestMsgMhfLoadFavoriteQuestOpcode(t *testing.T) {
pkt := &MsgMhfLoadFavoriteQuest{}
if pkt.Opcode() != network.MSG_MHF_LOAD_FAVORITE_QUEST {
t.Errorf("Opcode() = %s, want MSG_MHF_LOAD_FAVORITE_QUEST", pkt.Opcode())
}
}
// TestMsgMhfSaveFavoriteQuestOpcode tests MsgMhfSaveFavoriteQuest Opcode
func TestMsgMhfSaveFavoriteQuestOpcode(t *testing.T) {
pkt := &MsgMhfSaveFavoriteQuest{}
if pkt.Opcode() != network.MSG_MHF_SAVE_FAVORITE_QUEST {
t.Errorf("Opcode() = %s, want MSG_MHF_SAVE_FAVORITE_QUEST", pkt.Opcode())
}
}
// TestMsgMhfRegisterEventOpcode tests MsgMhfRegisterEvent Opcode
func TestMsgMhfRegisterEventOpcode(t *testing.T) {
pkt := &MsgMhfRegisterEvent{}
if pkt.Opcode() != network.MSG_MHF_REGISTER_EVENT {
t.Errorf("Opcode() = %s, want MSG_MHF_REGISTER_EVENT", pkt.Opcode())
}
}
// TestMsgMhfReleaseEventOpcode tests MsgMhfReleaseEvent Opcode
func TestMsgMhfReleaseEventOpcode(t *testing.T) {
pkt := &MsgMhfReleaseEvent{}
if pkt.Opcode() != network.MSG_MHF_RELEASE_EVENT {
t.Errorf("Opcode() = %s, want MSG_MHF_RELEASE_EVENT", pkt.Opcode())
}
}
// TestMsgMhfTransitMessageOpcode tests MsgMhfTransitMessage Opcode
func TestMsgMhfTransitMessageOpcode(t *testing.T) {
pkt := &MsgMhfTransitMessage{}
if pkt.Opcode() != network.MSG_MHF_TRANSIT_MESSAGE {
t.Errorf("Opcode() = %s, want MSG_MHF_TRANSIT_MESSAGE", pkt.Opcode())
}
}
// TestMsgMhfPresentBoxOpcode tests MsgMhfPresentBox Opcode
func TestMsgMhfPresentBoxOpcode(t *testing.T) {
pkt := &MsgMhfPresentBox{}
if pkt.Opcode() != network.MSG_MHF_PRESENT_BOX {
t.Errorf("Opcode() = %s, want MSG_MHF_PRESENT_BOX", pkt.Opcode())
}
}
// TestMsgMhfServerCommandOpcode tests MsgMhfServerCommand Opcode
func TestMsgMhfServerCommandOpcode(t *testing.T) {
pkt := &MsgMhfServerCommand{}
if pkt.Opcode() != network.MSG_MHF_SERVER_COMMAND {
t.Errorf("Opcode() = %s, want MSG_MHF_SERVER_COMMAND", pkt.Opcode())
}
}
// TestMsgMhfShutClientOpcode tests MsgMhfShutClient Opcode
func TestMsgMhfShutClientOpcode(t *testing.T) {
pkt := &MsgMhfShutClient{}
if pkt.Opcode() != network.MSG_MHF_SHUT_CLIENT {
t.Errorf("Opcode() = %s, want MSG_MHF_SHUT_CLIENT", pkt.Opcode())
}
}
// TestMsgMhfAnnounceOpcode tests MsgMhfAnnounce Opcode
func TestMsgMhfAnnounceOpcode(t *testing.T) {
pkt := &MsgMhfAnnounce{}
if pkt.Opcode() != network.MSG_MHF_ANNOUNCE {
t.Errorf("Opcode() = %s, want MSG_MHF_ANNOUNCE", pkt.Opcode())
}
}
// TestMsgMhfSetLoginwindowOpcode tests MsgMhfSetLoginwindow Opcode
func TestMsgMhfSetLoginwindowOpcode(t *testing.T) {
pkt := &MsgMhfSetLoginwindow{}
if pkt.Opcode() != network.MSG_MHF_SET_LOGINWINDOW {
t.Errorf("Opcode() = %s, want MSG_MHF_SET_LOGINWINDOW", pkt.Opcode())
}
}
// TestMsgMhfGetCaUniqueIDOpcode tests MsgMhfGetCaUniqueID Opcode
func TestMsgMhfGetCaUniqueIDOpcode(t *testing.T) {
pkt := &MsgMhfGetCaUniqueID{}
if pkt.Opcode() != network.MSG_MHF_GET_CA_UNIQUE_ID {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_CA_UNIQUE_ID", pkt.Opcode())
}
}
// TestMsgMhfSetCaAchievementOpcode tests MsgMhfSetCaAchievement Opcode
func TestMsgMhfSetCaAchievementOpcode(t *testing.T) {
pkt := &MsgMhfSetCaAchievement{}
if pkt.Opcode() != network.MSG_MHF_SET_CA_ACHIEVEMENT {
t.Errorf("Opcode() = %s, want MSG_MHF_SET_CA_ACHIEVEMENT", pkt.Opcode())
}
}
// TestMsgMhfCaravanMyScoreOpcode tests MsgMhfCaravanMyScore Opcode
func TestMsgMhfCaravanMyScoreOpcode(t *testing.T) {
pkt := &MsgMhfCaravanMyScore{}
if pkt.Opcode() != network.MSG_MHF_CARAVAN_MY_SCORE {
t.Errorf("Opcode() = %s, want MSG_MHF_CARAVAN_MY_SCORE", pkt.Opcode())
}
}
// TestMsgMhfCaravanRankingOpcode tests MsgMhfCaravanRanking Opcode
func TestMsgMhfCaravanRankingOpcode(t *testing.T) {
pkt := &MsgMhfCaravanRanking{}
if pkt.Opcode() != network.MSG_MHF_CARAVAN_RANKING {
t.Errorf("Opcode() = %s, want MSG_MHF_CARAVAN_RANKING", pkt.Opcode())
}
}
// TestMsgMhfCaravanMyRankOpcode tests MsgMhfCaravanMyRank Opcode
func TestMsgMhfCaravanMyRankOpcode(t *testing.T) {
pkt := &MsgMhfCaravanMyRank{}
if pkt.Opcode() != network.MSG_MHF_CARAVAN_MY_RANK {
t.Errorf("Opcode() = %s, want MSG_MHF_CARAVAN_MY_RANK", pkt.Opcode())
}
}
// TestMsgMhfEnumerateQuestOpcode tests MsgMhfEnumerateQuest Opcode
func TestMsgMhfEnumerateQuestOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateQuest{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_QUEST {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_QUEST", pkt.Opcode())
}
}
// TestMsgMhfEnumerateEventOpcode tests MsgMhfEnumerateEvent Opcode
func TestMsgMhfEnumerateEventOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateEvent{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_EVENT {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_EVENT", pkt.Opcode())
}
}
// TestMsgMhfEnumeratePriceOpcode tests MsgMhfEnumeratePrice Opcode
func TestMsgMhfEnumeratePriceOpcode(t *testing.T) {
pkt := &MsgMhfEnumeratePrice{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_PRICE {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_PRICE", pkt.Opcode())
}
}
// TestMsgMhfEnumerateRankingOpcode tests MsgMhfEnumerateRanking Opcode
func TestMsgMhfEnumerateRankingOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateRanking{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_RANKING {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_RANKING", pkt.Opcode())
}
}
// TestMsgMhfEnumerateOrderOpcode tests MsgMhfEnumerateOrder Opcode
func TestMsgMhfEnumerateOrderOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateOrder{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_ORDER {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_ORDER", pkt.Opcode())
}
}
// TestMsgMhfEnumerateShopOpcode tests MsgMhfEnumerateShop Opcode
func TestMsgMhfEnumerateShopOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateShop{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_SHOP {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_SHOP", pkt.Opcode())
}
}
// TestMsgMhfGetExtraInfoOpcode tests MsgMhfGetExtraInfo Opcode
func TestMsgMhfGetExtraInfoOpcode(t *testing.T) {
pkt := &MsgMhfGetExtraInfo{}
if pkt.Opcode() != network.MSG_MHF_GET_EXTRA_INFO {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_EXTRA_INFO", pkt.Opcode())
}
}
// TestMsgMhfEnumerateItemOpcode tests MsgMhfEnumerateItem Opcode
func TestMsgMhfEnumerateItemOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateItem{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_ITEM", pkt.Opcode())
}
}
// TestMsgMhfAcquireItemOpcode tests MsgMhfAcquireItem Opcode
func TestMsgMhfAcquireItemOpcode(t *testing.T) {
pkt := &MsgMhfAcquireItem{}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_ITEM", pkt.Opcode())
}
}
// TestMsgMhfTransferItemOpcode tests MsgMhfTransferItem Opcode
func TestMsgMhfTransferItemOpcode(t *testing.T) {
pkt := &MsgMhfTransferItem{}
if pkt.Opcode() != network.MSG_MHF_TRANSFER_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_TRANSFER_ITEM", pkt.Opcode())
}
}
// TestMsgMhfEntryRookieGuildOpcode tests MsgMhfEntryRookieGuild Opcode
func TestMsgMhfEntryRookieGuildOpcode(t *testing.T) {
pkt := &MsgMhfEntryRookieGuild{}
if pkt.Opcode() != network.MSG_MHF_ENTRY_ROOKIE_GUILD {
t.Errorf("Opcode() = %s, want MSG_MHF_ENTRY_ROOKIE_GUILD", pkt.Opcode())
}
}
// TestMsgCaExchangeItemOpcode tests MsgCaExchangeItem Opcode
func TestMsgCaExchangeItemOpcode(t *testing.T) {
pkt := &MsgCaExchangeItem{}
if pkt.Opcode() != network.MSG_CA_EXCHANGE_ITEM {
t.Errorf("Opcode() = %s, want MSG_CA_EXCHANGE_ITEM", pkt.Opcode())
}
}
// TestMsgMhfEnumerateCampaignOpcode tests MsgMhfEnumerateCampaign Opcode
func TestMsgMhfEnumerateCampaignOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateCampaign{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_CAMPAIGN {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_CAMPAIGN", pkt.Opcode())
}
}
// TestMsgMhfStateCampaignOpcode tests MsgMhfStateCampaign Opcode
func TestMsgMhfStateCampaignOpcode(t *testing.T) {
pkt := &MsgMhfStateCampaign{}
if pkt.Opcode() != network.MSG_MHF_STATE_CAMPAIGN {
t.Errorf("Opcode() = %s, want MSG_MHF_STATE_CAMPAIGN", pkt.Opcode())
}
}
// TestMsgMhfApplyCampaignOpcode tests MsgMhfApplyCampaign Opcode
func TestMsgMhfApplyCampaignOpcode(t *testing.T) {
pkt := &MsgMhfApplyCampaign{}
if pkt.Opcode() != network.MSG_MHF_APPLY_CAMPAIGN {
t.Errorf("Opcode() = %s, want MSG_MHF_APPLY_CAMPAIGN", pkt.Opcode())
}
}
// TestMsgMhfCreateJointOpcode tests MsgMhfCreateJoint Opcode
func TestMsgMhfCreateJointOpcode(t *testing.T) {
pkt := &MsgMhfCreateJoint{}
if pkt.Opcode() != network.MSG_MHF_CREATE_JOINT {
t.Errorf("Opcode() = %s, want MSG_MHF_CREATE_JOINT", pkt.Opcode())
}
}
// TestMsgMhfOperateJointOpcode tests MsgMhfOperateJoint Opcode
func TestMsgMhfOperateJointOpcode(t *testing.T) {
pkt := &MsgMhfOperateJoint{}
if pkt.Opcode() != network.MSG_MHF_OPERATE_JOINT {
t.Errorf("Opcode() = %s, want MSG_MHF_OPERATE_JOINT", pkt.Opcode())
}
}
// TestMsgMhfInfoJointOpcode tests MsgMhfInfoJoint Opcode
func TestMsgMhfInfoJointOpcode(t *testing.T) {
pkt := &MsgMhfInfoJoint{}
if pkt.Opcode() != network.MSG_MHF_INFO_JOINT {
t.Errorf("Opcode() = %s, want MSG_MHF_INFO_JOINT", pkt.Opcode())
}
}
// TestMsgMhfGetCogInfoOpcode tests MsgMhfGetCogInfo Opcode
func TestMsgMhfGetCogInfoOpcode(t *testing.T) {
pkt := &MsgMhfGetCogInfo{}
if pkt.Opcode() != network.MSG_MHF_GET_COG_INFO {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_COG_INFO", pkt.Opcode())
}
}
// TestMsgMhfCheckMonthlyItemOpcode tests MsgMhfCheckMonthlyItem Opcode
func TestMsgMhfCheckMonthlyItemOpcode(t *testing.T) {
pkt := &MsgMhfCheckMonthlyItem{}
if pkt.Opcode() != network.MSG_MHF_CHECK_MONTHLY_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_CHECK_MONTHLY_ITEM", pkt.Opcode())
}
}
// TestMsgMhfAcquireMonthlyItemOpcode tests MsgMhfAcquireMonthlyItem Opcode
func TestMsgMhfAcquireMonthlyItemOpcode(t *testing.T) {
pkt := &MsgMhfAcquireMonthlyItem{}
if pkt.Opcode() != network.MSG_MHF_ACQUIRE_MONTHLY_ITEM {
t.Errorf("Opcode() = %s, want MSG_MHF_ACQUIRE_MONTHLY_ITEM", pkt.Opcode())
}
}
// TestMsgMhfCheckWeeklyStampOpcode tests MsgMhfCheckWeeklyStamp Opcode
func TestMsgMhfCheckWeeklyStampOpcode(t *testing.T) {
pkt := &MsgMhfCheckWeeklyStamp{}
if pkt.Opcode() != network.MSG_MHF_CHECK_WEEKLY_STAMP {
t.Errorf("Opcode() = %s, want MSG_MHF_CHECK_WEEKLY_STAMP", pkt.Opcode())
}
}
// TestMsgMhfExchangeWeeklyStampOpcode tests MsgMhfExchangeWeeklyStamp Opcode
func TestMsgMhfExchangeWeeklyStampOpcode(t *testing.T) {
pkt := &MsgMhfExchangeWeeklyStamp{}
if pkt.Opcode() != network.MSG_MHF_EXCHANGE_WEEKLY_STAMP {
t.Errorf("Opcode() = %s, want MSG_MHF_EXCHANGE_WEEKLY_STAMP", pkt.Opcode())
}
}
// TestMsgMhfCreateMercenaryOpcode tests MsgMhfCreateMercenary Opcode
func TestMsgMhfCreateMercenaryOpcode(t *testing.T) {
pkt := &MsgMhfCreateMercenary{}
if pkt.Opcode() != network.MSG_MHF_CREATE_MERCENARY {
t.Errorf("Opcode() = %s, want MSG_MHF_CREATE_MERCENARY", pkt.Opcode())
}
}
// TestMsgMhfEnumerateMercenaryLogOpcode tests MsgMhfEnumerateMercenaryLog Opcode
func TestMsgMhfEnumerateMercenaryLogOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateMercenaryLog{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_MERCENARY_LOG {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_MERCENARY_LOG", pkt.Opcode())
}
}
// TestMsgMhfEnumerateGuacotOpcode tests MsgMhfEnumerateGuacot Opcode
func TestMsgMhfEnumerateGuacotOpcode(t *testing.T) {
pkt := &MsgMhfEnumerateGuacot{}
if pkt.Opcode() != network.MSG_MHF_ENUMERATE_GUACOT {
t.Errorf("Opcode() = %s, want MSG_MHF_ENUMERATE_GUACOT", pkt.Opcode())
}
}
// TestMsgMhfUpdateGuacotOpcode tests MsgMhfUpdateGuacot Opcode
func TestMsgMhfUpdateGuacotOpcode(t *testing.T) {
pkt := &MsgMhfUpdateGuacot{}
if pkt.Opcode() != network.MSG_MHF_UPDATE_GUACOT {
t.Errorf("Opcode() = %s, want MSG_MHF_UPDATE_GUACOT", pkt.Opcode())
}
}
// TestMsgMhfEnterTournamentQuestOpcode tests MsgMhfEnterTournamentQuest Opcode
func TestMsgMhfEnterTournamentQuestOpcode(t *testing.T) {
pkt := &MsgMhfEnterTournamentQuest{}
if pkt.Opcode() != network.MSG_MHF_ENTER_TOURNAMENT_QUEST {
t.Errorf("Opcode() = %s, want MSG_MHF_ENTER_TOURNAMENT_QUEST", pkt.Opcode())
}
}
// TestMsgMhfResetAchievementOpcode tests MsgMhfResetAchievement Opcode
func TestMsgMhfResetAchievementOpcode(t *testing.T) {
pkt := &MsgMhfResetAchievement{}
if pkt.Opcode() != network.MSG_MHF_RESET_ACHIEVEMENT {
t.Errorf("Opcode() = %s, want MSG_MHF_RESET_ACHIEVEMENT", pkt.Opcode())
}
}
// TestMsgMhfPaymentAchievementOpcode tests MsgMhfPaymentAchievement Opcode
func TestMsgMhfPaymentAchievementOpcode(t *testing.T) {
pkt := &MsgMhfPaymentAchievement{}
if pkt.Opcode() != network.MSG_MHF_PAYMENT_ACHIEVEMENT {
t.Errorf("Opcode() = %s, want MSG_MHF_PAYMENT_ACHIEVEMENT", pkt.Opcode())
}
}
// TestMsgMhfDisplayedAchievementOpcode tests MsgMhfDisplayedAchievement Opcode
func TestMsgMhfDisplayedAchievementOpcode(t *testing.T) {
pkt := &MsgMhfDisplayedAchievement{}
if pkt.Opcode() != network.MSG_MHF_DISPLAYED_ACHIEVEMENT {
t.Errorf("Opcode() = %s, want MSG_MHF_DISPLAYED_ACHIEVEMENT", pkt.Opcode())
}
}
// TestMsgMhfGetBbsSnsStatusOpcode tests MsgMhfGetBbsSnsStatus Opcode
func TestMsgMhfGetBbsSnsStatusOpcode(t *testing.T) {
pkt := &MsgMhfGetBbsSnsStatus{}
if pkt.Opcode() != network.MSG_MHF_GET_BBS_SNS_STATUS {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_BBS_SNS_STATUS", pkt.Opcode())
}
}
// TestMsgMhfApplyBbsArticleOpcode tests MsgMhfApplyBbsArticle Opcode
func TestMsgMhfApplyBbsArticleOpcode(t *testing.T) {
pkt := &MsgMhfApplyBbsArticle{}
if pkt.Opcode() != network.MSG_MHF_APPLY_BBS_ARTICLE {
t.Errorf("Opcode() = %s, want MSG_MHF_APPLY_BBS_ARTICLE", pkt.Opcode())
}
}
// TestMsgMhfGetEtcPointsOpcode tests MsgMhfGetEtcPoints Opcode
func TestMsgMhfGetEtcPointsOpcode(t *testing.T) {
pkt := &MsgMhfGetEtcPoints{}
if pkt.Opcode() != network.MSG_MHF_GET_ETC_POINTS {
t.Errorf("Opcode() = %s, want MSG_MHF_GET_ETC_POINTS", pkt.Opcode())
}
}
// TestMsgMhfUpdateEtcPointOpcode tests MsgMhfUpdateEtcPoint Opcode
func TestMsgMhfUpdateEtcPointOpcode(t *testing.T) {
pkt := &MsgMhfUpdateEtcPoint{}
if pkt.Opcode() != network.MSG_MHF_UPDATE_ETC_POINT {
t.Errorf("Opcode() = %s, want MSG_MHF_UPDATE_ETC_POINT", pkt.Opcode())
}
}
// TestAchievementPacketParse tests simple achievement packet parsing
func TestAchievementPacketParse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(5) // AchievementID
bf.WriteUint16(100) // Unk1
bf.WriteUint16(200) // Unk2
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddAchievement{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AchievementID != 5 {
t.Errorf("AchievementID = %d, want 5", pkt.AchievementID)
}
if pkt.Unk1 != 100 {
t.Errorf("Unk1 = %d, want 100", pkt.Unk1)
}
if pkt.Unk2 != 200 {
t.Errorf("Unk2 = %d, want 200", pkt.Unk2)
}
}

View File

@@ -0,0 +1,301 @@
package mhfpacket
import (
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// callBuildSafe calls Build on the packet, recovering from panics.
// Returns the error from Build, or nil if it panicked (panic is acceptable
// for "Not implemented" stubs).
func callBuildSafe(pkt MHFPacket, bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) (err error, panicked bool) {
defer func() {
if r := recover(); r != nil {
panicked = true
}
}()
err = pkt.Build(bf, ctx)
return err, false
}
// callParseSafe calls Parse on the packet, recovering from panics.
func callParseSafe(pkt MHFPacket, bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) (err error, panicked bool) {
defer func() {
if r := recover(); r != nil {
panicked = true
}
}()
err = pkt.Parse(bf, ctx)
return err, false
}
// TestBuildCoverage_NotImplemented exercises Build() on packet types whose Build
// method is not yet covered. These stubs either return errors.New("NOT IMPLEMENTED")
// or panic("Not implemented"). Both are acceptable outcomes that indicate the
// method was reached.
func TestBuildCoverage_NotImplemented(t *testing.T) {
tests := []struct {
name string
pkt MHFPacket
}{
// msg_ca_exchange_item.go
{"MsgCaExchangeItem", &MsgCaExchangeItem{}},
// msg_head.go
{"MsgHead", &MsgHead{}},
// msg_mhf_acquire_cafe_item.go
{"MsgMhfAcquireCafeItem", &MsgMhfAcquireCafeItem{}},
// msg_mhf_acquire_monthly_item.go
{"MsgMhfAcquireMonthlyItem", &MsgMhfAcquireMonthlyItem{}},
// msg_mhf_acquire_ud_item.go
{"MsgMhfAcquireUdItem", &MsgMhfAcquireUdItem{}},
// msg_mhf_announce.go
{"MsgMhfAnnounce", &MsgMhfAnnounce{}},
// msg_mhf_check_monthly_item.go
{"MsgMhfCheckMonthlyItem", &MsgMhfCheckMonthlyItem{}},
// msg_mhf_check_weekly_stamp.go
{"MsgMhfCheckWeeklyStamp", &MsgMhfCheckWeeklyStamp{}},
// msg_mhf_enumerate_festa_member.go
{"MsgMhfEnumerateFestaMember", &MsgMhfEnumerateFestaMember{}},
// msg_mhf_enumerate_inv_guild.go
{"MsgMhfEnumerateInvGuild", &MsgMhfEnumerateInvGuild{}},
// msg_mhf_enumerate_item.go
{"MsgMhfEnumerateItem", &MsgMhfEnumerateItem{}},
// msg_mhf_enumerate_order.go
{"MsgMhfEnumerateOrder", &MsgMhfEnumerateOrder{}},
// msg_mhf_enumerate_quest.go
{"MsgMhfEnumerateQuest", &MsgMhfEnumerateQuest{}},
// msg_mhf_enumerate_ranking.go
{"MsgMhfEnumerateRanking", &MsgMhfEnumerateRanking{}},
// msg_mhf_enumerate_shop.go
{"MsgMhfEnumerateShop", &MsgMhfEnumerateShop{}},
// msg_mhf_enumerate_warehouse.go
{"MsgMhfEnumerateWarehouse", &MsgMhfEnumerateWarehouse{}},
// msg_mhf_exchange_fpoint_2_item.go
{"MsgMhfExchangeFpoint2Item", &MsgMhfExchangeFpoint2Item{}},
// msg_mhf_exchange_item_2_fpoint.go
{"MsgMhfExchangeItem2Fpoint", &MsgMhfExchangeItem2Fpoint{}},
// msg_mhf_exchange_weekly_stamp.go
{"MsgMhfExchangeWeeklyStamp", &MsgMhfExchangeWeeklyStamp{}},
// msg_mhf_generate_ud_guild_map.go
{"MsgMhfGenerateUdGuildMap", &MsgMhfGenerateUdGuildMap{}},
// msg_mhf_get_boost_time.go
{"MsgMhfGetBoostTime", &MsgMhfGetBoostTime{}},
// msg_mhf_get_boost_time_limit.go
{"MsgMhfGetBoostTimeLimit", &MsgMhfGetBoostTimeLimit{}},
// msg_mhf_get_cafe_duration.go
{"MsgMhfGetCafeDuration", &MsgMhfGetCafeDuration{}},
// msg_mhf_get_cafe_duration_bonus_info.go
{"MsgMhfGetCafeDurationBonusInfo", &MsgMhfGetCafeDurationBonusInfo{}},
// msg_mhf_get_cog_info.go
{"MsgMhfGetCogInfo", &MsgMhfGetCogInfo{}},
// msg_mhf_get_gacha_point.go
{"MsgMhfGetGachaPoint", &MsgMhfGetGachaPoint{}},
// msg_mhf_get_gem_info.go
{"MsgMhfGetGemInfo", &MsgMhfGetGemInfo{}},
// msg_mhf_get_kiju_info.go
{"MsgMhfGetKijuInfo", &MsgMhfGetKijuInfo{}},
// msg_mhf_get_myhouse_info.go
{"MsgMhfGetMyhouseInfo", &MsgMhfGetMyhouseInfo{}},
// msg_mhf_get_notice.go
{"MsgMhfGetNotice", &MsgMhfGetNotice{}},
// msg_mhf_get_tower_info.go
{"MsgMhfGetTowerInfo", &MsgMhfGetTowerInfo{}},
// msg_mhf_get_ud_info.go
{"MsgMhfGetUdInfo", &MsgMhfGetUdInfo{}},
// msg_mhf_get_ud_schedule.go
{"MsgMhfGetUdSchedule", &MsgMhfGetUdSchedule{}},
// msg_mhf_get_weekly_schedule.go
{"MsgMhfGetWeeklySchedule", &MsgMhfGetWeeklySchedule{}},
// msg_mhf_guild_huntdata.go
{"MsgMhfGuildHuntdata", &MsgMhfGuildHuntdata{}},
// msg_mhf_info_joint.go
{"MsgMhfInfoJoint", &MsgMhfInfoJoint{}},
// msg_mhf_load_deco_myset.go
{"MsgMhfLoadDecoMyset", &MsgMhfLoadDecoMyset{}},
// msg_mhf_load_guild_adventure.go
{"MsgMhfLoadGuildAdventure", &MsgMhfLoadGuildAdventure{}},
// msg_mhf_load_guild_cooking.go
{"MsgMhfLoadGuildCooking", &MsgMhfLoadGuildCooking{}},
// msg_mhf_load_hunter_navi.go
{"MsgMhfLoadHunterNavi", &MsgMhfLoadHunterNavi{}},
// msg_mhf_load_otomo_airou.go
{"MsgMhfLoadOtomoAirou", &MsgMhfLoadOtomoAirou{}},
// msg_mhf_load_partner.go
{"MsgMhfLoadPartner", &MsgMhfLoadPartner{}},
// msg_mhf_load_plate_box.go
{"MsgMhfLoadPlateBox", &MsgMhfLoadPlateBox{}},
// msg_mhf_load_plate_data.go
{"MsgMhfLoadPlateData", &MsgMhfLoadPlateData{}},
// msg_mhf_post_notice.go
{"MsgMhfPostNotice", &MsgMhfPostNotice{}},
// msg_mhf_post_tower_info.go
{"MsgMhfPostTowerInfo", &MsgMhfPostTowerInfo{}},
// msg_mhf_reserve10f.go
{"MsgMhfReserve10F", &MsgMhfReserve10F{}},
// msg_mhf_server_command.go
{"MsgMhfServerCommand", &MsgMhfServerCommand{}},
// msg_mhf_set_loginwindow.go
{"MsgMhfSetLoginwindow", &MsgMhfSetLoginwindow{}},
// msg_mhf_shut_client.go
{"MsgMhfShutClient", &MsgMhfShutClient{}},
// msg_mhf_stampcard_stamp.go
{"MsgMhfStampcardStamp", &MsgMhfStampcardStamp{}},
// msg_sys_add_object.go
{"MsgSysAddObject", &MsgSysAddObject{}},
// msg_sys_back_stage.go
{"MsgSysBackStage", &MsgSysBackStage{}},
// msg_sys_cast_binary.go
{"MsgSysCastBinary", &MsgSysCastBinary{}},
// msg_sys_create_semaphore.go
{"MsgSysCreateSemaphore", &MsgSysCreateSemaphore{}},
// msg_sys_create_stage.go
{"MsgSysCreateStage", &MsgSysCreateStage{}},
// msg_sys_del_object.go
{"MsgSysDelObject", &MsgSysDelObject{}},
// msg_sys_disp_object.go
{"MsgSysDispObject", &MsgSysDispObject{}},
// msg_sys_echo.go
{"MsgSysEcho", &MsgSysEcho{}},
// msg_sys_enter_stage.go
{"MsgSysEnterStage", &MsgSysEnterStage{}},
// msg_sys_enumerate_client.go
{"MsgSysEnumerateClient", &MsgSysEnumerateClient{}},
// msg_sys_extend_threshold.go
{"MsgSysExtendThreshold", &MsgSysExtendThreshold{}},
// msg_sys_get_stage_binary.go
{"MsgSysGetStageBinary", &MsgSysGetStageBinary{}},
// msg_sys_hide_object.go
{"MsgSysHideObject", &MsgSysHideObject{}},
// msg_sys_leave_stage.go
{"MsgSysLeaveStage", &MsgSysLeaveStage{}},
// msg_sys_lock_stage.go
{"MsgSysLockStage", &MsgSysLockStage{}},
// msg_sys_login.go
{"MsgSysLogin", &MsgSysLogin{}},
// msg_sys_move_stage.go
{"MsgSysMoveStage", &MsgSysMoveStage{}},
// msg_sys_set_stage_binary.go
{"MsgSysSetStageBinary", &MsgSysSetStageBinary{}},
// msg_sys_set_stage_pass.go
{"MsgSysSetStagePass", &MsgSysSetStagePass{}},
// msg_sys_set_status.go
{"MsgSysSetStatus", &MsgSysSetStatus{}},
// msg_sys_wait_stage_binary.go
{"MsgSysWaitStageBinary", &MsgSysWaitStageBinary{}},
// Reserve files - sys reserves
{"MsgSysReserve01", &MsgSysReserve01{}},
{"MsgSysReserve02", &MsgSysReserve02{}},
{"MsgSysReserve03", &MsgSysReserve03{}},
{"MsgSysReserve04", &MsgSysReserve04{}},
{"MsgSysReserve05", &MsgSysReserve05{}},
{"MsgSysReserve06", &MsgSysReserve06{}},
{"MsgSysReserve07", &MsgSysReserve07{}},
{"MsgSysReserve0C", &MsgSysReserve0C{}},
{"MsgSysReserve0D", &MsgSysReserve0D{}},
{"MsgSysReserve0E", &MsgSysReserve0E{}},
{"MsgSysReserve4A", &MsgSysReserve4A{}},
{"MsgSysReserve4B", &MsgSysReserve4B{}},
{"MsgSysReserve4C", &MsgSysReserve4C{}},
{"MsgSysReserve4D", &MsgSysReserve4D{}},
{"MsgSysReserve4E", &MsgSysReserve4E{}},
{"MsgSysReserve4F", &MsgSysReserve4F{}},
{"MsgSysReserve55", &MsgSysReserve55{}},
{"MsgSysReserve56", &MsgSysReserve56{}},
{"MsgSysReserve57", &MsgSysReserve57{}},
{"MsgSysReserve5C", &MsgSysReserve5C{}},
{"MsgSysReserve5E", &MsgSysReserve5E{}},
{"MsgSysReserve5F", &MsgSysReserve5F{}},
{"MsgSysReserve71", &MsgSysReserve71{}},
{"MsgSysReserve72", &MsgSysReserve72{}},
{"MsgSysReserve73", &MsgSysReserve73{}},
{"MsgSysReserve74", &MsgSysReserve74{}},
{"MsgSysReserve75", &MsgSysReserve75{}},
{"MsgSysReserve76", &MsgSysReserve76{}},
{"MsgSysReserve77", &MsgSysReserve77{}},
{"MsgSysReserve78", &MsgSysReserve78{}},
{"MsgSysReserve79", &MsgSysReserve79{}},
{"MsgSysReserve7A", &MsgSysReserve7A{}},
{"MsgSysReserve7B", &MsgSysReserve7B{}},
{"MsgSysReserve7C", &MsgSysReserve7C{}},
{"MsgSysReserve7E", &MsgSysReserve7E{}},
{"MsgSysReserve180", &MsgSysReserve180{}},
{"MsgSysReserve188", &MsgSysReserve188{}},
{"MsgSysReserve18B", &MsgSysReserve18B{}},
{"MsgSysReserve18E", &MsgSysReserve18E{}},
{"MsgSysReserve18F", &MsgSysReserve18F{}},
{"MsgSysReserve192", &MsgSysReserve192{}},
{"MsgSysReserve193", &MsgSysReserve193{}},
{"MsgSysReserve194", &MsgSysReserve194{}},
{"MsgSysReserve19B", &MsgSysReserve19B{}},
{"MsgSysReserve19E", &MsgSysReserve19E{}},
{"MsgSysReserve19F", &MsgSysReserve19F{}},
{"MsgSysReserve1A4", &MsgSysReserve1A4{}},
{"MsgSysReserve1A6", &MsgSysReserve1A6{}},
{"MsgSysReserve1A7", &MsgSysReserve1A7{}},
{"MsgSysReserve1A8", &MsgSysReserve1A8{}},
{"MsgSysReserve1A9", &MsgSysReserve1A9{}},
{"MsgSysReserve1AA", &MsgSysReserve1AA{}},
{"MsgSysReserve1AB", &MsgSysReserve1AB{}},
{"MsgSysReserve1AC", &MsgSysReserve1AC{}},
{"MsgSysReserve1AD", &MsgSysReserve1AD{}},
{"MsgSysReserve1AE", &MsgSysReserve1AE{}},
{"MsgSysReserve1AF", &MsgSysReserve1AF{}},
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err, panicked := callBuildSafe(tt.pkt, bf, ctx)
if panicked {
// Build panicked with "Not implemented" - this is acceptable
// and still exercises the code path for coverage.
return
}
if err == nil {
// Build succeeded (some packets may have implemented Build)
return
}
// Build returned an error, which is expected for NOT IMPLEMENTED stubs
if err.Error() != "NOT IMPLEMENTED" {
t.Errorf("Build() returned unexpected error: %v", err)
}
})
}
}
// TestParseCoverage_NotImplemented exercises Parse() on packet types whose Parse
// method returns "NOT IMPLEMENTED" and is not yet covered by existing tests.
func TestParseCoverage_NotImplemented(t *testing.T) {
tests := []struct {
name string
pkt MHFPacket
}{
// msg_mhf_acquire_tournament.go - Parse returns NOT IMPLEMENTED
{"MsgMhfAcquireTournament", &MsgMhfAcquireTournament{}},
// msg_mhf_entry_tournament.go - Parse returns NOT IMPLEMENTED
{"MsgMhfEntryTournament", &MsgMhfEntryTournament{}},
// msg_mhf_update_guild.go - Parse returns NOT IMPLEMENTED
{"MsgMhfUpdateGuild", &MsgMhfUpdateGuild{}},
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err, panicked := callParseSafe(tt.pkt, bf, ctx)
if panicked {
return
}
if err == nil {
return
}
if err.Error() != "NOT IMPLEMENTED" {
t.Errorf("Parse() returned unexpected error: %v", err)
}
})
}
}

View File

@@ -0,0 +1,880 @@
package mhfpacket
import (
"bytes"
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// TestParseLargeMsgSysUpdateRightBuild tests Build for MsgSysUpdateRight (no Parse implementation).
func TestParseLargeMsgSysUpdateRightBuild(t *testing.T) {
ctx := &clientctx.ClientContext{}
original := &MsgSysUpdateRight{
ClientRespAckHandle: 0x12345678,
Bitfield: 0xDEADBEEF,
Rights: nil,
UnkSize: 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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.Unk1 != 1 {
t.Errorf("Unk1 = %d, want 1", pkt.Unk1)
}
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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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, nil); 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)
}
}

View File

@@ -0,0 +1,776 @@
package mhfpacket
import (
"bytes"
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// --- 5-stmt packets (medium complexity) ---
// TestParseMediumVoteFesta verifies Parse for MsgMhfVoteFesta.
// Fields: AckHandle(u32), FestaID(u32), GuildID(u32), TrialID(u32)
func TestParseMediumVoteFesta(t *testing.T) {
tests := []struct {
name string
ack uint32
festaID uint32
guildID uint32
trialID uint32
}{
{"typical", 0x11223344, 1, 500, 42},
{"zero", 0, 0, 0, 0},
{"max", 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint32(tt.festaID)
bf.WriteUint32(tt.guildID)
bf.WriteUint32(tt.trialID)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfVoteFesta{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.FestaID != tt.festaID {
t.Errorf("FestaID = %d, want %d", pkt.FestaID, tt.festaID)
}
if pkt.GuildID != tt.guildID {
t.Errorf("GuildID = %d, want %d", pkt.GuildID, tt.guildID)
}
if pkt.TrialID != tt.trialID {
t.Errorf("TrialID = %d, want %d", pkt.TrialID, tt.trialID)
}
})
}
}
// TestParseMediumAcquireSemaphore verifies Parse for MsgSysAcquireSemaphore.
// Fields: AckHandle(u32), SemaphoreIDLength(u8), SemaphoreID(string via bfutil.UpToNull)
func TestParseMediumAcquireSemaphore(t *testing.T) {
tests := []struct {
name string
ack uint32
semaphoreID string
}{
{"typical", 0xAABBCCDD, "quest_semaphore"},
{"short", 1, "s"},
{"empty", 0, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
// SemaphoreIDLength includes the null terminator in the read
idBytes := []byte(tt.semaphoreID)
idBytes = append(idBytes, 0x00) // null terminator
bf.WriteUint8(uint8(len(idBytes)))
bf.WriteBytes(idBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysAcquireSemaphore{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.SemaphoreID != tt.semaphoreID {
t.Errorf("SemaphoreID = %q, want %q", pkt.SemaphoreID, tt.semaphoreID)
}
})
}
}
// TestParseMediumCheckSemaphore verifies Parse for MsgSysCheckSemaphore.
// Fields: AckHandle(u32), semaphoreIDLength(u8), SemaphoreID(string via bfutil.UpToNull)
func TestParseMediumCheckSemaphore(t *testing.T) {
tests := []struct {
name string
ack uint32
semaphoreID string
}{
{"typical", 0x12345678, "global_semaphore"},
{"short id", 42, "x"},
{"empty id", 0, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
idBytes := []byte(tt.semaphoreID)
idBytes = append(idBytes, 0x00)
bf.WriteUint8(uint8(len(idBytes)))
bf.WriteBytes(idBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCheckSemaphore{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.SemaphoreID != tt.semaphoreID {
t.Errorf("SemaphoreID = %q, want %q", pkt.SemaphoreID, tt.semaphoreID)
}
})
}
}
// TestParseMediumGetUserBinary verifies Parse for MsgSysGetUserBinary.
// Fields: AckHandle(u32), CharID(u32), BinaryType(u8)
func TestParseMediumGetUserBinary(t *testing.T) {
tests := []struct {
name string
ack uint32
charID uint32
binaryType uint8
}{
{"typical", 0xDEADBEEF, 12345, 1},
{"zero", 0, 0, 0},
{"max", 0xFFFFFFFF, 0xFFFFFFFF, 255},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint32(tt.charID)
bf.WriteUint8(tt.binaryType)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysGetUserBinary{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.CharID != tt.charID {
t.Errorf("CharID = %d, want %d", pkt.CharID, tt.charID)
}
if pkt.BinaryType != tt.binaryType {
t.Errorf("BinaryType = %d, want %d", pkt.BinaryType, tt.binaryType)
}
})
}
}
// TestParseMediumSetObjectBinary verifies Parse for MsgSysSetObjectBinary.
// Fields: ObjID(u32), DataSize(u16), RawDataPayload([]byte of DataSize)
func TestParseMediumSetObjectBinary(t *testing.T) {
tests := []struct {
name string
objID uint32
payload []byte
}{
{"typical", 42, []byte{0x01, 0x02, 0x03, 0x04}},
{"empty", 0, []byte{}},
{"large", 0xCAFEBABE, []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.objID)
bf.WriteUint16(uint16(len(tt.payload)))
bf.WriteBytes(tt.payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysSetObjectBinary{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.ObjID != tt.objID {
t.Errorf("ObjID = %d, want %d", pkt.ObjID, tt.objID)
}
if pkt.DataSize != uint16(len(tt.payload)) {
t.Errorf("DataSize = %d, want %d", pkt.DataSize, len(tt.payload))
}
if !bytes.Equal(pkt.RawDataPayload, tt.payload) {
t.Errorf("RawDataPayload = %v, want %v", pkt.RawDataPayload, tt.payload)
}
})
}
}
// TestParseMediumSetUserBinary verifies Parse for MsgSysSetUserBinary.
// Fields: BinaryType(u8), DataSize(u16), RawDataPayload([]byte of DataSize)
func TestParseMediumSetUserBinary(t *testing.T) {
tests := []struct {
name string
binaryType uint8
payload []byte
}{
{"typical", 1, []byte{0xDE, 0xAD, 0xBE, 0xEF}},
{"empty", 0, []byte{}},
{"max type", 255, []byte{0x01}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(tt.binaryType)
bf.WriteUint16(uint16(len(tt.payload)))
bf.WriteBytes(tt.payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysSetUserBinary{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.BinaryType != tt.binaryType {
t.Errorf("BinaryType = %d, want %d", pkt.BinaryType, tt.binaryType)
}
if pkt.DataSize != uint16(len(tt.payload)) {
t.Errorf("DataSize = %d, want %d", pkt.DataSize, len(tt.payload))
}
if !bytes.Equal(pkt.RawDataPayload, tt.payload) {
t.Errorf("RawDataPayload = %v, want %v", pkt.RawDataPayload, tt.payload)
}
})
}
}
// --- 4-stmt packets ---
// TestParseMediumGetUdRanking verifies Parse for MsgMhfGetUdRanking.
// Fields: AckHandle(u32), Unk0(u8)
func TestParseMediumGetUdRanking(t *testing.T) {
tests := []struct {
name string
ack uint32
unk0 uint8
}{
{"typical", 0x11223344, 5},
{"zero", 0, 0},
{"max", 0xFFFFFFFF, 255},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint8(tt.unk0)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetUdRanking{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
})
}
}
// TestParseMediumGetUdTacticsRanking verifies Parse for MsgMhfGetUdTacticsRanking.
// Fields: AckHandle(u32), GuildID(u32)
func TestParseMediumGetUdTacticsRanking(t *testing.T) {
tests := []struct {
name string
ack uint32
guildID uint32
}{
{"typical", 0xAABBCCDD, 500},
{"zero", 0, 0},
{"max", 0xFFFFFFFF, 0xFFFFFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint32(tt.guildID)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetUdTacticsRanking{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if pkt.GuildID != tt.guildID {
t.Errorf("GuildID = %d, want %d", pkt.GuildID, tt.guildID)
}
})
}
}
// TestParseMediumRegistGuildTresure verifies Parse for MsgMhfRegistGuildTresure.
// Fields: AckHandle(u32), DataLen(u16), Data([]byte), trailing u32 (discarded)
func TestParseMediumRegistGuildTresure(t *testing.T) {
tests := []struct {
name string
ack uint32
data []byte
}{
{"typical", 0x12345678, []byte{0x01, 0x02, 0x03}},
{"empty data", 1, []byte{}},
{"larger data", 0xDEADBEEF, []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ack)
bf.WriteUint16(uint16(len(tt.data)))
bf.WriteBytes(tt.data)
bf.WriteUint32(0) // trailing uint32 that is read and discarded
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfRegistGuildTresure{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, tt.ack)
}
if !bytes.Equal(pkt.Data, tt.data) {
t.Errorf("Data = %v, want %v", pkt.Data, tt.data)
}
})
}
}
// TestParseMediumUpdateMyhouseInfo verifies Parse for MsgMhfUpdateMyhouseInfo.
// Fields: AckHandle(u32), Unk0([]byte of 0x16A bytes)
func TestParseMediumUpdateMyhouseInfo(t *testing.T) {
t.Run("typical", func(t *testing.T) {
bf := byteframe.NewByteFrame()
ack := uint32(0xCAFEBABE)
bf.WriteUint32(ack)
// 0x16A = 362 bytes
payload := make([]byte, 0x16A)
for i := range payload {
payload[i] = byte(i % 256)
}
bf.WriteBytes(payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateMyhouseInfo{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
if len(pkt.Data) != 0x16A {
t.Errorf("Unk0 length = %d, want %d", len(pkt.Data), 0x16A)
}
if !bytes.Equal(pkt.Data, payload) {
t.Error("Unk0 content mismatch")
}
})
t.Run("zero values", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0)
bf.WriteBytes(make([]byte, 0x16A))
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateMyhouseInfo{}
if err := pkt.Parse(bf, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0 {
t.Errorf("AckHandle = 0x%X, want 0", pkt.AckHandle)
}
if len(pkt.Data) != 0x16A {
t.Errorf("Unk0 length = %d, want %d", len(pkt.Data), 0x16A)
}
})
}
// --- 3-stmt packets (AckHandle-only Parse) ---
// TestParseMediumAckHandleOnlyBatch tests Parse for all 3-stmt packets that only
// read a single AckHandle uint32. These are verified to parse correctly and
// return the expected AckHandle value.
func TestParseMediumAckHandleOnlyBatch(t *testing.T) {
packets := []struct {
name string
pkt MHFPacket
// getAck extracts the AckHandle from the parsed packet
getAck func() uint32
}{
{
"MsgMhfGetUdBonusQuestInfo",
&MsgMhfGetUdBonusQuestInfo{},
nil,
},
{
"MsgMhfGetUdDailyPresentList",
&MsgMhfGetUdDailyPresentList{},
nil,
},
{
"MsgMhfGetUdGuildMapInfo",
&MsgMhfGetUdGuildMapInfo{},
nil,
},
{
"MsgMhfGetUdMonsterPoint",
&MsgMhfGetUdMonsterPoint{},
nil,
},
{
"MsgMhfGetUdMyRanking",
&MsgMhfGetUdMyRanking{},
nil,
},
{
"MsgMhfGetUdNormaPresentList",
&MsgMhfGetUdNormaPresentList{},
nil,
},
{
"MsgMhfGetUdRankingRewardList",
&MsgMhfGetUdRankingRewardList{},
nil,
},
{
"MsgMhfGetUdSelectedColorInfo",
&MsgMhfGetUdSelectedColorInfo{},
nil,
},
{
"MsgMhfGetUdShopCoin",
&MsgMhfGetUdShopCoin{},
nil,
},
{
"MsgMhfGetUdTacticsBonusQuest",
&MsgMhfGetUdTacticsBonusQuest{},
nil,
},
{
"MsgMhfGetUdTacticsFirstQuestBonus",
&MsgMhfGetUdTacticsFirstQuestBonus{},
nil,
},
{
"MsgMhfGetUdTacticsFollower",
&MsgMhfGetUdTacticsFollower{},
nil,
},
{
"MsgMhfGetUdTacticsLog",
&MsgMhfGetUdTacticsLog{},
nil,
},
{
"MsgMhfGetUdTacticsPoint",
&MsgMhfGetUdTacticsPoint{},
nil,
},
{
"MsgMhfGetUdTacticsRewardList",
&MsgMhfGetUdTacticsRewardList{},
nil,
},
{
"MsgMhfReceiveCafeDurationBonus",
&MsgMhfReceiveCafeDurationBonus{},
nil,
},
{
"MsgSysDeleteSemaphore",
&MsgSysDeleteSemaphore{},
nil,
},
{
"MsgSysReleaseSemaphore",
&MsgSysReleaseSemaphore{},
nil,
},
}
ctx := &clientctx.ClientContext{}
ackValues := []uint32{0x12345678, 0, 0xFFFFFFFF, 0xDEADBEEF}
for _, tc := range packets {
for _, ackVal := range ackValues {
t.Run(tc.name+"/ack_"+ackHex(ackVal), func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(ackVal)
bf.Seek(0, io.SeekStart)
err := tc.pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
})
}
}
}
// TestParseMediumAckHandleOnlyVerifyValues tests each 3-stmt AckHandle-only
// packet individually, verifying that the AckHandle field is correctly populated.
func TestParseMediumAckHandleOnlyVerifyValues(t *testing.T) {
ctx := &clientctx.ClientContext{}
ack := uint32(0xCAFEBABE)
makeFrame := func() *byteframe.ByteFrame {
bf := byteframe.NewByteFrame()
bf.WriteUint32(ack)
bf.Seek(0, io.SeekStart)
return bf
}
t.Run("MsgMhfGetUdBonusQuestInfo", func(t *testing.T) {
pkt := &MsgMhfGetUdBonusQuestInfo{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdDailyPresentList", func(t *testing.T) {
pkt := &MsgMhfGetUdDailyPresentList{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdGuildMapInfo", func(t *testing.T) {
pkt := &MsgMhfGetUdGuildMapInfo{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdMonsterPoint", func(t *testing.T) {
pkt := &MsgMhfGetUdMonsterPoint{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdMyRanking", func(t *testing.T) {
pkt := &MsgMhfGetUdMyRanking{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdNormaPresentList", func(t *testing.T) {
pkt := &MsgMhfGetUdNormaPresentList{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdRankingRewardList", func(t *testing.T) {
pkt := &MsgMhfGetUdRankingRewardList{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdSelectedColorInfo", func(t *testing.T) {
pkt := &MsgMhfGetUdSelectedColorInfo{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdShopCoin", func(t *testing.T) {
pkt := &MsgMhfGetUdShopCoin{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsBonusQuest", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsBonusQuest{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsFirstQuestBonus", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsFirstQuestBonus{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsFollower", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsFollower{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsLog", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsLog{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsPoint", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsPoint{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfGetUdTacticsRewardList", func(t *testing.T) {
pkt := &MsgMhfGetUdTacticsRewardList{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgMhfReceiveCafeDurationBonus", func(t *testing.T) {
pkt := &MsgMhfReceiveCafeDurationBonus{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
t.Run("MsgSysDeleteSemaphore", func(t *testing.T) {
pkt := &MsgSysDeleteSemaphore{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.SemaphoreID != ack {
t.Errorf("SemaphoreID = 0x%X, want 0x%X", pkt.SemaphoreID, ack)
}
})
t.Run("MsgSysReleaseSemaphore", func(t *testing.T) {
pkt := &MsgSysReleaseSemaphore{}
if err := pkt.Parse(makeFrame(), ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != ack {
t.Errorf("AckHandle = 0x%X, want 0x%X", pkt.AckHandle, ack)
}
})
}
// TestParseMediumDeleteUser verifies that MsgSysDeleteUser.Parse returns
// NOT IMPLEMENTED error (Parse is not implemented, only Build is).
func TestParseMediumDeleteUser(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(12345)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysDeleteUser{}
err := pkt.Parse(bf, nil)
if err == nil {
t.Fatal("Parse() should return error for NOT IMPLEMENTED")
}
if err.Error() != "NOT IMPLEMENTED" {
t.Errorf("Parse() error = %q, want %q", err.Error(), "NOT IMPLEMENTED")
}
}
// TestParseMediumInsertUser verifies that MsgSysInsertUser.Parse returns
// NOT IMPLEMENTED error (Parse is not implemented, only Build is).
func TestParseMediumInsertUser(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(12345)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysInsertUser{}
err := pkt.Parse(bf, nil)
if err == nil {
t.Fatal("Parse() should return error for NOT IMPLEMENTED")
}
if err.Error() != "NOT IMPLEMENTED" {
t.Errorf("Parse() error = %q, want %q", err.Error(), "NOT IMPLEMENTED")
}
}
// ackHex returns a hex string for a uint32 ack value, used for test naming.
func ackHex(v uint32) string {
const hex = "0123456789ABCDEF"
buf := make([]byte, 8)
for i := 7; i >= 0; i-- {
buf[i] = hex[v&0xF]
v >>= 4
}
return string(buf)
}

View File

@@ -0,0 +1,216 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// TestParseSmallNotImplemented tests Parse for packets whose Parse method returns
// "NOT IMPLEMENTED". We verify that Parse returns a non-nil error and does not panic.
func TestParseSmallNotImplemented(t *testing.T) {
packets := []struct {
name string
pkt MHFPacket
}{
// MHF packets - NOT IMPLEMENTED
{"MsgMhfAcceptReadReward", &MsgMhfAcceptReadReward{}},
{"MsgMhfAddRewardSongCount", &MsgMhfAddRewardSongCount{}},
{"MsgMhfDebugPostValue", &MsgMhfDebugPostValue{}},
{"MsgMhfEnterTournamentQuest", &MsgMhfEnterTournamentQuest{}},
{"MsgMhfGetCaAchievementHist", &MsgMhfGetCaAchievementHist{}},
{"MsgMhfGetCaUniqueID", &MsgMhfGetCaUniqueID{}},
{"MsgMhfGetDailyMissionMaster", &MsgMhfGetDailyMissionMaster{}},
{"MsgMhfGetDailyMissionPersonal", &MsgMhfGetDailyMissionPersonal{}},
{"MsgMhfGetExtraInfo", &MsgMhfGetExtraInfo{}},
{"MsgMhfGetRestrictionEvent", &MsgMhfGetRestrictionEvent{}},
{"MsgMhfKickExportForce", &MsgMhfKickExportForce{}},
{"MsgMhfPaymentAchievement", &MsgMhfPaymentAchievement{}},
{"MsgMhfPostRyoudama", &MsgMhfPostRyoudama{}},
{"MsgMhfRegistSpabiTime", &MsgMhfRegistSpabiTime{}},
{"MsgMhfResetAchievement", &MsgMhfResetAchievement{}},
{"MsgMhfResetTitle", &MsgMhfResetTitle{}},
{"MsgMhfSetCaAchievement", &MsgMhfSetCaAchievement{}},
{"MsgMhfSetDailyMissionPersonal", &MsgMhfSetDailyMissionPersonal{}},
{"MsgMhfSetUdTacticsFollower", &MsgMhfSetUdTacticsFollower{}},
{"MsgMhfStampcardPrize", &MsgMhfStampcardPrize{}},
{"MsgMhfUpdateForceGuildRank", &MsgMhfUpdateForceGuildRank{}},
{"MsgMhfUseUdShopCoin", &MsgMhfUseUdShopCoin{}},
// SYS packets - NOT IMPLEMENTED
{"MsgSysAuthData", &MsgSysAuthData{}},
{"MsgSysAuthQuery", &MsgSysAuthQuery{}},
{"MsgSysAuthTerminal", &MsgSysAuthTerminal{}},
{"MsgSysCloseMutex", &MsgSysCloseMutex{}},
{"MsgSysCollectBinary", &MsgSysCollectBinary{}},
{"MsgSysCreateMutex", &MsgSysCreateMutex{}},
{"MsgSysCreateOpenMutex", &MsgSysCreateOpenMutex{}},
{"MsgSysDeleteMutex", &MsgSysDeleteMutex{}},
{"MsgSysEnumlobby", &MsgSysEnumlobby{}},
{"MsgSysEnumuser", &MsgSysEnumuser{}},
{"MsgSysGetObjectBinary", &MsgSysGetObjectBinary{}},
{"MsgSysGetState", &MsgSysGetState{}},
{"MsgSysInfokyserver", &MsgSysInfokyserver{}},
{"MsgSysOpenMutex", &MsgSysOpenMutex{}},
{"MsgSysRotateObject", &MsgSysRotateObject{}},
{"MsgSysSerialize", &MsgSysSerialize{}},
{"MsgSysTransBinary", &MsgSysTransBinary{}},
}
ctx := &clientctx.ClientContext{}
for _, tc := range packets {
t.Run(tc.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
// Write some padding bytes so Parse has data available if it tries to read.
bf.WriteUint32(0)
bf.Seek(0, io.SeekStart)
err := tc.pkt.Parse(bf, ctx)
if err == nil {
t.Fatalf("Parse() expected error for NOT IMPLEMENTED packet, got nil")
}
if err.Error() != "NOT IMPLEMENTED" {
t.Fatalf("Parse() error = %q, want %q", err.Error(), "NOT IMPLEMENTED")
}
})
}
}
// TestParseSmallNoData tests Parse for packets with no fields that return nil.
func TestParseSmallNoData(t *testing.T) {
packets := []struct {
name string
pkt MHFPacket
}{
{"MsgSysCleanupObject", &MsgSysCleanupObject{}},
{"MsgSysUnreserveStage", &MsgSysUnreserveStage{}},
}
ctx := &clientctx.ClientContext{}
for _, tc := range packets {
t.Run(tc.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
err := tc.pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v, want nil", err)
}
})
}
}
// TestParseSmallLogout tests Parse for MsgSysLogout which reads a single uint8 field.
func TestParseSmallLogout(t *testing.T) {
tests := []struct {
name string
unk0 uint8
}{
{"hardcoded 1", 1},
{"zero", 0},
{"max", 255},
}
ctx := &clientctx.ClientContext{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(tt.unk0)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLogout{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
})
}
}
// TestParseSmallEnumerateHouse tests Parse for MsgMhfEnumerateHouse which reads
// AckHandle, CharID, Method, Unk, lenName, and optional Name.
func TestParseSmallEnumerateHouse(t *testing.T) {
ctx := &clientctx.ClientContext{}
t.Run("no name", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x11223344) // AckHandle
bf.WriteUint32(0xDEADBEEF) // CharID
bf.WriteUint8(2) // Method
bf.WriteUint16(100) // Unk
bf.WriteUint8(0) // lenName = 0 (no name)
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateHouse{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x11223344 {
t.Errorf("AckHandle = 0x%X, want 0x11223344", pkt.AckHandle)
}
if pkt.CharID != 0xDEADBEEF {
t.Errorf("CharID = 0x%X, want 0xDEADBEEF", pkt.CharID)
}
if pkt.Method != 2 {
t.Errorf("Method = %d, want 2", pkt.Method)
}
if pkt.Name != "" {
t.Errorf("Name = %q, want empty", pkt.Name)
}
})
t.Run("with name", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(42) // CharID
bf.WriteUint8(1) // Method
bf.WriteUint16(200) // Unk
// The name is SJIS null-terminated bytes. Use ASCII-compatible bytes.
nameBytes := []byte("Test\x00")
bf.WriteUint8(uint8(len(nameBytes))) // lenName > 0
bf.WriteBytes(nameBytes) // null-terminated name
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateHouse{}
err := pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 1 {
t.Errorf("AckHandle = %d, want 1", pkt.AckHandle)
}
if pkt.CharID != 42 {
t.Errorf("CharID = %d, want 42", pkt.CharID)
}
if pkt.Method != 1 {
t.Errorf("Method = %d, want 1", pkt.Method)
}
if pkt.Name != "Test" {
t.Errorf("Name = %q, want %q", pkt.Name, "Test")
}
})
}
// TestParseSmallNotImplementedDoesNotPanic ensures that calling Parse on NOT IMPLEMENTED
// packets with a nil ClientContext does not cause a nil pointer dereference panic.
func TestParseSmallNotImplementedDoesNotPanic(t *testing.T) {
packets := []MHFPacket{
&MsgMhfAcceptReadReward{},
&MsgSysAuthData{},
&MsgSysSerialize{},
}
for _, pkt := range packets {
t.Run("nil_ctx", func(t *testing.T) {
bf := byteframe.NewByteFrame()
err := pkt.Parse(bf, nil)
if err == nil {
t.Fatal("expected error, got nil")
}
})
}
}

View File

@@ -0,0 +1,218 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network/clientctx"
)
// TestMsgMhfGetAchievementParse tests MsgMhfGetAchievement parsing
func TestMsgMhfGetAchievementDetailedParse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(54321) // CharID
bf.WriteUint32(99999) // Unk1
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetAchievement{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != 0x12345678 {
t.Errorf("AckHandle = 0x%X, want 0x12345678", pkt.AckHandle)
}
if pkt.CharID != 54321 {
t.Errorf("CharID = %d, want 54321", pkt.CharID)
}
}
// TestMsgMhfAddAchievementDetailedParse tests MsgMhfAddAchievement parsing
func TestMsgMhfAddAchievementDetailedParse(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(42) // AchievementID
bf.WriteUint16(12345) // Unk1
bf.WriteUint16(0xFFFF) // Unk2 - max value
bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddAchievement{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AchievementID != 42 {
t.Errorf("AchievementID = %d, want 42", pkt.AchievementID)
}
if pkt.Unk1 != 12345 {
t.Errorf("Unk1 = %d, want 12345", pkt.Unk1)
}
if pkt.Unk2 != 0xFFFF {
t.Errorf("Unk2 = %d, want 65535", pkt.Unk2)
}
}
// TestMsgSysCastBinaryDetailedParse tests MsgSysCastBinary parsing with various payloads
func TestMsgSysCastBinaryDetailedParse(t *testing.T) {
tests := []struct {
name string
unk uint32
broadcastType uint8
messageType uint8
payload []byte
}{
{"empty payload", 0, 1, 2, []byte{}},
{"typical payload", 0x006400C8, 0x10, 0x20, []byte{0x01, 0x02, 0x03}},
{"chat message", 0, 0x01, 0x01, []byte("Hello, World!")},
{"binary data", 0xFFFFFFFF, 0xFF, 0xFF, []byte{0xDE, 0xAD, 0xBE, 0xEF}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.unk)
bf.WriteUint8(tt.broadcastType)
bf.WriteUint8(tt.messageType)
bf.WriteUint16(uint16(len(tt.payload)))
bf.WriteBytes(tt.payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCastBinary{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk != tt.unk {
t.Errorf("Unk = %d, want %d", pkt.Unk, tt.unk)
}
if pkt.BroadcastType != tt.broadcastType {
t.Errorf("BroadcastType = %d, want %d", pkt.BroadcastType, tt.broadcastType)
}
if pkt.MessageType != tt.messageType {
t.Errorf("MessageType = %d, want %d", pkt.MessageType, tt.messageType)
}
if len(pkt.RawDataPayload) != len(tt.payload) {
t.Errorf("RawDataPayload len = %d, want %d", len(pkt.RawDataPayload), len(tt.payload))
}
})
}
}
// TestMsgSysLogoutParse tests MsgSysLogout parsing
func TestMsgSysLogoutDetailedParse(t *testing.T) {
tests := []struct {
unk0 uint8
}{
{0},
{1},
{100},
{255},
}
for _, tt := range tests {
bf := byteframe.NewByteFrame()
bf.WriteUint8(tt.unk0)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLogout{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
}
}
// TestMsgSysBackStageParse tests MsgSysBackStage parsing
func TestMsgSysBackStageDetailedParse(t *testing.T) {
tests := []struct {
ackHandle uint32
}{
{0},
{1},
{0x12345678},
{0xFFFFFFFF},
}
for _, tt := range tests {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysBackStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if 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)
}
}
}
// TestMsgSysPingParse tests MsgSysPing parsing
func TestMsgSysPingDetailedParse(t *testing.T) {
tests := []struct {
ackHandle uint32
}{
{0},
{0xABCDEF12},
{0xFFFFFFFF},
}
for _, tt := range tests {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysPing{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if 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)
}
}
}
// TestMsgSysTimeParse tests MsgSysTime parsing
func TestMsgSysTimeDetailedParse(t *testing.T) {
tests := []struct {
getRemoteTime bool
timestamp uint32
}{
{false, 0},
{true, 1577836800}, // 2020-01-01 00:00:00
{false, 0xFFFFFFFF},
}
for _, tt := range tests {
bf := byteframe.NewByteFrame()
bf.WriteBool(tt.getRemoteTime)
bf.WriteUint32(tt.timestamp)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysTime{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.GetRemoteTime != tt.getRemoteTime {
t.Errorf("GetRemoteTime = %v, want %v", pkt.GetRemoteTime, tt.getRemoteTime)
}
if pkt.Timestamp != tt.timestamp {
t.Errorf("Timestamp = %d, want %d", pkt.Timestamp, tt.timestamp)
}
}
}

View File

@@ -0,0 +1,310 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func TestMsgSysAckRoundTrip(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
isBufferResponse bool
errorCode uint8
ackData []byte
}{
{
name: "simple non-buffer response",
ackHandle: 1,
isBufferResponse: false,
errorCode: 0,
ackData: []byte{0x00, 0x00, 0x00, 0x00},
},
{
name: "buffer response with small data",
ackHandle: 0x12345678,
isBufferResponse: true,
errorCode: 0,
ackData: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
},
{
name: "error response",
ackHandle: 100,
isBufferResponse: false,
errorCode: 1,
ackData: []byte{0xDE, 0xAD, 0xBE, 0xEF},
},
{
name: "empty buffer response",
ackHandle: 999,
isBufferResponse: true,
errorCode: 0,
ackData: []byte{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
original := &MsgSysAck{
AckHandle: tt.ackHandle,
IsBufferResponse: tt.isBufferResponse,
ErrorCode: tt.errorCode,
AckData: tt.ackData,
}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, io.SeekStart)
parsed := &MsgSysAck{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Compare
if parsed.AckHandle != original.AckHandle {
t.Errorf("AckHandle = %d, want %d", parsed.AckHandle, original.AckHandle)
}
if parsed.IsBufferResponse != original.IsBufferResponse {
t.Errorf("IsBufferResponse = %v, want %v", parsed.IsBufferResponse, original.IsBufferResponse)
}
if parsed.ErrorCode != original.ErrorCode {
t.Errorf("ErrorCode = %d, want %d", parsed.ErrorCode, original.ErrorCode)
}
})
}
}
func TestMsgSysAckLargePayload(t *testing.T) {
// Test with payload larger than 0xFFFF to trigger extended size field
largeData := make([]byte, 0x10000) // 65536 bytes
for i := range largeData {
largeData[i] = byte(i % 256)
}
original := &MsgSysAck{
AckHandle: 1,
IsBufferResponse: true,
ErrorCode: 0,
AckData: largeData,
}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Parse
bf.Seek(0, io.SeekStart)
parsed := &MsgSysAck{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if len(parsed.AckData) != len(largeData) {
t.Errorf("AckData len = %d, want %d", len(parsed.AckData), len(largeData))
}
}
func TestMsgSysAckOpcode(t *testing.T) {
pkt := &MsgSysAck{}
if pkt.Opcode() != network.MSG_SYS_ACK {
t.Errorf("Opcode() = %s, want MSG_SYS_ACK", pkt.Opcode())
}
}
func TestMsgSysNopRoundTrip(t *testing.T) {
original := &MsgSysNop{}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Should write no data
if len(bf.Data()) != 0 {
t.Errorf("MsgSysNop.Build() wrote %d bytes, want 0", len(bf.Data()))
}
// Parse (from empty buffer)
parsed := &MsgSysNop{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
}
func TestMsgSysNopOpcode(t *testing.T) {
pkt := &MsgSysNop{}
if pkt.Opcode() != network.MSG_SYS_NOP {
t.Errorf("Opcode() = %s, want MSG_SYS_NOP", pkt.Opcode())
}
}
func TestMsgSysEndRoundTrip(t *testing.T) {
original := &MsgSysEnd{}
ctx := &clientctx.ClientContext{}
// Build
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
// Should write no data
if len(bf.Data()) != 0 {
t.Errorf("MsgSysEnd.Build() wrote %d bytes, want 0", len(bf.Data()))
}
// Parse (from empty buffer)
parsed := &MsgSysEnd{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
}
func TestMsgSysEndOpcode(t *testing.T) {
pkt := &MsgSysEnd{}
if pkt.Opcode() != network.MSG_SYS_END {
t.Errorf("Opcode() = %s, want MSG_SYS_END", pkt.Opcode())
}
}
func TestMsgSysAckNonBufferResponse(t *testing.T) {
// Non-buffer response should always read/write 4 bytes of data
original := &MsgSysAck{
AckHandle: 1,
IsBufferResponse: false,
ErrorCode: 0,
AckData: []byte{0xAA, 0xBB, 0xCC, 0xDD},
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
bf.Seek(0, io.SeekStart)
parsed := &MsgSysAck{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Non-buffer response should have exactly 4 bytes of data
if len(parsed.AckData) != 4 {
t.Errorf("Non-buffer AckData len = %d, want 4", len(parsed.AckData))
}
}
func TestMsgSysAckNonBufferShortData(t *testing.T) {
// Non-buffer response with short data should pad to 4 bytes
original := &MsgSysAck{
AckHandle: 1,
IsBufferResponse: false,
ErrorCode: 0,
AckData: []byte{0x01}, // Only 1 byte
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
err := original.Build(bf, ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
bf.Seek(0, io.SeekStart)
parsed := &MsgSysAck{}
err = parsed.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Should still read 4 bytes
if len(parsed.AckData) != 4 {
t.Errorf("AckData len = %d, want 4", len(parsed.AckData))
}
}
func TestMsgSysAckBuildFormat(t *testing.T) {
pkt := &MsgSysAck{
AckHandle: 0x12345678,
IsBufferResponse: true,
ErrorCode: 0x55,
AckData: []byte{0xAA, 0xBB},
}
ctx := &clientctx.ClientContext{}
bf := byteframe.NewByteFrame()
pkt.Build(bf, ctx)
data := bf.Data()
// Check AckHandle (big-endian)
if data[0] != 0x12 || data[1] != 0x34 || data[2] != 0x56 || data[3] != 0x78 {
t.Errorf("AckHandle bytes = %X, want 12345678", data[:4])
}
// Check IsBufferResponse (1 = true)
if data[4] != 1 {
t.Errorf("IsBufferResponse byte = %d, want 1", data[4])
}
// Check ErrorCode
if data[5] != 0x55 {
t.Errorf("ErrorCode byte = %X, want 55", data[5])
}
// Check payload size (2 bytes, big-endian)
if data[6] != 0x00 || data[7] != 0x02 {
t.Errorf("PayloadSize bytes = %X %X, want 00 02", data[6], data[7])
}
// Check actual data
if data[8] != 0xAA || data[9] != 0xBB {
t.Errorf("AckData bytes = %X %X, want AA BB", data[8], data[9])
}
}
func TestCorePacketsFromOpcode(t *testing.T) {
coreOpcodes := []network.PacketID{
network.MSG_SYS_NOP,
network.MSG_SYS_END,
network.MSG_SYS_ACK,
network.MSG_SYS_PING,
}
for _, opcode := range coreOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Fatalf("FromOpcode(%s) returned nil", opcode)
}
if pkt.Opcode() != opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), opcode)
}
})
}
}

View File

@@ -0,0 +1,592 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
// TestMsgSysCastBinaryParse tests parsing MsgSysCastBinary
func TestMsgSysCastBinaryParse(t *testing.T) {
tests := []struct {
name string
unk uint32
broadcastType uint8
messageType uint8
payload []byte
}{
{"empty payload", 0, 1, 2, []byte{}},
{"small payload", 0x006400C8, 3, 4, []byte{0xAA, 0xBB, 0xCC}},
{"large payload", 0xFFFFFFFF, 0xFF, 0xFF, make([]byte, 100)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.unk)
bf.WriteUint8(tt.broadcastType)
bf.WriteUint8(tt.messageType)
bf.WriteUint16(uint16(len(tt.payload)))
bf.WriteBytes(tt.payload)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCastBinary{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.Unk != tt.unk {
t.Errorf("Unk = %d, want %d", pkt.Unk, tt.unk)
}
if pkt.BroadcastType != tt.broadcastType {
t.Errorf("BroadcastType = %d, want %d", pkt.BroadcastType, tt.broadcastType)
}
if pkt.MessageType != tt.messageType {
t.Errorf("MessageType = %d, want %d", pkt.MessageType, tt.messageType)
}
if len(pkt.RawDataPayload) != len(tt.payload) {
t.Errorf("RawDataPayload len = %d, want %d", len(pkt.RawDataPayload), len(tt.payload))
}
})
}
}
// TestMsgSysCastBinaryOpcode tests Opcode method
func TestMsgSysCastBinaryOpcode(t *testing.T) {
pkt := &MsgSysCastBinary{}
if pkt.Opcode() != network.MSG_SYS_CAST_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_CAST_BINARY", pkt.Opcode())
}
}
// TestMsgSysCreateSemaphoreOpcode tests Opcode method
func TestMsgSysCreateSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysCreateSemaphore{}
if pkt.Opcode() != network.MSG_SYS_CREATE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysCastedBinaryOpcode tests Opcode method
func TestMsgSysCastedBinaryOpcode(t *testing.T) {
pkt := &MsgSysCastedBinary{}
if pkt.Opcode() != network.MSG_SYS_CASTED_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_CASTED_BINARY", pkt.Opcode())
}
}
// TestMsgSysSetStageBinaryOpcode tests Opcode method
func TestMsgSysSetStageBinaryOpcode(t *testing.T) {
pkt := &MsgSysSetStageBinary{}
if pkt.Opcode() != network.MSG_SYS_SET_STAGE_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_STAGE_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetStageBinaryOpcode tests Opcode method
func TestMsgSysGetStageBinaryOpcode(t *testing.T) {
pkt := &MsgSysGetStageBinary{}
if pkt.Opcode() != network.MSG_SYS_GET_STAGE_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_STAGE_BINARY", pkt.Opcode())
}
}
// TestMsgSysWaitStageBinaryOpcode tests Opcode method
func TestMsgSysWaitStageBinaryOpcode(t *testing.T) {
pkt := &MsgSysWaitStageBinary{}
if pkt.Opcode() != network.MSG_SYS_WAIT_STAGE_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_WAIT_STAGE_BINARY", pkt.Opcode())
}
}
// TestMsgSysEnumerateClientOpcode tests Opcode method
func TestMsgSysEnumerateClientOpcode(t *testing.T) {
pkt := &MsgSysEnumerateClient{}
if pkt.Opcode() != network.MSG_SYS_ENUMERATE_CLIENT {
t.Errorf("Opcode() = %s, want MSG_SYS_ENUMERATE_CLIENT", pkt.Opcode())
}
}
// TestMsgSysEnumerateStageOpcode tests Opcode method
func TestMsgSysEnumerateStageOpcode(t *testing.T) {
pkt := &MsgSysEnumerateStage{}
if pkt.Opcode() != network.MSG_SYS_ENUMERATE_STAGE {
t.Errorf("Opcode() = %s, want MSG_SYS_ENUMERATE_STAGE", pkt.Opcode())
}
}
// TestMsgSysCreateMutexOpcode tests Opcode method
func TestMsgSysCreateMutexOpcode(t *testing.T) {
pkt := &MsgSysCreateMutex{}
if pkt.Opcode() != network.MSG_SYS_CREATE_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_MUTEX", pkt.Opcode())
}
}
// TestMsgSysCreateOpenMutexOpcode tests Opcode method
func TestMsgSysCreateOpenMutexOpcode(t *testing.T) {
pkt := &MsgSysCreateOpenMutex{}
if pkt.Opcode() != network.MSG_SYS_CREATE_OPEN_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_OPEN_MUTEX", pkt.Opcode())
}
}
// TestMsgSysDeleteMutexOpcode tests Opcode method
func TestMsgSysDeleteMutexOpcode(t *testing.T) {
pkt := &MsgSysDeleteMutex{}
if pkt.Opcode() != network.MSG_SYS_DELETE_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_DELETE_MUTEX", pkt.Opcode())
}
}
// TestMsgSysOpenMutexOpcode tests Opcode method
func TestMsgSysOpenMutexOpcode(t *testing.T) {
pkt := &MsgSysOpenMutex{}
if pkt.Opcode() != network.MSG_SYS_OPEN_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_OPEN_MUTEX", pkt.Opcode())
}
}
// TestMsgSysCloseMutexOpcode tests Opcode method
func TestMsgSysCloseMutexOpcode(t *testing.T) {
pkt := &MsgSysCloseMutex{}
if pkt.Opcode() != network.MSG_SYS_CLOSE_MUTEX {
t.Errorf("Opcode() = %s, want MSG_SYS_CLOSE_MUTEX", pkt.Opcode())
}
}
// TestMsgSysDeleteSemaphoreOpcode tests Opcode method
func TestMsgSysDeleteSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysDeleteSemaphore{}
if pkt.Opcode() != network.MSG_SYS_DELETE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_DELETE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysAcquireSemaphoreOpcode tests Opcode method
func TestMsgSysAcquireSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysAcquireSemaphore{}
if pkt.Opcode() != network.MSG_SYS_ACQUIRE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_ACQUIRE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysReleaseSemaphoreOpcode tests Opcode method
func TestMsgSysReleaseSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysReleaseSemaphore{}
if pkt.Opcode() != network.MSG_SYS_RELEASE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_RELEASE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysCheckSemaphoreOpcode tests Opcode method
func TestMsgSysCheckSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysCheckSemaphore{}
if pkt.Opcode() != network.MSG_SYS_CHECK_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_CHECK_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysCreateAcquireSemaphoreOpcode tests Opcode method
func TestMsgSysCreateAcquireSemaphoreOpcode(t *testing.T) {
pkt := &MsgSysCreateAcquireSemaphore{}
if pkt.Opcode() != network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_ACQUIRE_SEMAPHORE", pkt.Opcode())
}
}
// TestMsgSysOperateRegisterOpcode tests Opcode method
func TestMsgSysOperateRegisterOpcode(t *testing.T) {
pkt := &MsgSysOperateRegister{}
if pkt.Opcode() != network.MSG_SYS_OPERATE_REGISTER {
t.Errorf("Opcode() = %s, want MSG_SYS_OPERATE_REGISTER", pkt.Opcode())
}
}
// TestMsgSysLoadRegisterOpcode tests Opcode method
func TestMsgSysLoadRegisterOpcode(t *testing.T) {
pkt := &MsgSysLoadRegister{}
if pkt.Opcode() != network.MSG_SYS_LOAD_REGISTER {
t.Errorf("Opcode() = %s, want MSG_SYS_LOAD_REGISTER", pkt.Opcode())
}
}
// TestMsgSysNotifyRegisterOpcode tests Opcode method
func TestMsgSysNotifyRegisterOpcode(t *testing.T) {
pkt := &MsgSysNotifyRegister{}
if pkt.Opcode() != network.MSG_SYS_NOTIFY_REGISTER {
t.Errorf("Opcode() = %s, want MSG_SYS_NOTIFY_REGISTER", pkt.Opcode())
}
}
// TestMsgSysCreateObjectOpcode tests Opcode method
func TestMsgSysCreateObjectOpcode(t *testing.T) {
pkt := &MsgSysCreateObject{}
if pkt.Opcode() != network.MSG_SYS_CREATE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_CREATE_OBJECT", pkt.Opcode())
}
}
// TestMsgSysDeleteObjectOpcode tests Opcode method
func TestMsgSysDeleteObjectOpcode(t *testing.T) {
pkt := &MsgSysDeleteObject{}
if pkt.Opcode() != network.MSG_SYS_DELETE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_DELETE_OBJECT", pkt.Opcode())
}
}
// TestMsgSysPositionObjectOpcode tests Opcode method
func TestMsgSysPositionObjectOpcode(t *testing.T) {
pkt := &MsgSysPositionObject{}
if pkt.Opcode() != network.MSG_SYS_POSITION_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_POSITION_OBJECT", pkt.Opcode())
}
}
// TestMsgSysRotateObjectOpcode tests Opcode method
func TestMsgSysRotateObjectOpcode(t *testing.T) {
pkt := &MsgSysRotateObject{}
if pkt.Opcode() != network.MSG_SYS_ROTATE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_ROTATE_OBJECT", pkt.Opcode())
}
}
// TestMsgSysDuplicateObjectOpcode tests Opcode method
func TestMsgSysDuplicateObjectOpcode(t *testing.T) {
pkt := &MsgSysDuplicateObject{}
if pkt.Opcode() != network.MSG_SYS_DUPLICATE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_DUPLICATE_OBJECT", pkt.Opcode())
}
}
// TestMsgSysSetObjectBinaryOpcode tests Opcode method
func TestMsgSysSetObjectBinaryOpcode(t *testing.T) {
pkt := &MsgSysSetObjectBinary{}
if pkt.Opcode() != network.MSG_SYS_SET_OBJECT_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_OBJECT_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetObjectBinaryOpcode tests Opcode method
func TestMsgSysGetObjectBinaryOpcode(t *testing.T) {
pkt := &MsgSysGetObjectBinary{}
if pkt.Opcode() != network.MSG_SYS_GET_OBJECT_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_OBJECT_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetObjectOwnerOpcode tests Opcode method
func TestMsgSysGetObjectOwnerOpcode(t *testing.T) {
pkt := &MsgSysGetObjectOwner{}
if pkt.Opcode() != network.MSG_SYS_GET_OBJECT_OWNER {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_OBJECT_OWNER", pkt.Opcode())
}
}
// TestMsgSysUpdateObjectBinaryOpcode tests Opcode method
func TestMsgSysUpdateObjectBinaryOpcode(t *testing.T) {
pkt := &MsgSysUpdateObjectBinary{}
if pkt.Opcode() != network.MSG_SYS_UPDATE_OBJECT_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_UPDATE_OBJECT_BINARY", pkt.Opcode())
}
}
// TestMsgSysCleanupObjectOpcode tests Opcode method
func TestMsgSysCleanupObjectOpcode(t *testing.T) {
pkt := &MsgSysCleanupObject{}
if pkt.Opcode() != network.MSG_SYS_CLEANUP_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_CLEANUP_OBJECT", pkt.Opcode())
}
}
// TestMsgSysInsertUserOpcode tests Opcode method
func TestMsgSysInsertUserOpcode(t *testing.T) {
pkt := &MsgSysInsertUser{}
if pkt.Opcode() != network.MSG_SYS_INSERT_USER {
t.Errorf("Opcode() = %s, want MSG_SYS_INSERT_USER", pkt.Opcode())
}
}
// TestMsgSysDeleteUserOpcode tests Opcode method
func TestMsgSysDeleteUserOpcode(t *testing.T) {
pkt := &MsgSysDeleteUser{}
if pkt.Opcode() != network.MSG_SYS_DELETE_USER {
t.Errorf("Opcode() = %s, want MSG_SYS_DELETE_USER", pkt.Opcode())
}
}
// TestMsgSysSetUserBinaryOpcode tests Opcode method
func TestMsgSysSetUserBinaryOpcode(t *testing.T) {
pkt := &MsgSysSetUserBinary{}
if pkt.Opcode() != network.MSG_SYS_SET_USER_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_USER_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetUserBinaryOpcode tests Opcode method
func TestMsgSysGetUserBinaryOpcode(t *testing.T) {
pkt := &MsgSysGetUserBinary{}
if pkt.Opcode() != network.MSG_SYS_GET_USER_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_USER_BINARY", pkt.Opcode())
}
}
// TestMsgSysNotifyUserBinaryOpcode tests Opcode method
func TestMsgSysNotifyUserBinaryOpcode(t *testing.T) {
pkt := &MsgSysNotifyUserBinary{}
if pkt.Opcode() != network.MSG_SYS_NOTIFY_USER_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_NOTIFY_USER_BINARY", pkt.Opcode())
}
}
// TestMsgSysUpdateRightOpcode tests Opcode method
func TestMsgSysUpdateRightOpcode(t *testing.T) {
pkt := &MsgSysUpdateRight{}
if pkt.Opcode() != network.MSG_SYS_UPDATE_RIGHT {
t.Errorf("Opcode() = %s, want MSG_SYS_UPDATE_RIGHT", pkt.Opcode())
}
}
// TestMsgSysAuthQueryOpcode tests Opcode method
func TestMsgSysAuthQueryOpcode(t *testing.T) {
pkt := &MsgSysAuthQuery{}
if pkt.Opcode() != network.MSG_SYS_AUTH_QUERY {
t.Errorf("Opcode() = %s, want MSG_SYS_AUTH_QUERY", pkt.Opcode())
}
}
// TestMsgSysAuthDataOpcode tests Opcode method
func TestMsgSysAuthDataOpcode(t *testing.T) {
pkt := &MsgSysAuthData{}
if pkt.Opcode() != network.MSG_SYS_AUTH_DATA {
t.Errorf("Opcode() = %s, want MSG_SYS_AUTH_DATA", pkt.Opcode())
}
}
// TestMsgSysAuthTerminalOpcode tests Opcode method
func TestMsgSysAuthTerminalOpcode(t *testing.T) {
pkt := &MsgSysAuthTerminal{}
if pkt.Opcode() != network.MSG_SYS_AUTH_TERMINAL {
t.Errorf("Opcode() = %s, want MSG_SYS_AUTH_TERMINAL", pkt.Opcode())
}
}
// TestMsgSysRightsReloadOpcode tests Opcode method
func TestMsgSysRightsReloadOpcode(t *testing.T) {
pkt := &MsgSysRightsReload{}
if pkt.Opcode() != network.MSG_SYS_RIGHTS_RELOAD {
t.Errorf("Opcode() = %s, want MSG_SYS_RIGHTS_RELOAD", pkt.Opcode())
}
}
// TestMsgSysTerminalLogOpcode tests Opcode method
func TestMsgSysTerminalLogOpcode(t *testing.T) {
pkt := &MsgSysTerminalLog{}
if pkt.Opcode() != network.MSG_SYS_TERMINAL_LOG {
t.Errorf("Opcode() = %s, want MSG_SYS_TERMINAL_LOG", pkt.Opcode())
}
}
// TestMsgSysIssueLogkeyOpcode tests Opcode method
func TestMsgSysIssueLogkeyOpcode(t *testing.T) {
pkt := &MsgSysIssueLogkey{}
if pkt.Opcode() != network.MSG_SYS_ISSUE_LOGKEY {
t.Errorf("Opcode() = %s, want MSG_SYS_ISSUE_LOGKEY", pkt.Opcode())
}
}
// TestMsgSysRecordLogOpcode tests Opcode method
func TestMsgSysRecordLogOpcode(t *testing.T) {
pkt := &MsgSysRecordLog{}
if pkt.Opcode() != network.MSG_SYS_RECORD_LOG {
t.Errorf("Opcode() = %s, want MSG_SYS_RECORD_LOG", pkt.Opcode())
}
}
// TestMsgSysEchoOpcode tests Opcode method
func TestMsgSysEchoOpcode(t *testing.T) {
pkt := &MsgSysEcho{}
if pkt.Opcode() != network.MSG_SYS_ECHO {
t.Errorf("Opcode() = %s, want MSG_SYS_ECHO", pkt.Opcode())
}
}
// TestMsgSysGetFileOpcode tests Opcode method
func TestMsgSysGetFileOpcode(t *testing.T) {
pkt := &MsgSysGetFile{}
if pkt.Opcode() != network.MSG_SYS_GET_FILE {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_FILE", pkt.Opcode())
}
}
// TestMsgSysHideClientOpcode tests Opcode method
func TestMsgSysHideClientOpcode(t *testing.T) {
pkt := &MsgSysHideClient{}
if pkt.Opcode() != network.MSG_SYS_HIDE_CLIENT {
t.Errorf("Opcode() = %s, want MSG_SYS_HIDE_CLIENT", pkt.Opcode())
}
}
// TestMsgSysSetStatusOpcode tests Opcode method
func TestMsgSysSetStatusOpcode(t *testing.T) {
pkt := &MsgSysSetStatus{}
if pkt.Opcode() != network.MSG_SYS_SET_STATUS {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_STATUS", pkt.Opcode())
}
}
// TestMsgSysStageDestructOpcode tests Opcode method
func TestMsgSysStageDestructOpcode(t *testing.T) {
pkt := &MsgSysStageDestruct{}
if pkt.Opcode() != network.MSG_SYS_STAGE_DESTRUCT {
t.Errorf("Opcode() = %s, want MSG_SYS_STAGE_DESTRUCT", pkt.Opcode())
}
}
// TestMsgSysLeaveStageOpcode tests Opcode method
func TestMsgSysLeaveStageOpcode(t *testing.T) {
pkt := &MsgSysLeaveStage{}
if pkt.Opcode() != network.MSG_SYS_LEAVE_STAGE {
t.Errorf("Opcode() = %s, want MSG_SYS_LEAVE_STAGE", pkt.Opcode())
}
}
// TestMsgSysReserveStageOpcode tests Opcode method
func TestMsgSysReserveStageOpcode(t *testing.T) {
pkt := &MsgSysReserveStage{}
if pkt.Opcode() != network.MSG_SYS_RESERVE_STAGE {
t.Errorf("Opcode() = %s, want MSG_SYS_RESERVE_STAGE", pkt.Opcode())
}
}
// TestMsgSysUnreserveStageOpcode tests Opcode method
func TestMsgSysUnreserveStageOpcode(t *testing.T) {
pkt := &MsgSysUnreserveStage{}
if pkt.Opcode() != network.MSG_SYS_UNRESERVE_STAGE {
t.Errorf("Opcode() = %s, want MSG_SYS_UNRESERVE_STAGE", pkt.Opcode())
}
}
// TestMsgSysSetStagePassOpcode tests Opcode method
func TestMsgSysSetStagePassOpcode(t *testing.T) {
pkt := &MsgSysSetStagePass{}
if pkt.Opcode() != network.MSG_SYS_SET_STAGE_PASS {
t.Errorf("Opcode() = %s, want MSG_SYS_SET_STAGE_PASS", pkt.Opcode())
}
}
// TestMsgSysLockGlobalSemaOpcode tests Opcode method
func TestMsgSysLockGlobalSemaOpcode(t *testing.T) {
pkt := &MsgSysLockGlobalSema{}
if pkt.Opcode() != network.MSG_SYS_LOCK_GLOBAL_SEMA {
t.Errorf("Opcode() = %s, want MSG_SYS_LOCK_GLOBAL_SEMA", pkt.Opcode())
}
}
// TestMsgSysUnlockGlobalSemaOpcode tests Opcode method
func TestMsgSysUnlockGlobalSemaOpcode(t *testing.T) {
pkt := &MsgSysUnlockGlobalSema{}
if pkt.Opcode() != network.MSG_SYS_UNLOCK_GLOBAL_SEMA {
t.Errorf("Opcode() = %s, want MSG_SYS_UNLOCK_GLOBAL_SEMA", pkt.Opcode())
}
}
// TestMsgSysTransBinaryOpcode tests Opcode method
func TestMsgSysTransBinaryOpcode(t *testing.T) {
pkt := &MsgSysTransBinary{}
if pkt.Opcode() != network.MSG_SYS_TRANS_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_TRANS_BINARY", pkt.Opcode())
}
}
// TestMsgSysCollectBinaryOpcode tests Opcode method
func TestMsgSysCollectBinaryOpcode(t *testing.T) {
pkt := &MsgSysCollectBinary{}
if pkt.Opcode() != network.MSG_SYS_COLLECT_BINARY {
t.Errorf("Opcode() = %s, want MSG_SYS_COLLECT_BINARY", pkt.Opcode())
}
}
// TestMsgSysGetStateOpcode tests Opcode method
func TestMsgSysGetStateOpcode(t *testing.T) {
pkt := &MsgSysGetState{}
if pkt.Opcode() != network.MSG_SYS_GET_STATE {
t.Errorf("Opcode() = %s, want MSG_SYS_GET_STATE", pkt.Opcode())
}
}
// TestMsgSysSerializeOpcode tests Opcode method
func TestMsgSysSerializeOpcode(t *testing.T) {
pkt := &MsgSysSerialize{}
if pkt.Opcode() != network.MSG_SYS_SERIALIZE {
t.Errorf("Opcode() = %s, want MSG_SYS_SERIALIZE", pkt.Opcode())
}
}
// TestMsgSysEnumlobbyOpcode tests Opcode method
func TestMsgSysEnumlobbyOpcode(t *testing.T) {
pkt := &MsgSysEnumlobby{}
if pkt.Opcode() != network.MSG_SYS_ENUMLOBBY {
t.Errorf("Opcode() = %s, want MSG_SYS_ENUMLOBBY", pkt.Opcode())
}
}
// TestMsgSysEnumuserOpcode tests Opcode method
func TestMsgSysEnumuserOpcode(t *testing.T) {
pkt := &MsgSysEnumuser{}
if pkt.Opcode() != network.MSG_SYS_ENUMUSER {
t.Errorf("Opcode() = %s, want MSG_SYS_ENUMUSER", pkt.Opcode())
}
}
// TestMsgSysInfokyserverOpcode tests Opcode method
func TestMsgSysInfokyserverOpcode(t *testing.T) {
pkt := &MsgSysInfokyserver{}
if pkt.Opcode() != network.MSG_SYS_INFOKYSERVER {
t.Errorf("Opcode() = %s, want MSG_SYS_INFOKYSERVER", pkt.Opcode())
}
}
// TestMsgSysExtendThresholdOpcode tests Opcode method
func TestMsgSysExtendThresholdOpcode(t *testing.T) {
pkt := &MsgSysExtendThreshold{}
if pkt.Opcode() != network.MSG_SYS_EXTEND_THRESHOLD {
t.Errorf("Opcode() = %s, want MSG_SYS_EXTEND_THRESHOLD", pkt.Opcode())
}
}
// TestMsgSysAddObjectOpcode tests Opcode method
func TestMsgSysAddObjectOpcode(t *testing.T) {
pkt := &MsgSysAddObject{}
if pkt.Opcode() != network.MSG_SYS_ADD_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_ADD_OBJECT", pkt.Opcode())
}
}
// TestMsgSysDelObjectOpcode tests Opcode method
func TestMsgSysDelObjectOpcode(t *testing.T) {
pkt := &MsgSysDelObject{}
if pkt.Opcode() != network.MSG_SYS_DEL_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_DEL_OBJECT", pkt.Opcode())
}
}
// TestMsgSysDispObjectOpcode tests Opcode method
func TestMsgSysDispObjectOpcode(t *testing.T) {
pkt := &MsgSysDispObject{}
if pkt.Opcode() != network.MSG_SYS_DISP_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_DISP_OBJECT", pkt.Opcode())
}
}
// TestMsgSysHideObjectOpcode tests Opcode method
func TestMsgSysHideObjectOpcode(t *testing.T) {
pkt := &MsgSysHideObject{}
if pkt.Opcode() != network.MSG_SYS_HIDE_OBJECT {
t.Errorf("Opcode() = %s, want MSG_SYS_HIDE_OBJECT", pkt.Opcode())
}
}

View File

@@ -0,0 +1,332 @@
package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
)
func TestStagePacketOpcodes(t *testing.T) {
tests := []struct {
name string
pkt MHFPacket
expect network.PacketID
}{
{"MsgSysCreateStage", &MsgSysCreateStage{}, network.MSG_SYS_CREATE_STAGE},
{"MsgSysEnterStage", &MsgSysEnterStage{}, network.MSG_SYS_ENTER_STAGE},
{"MsgSysMoveStage", &MsgSysMoveStage{}, network.MSG_SYS_MOVE_STAGE},
{"MsgSysBackStage", &MsgSysBackStage{}, network.MSG_SYS_BACK_STAGE},
{"MsgSysLockStage", &MsgSysLockStage{}, network.MSG_SYS_LOCK_STAGE},
{"MsgSysUnlockStage", &MsgSysUnlockStage{}, network.MSG_SYS_UNLOCK_STAGE},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.pkt.Opcode(); got != tt.expect {
t.Errorf("Opcode() = %v, want %v", got, tt.expect)
}
})
}
}
func TestMsgSysCreateStageFields(t *testing.T) {
tests := []struct {
name string
ackHandle uint32
unk0 uint8
playerCount uint8
stageID string
}{
{"empty stage", 1, 1, 4, ""},
{"mezeporta", 0x12345678, 2, 8, "sl1Ns200p0a0u0"},
{"quest room", 100, 1, 4, "q1234"},
{"max players", 0xFFFFFFFF, 2, 16, "max_stage"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.ackHandle)
bf.WriteUint8(tt.unk0)
bf.WriteUint8(tt.playerCount)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(append(stageIDBytes, 0x00))
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.ackHandle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.ackHandle)
}
if pkt.Unk0 != tt.unk0 {
t.Errorf("Unk0 = %d, want %d", pkt.Unk0, tt.unk0)
}
if pkt.PlayerCount != tt.playerCount {
t.Errorf("PlayerCount = %d, want %d", pkt.PlayerCount, tt.playerCount)
}
if pkt.StageID != tt.stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
}
})
}
}
func TestMsgSysEnterStageFields(t *testing.T) {
tests := []struct {
name string
handle uint32
unk bool
stageID string
}{
{"enter town", 1, false, "town01"},
{"force enter", 2, true, "quest_stage"},
{"rasta bar", 999, false, "sl1Ns211p0a0u0"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.WriteBool(tt.unk)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(append(stageIDBytes, 0x00))
bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnterStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.handle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
}
if pkt.Unk != tt.unk {
t.Errorf("Unk = %v, want %v", pkt.Unk, tt.unk)
}
if pkt.StageID != tt.stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
}
})
}
}
func TestMsgSysMoveStageFields(t *testing.T) {
tests := []struct {
name string
handle uint32
unkBool uint8
stageID string
}{
{"move to area", 1, 0, "area01"},
{"move to quest", 0xABCD, 1, "quest12345"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.WriteUint8(tt.unkBool)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(stageIDBytes)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysMoveStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.handle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
}
if pkt.UnkBool != tt.unkBool {
t.Errorf("UnkBool = %d, want %d", pkt.UnkBool, tt.unkBool)
}
if pkt.StageID != tt.stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
}
})
}
}
func TestMsgSysLockStageFields(t *testing.T) {
tests := []struct {
name string
handle uint32
unk0 uint8
unk1 uint8
stageID string
}{
{"lock room", 1, 1, 1, "room01"},
{"private party", 0x1234, 1, 1, "party_stage"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.WriteUint8(tt.unk0)
bf.WriteUint8(tt.unk1)
stageIDBytes := []byte(tt.stageID)
bf.WriteUint8(uint8(len(stageIDBytes)))
bf.WriteBytes(append(stageIDBytes, 0x00))
bf.Seek(0, io.SeekStart)
pkt := &MsgSysLockStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.handle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
}
// Unk0 and Unk1 are read but discarded by Parse, so we only verify
// that Parse consumed the bytes without error
if pkt.StageID != tt.stageID {
t.Errorf("StageID = %q, want %q", pkt.StageID, tt.stageID)
}
})
}
}
func TestMsgSysUnlockStageFields(t *testing.T) {
tests := []struct {
name string
unk0 uint16
}{
{"zero", 0},
{"typical", 1},
{"max", 0xFFFF},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(tt.unk0)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysUnlockStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// MsgSysUnlockStage is an empty struct; Parse reads and discards a uint16.
// We just verify Parse doesn't error.
})
}
}
func TestMsgSysBackStageFields(t *testing.T) {
tests := []struct {
name string
handle uint32
}{
{"small handle", 1},
{"large handle", 0xDEADBEEF},
{"zero", 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(tt.handle)
bf.Seek(0, io.SeekStart)
pkt := &MsgSysBackStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.AckHandle != tt.handle {
t.Errorf("AckHandle = %d, want %d", pkt.AckHandle, tt.handle)
}
})
}
}
func TestStageIDEdgeCases(t *testing.T) {
t.Run("long stage ID", func(t *testing.T) {
// Stage ID with max length (255 bytes)
longID := make([]byte, 200)
for i := range longID {
longID[i] = 'a' + byte(i%26)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
bf.WriteUint8(1)
bf.WriteUint8(4)
bf.WriteUint8(uint8(len(longID)))
bf.WriteBytes(append(longID, 0x00))
bf.Seek(0, io.SeekStart)
pkt := &MsgSysCreateStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if pkt.StageID != string(longID) {
t.Errorf("StageID length = %d, want %d", len(pkt.StageID), len(longID))
}
})
t.Run("stage ID with null terminator", func(t *testing.T) {
// String terminated with null byte
stageID := "test\x00extra"
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
bf.WriteUint8(0)
bf.WriteUint8(uint8(len(stageID)))
bf.WriteBytes([]byte(stageID))
bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnterStage{}
err := pkt.Parse(bf, &clientctx.ClientContext{})
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
// Should truncate at null
if pkt.StageID != "test" {
t.Errorf("StageID = %q, want %q (should truncate at null)", pkt.StageID, "test")
}
})
}
func TestStagePacketFromOpcode(t *testing.T) {
stageOpcodes := []network.PacketID{
network.MSG_SYS_CREATE_STAGE,
network.MSG_SYS_ENTER_STAGE,
network.MSG_SYS_BACK_STAGE,
network.MSG_SYS_MOVE_STAGE,
network.MSG_SYS_LOCK_STAGE,
network.MSG_SYS_UNLOCK_STAGE,
}
for _, opcode := range stageOpcodes {
t.Run(opcode.String(), func(t *testing.T) {
pkt := FromOpcode(opcode)
if pkt == nil {
t.Fatalf("FromOpcode(%s) returned nil", opcode)
}
if pkt.Opcode() != opcode {
t.Errorf("Opcode() = %s, want %s", pkt.Opcode(), opcode)
}
})
}
}

211
network/packetid_test.go Normal file
View File

@@ -0,0 +1,211 @@
package network
import (
"testing"
)
func TestPacketIDType(t *testing.T) {
// PacketID is based on uint16
var p PacketID = 0xFFFF
if uint16(p) != 0xFFFF {
t.Errorf("PacketID max value = %d, want %d", uint16(p), 0xFFFF)
}
}
func TestPacketIDConstants(t *testing.T) {
// Test critical packet IDs are correct
tests := []struct {
name string
id PacketID
expect uint16
}{
{"MSG_HEAD", MSG_HEAD, 0},
{"MSG_SYS_END", MSG_SYS_END, 0x10},
{"MSG_SYS_NOP", MSG_SYS_NOP, 0x11},
{"MSG_SYS_ACK", MSG_SYS_ACK, 0x12},
{"MSG_SYS_LOGIN", MSG_SYS_LOGIN, 0x14},
{"MSG_SYS_LOGOUT", MSG_SYS_LOGOUT, 0x15},
{"MSG_SYS_PING", MSG_SYS_PING, 0x17},
{"MSG_SYS_TIME", MSG_SYS_TIME, 0x1A},
{"MSG_SYS_CREATE_STAGE", MSG_SYS_CREATE_STAGE, 0x20},
{"MSG_SYS_ENTER_STAGE", MSG_SYS_ENTER_STAGE, 0x22},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if uint16(tt.id) != tt.expect {
t.Errorf("%s = 0x%X, want 0x%X", tt.name, uint16(tt.id), tt.expect)
}
})
}
}
func TestPacketIDString(t *testing.T) {
// Test that String() method works for known packet IDs
tests := []struct {
id PacketID
contains string
}{
{MSG_HEAD, "MSG_HEAD"},
{MSG_SYS_PING, "MSG_SYS_PING"},
{MSG_SYS_END, "MSG_SYS_END"},
{MSG_SYS_NOP, "MSG_SYS_NOP"},
{MSG_SYS_ACK, "MSG_SYS_ACK"},
{MSG_SYS_LOGIN, "MSG_SYS_LOGIN"},
{MSG_SYS_LOGOUT, "MSG_SYS_LOGOUT"},
}
for _, tt := range tests {
t.Run(tt.contains, func(t *testing.T) {
got := tt.id.String()
if got != tt.contains {
t.Errorf("String() = %q, want %q", got, tt.contains)
}
})
}
}
func TestPacketIDUnknown(t *testing.T) {
// Unknown packet ID should still have a valid string representation
unknown := PacketID(0xFFFF)
str := unknown.String()
if str == "" {
t.Error("String() for unknown PacketID should not be empty")
}
}
func TestPacketIDZero(t *testing.T) {
// MSG_HEAD should be 0
if MSG_HEAD != 0 {
t.Errorf("MSG_HEAD = %d, want 0", MSG_HEAD)
}
}
func TestSystemPacketIDRange(t *testing.T) {
// System packets should be in a specific range
systemPackets := []PacketID{
MSG_SYS_reserve01,
MSG_SYS_reserve02,
MSG_SYS_reserve03,
MSG_SYS_ADD_OBJECT,
MSG_SYS_DEL_OBJECT,
MSG_SYS_END,
MSG_SYS_NOP,
MSG_SYS_ACK,
MSG_SYS_LOGIN,
MSG_SYS_LOGOUT,
MSG_SYS_PING,
MSG_SYS_TIME,
}
for _, pkt := range systemPackets {
// System packets should have IDs > 0 (MSG_HEAD is 0)
if pkt < MSG_SYS_reserve01 {
t.Errorf("System packet %s has ID %d, should be >= MSG_SYS_reserve01", pkt, pkt)
}
}
}
func TestMHFPacketIDRange(t *testing.T) {
// MHF packets start at MSG_MHF_SAVEDATA (0x60)
mhfPackets := []PacketID{
MSG_MHF_SAVEDATA,
MSG_MHF_LOADDATA,
MSG_MHF_ENUMERATE_QUEST,
MSG_MHF_ACQUIRE_TITLE,
MSG_MHF_ACQUIRE_DIST_ITEM,
MSG_MHF_ACQUIRE_MONTHLY_ITEM,
}
for _, pkt := range mhfPackets {
// MHF packets should be >= MSG_MHF_SAVEDATA
if pkt < MSG_MHF_SAVEDATA {
t.Errorf("MHF packet %s has ID %d, should be >= MSG_MHF_SAVEDATA (%d)", pkt, pkt, MSG_MHF_SAVEDATA)
}
}
}
func TestStagePacketIDsSequential(t *testing.T) {
// Stage-related packets should be sequential
stagePackets := []PacketID{
MSG_SYS_CREATE_STAGE,
MSG_SYS_STAGE_DESTRUCT,
MSG_SYS_ENTER_STAGE,
MSG_SYS_BACK_STAGE,
MSG_SYS_MOVE_STAGE,
MSG_SYS_LEAVE_STAGE,
MSG_SYS_LOCK_STAGE,
MSG_SYS_UNLOCK_STAGE,
}
for i := 1; i < len(stagePackets); i++ {
if stagePackets[i] != stagePackets[i-1]+1 {
t.Errorf("Stage packets not sequential: %s (%d) should follow %s (%d)",
stagePackets[i], stagePackets[i], stagePackets[i-1], stagePackets[i-1])
}
}
}
func TestPacketIDUniqueness(t *testing.T) {
// Sample of important packet IDs should be unique
packets := []PacketID{
MSG_HEAD,
MSG_SYS_END,
MSG_SYS_NOP,
MSG_SYS_ACK,
MSG_SYS_LOGIN,
MSG_SYS_LOGOUT,
MSG_SYS_PING,
MSG_SYS_TIME,
MSG_SYS_CREATE_STAGE,
MSG_SYS_ENTER_STAGE,
MSG_MHF_SAVEDATA,
MSG_MHF_LOADDATA,
}
seen := make(map[PacketID]bool)
for _, pkt := range packets {
if seen[pkt] {
t.Errorf("Duplicate PacketID: %s (%d)", pkt, pkt)
}
seen[pkt] = true
}
}
func TestAcquirePacketIDs(t *testing.T) {
// Verify acquire-related packet IDs exist and are correct type
acquirePackets := []PacketID{
MSG_MHF_ACQUIRE_DIST_ITEM,
MSG_MHF_ACQUIRE_TITLE,
MSG_MHF_ACQUIRE_ITEM,
MSG_MHF_ACQUIRE_MONTHLY_ITEM,
MSG_MHF_ACQUIRE_CAFE_ITEM,
MSG_MHF_ACQUIRE_GUILD_TRESURE,
}
for _, pkt := range acquirePackets {
str := pkt.String()
if str == "" {
t.Errorf("PacketID %d should have a string representation", pkt)
}
}
}
func TestGuildPacketIDs(t *testing.T) {
// Verify guild-related packet IDs
guildPackets := []PacketID{
MSG_MHF_CREATE_GUILD,
MSG_MHF_OPERATE_GUILD,
MSG_MHF_OPERATE_GUILD_MEMBER,
MSG_MHF_INFO_GUILD,
MSG_MHF_ENUMERATE_GUILD,
MSG_MHF_UPDATE_GUILD,
}
for _, pkt := range guildPackets {
// All guild packets should be MHF packets
if pkt < MSG_MHF_SAVEDATA {
t.Errorf("Guild packet %s should be an MHF packet (>= 0x60)", pkt)
}
}
}