package gameserver import ( "fmt" "google.golang.org/protobuf/proto" "gac-hkrpg-go/internal/common" goproto "gac-hkrpg-go/protocol/proto" ) // This file ports the raw special-case arms of CommandHandler::on_message and // the dummy! macro list from the Rust packet.rs / handlers/mod.rs. func init() { // ---- dummy (empty ScRsp) list ---- for _, name := range []string{ "GetLevelRewardTakenList", "QueryProductInfo", "GetQuestRecord", "GetCurAssist", "GetDailyActiveInfo", "GetFightActivityData", "GetExpeditionData", "GetLoginActivity", "GetRaidInfo", "GetTrialActivityData", "GetNpcStatus", "GetSecretKeyInfo", "GetVideoVersionKey", "FinishTalkMission", "GetRechargeGiftInfo", "GetPreAvatarGrowthInfo", "GetPreAvatarActivityList", "GetFriendAssistList", "GetAssistList", "B51RacingGetData", } { dummy(name) } // ---- fixed-body raw replies (cmd -> rsp cmd + body bytes) ---- simpleReply := func(reqIDs []uint16, rspID uint16, body []byte) { for _, id := range reqIDs { id := id rawHandlers[id] = func(s *Session, payload []byte) error { return s.SendRaw(&NetPacket{CmdType: rspID, Body: body}) } } } simpleReply([]uint16{8909}, 8923, []byte{0x60, 0x00}) // tag 12 retcode 0 simpleReply([]uint16{8924, 8968}, 8920, []byte{0x10, 0x00}) // tag 2 retcode 0 simpleReply([]uint16{8935}, 8915, []byte{0x08, 0x00}) // tag 1 retcode 0 simpleReply([]uint16{8917}, 8929, []byte{0x08, 0x00}) // tag 1 retcode 0 simpleReply([]uint16{8933}, 8906, []byte{0x08, 0x00}) // tag 1 retcode 0 simpleReply([]uint16{8919}, 8948, []byte{0x08, 0x00}) // tag 1 retcode 0 simpleReply([]uint16{8910}, 8921, []byte{0x08, 0x00}) // tag 1 retcode 0 simpleReply([]uint16{8975}, 8992, []byte{0x40, 0x00}) // tag 8 retcode 0 simpleReply([]uint16{8990}, 8977, []byte{0x40, 0x00}) // tag 8 retcode 0 simpleReply([]uint16{8977}, 8999, []byte{0x50, 0x00}) // tag 10 retcode 0 simpleReply([]uint16{2966}, 2955, []byte{0x30, 0x00}) // tag 6 retcode 0 simpleReply([]uint16{2916}, 2906, []byte{0x18, 0x00}) // tag 3 retcode 0 simpleReply([]uint16{2919}, 2928, []byte{0x48, 0x00}) // tag 9 retcode 0 simpleReply([]uint16{2974}, 2919, []byte{0x18, 0x00}) // GetFriendListInfo tag 3 retcode 0 simpleReply([]uint16{740}, 733, []byte{0x58, 0x00}) // tag 11 retcode 0 simpleReply([]uint16{1748}, 1745, []byte{0x08, 0x00}) // tag 1 retcode 0 simpleReply([]uint16{1765}, 1730, []byte{0x08, 0x00}) // tag 1 retcode 0 simpleReply([]uint16{1742}, 1772, []byte{0x08, 0x00}) // tag 1 retcode 0 simpleReply([]uint16{1738}, 1748, nil) // empty body simpleReply([]uint16{188}, 181, []byte{0x50, 0x00}) // tag 10 retcode 0 simpleReply([]uint16{4130}, 4124, []byte{0x70, 0x00}) // tag 14 retcode 0 simpleReply([]uint16{8110, 8115}, 8108, []byte{0x18, 0x00}) // GetSwitchHandData tag 3 retcode 0 (4.6.51 CmdID: 8108) simpleReply([]uint16{8993, 8991}, 8981, []byte{0x70, 0x00}) // 4.6.51: tag 14 retcode 0 simpleReply([]uint16{8987}, 8986, []byte{0x38, 0x00}) // ConfirmChallengeTierceStageSettle tag 7 retcode 0 // ---- 1775 | 1711 -> GetChallengeGroupStatisticsScRsp (cmd 1708) ---- rawHandlers[1775] = challengeGroupStatsReply rawHandlers[1711] = challengeGroupStatsReply // ---- 8995 | 8979 | 8981 -> tierce data (cmd 8996 + legacy 8994) ---- rawHandlers[8995] = challengeTierceReply rawHandlers[8979] = challengeTierceReply rawHandlers[8981] = challengeTierceReply // ---- challenge flow (StartChallenge family) ---- rawHandlers[1731] = handleStartChallenge rawHandlers[1793] = handleStartChallenge rawHandlers[8986] = handleStartChallengeTierce rawHandlers[8988] = handleStartChallengeTierce rawHandlers[8978] = handleSetChallengeTierceLineup rawHandlers[9000] = handleSetChallengeTierceLineup rawHandlers[1705] = handleLeaveChallenge rawHandlers[1760] = handleLeaveChallenge rawHandlers[1788] = handleLeaveChallenge rawHandlers[8989] = handleLeaveChallengeTierce rawHandlers[1739] = handleTakeChallengeReward // ---- 400 -> Ex-Rank / Affinitive Eidolon Unlock ---- rawHandlers[400] = handleExRankUnlock } // handleExRankUnlock handles CmdID 400 (Affinitive Eidolon / Ex-Rank unlock request from client). func handleExRankUnlock(s *Session, payload []byte) error { Log(fmt.Sprintf("[EX-RANK] Received cmd 400 (len: %d) payload: %x", len(payload), payload)) var parsedVal uint32 buf := payload for len(buf) > 0 { tag, n := readUvarint(buf) if n == 0 { break } buf = buf[n:] wireType := tag & 0x7 fieldNum := tag >> 3 if wireType == 0 { v, n2 := readUvarint(buf) if n2 > 0 { buf = buf[n2:] Log(fmt.Sprintf("[EX-RANK] field %d = %d", fieldNum, v)) if v >= 100000 || (v >= 1000 && v < 10000 && parsedVal == 0) { parsedVal = uint32(v) } continue } } skipWireField(uint32(wireType), &buf) } var avatarID uint32 if parsedVal >= 100000 { avatarID = parsedVal / 100 // e.g. 120401 -> 1204, 121301 -> 1213, 130801 -> 1308 } else if parsedVal >= 1000 && parsedVal < 10000 { avatarID = parsedVal } Log(fmt.Sprintf("[EX-RANK] Target avatarID: %d (from val: %d)", avatarID, parsedVal)) player := s.JSON() if player != nil { if avatarID == 0 { // Fallback: unlock for all avatars that have ExRank defined (1204, 1213, 1308) for aid := range common.AvatarExRankMap { if av, ok := player.Avatars[aid]; ok { av.ExRankUnlocked = true Log(fmt.Sprintf("[EX-RANK] Fallback set ExRankUnlocked=true for avatar %d", aid)) syncNotify := &goproto.PlayerSyncScNotify{} buildSync(player, syncNotify, []uint32{aid}, nil, nil) _ = s.Send(syncNotify) } } } else { if av, ok := player.Avatars[avatarID]; ok { av.ExRankUnlocked = true Log(fmt.Sprintf("[EX-RANK] Set ExRankUnlocked=true for avatar %d", avatarID)) } syncNotify := &goproto.PlayerSyncScNotify{} buildSync(player, syncNotify, []uint32{avatarID}, nil, nil) _ = s.Send(syncNotify) } player.SavePersistent() } // Send candidate response packets so client RPC callback resolves without spinning // Candidates in CmdAvatarType: 395 (CmdAvatarType_ENKEIJOLCNO), 354, 355, 364, 341 for _, rspID := range []uint16{395, 354, 355, 364, 341} { _ = s.SendRaw(&NetPacket{CmdType: rspID, Body: []byte{}}) } return nil } // challengeGroupStatsReply handles CmdGetChallengeGroupStatisticsCsReq (1775/1711) // and returns GetChallengeGroupStatisticsScRsp (1708) using standard 4.6.51 proto. func challengeGroupStatsReply(s *Session, payload []byte) error { groupID := uint32(100) var req goproto.GetChallengeGroupStatisticsCsReq if err := proto.Unmarshal(payload, &req); err == nil && req.GroupId != 0 { groupID = req.GroupId } else { buf := payload for len(buf) > 0 { tag, n := readUvarint(buf) if n == 0 { break } buf = buf[n:] fieldNumber := tag >> 3 wireType := tag & 0x7 if (fieldNumber == 5 || fieldNumber == 12) && wireType == 0 { v, n2 := readUvarint(buf) if n2 > 0 { groupID = uint32(v) break } } skipWireField(uint32(wireType), &buf) } } rsp := &goproto.GetChallengeGroupStatisticsScRsp{ GroupId: groupID, Retcode: 0, } switch { case groupID >= 3000: // Apocalyptic Shadow: challenge_boss (field 1) rsp.ChallengeBoss = &goproto.ChallengeBossStatistics{ RecordId: 1, PPBHLLOJNEK: &goproto.AANLJBLOOFO{ Level: 4, ScoreId: 8000, }, } case groupID >= 2000 && groupID < 3000: // Pure Fiction: challenge_story (field 3) rsp.ChallengeStory = &goproto.ChallengeStoryStatistics{ RecordId: 1, PPBHLLOJNEK: &goproto.EIKPHEMHIOH{ ScoreId: 80000, }, } case groupID >= 1000 && groupID < 2000: // Memory of Chaos: challenge_default (field 11) rsp.ChallengeDefault = &goproto.ChallengeStatistics{ RecordId: 1, PPBHLLOJNEK: &goproto.ADKJKMKBFDC{ RoundCount: 20, Level: 12, }, } } body, err := proto.Marshal(rsp) if err != nil { return err } return s.SendRaw(&NetPacket{CmdType: 1708, Body: body}) } // challengeTierceReply ports the 8995/8979/8981 arm: cmd 8996 (4.6.51) plus // legacy cmd 8994 with the same tierce body. func challengeTierceReply(s *Session, payload []byte) error { body := buildGetChallengeTierceDataScRsp() if err := s.SendRaw(&NetPacket{CmdType: 8996, Body: body}); err != nil { return err } _ = s.SendRaw(&NetPacket{CmdType: 8994, Body: body}) return nil } // buildGetChallengeTierceDataScRsp mirrors challenge::build_get_challenge_tierce_data_sc_rsp // (hand-built protobuf, verified against the 4.6.51 client). func buildGetChallengeTierceDataScRsp() []byte { return buildGetChallengeTierceDataScRspReal() } func readUvarint(b []byte) (uint64, int) { var x uint64 var s uint for i := 0; i < len(b) && i < 10; i++ { y := b[i] if y < 0x80 { if i == 9 && y > 1 { return 0, 0 } return x | uint64(y)<= 0x80 { b = append(b, byte(v)|0x80) v >>= 7 } return append(b, byte(v)) } var _ = goproto.CmdIDByName