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.Unk0 != 2 || pkt.Unk1 != 3 || pkt.Unk2 != 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) } }) }