pre cw with probe and a bunch script to read game behavior
This commit is contained in:
1 parent
5120219ae2
commit
da8a32f60d
310 files changed
+219693
-872
No files matched your search
@@ -0,0 +1,186 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// Initial entry presets are captured reference states, not a replacement for
|
||||
// the procedural run/action processor. IDs, route displays, pending choices
|
||||
// and modifiers stay in the revision-bound resource rather than handler code.
|
||||
func buildCurrencyWarsInitialRun(catalog *common.CurrencyWarsCatalog, req *goproto.GridFightStartGamePlayCsReq, uniqueID uint32) (*goproto.MPIMAEMCEFH, error) {
|
||||
if catalog == nil || req == nil || uniqueID == 0 {
|
||||
return nil, fmt.Errorf("CW start inputs are incomplete")
|
||||
}
|
||||
if req.LJNGPCKPJLF != catalog.ActiveSeasonID {
|
||||
return nil, fmt.Errorf("CW requested season is not active")
|
||||
}
|
||||
raw, err := common.ReadResourceFile("resources/CurrencyWarsInitialRuns.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resource struct {
|
||||
Format uint32 `json:"format_version"`
|
||||
Client string `json:"target_client_version"`
|
||||
Assembly string `json:"target_assembly_sha256"`
|
||||
SourceSHA string `json:"source_binary_sha256"`
|
||||
Entries []struct {
|
||||
Division uint32 `json:"division_id"`
|
||||
Overclock bool `json:"is_overclock"`
|
||||
Wire []byte `json:"run_wire"`
|
||||
CaptureSHA string `json:"capture_sha256"`
|
||||
} `json:"entries"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &resource); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resource.Format != 1 || resource.Client != catalog.ClientVersion || resource.Assembly != catalog.Provenance.GameAssemblySHA256 {
|
||||
return nil, fmt.Errorf("CW initial preset revision differs from the current catalog")
|
||||
}
|
||||
if h, err := hex.DecodeString(resource.SourceSHA); err != nil || len(h) != sha256.Size {
|
||||
return nil, fmt.Errorf("CW initial preset source identity is missing")
|
||||
}
|
||||
var selected *goproto.MPIMAEMCEFH
|
||||
for _, entry := range resource.Entries {
|
||||
if entry.Division != req.DivisionId || entry.Overclock != req.IsOverlock {
|
||||
continue
|
||||
}
|
||||
if selected != nil {
|
||||
return nil, fmt.Errorf("CW initial preset key is duplicated")
|
||||
}
|
||||
if h, err := hex.DecodeString(entry.CaptureSHA); err != nil || len(h) != sha256.Size {
|
||||
return nil, fmt.Errorf("CW initial preset capture identity is missing")
|
||||
}
|
||||
var run goproto.MPIMAEMCEFH
|
||||
if err := proto.Unmarshal(entry.Wire, &run); err != nil {
|
||||
return nil, fmt.Errorf("decode CW initial preset: %w", err)
|
||||
}
|
||||
if run.DivisionId != req.DivisionId || run.IsOverlock != req.IsOverlock || run.LJNGPCKPJLF != req.LJNGPCKPJLF {
|
||||
return nil, fmt.Errorf("CW initial preset identity differs from its resource key")
|
||||
}
|
||||
if err := validateCurrencyWarsEntryComponents(&run, catalog); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
run.UniqueId = uniqueID
|
||||
selected = &run
|
||||
}
|
||||
if selected == nil {
|
||||
return nil, fmt.Errorf("CW requested division/mode has no captured initial preset")
|
||||
}
|
||||
cwEnsureSpotlights(selected)
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
func validateCurrencyWarsEntryComponents(run *goproto.MPIMAEMCEFH, catalog *common.CurrencyWarsCatalog) error {
|
||||
if run == nil || run.FMIGOKIDKJL == nil || run.JLDDDLDNFEF == nil || run.CAAGABEPPPE == nil {
|
||||
return fmt.Errorf("CW initial pending/context/modifier containers are missing")
|
||||
}
|
||||
var player *goproto.NIHNGPKHEPC
|
||||
var stage *goproto.BEEFLEJAOCN
|
||||
var shop *goproto.POCJDMMANBM
|
||||
for _, info := range run.NAGDJGMMCBE {
|
||||
if info == nil {
|
||||
return fmt.Errorf("CW initial component is nil")
|
||||
}
|
||||
if p := info.GetFAOCFBOFCJP(); p != nil {
|
||||
if player != nil {
|
||||
return fmt.Errorf("CW player component is duplicated")
|
||||
}
|
||||
player = p
|
||||
}
|
||||
if p := info.GetKLCPAAHDPJF(); p != nil {
|
||||
if stage != nil {
|
||||
return fmt.Errorf("CW stage component is duplicated")
|
||||
}
|
||||
stage = p
|
||||
}
|
||||
if p := info.GetEBHDIOGALIE(); p != nil {
|
||||
if shop != nil {
|
||||
return fmt.Errorf("CW shop component is duplicated")
|
||||
}
|
||||
shop = p
|
||||
}
|
||||
}
|
||||
if player == nil || stage == nil || shop == nil || shop.ABBNGCPECAI == nil {
|
||||
return fmt.Errorf("CW initial player/stage/shop is incomplete")
|
||||
}
|
||||
if player.PPKKHHNEOHB == 0 || player.GridFightLineupHp > player.PPKKHHNEOHB || player.EHNJDIDABHD == 0 || player.EHNJDIDABHD > player.NDAINFLOLPJ {
|
||||
return fmt.Errorf("CW initial health/level is invalid")
|
||||
}
|
||||
if _, err := catalog.PlayerLevel(player.EHNJDIDABHD); err != nil {
|
||||
return err
|
||||
}
|
||||
displays := make(map[currencyWarsNodeCoordinate]*goproto.OHMDHKGAEMM)
|
||||
for _, node := range stage.OOMMIIKPHBC {
|
||||
if node == nil || node.DisplayValue == nil {
|
||||
return fmt.Errorf("CW initial route node is incomplete")
|
||||
}
|
||||
coord := currencyWarsNodeCoordinate{Chapter: node.DCAHJELNJNP, Section: node.SectionId}
|
||||
if _, exists := displays[coord]; exists {
|
||||
return fmt.Errorf("CW initial route coordinate is duplicated")
|
||||
}
|
||||
if _, err := catalog.NodeTypeName(node.DisplayValue.CJCCMAACJDB); err != nil {
|
||||
return err
|
||||
}
|
||||
displays[coord] = node.DisplayValue
|
||||
}
|
||||
copyStage := proto.Clone(stage).(*goproto.BEEFLEJAOCN)
|
||||
return applyCurrencyWarsRoute(copyStage, catalog, stage.EMOHLJHPOPC, currencyWarsNodeCoordinate{Chapter: stage.DCAHJELNJNP, Section: stage.SectionId}, displays)
|
||||
}
|
||||
|
||||
func newCurrencyWarsData(catalog *common.CurrencyWarsCatalog) (*goproto.GridFightGetDataScRsp, error) {
|
||||
// March7's freeplay profile marks the tutorial as unlocked. Without this
|
||||
// metadata the current Client sends a tutorial request (division omitted),
|
||||
// not the division selected by the normal entry page. This policy does not
|
||||
// implement or award completion of the scripted tutorial itself.
|
||||
raw, err := common.ReadResourceFile("resources/CurrencyWarsInitialRuns.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entry struct {
|
||||
Client string `json:"target_client_version"`
|
||||
Assembly string `json:"target_assembly_sha256"`
|
||||
Profile struct {
|
||||
Policy string `json:"tutorial_policy"`
|
||||
Completion uint32 `json:"tutorial_completion"`
|
||||
Season uint32 `json:"last_tutorial_season"`
|
||||
History map[uint32]*goproto.GLHCHCJMFGA `json:"tutorial_history"`
|
||||
} `json:"entry_profile"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry.Client != catalog.ClientVersion || entry.Assembly != catalog.Provenance.GameAssemblySHA256 || entry.Profile.Policy != "freeplay_unlocked" || entry.Profile.Completion == 0 || entry.Profile.Season != catalog.ActiveSeasonID {
|
||||
return nil, fmt.Errorf("CW entry profile revision or freeplay tutorial policy is invalid")
|
||||
}
|
||||
if len(entry.Profile.History) == 0 {
|
||||
return nil, fmt.Errorf("CW freeplay tutorial history is missing")
|
||||
}
|
||||
for season, history := range entry.Profile.History {
|
||||
if season == 0 || history == nil || history.DivisionId == 0 || history.GDAKCOICIDA == 0 {
|
||||
return nil, fmt.Errorf("CW freeplay tutorial history is incomplete")
|
||||
}
|
||||
}
|
||||
var division uint32
|
||||
for _, d := range catalog.SeasonDivisions(catalog.ActiveSeasonID) {
|
||||
if !d.IsPromotion {
|
||||
division = d.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
if division == 0 {
|
||||
return nil, fmt.Errorf("CW has no initial division in active season")
|
||||
}
|
||||
return &goproto.GridFightGetDataScRsp{LFLCDNGALME: &goproto.CLEDCMIOIPF{
|
||||
PKELAIPIHMF: &goproto.CKPOBLIFMGN{FBJMBJKMAJA: entry.Profile.History, ENECKKGPKHI: entry.Profile.Season, MHFHLBHFIBC: &goproto.FMNKKAMOHEE{}},
|
||||
HFPEJIDDDJG: &goproto.AMEEHIIPMFP{GDAKCOICIDA: entry.Profile.Completion, ADFEKPNIFOJ: catalog.ActiveSeasonID, GEGMHOMNBDG: catalog.ActiveSubSeasonID, DivisionId: division, Exp: &goproto.GLIFOLCEMIN{}, MHFHLBHFIBC: &goproto.FMNKKAMOHEE{}, NLENPEPIHAC: &goproto.NMECHIFGKAD{
|
||||
DNEGDNEBJMB: &goproto.NCHKFJPOHAH{}, NIOAIKLPACM: &goproto.AHPHDNBNLPJ{}, EECJFCNKKMB: &goproto.LLBMKOILIDN{}, KJIJEGAHLGB: &goproto.GNIIDINFPDO{}, NNIJIFGJJJA: &goproto.ODKODPBCDKD{}, JLFANEOAHOC: &goproto.GKNIJKHENBF{}, EJHDGLHCNLH: &goproto.NEDHMLJKIOA{}, PJBIPMKACOF: &goproto.FCEILNCDMAH{},
|
||||
}},
|
||||
}}, nil
|
||||
}
|
||||
Reference in new issue
Block a user