pre cw with probe and a bunch script to read game behavior

This commit is contained in:
CAtlas0784 committed 2026-10-21 20:40:13 +07:00
1 parent 5120219ae2
commit da8a32f60d
310 files changed
+219693 -872

No files matched your search

@@ -0,0 +1,218 @@
package gameserver
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"gac-hkrpg-go/internal/common"
goproto "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
// This advances the existing captured-entry prototype, not the final
// procedural portal/effect processor. Each starter outcome is a complete,
// internally consistent reference capture. No adventure inventory is touched.
func currencyWarsEntryChoice(catalog *common.CurrencyWarsCatalog, before *goproto.MPIMAEMCEFH, queue, portal uint32) (*goproto.MPIMAEMCEFH, *goproto.GridFightSyncUpdateResultScNotify, error) {
if catalog == nil || before == nil || before.FMIGOKIDKJL == nil || before.FMIGOKIDKJL.QueuePosition != queue {
return nil, nil, fmt.Errorf("CW pending queue differs from the active run")
}
offer := before.FMIGOKIDKJL.GetMFFCPLBNHCF()
if offer == nil {
return nil, nil, fmt.Errorf("CW active pending action is not an initial portal selection")
}
found := false
for _, id := range offer.NPHOIMEEDHM {
found = found || id == portal
}
if portal == 0 || !found {
return nil, nil, fmt.Errorf("CW portal is not in the active offer")
}
for _, component := range before.NAGDJGMMCBE {
if team := component.GetEJIPINIKNCK(); team != nil && len(team.GridGameRoleList) != 0 {
return nil, nil, fmt.Errorf("CW entry prototype cannot replace an existing team")
}
}
raw, err := common.ReadResourceFile("resources/CurrencyWarsEntryTransitions.json")
if err != nil {
return nil, nil, err
}
var resource struct {
Format uint32 `json:"format_version"`
Client string `json:"target_client_version"`
Assembly string `json:"target_assembly_sha256"`
Source string `json:"source_binary_sha256"`
Choices []struct {
Division uint32 `json:"division_id"`
Season uint32 `json:"season_id"`
Overclock bool `json:"is_overclock"`
Portal uint32 `json:"portal_id"`
BeforeQueue uint32 `json:"before_queue"`
AfterWire []byte `json:"after_wire"`
NotifyWire []byte `json:"notify_wire"`
AfterSHA string `json:"after_sha256"`
NotifySHA string `json:"notify_sha256"`
} `json:"choices"`
}
if err := json.Unmarshal(raw, &resource); err != nil {
return nil, nil, err
}
if resource.Format != 1 || resource.Client != catalog.ClientVersion || resource.Assembly != catalog.Provenance.GameAssemblySHA256 {
return nil, nil, fmt.Errorf("CW entry transition revision differs from the active catalog")
}
if h, err := hex.DecodeString(resource.Source); err != nil || len(h) != sha256.Size {
return nil, nil, fmt.Errorf("CW entry transition source identity is missing")
}
var after *goproto.MPIMAEMCEFH
var notify *goproto.GridFightSyncUpdateResultScNotify
for _, choice := range resource.Choices {
if choice.Division != before.DivisionId || choice.Season != before.LJNGPCKPJLF || choice.Overclock != before.IsOverlock || choice.Portal != portal || choice.BeforeQueue != queue {
continue
}
if after != nil {
return nil, nil, fmt.Errorf("CW entry transition key is duplicated")
}
if !currencyWarsWireDigest(choice.AfterWire, choice.AfterSHA) || !currencyWarsWireDigest(choice.NotifyWire, choice.NotifySHA) {
return nil, nil, fmt.Errorf("CW entry transition wire digest differs")
}
after = new(goproto.MPIMAEMCEFH)
notify = new(goproto.GridFightSyncUpdateResultScNotify)
if err := proto.Unmarshal(choice.AfterWire, after); err != nil {
return nil, nil, err
}
if err := proto.Unmarshal(choice.NotifyWire, notify); err != nil {
return nil, nil, err
}
if after.DivisionId != before.DivisionId || after.LJNGPCKPJLF != before.LJNGPCKPJLF || after.IsOverlock != before.IsOverlock || after.FMIGOKIDKJL == nil || after.FMIGOKIDKJL.QueuePosition <= queue {
return nil, nil, fmt.Errorf("CW captured transition identity or next queue is invalid")
}
selected := false
finished := false
for _, result := range notify.GCPNIHHBGGD {
for _, update := range result.JKPJFKCBIEM {
if value := update.GetHPIIGHOELBH(); value != nil && value.PortalBuffId == portal {
selected = true
}
finished = finished || update.GetPMFBIGLPJJA() == queue
}
}
if !selected || !finished {
return nil, nil, fmt.Errorf("CW captured transition lacks portal or queue acknowledgement")
}
if err := validateCurrencyWarsEntryComponents(after, catalog); err != nil {
return nil, nil, err
}
after.UniqueId = before.UniqueId
}
if after == nil {
return nil, nil, fmt.Errorf("CW initial portal outcome is not captured for this division/mode")
}
// The native reference's GetData projection omits selected portals even
// though the live sync includes them. Preserve this in our resume snapshot.
for _, component := range after.NAGDJGMMCBE {
if stage := component.GetKLCPAAHDPJF(); stage != nil {
stage.GridFightPortalBuffList = append(stage.GridFightPortalBuffList, &goproto.GIDEDMOPFKF{PortalBuffId: portal})
}
}
cwEnsureSpotlights(after)
return after, notify, nil
}
func currencyWarsWireDigest(wire []byte, expected string) bool {
hash := sha256.Sum256(wire)
return len(wire) > 0 && hex.EncodeToString(hash[:]) == expected
}
func init() {
// The revision's request name is misleading. Controlled reference replay
// establishes this request's MFFCPLBNHCF arm as portal selection and its
// response as GridFightHandlePendingActionScRsp. No numeric alias is used.
handleNamed("GridFightHandleBonfireHealHp", "GridFightHandlePendingActionScRsp", func(s *Session, req *goproto.GridFightHandleBonfireHealHpCsReq, rsp *goproto.GridFightHandlePendingActionScRsp) (resultErr error) {
rsp.QueuePosition = req.QueuePosition
rsp.Retcode = uint32(goproto.Retcode_RET_GRID_FIGHT_CONF_MISS)
defer func() {
if resultErr != nil {
PacketLog(fmt.Sprintf("CW_PENDING_ACTION failed queue=%d error=%v", req.QueuePosition, resultErr))
rsp.Retcode = uint32(goproto.Retcode_RET_GRID_FIGHT_CONF_MISS)
resultErr = nil // Send the typed error response so the wait block is released.
}
}()
selection := req.GetMFFCPLBNHCF()
if selection == nil && req.GetNNDGEJFLGGF() == nil && req.GetLGNPHHBGKFH() == nil && req.GetIBCHMBNIPLK() == nil && req.GetDKDFDGJGIHM() == nil {
PacketLog(fmt.Sprintf("CW_PENDING_ACTION unsupported queue=%d action=%T", req.QueuePosition, req.FGLIODAJFML))
return nil
}
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
return err
}
data, revision, err := readCurrencyWarsData(catalog)
if err != nil {
return err
}
if data == nil || data.DHFOFLFCLAH == nil {
rsp.Retcode = uint32(goproto.Retcode_RET_GRID_FIGHT_NOT_IN_GAMEPLAY)
return nil
}
if revision != s.currencyWarsRevision {
rsp.Retcode = uint32(goproto.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH)
return nil
}
before := data.DHFOFLFCLAH
if before.FMIGOKIDKJL == nil || before.FMIGOKIDKJL.QueuePosition != req.QueuePosition {
rsp.Retcode = uint32(goproto.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH)
return nil
}
var after *goproto.MPIMAEMCEFH
var notify *goproto.GridFightSyncUpdateResultScNotify
if selection != nil {
after, notify, err = currencyWarsEntryChoice(catalog, before, req.QueuePosition, selection.JCAPCNLJGFC)
} else {
after = proto.Clone(before).(*goproto.MPIMAEMCEFH)
switch {
case req.GetDKDFDGJGIHM() != nil && before.FMIGOKIDKJL.GetEEKMIAMGHFL() != nil:
notify, err = cwSelectSpotlight(catalog, after, req.GetDKDFDGJGIHM())
case req.GetNNDGEJFLGGF() != nil && before.FMIGOKIDKJL.GetNNDGEJFLGGF() != nil:
stage, _ := cwStage(after)
entries, loadErr := cwNodeTransitions(catalog)
isNextRound := false
if loadErr == nil && stage != nil {
for _, entry := range entries {
level := new(goproto.AINFNFIDGFF)
if proto.Unmarshal(entry.LevelWire, level) == nil && level.DCAHJELNJNP == stage.DCAHJELNJNP && level.SectionId == stage.SectionId {
isNextRound = true
}
}
}
if isNextRound {
notify, err = cwNextRound(catalog, after)
} else {
notify, err = cwInitialRound(catalog, after)
}
case req.GetLGNPHHBGKFH() != nil && before.FMIGOKIDKJL.GetLGNPHHBGKFH() != nil:
notify, err = cwReturnPreparation(after)
case req.GetIBCHMBNIPLK() != nil && before.FMIGOKIDKJL.GetIBCHMBNIPLK() != nil:
notify, err = cwSelectInvestment(catalog, after, req.GetIBCHMBNIPLK().AugmentId)
default:
err = fmt.Errorf("pending request kind differs from active queue")
}
}
if err != nil {
PacketLog(fmt.Sprintf("CW_PENDING_ACTION rejected queue=%d action=%T error=%v", req.QueuePosition, req.FGLIODAJFML, err))
return nil
}
data.DHFOFLFCLAH = after
if err := s.saveCurrencyWarsData(data, catalog); err != nil {
return err
}
// Save before synchronizing, then finish the request after the ordered
// notification, matching the reference's pending-action callback flow.
if err := s.Send(notify); err != nil {
return err
}
rsp.Retcode = 0
PacketLog(fmt.Sprintf("CW_PENDING_ACTION advanced action=%T queue=%d next_queue=%d preparing=%t revision=%d", req.FGLIODAJFML, req.QueuePosition, after.GetFMIGOKIDKJL().GetQueuePosition(), after.FMIGOKIDKJL == nil, s.currencyWarsRevision))
return nil
})
}