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) } // Bind requests by the current command names; do not retain numeric aliases // from earlier builds, which may now identify responses or notifications. rawNamed("GetChallengeGroupStatisticsCsReq", challengeGroupStatsReply) rawNamed("GetChallengeTierceDataCsReq", challengeTierceReply) rawNamed("StartChallengeCsReq", handleStartChallenge) rawNamed("StartChallengeTierceCsReq", handleStartChallengeTierce) rawNamed("SetChallengeTierceLineupCsReq", handleSetChallengeTierceLineup) rawNamed("LeaveChallengeCsReq", handleLeaveChallenge) rawNamed("LeaveChallengeTierceCsReq", handleLeaveChallengeTierce) rawNamed("TakeChallengeCumulativeRewardCsReq", handleTakeChallengeReward) // The request/response names remain obfuscated in the upstream schema. rawNamed("HIGLJHLNODG", handleExRankUnlock) rawNamed("GetSwitchHandDataCsReq", func(s *Session, payload []byte) error { return s.Send(&goproto.GetSwitchHandDataScRsp{}) }) handle("GetChallengeTierceController", func(s *Session, req *goproto.GetChallengeTierceControllerCsReq, rsp *goproto.GetChallengeTierceControllerScRsp) error { rsp.Retcode = 0 if run := s.ChallengeRun; run != nil && run.Tierce { rsp.CurTierceChallenge = run.TierceCur } return nil }) handle("StartChallengePeak", handleStartChallengePeak) rawNamed("LeaveChallengePeakCsReq", func(s *Session, payload []byte) error { return leaveChallengeScene(s, &goproto.LeaveChallengePeakScRsp{}) }) } // The EX request remains obfuscated in PearlSR 005d2c1: field 5 is // interpreted as an avatar, EX-rank or material ID only by an explicit mapping. // Never unlock every avatar when an unknown value is received. func handleExRankUnlock(s *Session, payload []byte) error { var req goproto.HIGLJHLNODG if err := proto.Unmarshal(payload, &req); err != nil { return err } value := req.OJFIGABCKLG resources, err := loadEidolonResources() if err != nil { return err } var avatarID uint32 for id, exID := range common.AvatarExRankMap { if value == id || value == exID || value == resources.ExMaterials[id].ItemID { avatarID = id break } } rsp := &goproto.LPOLGDLAADM{} player := s.JSON() if player == nil || avatarID == 0 || player.Avatars[avatarID] == nil { rsp.Retcode = uint32(goproto.Retcode_RET_AVATAR_NOT_EXIST) Log(fmt.Sprintf("[EX-RANK] rejected unmapped value=%d", value)) return s.Send(rsp) } if _, active := CURRENT_BATTLE.Load(s.Token); active { rsp.Retcode = uint32(goproto.Retcode_RET_AVATAR_RANK_NOT_MATCH) return s.Send(rsp) } avatar := player.Avatars[avatarID] previous := avatar.ExRankUnlocked avatar.ExRankUnlocked = true if err := player.SavePersistentChecked(); err != nil { avatar.ExRankUnlocked = previous return err } notify := &goproto.PlayerSyncScNotify{} buildSync(player, notify, []uint32{avatarID}, nil, nil) notify.MaterialList, err = eidolonMaterials(player) if err != nil { return err } if err := s.Send(notify); err != nil { return err } Log(fmt.Sprintf("[EX-RANK] unlocked avatar=%d value=%d", avatarID, value)) return s.Send(rsp) } // challengeGroupStatsReply uses the current typed statistics response. func challengeGroupStatsReply(s *Session, payload []byte) error { var req goproto.GetChallengeGroupStatisticsCsReq if err := proto.Unmarshal(payload, &req); err != nil { return err } // Match upstream: echo the group without fabricating historical scores. return s.Send(&goproto.GetChallengeGroupStatisticsScRsp{GroupId: req.GroupId}) } // challengeTierceReply sends only the response paired with the current request. func challengeTierceReply(s *Session, payload []byte) error { return s.Send(buildGetChallengeTierceDataScRspReal(s)) } 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