- mihoyo-variant KCP in pure Go (28-byte conv+token header), UDP gateway with 0xFF/0x145/0x194 handshake, ms-precision KCP clock + retransmit ticker - protobuf for 4.6.51 generated from the AstralOS StarRail.proto (4932 messages) plus CmdID map from // CmdID: comments (2114 ids) - sdkserver port: query_dispatch/query_gateway (Gateserver pb + base64), hotfix fetch, auth endpoints, SRTools freesr-data.json upload - gameserver handlers: login flow, scene load/spawn with real monster entity tracking, lineup, avatar/skin, missions, tutorials (1249+855), bag/equip, challenge incl. StartChallenge 1787 layout + Tierce 8973 + peak 8948, all raw tierce replies - SceneCastSkill camera bug fix: only real scene monsters (tracked per session) trigger battle; attacking props/NPCs/air replies retcode 0 without battle_info so the camera stays locked on the character - end-to-end smoke test simulating a client over real UDP: handshake, PlayerGetToken, and both SceneCastSkill paths - reuses the Rust PS data files unchanged (freesr-data.json, persistent, res.json, teleports.json, challenge_data.json, mission_unlocks.json)
237 lines
7.6 KiB
Go
237 lines
7.6 KiB
Go
package gameserver
|
|
|
|
import (
|
|
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{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}, 8104, []byte{0x18, 0x00}) // GetSwitchHandData tag 3 retcode 0
|
|
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[8983] = handleStartChallengeTierce
|
|
rawHandlers[8988] = handleStartChallengeTierce
|
|
rawHandlers[8978] = handleSetChallengeTierceLineup
|
|
rawHandlers[1705] = handleLeaveChallenge
|
|
rawHandlers[1760] = handleLeaveChallenge
|
|
rawHandlers[1788] = handleLeaveChallenge
|
|
rawHandlers[8989] = handleLeaveChallengeTierce
|
|
rawHandlers[8998] = handleLeaveChallengeTierce
|
|
rawHandlers[1739] = handleTakeChallengeReward
|
|
}
|
|
|
|
// challengeGroupStatsReply ports the 1775/1711 arm: pull group_id (field 12
|
|
// varint) from the request and answer cmd 1708 with per-mode statistics.
|
|
func challengeGroupStatsReply(s *Session, payload []byte) error {
|
|
groupID := uint32(100)
|
|
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 == 12 && wireType == 0 {
|
|
v, n2 := readUvarint(buf)
|
|
if n2 == 0 {
|
|
break
|
|
}
|
|
buf = buf[n2:]
|
|
groupID = uint32(v)
|
|
break
|
|
} else if wireType == 0 {
|
|
_, n2 := readUvarint(buf)
|
|
if n2 == 0 {
|
|
break
|
|
}
|
|
buf = buf[n2:]
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
var body []byte
|
|
// tag 9: group_id
|
|
body = append(body, 0x48)
|
|
body = appendUvarint(body, uint64(groupID))
|
|
// tag 15: retcode 0
|
|
body = append(body, 0x78, 0x00)
|
|
|
|
switch {
|
|
case groupID >= 2000 && groupID < 3000:
|
|
// Pure Fiction: tag 8 = challenge_story (ChallengeStoryStatistics)
|
|
var eik []byte
|
|
eik = append(eik, 0x50) // tag 10: score_id 80000
|
|
eik = appendUvarint(eik, 80000)
|
|
eik = append(eik, 0x58) // tag 11: level 4
|
|
eik = appendUvarint(eik, 4)
|
|
|
|
var css []byte
|
|
css = append(css, 0x32) // tag 6
|
|
css = appendUvarint(css, uint64(len(eik)))
|
|
css = append(css, eik...)
|
|
css = append(css, 0x38) // tag 7: record_id 1
|
|
css = appendUvarint(css, 1)
|
|
|
|
body = append(body, 0x42) // tag 8
|
|
body = appendUvarint(body, uint64(len(css)))
|
|
body = append(body, css...)
|
|
case groupID >= 1000 && groupID < 2000:
|
|
// Memory of Chaos: tag 2 = challenge_default (ChallengeStatistics)
|
|
var adk []byte
|
|
adk = append(adk, 0x10) // tag 2: round_count 20
|
|
adk = appendUvarint(adk, 20)
|
|
adk = append(adk, 0x18) // tag 3: level 12
|
|
adk = appendUvarint(adk, 12)
|
|
|
|
var cs []byte
|
|
cs = append(cs, 0x32) // tag 6
|
|
cs = appendUvarint(cs, uint64(len(adk)))
|
|
cs = append(cs, adk...)
|
|
cs = append(cs, 0x50) // tag 10: record_id 1
|
|
cs = appendUvarint(cs, 1)
|
|
|
|
body = append(body, 0x12) // tag 2
|
|
body = appendUvarint(body, uint64(len(cs)))
|
|
body = append(body, cs...)
|
|
case groupID >= 3000:
|
|
// Apocalyptic Shadow: tag 11 = challenge_boss (ChallengeBossStatistics)
|
|
var aan []byte
|
|
aan = append(aan, 0x50) // tag 10: score_id 8000
|
|
aan = appendUvarint(aan, 8000)
|
|
aan = append(aan, 0x18) // tag 3: level 4
|
|
aan = appendUvarint(aan, 4)
|
|
|
|
var cbs []byte
|
|
cbs = append(cbs, 0x0A) // tag 1
|
|
cbs = appendUvarint(cbs, uint64(len(aan)))
|
|
cbs = append(cbs, aan...)
|
|
cbs = append(cbs, 0x70) // tag 14: record_id 1
|
|
cbs = appendUvarint(cbs, 1)
|
|
|
|
body = append(body, 0x5A) // tag 11
|
|
body = appendUvarint(body, uint64(len(cbs)))
|
|
body = append(body, cbs...)
|
|
}
|
|
|
|
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
|