Files
Erupe/network/mhfpacket/msg_batch_parse_test.go

2230 lines
66 KiB
Go

package mhfpacket
import (
"io"
"testing"
"erupe-ce/common/byteframe"
cfg "erupe-ce/config"
"erupe-ce/network/clientctx"
)
// TestBatchParseAckHandleOnly tests Parse for packets that only read AckHandle (uint32).
func TestBatchParseAckHandleOnly(t *testing.T) {
packets := []struct {
name string
pkt MHFPacket
}{
{"MsgMhfLoaddata", &MsgMhfLoaddata{}},
{"MsgMhfLoadFavoriteQuest", &MsgMhfLoadFavoriteQuest{}},
{"MsgMhfReadGuildcard", &MsgMhfReadGuildcard{}},
{"MsgMhfGetEtcPoints", &MsgMhfGetEtcPoints{}},
{"MsgMhfGetGuildMissionList", &MsgMhfGetGuildMissionList{}},
{"MsgMhfGetGuildMissionRecord", &MsgMhfGetGuildMissionRecord{}},
{"MsgMhfGetGuildTresureSouvenir", &MsgMhfGetGuildTresureSouvenir{}},
{"MsgMhfAcquireGuildTresureSouvenir", &MsgMhfAcquireGuildTresureSouvenir{}},
{"MsgMhfEnumerateFestaIntermediatePrize", &MsgMhfEnumerateFestaIntermediatePrize{}},
{"MsgMhfEnumerateFestaPersonalPrize", &MsgMhfEnumerateFestaPersonalPrize{}},
{"MsgMhfGetGuildWeeklyBonusMaster", &MsgMhfGetGuildWeeklyBonusMaster{}},
{"MsgMhfGetGuildWeeklyBonusActiveCount", &MsgMhfGetGuildWeeklyBonusActiveCount{}},
{"MsgMhfGetEquipSkinHist", &MsgMhfGetEquipSkinHist{}},
{"MsgMhfGetRejectGuildScout", &MsgMhfGetRejectGuildScout{}},
{"MsgMhfGetKeepLoginBoostStatus", &MsgMhfGetKeepLoginBoostStatus{}},
{"MsgMhfAcquireMonthlyReward", &MsgMhfAcquireMonthlyReward{}},
{"MsgMhfGetGuildScoutList", &MsgMhfGetGuildScoutList{}},
{"MsgMhfGetGuildManageRight", &MsgMhfGetGuildManageRight{}},
{"MsgMhfGetRengokuRankingRank", &MsgMhfGetRengokuRankingRank{}},
{"MsgMhfGetUdMyPoint", &MsgMhfGetUdMyPoint{}},
{"MsgMhfGetUdTotalPointInfo", &MsgMhfGetUdTotalPointInfo{}},
{"MsgMhfCreateMercenary", &MsgMhfCreateMercenary{}},
{"MsgMhfEnumerateMercenaryLog", &MsgMhfEnumerateMercenaryLog{}},
{"MsgMhfLoadLegendDispatch", &MsgMhfLoadLegendDispatch{}},
{"MsgMhfGetBoostRight", &MsgMhfGetBoostRight{}},
{"MsgMhfPostBoostTimeQuestReturn", &MsgMhfPostBoostTimeQuestReturn{}},
{"MsgMhfGetFpointExchangeList", &MsgMhfGetFpointExchangeList{}},
{"MsgMhfGetRewardSong", &MsgMhfGetRewardSong{}},
{"MsgMhfUseRewardSong", &MsgMhfUseRewardSong{}},
{"MsgMhfGetKouryouPoint", &MsgMhfGetKouryouPoint{}},
{"MsgMhfGetTrendWeapon", &MsgMhfGetTrendWeapon{}},
{"MsgMhfInfoScenarioCounter", &MsgMhfInfoScenarioCounter{}},
{"MsgMhfLoadScenarioData", &MsgMhfLoadScenarioData{}},
{"MsgMhfLoadRengokuData", &MsgMhfLoadRengokuData{}},
{"MsgMhfLoadMezfesData", &MsgMhfLoadMezfesData{}},
{"MsgMhfLoadPlateMyset", &MsgMhfLoadPlateMyset{}},
}
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
for _, tc := range packets {
t.Run(tc.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
_, _ = bf.Seek(0, io.SeekStart)
err := tc.pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
})
}
}
// TestBatchParseTwoUint32 tests packets with AckHandle + one uint32 field.
func TestBatchParseTwoUint32(t *testing.T) {
packets := []struct {
name string
pkt MHFPacket
}{
{"MsgMhfListMail", &MsgMhfListMail{}},
{"MsgMhfEnumerateTitle", &MsgMhfEnumerateTitle{}},
{"MsgMhfInfoGuild", &MsgMhfInfoGuild{}},
{"MsgMhfCheckDailyCafepoint", &MsgMhfCheckDailyCafepoint{}},
{"MsgMhfEntryRookieGuild", &MsgMhfEntryRookieGuild{}},
{"MsgMhfReleaseEvent", &MsgMhfReleaseEvent{}},
{"MsgMhfSetGuildMissionTarget", &MsgMhfSetGuildMissionTarget{}},
{"MsgMhfCancelGuildMissionTarget", &MsgMhfCancelGuildMissionTarget{}},
{"MsgMhfAcquireFestaIntermediatePrize", &MsgMhfAcquireFestaIntermediatePrize{}},
{"MsgMhfAcquireFestaPersonalPrize", &MsgMhfAcquireFestaPersonalPrize{}},
{"MsgMhfGetGachaPlayHistory", &MsgMhfGetGachaPlayHistory{}},
{"MsgMhfPostGuildScout", &MsgMhfPostGuildScout{}},
{"MsgMhfCancelGuildScout", &MsgMhfCancelGuildScout{}},
{"MsgMhfGetEnhancedMinidata", &MsgMhfGetEnhancedMinidata{}},
{"MsgMhfPostBoostTime", &MsgMhfPostBoostTime{}},
{"MsgMhfStartBoostTime", &MsgMhfStartBoostTime{}},
{"MsgMhfAcquireGuildAdventure", &MsgMhfAcquireGuildAdventure{}},
{"MsgMhfGetBoxGachaInfo", &MsgMhfGetBoxGachaInfo{}},
{"MsgMhfResetBoxGachaInfo", &MsgMhfResetBoxGachaInfo{}},
{"MsgMhfAddKouryouPoint", &MsgMhfAddKouryouPoint{}},
{"MsgMhfExchangeKouryouPoint", &MsgMhfExchangeKouryouPoint{}},
{"MsgMhfInfoJoint", &MsgMhfInfoJoint{}},
}
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
for _, tc := range packets {
t.Run(tc.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678) // AckHandle
bf.WriteUint32(0xDEADBEEF) // Second uint32
bf.WriteUint32(0xCAFEBABE) // Padding for 3-field packets
_, _ = bf.Seek(0, io.SeekStart)
err := tc.pkt.Parse(bf, ctx)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
})
}
}
// TestBatchParseMultiField tests packets with various field combinations.
func TestBatchParseMultiField(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("MsgMhfGetRengokuBinary", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(0) // Unk0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetRengokuBinary{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateDistItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // DistType
bf.WriteUint8(3) // Unk1
bf.WriteUint16(4) // Unk2
bf.WriteUint8(0) // Unk3 length (Z1+ mode)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateDistItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.DistType != 2 || pkt.Unk1 != 3 || pkt.MaxCount != 4 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfApplyDistItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // DistributionType
bf.WriteUint32(3) // DistributionID
bf.WriteUint32(4) // Unk2
bf.WriteUint32(5) // Unk3
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfApplyDistItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.DistributionType != 2 || pkt.DistributionID != 3 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfAcquireDistItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // DistributionType
bf.WriteUint32(3) // DistributionID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireDistItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.DistributionType != 2 || pkt.DistributionID != 3 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfGetDistDescription", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
bf.WriteUint32(3) // DistributionID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetDistDescription{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.Unk0 != 2 || pkt.DistributionID != 3 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfRegisterEvent", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
bf.WriteUint16(3) // WorldID
bf.WriteUint16(4) // LandID
bf.WriteBool(true) // Unk1
bf.WriteUint8(0) // Zeroed (discarded)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfRegisterEvent{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.Unk0 != 2 || pkt.WorldID != 3 || pkt.LandID != 4 || !pkt.CheckOnly {
t.Error("field mismatch")
}
})
t.Run("MsgMhfUpdateCafepoint", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Zeroed (discarded)
bf.WriteUint16(3) // Zeroed (discarded)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateCafepoint{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfUpdateEtcPoint", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // PointType
bf.WriteInt16(-5) // Delta
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateEtcPoint{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.PointType != 2 || pkt.Delta != -5 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfAcquireTitle", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Title count
bf.WriteUint16(0) // Zeroed
bf.WriteUint16(4) // TitleIDs[0]
bf.WriteUint16(5) // TitleIDs[1]
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireTitle{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if len(pkt.TitleIDs) != 2 || pkt.TitleIDs[0] != 4 || pkt.TitleIDs[1] != 5 {
t.Errorf("TitleIDs = %v, want [4, 5]", pkt.TitleIDs)
}
})
t.Run("MsgSysHideClient", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteBool(true) // Hide
bf.WriteUint8(0) // Zeroed (discarded)
bf.WriteUint8(0) // Zeroed (discarded)
bf.WriteUint8(0) // Zeroed (discarded)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysHideClient{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if !pkt.Hide {
t.Error("field mismatch")
}
})
t.Run("MsgSysIssueLogkey", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
bf.WriteUint16(0) // Zeroed (discarded)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysIssueLogkey{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.Unk0 != 2 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfGetTinyBin", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
bf.WriteUint8(3) // Unk1
bf.WriteUint8(4) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetTinyBin{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.Unk0 != 2 || pkt.Unk1 != 3 || pkt.Unk2 != 4 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfGetPaperData", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk0
bf.WriteUint32(3) // Unk1
bf.WriteUint32(4) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetPaperData{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.DataType != 4 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfGetEarthValue", func(t *testing.T) {
bf := byteframe.NewByteFrame()
for i := 0; i < 8; i++ {
bf.WriteUint32(uint32(i + 1))
}
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetEarthValue{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.Unk6 != 8 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfPresentBox", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk0
bf.WriteUint32(3) // Unk1
bf.WriteUint32(2) // Unk2 (controls Unk7 slice length)
bf.WriteUint32(5) // Unk3
bf.WriteUint32(6) // Unk4
bf.WriteUint32(7) // Unk5
bf.WriteUint32(8) // Unk6
bf.WriteUint32(9) // Unk7[0]
bf.WriteUint32(10) // Unk7[1]
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPresentBox{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 || pkt.Unk2 != 2 || pkt.Unk6 != 8 || len(pkt.Unk7) != 2 || pkt.Unk7[1] != 10 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfReadMail", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // AccIndex
bf.WriteUint8(3) // Index
bf.WriteUint16(4) // Unk0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfReadMail{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AccIndex != 2 || pkt.Index != 3 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfOprMember", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteBool(true) // Blacklist
bf.WriteBool(false) // Operation
bf.WriteUint8(0) // Padding
bf.WriteUint8(1) // CharID count
bf.WriteUint32(99) // CharIDs[0]
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOprMember{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if !pkt.Blacklist || pkt.Operation || len(pkt.CharIDs) != 1 || pkt.CharIDs[0] != 99 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfListMember", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
bf.WriteUint8(0) // Zeroed
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfListMember{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.Unk0 != 2 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfTransferItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk0
bf.WriteUint8(3) // Unk1
bf.WriteUint8(0) // Zeroed
bf.WriteUint16(4) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfTransferItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.QuestID != 2 || pkt.ItemType != 3 || pkt.Quantity != 4 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfMercenaryHuntdata", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfMercenaryHuntdata{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.RequestType != 2 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfEnumeratePrice", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(0) // Unk0
bf.WriteUint16(0) // Unk1
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumeratePrice{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateUnionItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateUnionItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.AckHandle != 1 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfEnumerateGuildItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GuildId
bf.WriteUint16(3) // Unk0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateGuildItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.GuildID != 2 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfEnumerateGuildMember", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
bf.WriteUint32(3) // Unk1
bf.WriteUint32(99) // GuildID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateGuildMember{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.GuildID != 99 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfOperateGuildMember", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GuildID
bf.WriteUint32(99) // CharID
bf.WriteUint8(1) // Action
bf.WriteBytes([]byte{0, 0, 0}) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateGuildMember{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.CharID != 99 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfUpdateEquipSkinHist", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // MogType
bf.WriteUint16(3) // ArmourID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateEquipSkinHist{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.MogType != 2 || pkt.ArmourID != 3 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfSetRejectGuildScout", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteBool(true) // Reject
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSetRejectGuildScout{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if !pkt.Reject {
t.Error("field mismatch")
}
})
t.Run("MsgMhfUseKeepLoginBoost", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(3) // BoostWeekUsed
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUseKeepLoginBoost{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.BoostWeekUsed != 3 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfSetCaAchievementHist", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk0
bf.WriteUint32(3) // Unk1
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSetCaAchievementHist{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfAddGuildWeeklyBonusExceptionalUser", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // NumUsers
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddGuildWeeklyBonusExceptionalUser{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetLobbyCrowd", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Server
bf.WriteUint32(3) // Room
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetLobbyCrowd{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSexChanger", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(1) // Gender
bf.WriteUint8(0) // Unk0
bf.WriteUint8(0) // Unk1
bf.WriteUint8(0) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSexChanger{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.Gender != 1 {
t.Error("field mismatch")
}
})
t.Run("MsgMhfSetKiju", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(5) // Unk1
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSetKiju{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfAddUdPoint", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk1
bf.WriteUint32(3) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddUdPoint{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetWeeklySeibatuRankingReward", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2)
bf.WriteUint32(3)
bf.WriteUint32(4)
bf.WriteUint32(5)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetWeeklySeibatuRankingReward{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetEarthStatus", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk0
bf.WriteUint32(3) // Unk1
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetEarthStatus{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfAddGuildMissionCount", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // MissionID
bf.WriteUint32(3) // Count
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddGuildMissionCount{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateAiroulist", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
bf.WriteUint16(3) // Unk1
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateAiroulist{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfOperateGuildTresureReport", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(10) // HuntID
bf.WriteUint16(2) // State
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOperateGuildTresureReport{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfAcquireGuildTresure", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(10) // HuntID
bf.WriteUint8(1) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireGuildTresure{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateGuildTresure", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(5) // MaxHunts
bf.WriteUint32(0) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateGuildTresure{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetTenrouirai", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
bf.WriteUint32(3) // Unk1
bf.WriteUint16(4) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetTenrouirai{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfPostTenrouirai", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
bf.WriteUint32(3) // Unk1
bf.WriteUint32(4) // Unk2
bf.WriteUint32(5) // Unk3
bf.WriteUint32(6) // Unk4
bf.WriteUint8(7) // Unk5
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPostTenrouirai{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetSeibattle", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
bf.WriteUint8(3) // Unk1
bf.WriteUint32(4) // Unk2
bf.WriteUint8(5) // Unk3
bf.WriteUint16(6) // Unk4
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetSeibattle{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetRyoudama", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
bf.WriteUint8(3) // Unk1
bf.WriteUint32(99) // GuildID
bf.WriteUint8(4) // Unk3
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetRyoudama{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateRengokuRanking", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Leaderboard
bf.WriteUint16(3) // Unk1
bf.WriteUint16(4) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateRengokuRanking{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetAdditionalBeatReward", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2)
bf.WriteUint32(3)
bf.WriteUint32(4)
bf.WriteUint32(5)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetAdditionalBeatReward{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSetRestrictionEvent", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk0
bf.WriteUint32(3) // Unk1
bf.WriteUint32(4) // Unk2
bf.WriteUint8(5) // Unk3
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSetRestrictionEvent{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfUpdateUseTrendWeaponLog", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
bf.WriteUint16(3) // Unk1
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateUseTrendWeaponLog{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfDisplayedAchievement", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(42) // AchievementID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfDisplayedAchievement{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfRegistGuildCooking", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // OverwriteID
bf.WriteUint16(3) // MealID
bf.WriteUint8(4) // Success
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfRegistGuildCooking{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfChargeGuildAdventure", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // ID
bf.WriteUint32(3) // Amount
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfChargeGuildAdventure{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfRegistGuildAdventure", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Destination
bf.WriteUint32(0) // discard CharID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfRegistGuildAdventure{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfReadMercenaryW", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Op
bf.WriteUint8(3) // Unk1
bf.WriteUint16(4) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfReadMercenaryW{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfReadMercenaryM", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // CharID
bf.WriteUint32(3) // MercID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfReadMercenaryM{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfContractMercenary", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // PactMercID
bf.WriteUint32(3) // CID
bf.WriteUint8(4) // Op
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfContractMercenary{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetGuildTargetMemberNum", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GuildID
bf.WriteUint8(3) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetGuildTargetMemberNum{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSetGuildManageRight", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // CharID
bf.WriteBool(true) // Allowed
bf.WriteBytes([]byte{0, 0, 0}) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSetGuildManageRight{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfAnswerGuildScout", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // LeaderID
bf.WriteBool(true) // Answer
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAnswerGuildScout{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfPlayStepupGacha", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GachaID
bf.WriteUint8(3) // RollType
bf.WriteUint8(4) // GachaType
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPlayStepupGacha{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfPlayBoxGacha", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GachaID
bf.WriteUint8(3) // RollType
bf.WriteUint8(4) // GachaType
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPlayBoxGacha{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfPlayNormalGacha", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GachaID
bf.WriteUint8(3) // RollType
bf.WriteUint8(4) // GachaType
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPlayNormalGacha{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfReceiveGachaItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(5) // Max
bf.WriteBool(false) // Freeze
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfReceiveGachaItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetStepupStatus", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GachaID
bf.WriteUint8(3) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetStepupStatus{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfUseGachaPoint", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
bf.WriteUint32(3) // TrialCoins
bf.WriteUint32(4) // PremiumCoins
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUseGachaPoint{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateGuildMessageBoard", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk0
bf.WriteUint32(3) // MaxPosts
bf.WriteUint32(4) // BoardType
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateGuildMessageBoard{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
}
// TestBatchParseVariableLength tests packets with variable-length data.
func TestBatchParseVariableLength(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("MsgMhfSaveFavoriteQuest", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(4) // DataSize
bf.WriteBytes([]byte{0x01, 0x02, 0x03, 0x04}) // Data
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveFavoriteQuest{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if len(pkt.Data) != 4 {
t.Errorf("Data len = %d, want 4", len(pkt.Data))
}
})
t.Run("MsgMhfSavedata_withDataSize", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(0) // AllocMemSize
bf.WriteUint8(0) // SaveType
bf.WriteUint32(0) // Unk1
bf.WriteUint32(3) // DataSize (non-zero)
bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSavedata{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if len(pkt.RawDataPayload) != 3 {
t.Errorf("RawDataPayload len = %d, want 3", len(pkt.RawDataPayload))
}
})
t.Run("MsgMhfSavedata_withAllocMem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // AllocMemSize
bf.WriteUint8(0) // SaveType
bf.WriteUint32(0) // Unk1
bf.WriteUint32(0) // DataSize (zero -> use AllocMemSize)
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSavedata{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if len(pkt.RawDataPayload) != 2 {
t.Errorf("RawDataPayload len = %d, want 2", len(pkt.RawDataPayload))
}
})
t.Run("MsgMhfTransitMessage", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
bf.WriteUint8(3) // Unk1
bf.WriteUint16(4) // SearchType
bf.WriteUint16(3) // inline data length
bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfTransitMessage{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if len(pkt.MessageData) != 3 {
t.Errorf("MessageData len = %d, want 3", len(pkt.MessageData))
}
})
t.Run("MsgMhfPostTinyBin", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // Unk0
bf.WriteUint8(3) // Unk1
bf.WriteUint8(4) // Unk2
bf.WriteUint16(2) // inline data length
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPostTinyBin{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if len(pkt.Data) != 2 {
t.Errorf("Data len = %d, want 2", len(pkt.Data))
}
})
t.Run("MsgSysRecordLog", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // Unk0
bf.WriteUint16(3) // Unk1
bf.WriteUint16(4) // HardcodedDataSize
bf.WriteUint32(5) // Unk3
bf.WriteBytes([]byte{0x01, 0x02, 0x03, 0x04})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysRecordLog{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if len(pkt.Data) != 4 {
t.Errorf("Data len = %d, want 4", len(pkt.Data))
}
})
t.Run("MsgMhfUpdateInterior", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteBytes(make([]byte, 20)) // InteriorData
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateInterior{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if len(pkt.InteriorData) != 20 {
t.Error("InteriorData wrong size")
}
})
t.Run("MsgMhfSavePartner", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(3) // DataSize
bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSavePartner{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSaveOtomoAirou", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // DataSize
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveOtomoAirou{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSaveHunterNavi", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // DataSize
bf.WriteBool(true) // IsDataDiff
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveHunterNavi{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSavePlateData", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(3) // DataSize
bf.WriteBool(false) // IsDataDiff
bf.WriteBytes([]byte{0x01, 0x02, 0x03})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSavePlateData{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSavePlateBox", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // DataSize
bf.WriteBool(true) // IsDataDiff
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSavePlateBox{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSavePlateMyset", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // DataSize
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSavePlateMyset{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSaveDecoMyset", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // DataSize
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveDecoMyset{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSaveRengokuData", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // DataSize
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveRengokuData{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSaveMezfesData", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // DataSize
bf.WriteBytes([]byte{0xAA, 0xBB})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveMezfesData{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSaveScenarioData", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(3) // DataSize
bf.WriteBytes([]byte{0x01, 0x02, 0x03})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSaveScenarioData{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfAcquireExchangeShop", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(3) // DataSize
bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireExchangeShop{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfSetEnhancedMinidata", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(0) // Unk0
bf.WriteBytes(make([]byte, 0x400)) // RawDataPayload
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfSetEnhancedMinidata{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetBbsUserStatus", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteBytes(make([]byte, 12)) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetBbsUserStatus{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetBbsSnsStatus", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteBytes(make([]byte, 12)) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetBbsSnsStatus{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
}
// TestBatchParseArrangeGuildMember tests the array-parsing packet.
func TestBatchParseArrangeGuildMember(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GuildID
bf.WriteUint16(3) // charCount
bf.WriteUint32(10) // CharIDs[0]
bf.WriteUint32(20) // CharIDs[1]
bf.WriteUint32(30) // CharIDs[2]
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfArrangeGuildMember{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
t.Fatal(err)
}
if len(pkt.CharIDs) != 3 || pkt.CharIDs[2] != 30 {
t.Errorf("CharIDs = %v, want [10 20 30]", pkt.CharIDs)
}
}
// TestBatchParseUpdateGuildIcon tests the guild icon array packet.
func TestBatchParseUpdateGuildIcon(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // GuildID
bf.WriteUint16(1) // PartCount
bf.WriteUint16(0) // Unk1
// One part: 14 bytes
bf.WriteUint16(0) // Index
bf.WriteUint16(1) // ID
bf.WriteUint8(2) // Page
bf.WriteUint8(3) // Size
bf.WriteUint8(4) // Rotation
bf.WriteUint8(0xFF) // Red
bf.WriteUint8(0x00) // Green
bf.WriteUint8(0x80) // Blue
bf.WriteUint16(100) // PosX
bf.WriteUint16(200) // PosY
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfUpdateGuildIcon{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
t.Fatal(err)
}
if len(pkt.IconParts) != 1 || pkt.IconParts[0].Red != 0xFF {
t.Error("icon parts mismatch")
}
}
// TestBatchParseSysLoadRegister tests the fixed-zero validation packet.
func TestBatchParseSysLoadRegister(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // RegisterID
bf.WriteUint8(3) // Unk1
bf.WriteUint16(0) // fixedZero0
bf.WriteUint8(0) // fixedZero1
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysLoadRegister{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
t.Fatal(err)
}
if pkt.RegisterID != 2 || pkt.Values != 3 {
t.Error("field mismatch")
}
}
// TestBatchParseSysLoadRegisterNonZeroPadding tests that SysLoadRegister Parse
// succeeds even with non-zero values in the padding fields (they are discarded).
func TestBatchParseSysLoadRegisterNonZeroPadding(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // RegisterID
bf.WriteUint8(3) // Values
bf.WriteUint8(1) // Zeroed (discarded, non-zero is OK)
bf.WriteUint16(1) // Zeroed (discarded, non-zero is OK)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysLoadRegister{}
err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if pkt.AckHandle != 1 {
t.Errorf("AckHandle = %d, want 1", pkt.AckHandle)
}
if pkt.RegisterID != 2 {
t.Errorf("RegisterID = %d, want 2", pkt.RegisterID)
}
if pkt.Values != 3 {
t.Errorf("Values = %d, want 3", pkt.Values)
}
}
// TestBatchParseSysOperateRegister tests the operate register packet.
func TestBatchParseSysOperateRegister(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // SemaphoreID
bf.WriteUint16(0) // fixedZero
bf.WriteUint16(3) // dataSize
bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysOperateRegister{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
t.Fatal(err)
}
if len(pkt.RawDataPayload) != 3 {
t.Error("payload size mismatch")
}
}
// TestBatchParseSysOperateRegisterNonZeroPadding tests that SysOperateRegister Parse
// succeeds even with non-zero values in the padding field (it is discarded).
func TestBatchParseSysOperateRegisterNonZeroPadding(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // SemaphoreID
bf.WriteUint16(1) // Zeroed (discarded, non-zero is OK)
bf.WriteUint16(0) // dataSize
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysOperateRegister{}
err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if pkt.AckHandle != 1 {
t.Errorf("AckHandle = %d, want 1", pkt.AckHandle)
}
if pkt.SemaphoreID != 2 {
t.Errorf("SemaphoreID = %d, want 2", pkt.SemaphoreID)
}
if len(pkt.RawDataPayload) != 0 {
t.Errorf("RawDataPayload len = %d, want 0", len(pkt.RawDataPayload))
}
}
// TestBatchParseSysGetFile tests the conditional scenario file packet.
func TestBatchParseSysGetFile(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("non-scenario", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteBool(false) // IsScenario
bf.WriteUint8(5) // filenameLength
bf.WriteBytes([]byte("test\x00"))
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysGetFile{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.Filename != "test" || pkt.IsScenario {
t.Error("field mismatch")
}
})
t.Run("scenario", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteBool(true) // IsScenario
bf.WriteUint8(0) // filenameLength (empty)
bf.WriteUint8(10) // CategoryID
bf.WriteUint32(100) // MainID
bf.WriteUint8(5) // ChapterID
bf.WriteUint8(0) // Flags
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysGetFile{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if !pkt.IsScenario || pkt.ScenarioIdentifer.MainID != 100 {
t.Error("field mismatch")
}
})
}
// TestBatchParseSysTerminalLog tests the entry-array packet.
func TestBatchParseSysTerminalLog(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(2) // LogID
bf.WriteUint16(1) // EntryCount
bf.WriteUint16(0) // Unk0
// One entry: 4 + 1 + 1 + (15*2) = 36 bytes
bf.WriteUint32(0) // Index
bf.WriteUint8(1) // Type1
bf.WriteUint8(2) // Type2
for i := 0; i < 15; i++ {
bf.WriteInt16(int16(i))
}
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysTerminalLog{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
t.Fatal(err)
}
if len(pkt.Entries) != 1 || pkt.Entries[0].Type1 != 1 {
t.Error("entries mismatch")
}
}
// TestBatchParseNoOpPackets tests packets with empty Parse (return nil).
func TestBatchParseNoOpPackets(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
bf := byteframe.NewByteFrame()
packets := []struct {
name string
pkt MHFPacket
}{
{"MsgSysExtendThreshold", &MsgSysExtendThreshold{}},
{"MsgSysEnd", &MsgSysEnd{}},
{"MsgSysNop", &MsgSysNop{}},
{"MsgSysStageDestruct", &MsgSysStageDestruct{}},
}
for _, tc := range packets {
t.Run(tc.name, func(t *testing.T) {
if err := tc.pkt.Parse(bf, ctx); err != nil {
t.Fatalf("Parse() error = %v", err)
}
})
}
}
// TestBatchParseNotImplemented tests that Parse returns NOT IMPLEMENTED for stub packets.
func TestBatchParseNotImplemented(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
bf := byteframe.NewByteFrame()
packets := []MHFPacket{
&MsgSysReserve01{}, &MsgSysReserve02{}, &MsgSysReserve03{},
&MsgSysReserve04{}, &MsgSysReserve05{}, &MsgSysReserve06{},
&MsgSysReserve07{}, &MsgSysReserve0C{}, &MsgSysReserve0D{},
&MsgSysReserve0E{}, &MsgSysReserve4A{}, &MsgSysReserve4B{},
&MsgSysReserve4C{}, &MsgSysReserve4D{}, &MsgSysReserve4E{},
&MsgSysReserve4F{}, &MsgSysReserve55{}, &MsgSysReserve56{},
&MsgSysReserve57{}, &MsgSysReserve5C{}, &MsgSysReserve5E{},
&MsgSysReserve5F{}, &MsgSysReserve71{}, &MsgSysReserve72{},
&MsgSysReserve73{}, &MsgSysReserve74{}, &MsgSysReserve75{},
&MsgSysReserve76{}, &MsgSysReserve77{}, &MsgSysReserve78{},
&MsgSysReserve79{}, &MsgSysReserve7A{}, &MsgSysReserve7B{},
&MsgSysReserve7C{}, &MsgSysReserve7E{}, &MsgSysReserve18E{},
&MsgSysReserve18F{}, &MsgSysReserve19E{}, &MsgSysReserve19F{},
&MsgSysReserve1A4{}, &MsgSysReserve1A6{}, &MsgSysReserve1A7{},
&MsgSysReserve1A8{}, &MsgSysReserve1A9{}, &MsgSysReserve1AA{},
&MsgSysReserve1AB{}, &MsgSysReserve1AC{}, &MsgSysReserve1AD{},
&MsgSysReserve1AE{}, &MsgSysReserve1AF{}, &MsgSysReserve19B{},
&MsgSysReserve192{}, &MsgSysReserve193{}, &MsgSysReserve194{},
&MsgSysReserve180{},
&MsgMhfReserve10F{},
// Empty-struct packets with NOT IMPLEMENTED Parse
&MsgHead{}, &MsgSysSetStatus{}, &MsgSysEcho{},
&MsgSysLeaveStage{}, &MsgSysAddObject{}, &MsgSysDelObject{},
&MsgSysDispObject{}, &MsgSysHideObject{},
&MsgMhfServerCommand{}, &MsgMhfSetLoginwindow{}, &MsgMhfShutClient{},
&MsgMhfUpdateGuildcard{},
&MsgCaExchangeItem{},
}
for _, pkt := range packets {
t.Run(pkt.Opcode().String(), func(t *testing.T) {
err := pkt.Parse(bf, ctx)
if err == nil {
t.Error("expected NOT IMPLEMENTED error")
}
})
}
}
// TestBatchBuildNotImplemented tests that Build returns NOT IMPLEMENTED for many packets.
func TestBatchBuildNotImplemented(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
bf := byteframe.NewByteFrame()
packets := []MHFPacket{
&MsgMhfLoaddata{}, &MsgMhfSavedata{},
&MsgMhfListMember{}, &MsgMhfOprMember{},
&MsgMhfEnumerateDistItem{}, &MsgMhfApplyDistItem{}, &MsgMhfAcquireDistItem{},
&MsgMhfGetDistDescription{}, &MsgMhfSendMail{}, &MsgMhfReadMail{},
&MsgMhfListMail{}, &MsgMhfOprtMail{},
&MsgMhfLoadFavoriteQuest{}, &MsgMhfSaveFavoriteQuest{},
&MsgMhfRegisterEvent{}, &MsgMhfReleaseEvent{},
&MsgMhfTransitMessage{}, &MsgMhfPresentBox{},
&MsgMhfAcquireTitle{}, &MsgMhfEnumerateTitle{},
&MsgMhfInfoGuild{}, &MsgMhfEnumerateGuild{},
&MsgMhfCreateGuild{}, &MsgMhfOperateGuild{},
&MsgMhfOperateGuildMember{}, &MsgMhfArrangeGuildMember{},
&MsgMhfEnumerateGuildMember{}, &MsgMhfUpdateGuildIcon{},
&MsgMhfInfoFesta{}, &MsgMhfEntryFesta{},
&MsgMhfChargeFesta{}, &MsgMhfAcquireFesta{},
&MsgMhfVoteFesta{}, &MsgMhfInfoTournament{},
&MsgMhfEntryTournament{}, &MsgMhfAcquireTournament{},
&MsgMhfUpdateCafepoint{}, &MsgMhfCheckDailyCafepoint{},
&MsgMhfGetEtcPoints{}, &MsgMhfUpdateEtcPoint{},
&MsgMhfReadGuildcard{}, &MsgMhfUpdateGuildcard{},
&MsgMhfGetTinyBin{}, &MsgMhfPostTinyBin{},
&MsgMhfGetPaperData{}, &MsgMhfGetEarthValue{},
&MsgSysRecordLog{}, &MsgSysIssueLogkey{}, &MsgSysTerminalLog{},
&MsgSysHideClient{}, &MsgSysGetFile{},
&MsgSysOperateRegister{}, &MsgSysLoadRegister{},
&MsgMhfGetGuildMissionList{}, &MsgMhfGetGuildMissionRecord{},
&MsgMhfAddGuildMissionCount{}, &MsgMhfSetGuildMissionTarget{},
&MsgMhfCancelGuildMissionTarget{},
&MsgMhfEnumerateGuildTresure{}, &MsgMhfRegistGuildTresure{},
&MsgMhfAcquireGuildTresure{}, &MsgMhfOperateGuildTresureReport{},
&MsgMhfGetGuildTresureSouvenir{}, &MsgMhfAcquireGuildTresureSouvenir{},
&MsgMhfEnumerateFestaIntermediatePrize{}, &MsgMhfAcquireFestaIntermediatePrize{},
&MsgMhfEnumerateFestaPersonalPrize{}, &MsgMhfAcquireFestaPersonalPrize{},
&MsgMhfGetGuildWeeklyBonusMaster{}, &MsgMhfGetGuildWeeklyBonusActiveCount{},
&MsgMhfAddGuildWeeklyBonusExceptionalUser{},
&MsgMhfGetEquipSkinHist{}, &MsgMhfUpdateEquipSkinHist{},
&MsgMhfGetEnhancedMinidata{}, &MsgMhfSetEnhancedMinidata{},
&MsgMhfGetLobbyCrowd{},
&MsgMhfGetRejectGuildScout{}, &MsgMhfSetRejectGuildScout{},
&MsgMhfGetKeepLoginBoostStatus{}, &MsgMhfUseKeepLoginBoost{},
&MsgMhfAcquireMonthlyReward{},
&MsgMhfPostGuildScout{}, &MsgMhfCancelGuildScout{},
&MsgMhfAnswerGuildScout{}, &MsgMhfGetGuildScoutList{},
&MsgMhfGetGuildManageRight{}, &MsgMhfSetGuildManageRight{},
&MsgMhfGetGuildTargetMemberNum{},
&MsgMhfPlayStepupGacha{}, &MsgMhfReceiveGachaItem{},
&MsgMhfGetStepupStatus{}, &MsgMhfPlayNormalGacha{},
&MsgMhfPlayBoxGacha{}, &MsgMhfGetBoxGachaInfo{}, &MsgMhfResetBoxGachaInfo{},
&MsgMhfUseGachaPoint{}, &MsgMhfGetGachaPlayHistory{},
&MsgMhfSavePartner{}, &MsgMhfSaveOtomoAirou{},
&MsgMhfSaveHunterNavi{}, &MsgMhfSavePlateData{},
&MsgMhfSavePlateBox{}, &MsgMhfSavePlateMyset{},
&MsgMhfSaveDecoMyset{}, &MsgMhfSaveRengokuData{}, &MsgMhfSaveMezfesData{},
&MsgMhfCreateMercenary{}, &MsgMhfSaveMercenary{},
&MsgMhfReadMercenaryW{}, &MsgMhfReadMercenaryM{},
&MsgMhfContractMercenary{}, &MsgMhfEnumerateMercenaryLog{},
&MsgMhfRegistGuildCooking{}, &MsgMhfRegistGuildAdventure{},
&MsgMhfAcquireGuildAdventure{}, &MsgMhfChargeGuildAdventure{},
&MsgMhfLoadLegendDispatch{},
&MsgMhfPostBoostTime{}, &MsgMhfStartBoostTime{},
&MsgMhfPostBoostTimeQuestReturn{}, &MsgMhfGetBoostRight{},
&MsgMhfGetFpointExchangeList{},
&MsgMhfGetRewardSong{}, &MsgMhfUseRewardSong{},
&MsgMhfGetKouryouPoint{}, &MsgMhfAddKouryouPoint{}, &MsgMhfExchangeKouryouPoint{},
&MsgMhfSexChanger{}, &MsgMhfSetKiju{}, &MsgMhfAddUdPoint{},
&MsgMhfGetTrendWeapon{}, &MsgMhfUpdateUseTrendWeaponLog{},
&MsgMhfSetRestrictionEvent{},
&MsgMhfGetWeeklySeibatuRankingReward{}, &MsgMhfGetEarthStatus{},
&MsgMhfAddGuildMissionCount{},
&MsgMhfEnumerateAiroulist{},
&MsgMhfEnumerateRengokuRanking{}, &MsgMhfGetRengokuRankingRank{},
&MsgMhfGetAdditionalBeatReward{},
&MsgMhfSetCaAchievementHist{},
&MsgMhfGetUdMyPoint{}, &MsgMhfGetUdTotalPointInfo{},
&MsgMhfDisplayedAchievement{},
&MsgMhfUpdateInterior{},
&MsgMhfEnumerateUnionItem{},
&MsgMhfEnumerateGuildItem{},
&MsgMhfEnumerateGuildMember{},
&MsgMhfEnumerateGuildMessageBoard{},
&MsgMhfMercenaryHuntdata{},
&MsgMhfEntryRookieGuild{},
&MsgMhfEnumeratePrice{},
&MsgMhfTransferItem{},
&MsgMhfGetSeibattle{}, &MsgMhfGetRyoudama{},
&MsgMhfGetTenrouirai{}, &MsgMhfPostTenrouirai{},
&MsgMhfGetBbsUserStatus{}, &MsgMhfGetBbsSnsStatus{},
&MsgMhfInfoScenarioCounter{}, &MsgMhfLoadScenarioData{},
&MsgMhfSaveScenarioData{},
&MsgMhfAcquireExchangeShop{},
&MsgMhfLoadRengokuData{}, &MsgMhfGetRengokuBinary{},
&MsgMhfLoadMezfesData{}, &MsgMhfLoadPlateMyset{},
}
for _, pkt := range packets {
t.Run(pkt.Opcode().String(), func(t *testing.T) {
err := pkt.Build(bf, ctx)
if err == nil {
// Some packets may have Build implemented - that's fine
t.Logf("Build() succeeded (has implementation)")
}
})
}
}
// TestBatchParseReserve188and18B tests reserve packets with AckHandle.
func TestBatchParseReserve188and18B(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
for _, tc := range []struct {
name string
pkt MHFPacket
}{
{"MsgSysReserve188", &MsgSysReserve188{}},
{"MsgSysReserve18B", &MsgSysReserve18B{}},
} {
t.Run(tc.name, func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x12345678)
_, _ = bf.Seek(0, io.SeekStart)
if err := tc.pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
}
}
// TestBatchParseStageStringPackets tests packets that read a stage ID string.
func TestBatchParseStageStringPackets(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("MsgSysGetStageBinary", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // BinaryType0
bf.WriteUint8(3) // BinaryType1
bf.WriteUint32(0) // Unk0
bf.WriteUint8(6) // stageIDLength
bf.WriteBytes(append([]byte("room1"), 0x00))
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysGetStageBinary{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.StageID != "room1" {
t.Errorf("StageID = %q, want room1", pkt.StageID)
}
})
t.Run("MsgSysWaitStageBinary", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // BinaryType0
bf.WriteUint8(3) // BinaryType1
bf.WriteUint32(0) // Unk0
bf.WriteUint8(6) // stageIDLength
bf.WriteBytes(append([]byte("room2"), 0x00))
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysWaitStageBinary{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.StageID != "room2" {
t.Errorf("StageID = %q, want room2", pkt.StageID)
}
})
t.Run("MsgSysSetStageBinary", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(1) // BinaryType0
bf.WriteUint8(2) // BinaryType1
bf.WriteUint8(6) // stageIDLength
bf.WriteUint16(3) // dataSize
bf.WriteBytes(append([]byte("room3"), 0x00))
bf.WriteBytes([]byte{0xAA, 0xBB, 0xCC})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysSetStageBinary{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.StageID != "room3" || len(pkt.RawDataPayload) != 3 {
t.Error("field mismatch")
}
})
t.Run("MsgSysEnumerateClient", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // Unk0
bf.WriteUint8(3) // Get
bf.WriteUint8(6) // stageIDLength
bf.WriteBytes(append([]byte("room4"), 0x00))
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysEnumerateClient{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.StageID != "room4" {
t.Errorf("StageID = %q, want room4", pkt.StageID)
}
})
t.Run("MsgSysSetStagePass", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(1) // Unk0
bf.WriteUint8(5) // Password length
bf.WriteBytes(append([]byte("pass"), 0x00))
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgSysSetStagePass{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.Password != "pass" {
t.Errorf("Password = %q, want pass", pkt.Password)
}
})
}
// TestBatchParseStampcardStamp tests the stampcard packet with downcasts.
func TestBatchParseStampcardStamp(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(2) // HR
bf.WriteUint16(3) // GR
bf.WriteUint16(4) // Stamps
bf.WriteUint16(0) // discard
bf.WriteUint32(5) // Reward1 (downcast to uint16)
bf.WriteUint32(6) // Reward2
bf.WriteUint32(7) // Item1
bf.WriteUint32(8) // Item2
bf.WriteUint32(9) // Quantity1
bf.WriteUint32(10) // Quantity2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfStampcardStamp{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
t.Fatal(err)
}
if pkt.HR != 2 || pkt.GR != 3 || pkt.Stamps != 4 || pkt.Reward1 != 5 {
t.Error("field mismatch")
}
}
// TestBatchParseAnnounce tests the announce packet with fixed-size byte array.
func TestBatchParseAnnounce(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(0x7F000001) // IPAddress (127.0.0.1)
bf.WriteUint16(54001) // Port
bf.WriteUint8(0) // discard
bf.WriteUint16(0) // discard
bf.WriteBytes(make([]byte, 32)) // StageID
bf.WriteUint32(0) // Data length (0 bytes)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAnnounce{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
t.Fatal(err)
}
if pkt.IPAddress != 0x7F000001 || pkt.Port != 54001 {
t.Error("field mismatch")
}
}
// TestBatchParseOprtMail tests conditional parsing.
func TestBatchParseOprtMail(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("delete", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(0) // AccIndex
bf.WriteUint8(1) // Index
bf.WriteUint8(0x01) // Operation = DELETE
bf.WriteUint8(0) // Unk0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOprtMail{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("acquire_item", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(0) // AccIndex
bf.WriteUint8(1) // Index
bf.WriteUint8(0x05) // Operation = ACQUIRE_ITEM
bf.WriteUint8(0) // Unk0
bf.WriteUint16(5) // Amount
bf.WriteUint16(100) // ItemID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfOprtMail{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
if pkt.Amount != 5 || pkt.ItemID != 100 {
t.Error("field mismatch")
}
})
}
// TestBatchParsePostTowerInfo tests the 11-field packet.
func TestBatchParsePostTowerInfo(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
for i := 0; i < 11; i++ {
bf.WriteUint32(uint32(i + 10))
}
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfPostTowerInfo{}
if err := pkt.Parse(bf, &clientctx.ClientContext{RealClientMode: cfg.ZZ}); err != nil {
t.Fatal(err)
}
}
// TestBatchParseGuildHuntdata tests conditional guild huntdata.
// TestBatchParseAdditionalMultiField tests Parse for more packets with multiple fields.
func TestBatchParseAdditionalMultiField(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("MsgMhfAcquireFesta", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(100) // FestaID
bf.WriteUint32(200) // GuildID
bf.WriteUint16(0) // Unk
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireFesta{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfAddUdTacticsPoint", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint16(10) // Unk0
bf.WriteUint32(500) // Unk1
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddUdTacticsPoint{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfApplyCampaign", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(1) // Unk0
bf.WriteUint16(2) // Unk1
bf.WriteBytes(make([]byte, 16)) // Unk2 (16 bytes)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfApplyCampaign{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfCheckMonthlyItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(1) // Type
bf.WriteBytes(make([]byte, 3))
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfCheckMonthlyItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfCheckWeeklyStamp_hl", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(1) // StampType = 1 ("hl")
bf.WriteUint8(0) // Unk1 (bool)
bf.WriteUint16(10) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfCheckWeeklyStamp{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfCheckWeeklyStamp_ex", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // StampType = 2 ("ex")
bf.WriteUint8(1) // Unk1 (bool)
bf.WriteUint16(20) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfCheckWeeklyStamp{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEntryFesta", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(100) // FestaID
bf.WriteUint32(200) // GuildID
bf.WriteUint16(0) // padding
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEntryFesta{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateFestaMember", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(100) // FestaID
bf.WriteUint32(200) // GuildID
bf.WriteUint16(0) // padding
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateFestaMember{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateInvGuild", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteBytes(make([]byte, 9))
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateInvGuild{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateWarehouse_item", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(0) // boxType = 0 ("item")
bf.WriteUint8(1) // BoxIndex
bf.WriteUint16(0) // padding
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateWarehouse{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateWarehouse_equip", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(1) // boxType = 1 ("equip")
bf.WriteUint8(2) // BoxIndex
bf.WriteUint16(0) // padding
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateWarehouse{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfExchangeFpoint2Item", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(100) // TradeID
bf.WriteUint16(1) // ItemType
bf.WriteUint16(50) // ItemId
bf.WriteUint8(5) // Quantity
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfExchangeFpoint2Item{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfExchangeItem2Fpoint", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(100) // TradeID
bf.WriteUint16(1) // ItemType
bf.WriteUint16(50) // ItemId
bf.WriteUint8(5) // Quantity
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfExchangeItem2Fpoint{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfExchangeWeeklyStamp_hl", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(1) // StampType = 1 ("hl")
bf.WriteUint8(0) // Unk1
bf.WriteUint16(0) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfExchangeWeeklyStamp{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfExchangeWeeklyStamp_ex", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(2) // StampType = 2 ("ex")
bf.WriteUint8(1) // Unk1
bf.WriteUint16(5) // Unk2
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfExchangeWeeklyStamp{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGenerateUdGuildMap", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGenerateUdGuildMap{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetBoostTimeLimit", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetBoostTimeLimit{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetCafeDurationBonusInfo", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetCafeDurationBonusInfo{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfGetMyhouseInfo", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(100) // Unk0
bf.WriteUint8(4) // DataSize
bf.WriteBytes([]byte{0x01, 0x02, 0x03, 0x04})
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGetMyhouseInfo{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfAcquireUdItem", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(1) // Unk0
bf.WriteUint8(2) // RewardType
bf.WriteUint8(2) // Unk2 (count)
bf.WriteUint32(10)
bf.WriteUint32(20)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAcquireUdItem{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("MsgMhfEnumerateHouse_noname", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint32(100) // CharID
bf.WriteUint8(1) // Method
bf.WriteUint16(0) // Unk
bf.WriteUint8(0) // lenName = 0 (no name)
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfEnumerateHouse{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
}
func TestBatchParseGuildHuntdata(t *testing.T) {
ctx := &clientctx.ClientContext{RealClientMode: cfg.ZZ}
t.Run("operation_0", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(0) // Operation = 0
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGuildHuntdata{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
t.Run("operation_1", func(t *testing.T) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(1) // AckHandle
bf.WriteUint8(1) // Operation = 1 (reads GuildID)
bf.WriteUint32(99) // GuildID
_, _ = bf.Seek(0, io.SeekStart)
pkt := &MsgMhfGuildHuntdata{}
if err := pkt.Parse(bf, ctx); err != nil {
t.Fatal(err)
}
})
}