Files
GaCy-GO/internal/gameserver/handlers_raw.go
T

286 lines
9.0 KiB
Go

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)<<s, i + 1
}
x |= uint64(y&0x7f) << s
s += 7
}
return 0, 0
}
func appendUvarint(b []byte, v uint64) []byte {
for v >= 0x80 {
b = append(b, byte(v)|0x80)
v >>= 7
}
return append(b, byte(v))
}
var _ = goproto.CmdIDByName