From 74f3c4411bf6a6e767ceea42ec4c220260cc6189 Mon Sep 17 00:00:00 2001 From: CAtlas0784 Date: Fri, 16 Oct 2026 22:19:52 +0700 Subject: [PATCH] fix: sync current handlers, repair relic data and restore visible launcher consoles --- .gitignore | 7 + AGENTS.md | 16 + README.md | 265 +- cmd/gameserver/main.go | 20 +- cmd/sdkserver/main.go | 5 +- docs/battle-investigation.md | 77 + docs/protocol-update.md | 40 + freesr-data.json | 2 +- internal/common/models.go | 2 +- internal/common/proto_convert.go | 30 +- internal/common/proto_convert_test.go | 35 + internal/common/runtime_config.go | 88 + internal/common/runtime_config_test.go | 58 + internal/gameserver/battle_capture.go | 140 + internal/gameserver/battle_capture_test.go | 72 + internal/gameserver/battle_wire_probe.lua | 95 + internal/gameserver/dispatch.go | 39 +- internal/gameserver/handlers_activity_data.go | 156 - internal/gameserver/handlers_battle.go | 68 +- internal/gameserver/handlers_challenge.go | 16 +- .../gameserver/handlers_challenge_flow.go | 656 +--- internal/gameserver/handlers_misc.go | 2 +- internal/gameserver/handlers_player.go | 8 +- internal/gameserver/handlers_raw.go | 197 +- internal/gameserver/handlers_scene.go | 118 +- internal/gameserver/handlers_tutorial.go | 8 + internal/gameserver/handlers_tutorial_ids.go | 178 +- internal/gameserver/handlers_ui.go | 15 +- internal/gameserver/session.go | 10 +- internal/sdkserver/api.go | 21 +- internal/sdkserver/runtime_config_test.go | 43 + internal/sdkserver/server.go | 28 +- protocol/proto/StarRail.pb.go | 94 +- protocol/proto/StarRail.proto | 9 +- protocol/proto/current_handlers.pb.go | 184 ++ protocol/proto/current_handlers.proto | 15 + protocol/proto/download_format.go | 7 + protocol/proto/upstream_cmdids.go | 2709 +++++++++++++++++ run.bat | 166 +- run.ps1 | 138 + run_go.bat | 3 - server.json | 17 + server.local.example.json | 6 + tools/AI TOOLBOX/memory/2026-10-16.md | 23 + tools/audit_protocol_handlers.py | 70 + tools/sync_protocol_commands.py | 45 + 46 files changed, 4599 insertions(+), 1402 deletions(-) create mode 100644 AGENTS.md create mode 100644 docs/battle-investigation.md create mode 100644 docs/protocol-update.md create mode 100644 internal/common/proto_convert_test.go create mode 100644 internal/common/runtime_config.go create mode 100644 internal/common/runtime_config_test.go create mode 100644 internal/gameserver/battle_capture.go create mode 100644 internal/gameserver/battle_capture_test.go create mode 100644 internal/gameserver/battle_wire_probe.lua delete mode 100644 internal/gameserver/handlers_activity_data.go create mode 100644 internal/sdkserver/runtime_config_test.go create mode 100644 protocol/proto/current_handlers.pb.go create mode 100644 protocol/proto/current_handlers.proto create mode 100644 protocol/proto/download_format.go create mode 100644 protocol/proto/upstream_cmdids.go create mode 100644 run.ps1 delete mode 100644 run_go.bat create mode 100644 server.json create mode 100644 server.local.example.json create mode 100644 tools/audit_protocol_handlers.py create mode 100644 tools/sync_protocol_commands.py diff --git a/.gitignore b/.gitignore index e8c6b11..e026606 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ gameserver.log server_debug.log *.log *.exe~ +battle_last.json +server.local.json +*.next.exe +battle_last.bin +battle_last_response.bin +battle_last_capture.json +battle_diagnostics/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6d756c8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# Repository rules + +## ห้าม hardcode ค่าที่เปลี่ยนตามการใช้งาน + +- ห้ามใส่ path ของเครื่อง, ชื่อผู้ใช้, Desktop, ไดรฟ์ติดตั้ง, path ของเกม/Proxy/Go, IP, port, URL ของ deployment หรือเวอร์ชัน Client ลงใน source code หรือ startup scripts +- ค่ารันระบบต้องมาจาก `server.json` โดย override ผ่าน `server.local.json` หรือ environment ได้ ใช้ตัวโหลดร่วมกันและตรวจค่าก่อนเริ่มทำงาน ห้ามมี fallback ที่ซ่อนค่าคงที่ไว้ในโค้ด +- ค่าเฉพาะเครื่องต้องอยู่ในไฟล์ local ที่ถูก ignore หรือ environment ห้าม commit ข้อมูลเฉพาะเครื่อง ห้ามเดาตำแหน่งโปรแกรมจากชื่อโฟลเดอร์หรือเลขเวอร์ชัน +- ค่าจากระบบเกม เช่น StageID, MonsterID, floor, group, anchor, level, buff และรายการปลดล็อก ต้องอ่านจาก resource/config ที่ตรงกับ Client เมื่อมีตารางรองรับ ห้ามเพิ่มการแก้บัคแบบเจาะจง ID โดยไม่มีข้อมูลรองรับ +- main affix ของ relic ต้องตรวจในกลุ่ม MainAffixGroup ของชิ้นนั้น ห้ามใช้ ID ข้ามกลุ่มหรือเดาจากเลข stat ทั่วไป และห้ามแก้ converter ให้บังคับเปลี่ยน ID เฉพาะตัวละครเพื่อกลบข้อมูล inventory ที่ผิด +- ค่าคงที่ที่เป็นนิยามของ protocol, enum, schema, โครงสร้างโปรเจกต์ และขอบเขตของชนิดข้อมูลใช้ได้เมื่อมีที่มา ตรวจสอบได้ และระบุชื่อที่สื่อความหมาย ห้ามใช้ข้อยกเว้นนี้แทนค่าตั้งระบบ +- ห้ามอ้างว่าเอา hardcode ออกทั้ง repository หากตรวจหรือแก้เพียงบางส่วน ต้องบอกขอบเขตและส่วนที่ยังเหลือจริง +- เมื่อแก้ startup/config ต้องตรวจว่าทั้ง SDK dispatch, game gateway และ launcher ใช้ค่าเดียวกัน พร้อมอัปเดต README และทดสอบ config override/ค่าผิดก่อน build +- ห้ามอ้างว่า build/test ผ่านแล้วแปลว่า Client เข้า battle หรือโหลดฉากได้ ต้องยืนยันจาก Client หรือรายงานว่ายังรอตรวจ +- handler ต้อง resolve CmdID จากชื่อในตาราง protocol ที่มี revision อ้างอิง ห้ามรับ alias เลขจากหลายเวอร์ชันหรือส่ง response หลายเลขเพื่อเดา callback; ถ้า schema ยัง obfuscated ต้องบันทึกข้อสันนิษฐานและข้อจำกัดไว้ +- อย่าเปลี่ยนค่า protocol เช่น ClientDownloadData Lua format ตามเลข patch ของ Client โดยไม่มีต้นฉบับรองรับ และอย่าเปลี่ยนแค่ข้อความเวอร์ชันเพื่ออ้างว่าอัปเดต handler แล้ว +- SDK และ game server ที่เปิดผ่าน launcher ต้องแสดงคอนโซลและ log ให้ผู้ใช้เห็น ห้ามซ่อนหน้าต่างหรือ redirect จนคอนโซลว่าง เว้นแต่ผู้ใช้ขอเอง เมนูแบบ interactive ปิดหลังทำรายการเสร็จ 5 วินาที โดยไม่ปิดเซิร์ฟเวอร์ตามไปด้วย diff --git a/README.md b/README.md index a66bac7..05f2d3d 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,201 @@ -# GaC hkrpg GO — StarRail 4.6.51 Private Server (ภาษา Go) +# GaC hkrpg GO -โอนย้าย (port) จากโปรเจกต์ Rust `GaC-hkrpg-ps` มาเป็นภาษา Go ทั้งหมด -พร้อม **แก้บัคกล้องหลุดจากตัวละครตอนกดตี (SceneCastSkill)** ตั้งแต่ต้นทาง +เซิร์ฟเวอร์จำลอง Honkai: Star Rail ภาษา Go พอร์ตจาก `GaC-hkrpg-ps` และอ้างอิงการทำงานจาก [PearlSR](https://git.xeondev.com/HonkaiSlopRail/pearl-sr) ประกอบด้วย game server, SDK/dispatch server และ REST API สำหรับ GaC-Srtool ---- +โปรเจกต์อยู่ระหว่างพัฒนา โดยกำลังตรวจการใช้งานกับ Client **4.6.53 OS** ตารางคำสั่งและ handler ที่แก้ล่าสุดอ้างอิง PearlSR commit `005d2c12db4781071f4b5b68e8f364587886a710` รายละเอียดอยู่ใน [บันทึกอัปเดต protocol](docs/protocol-update.md) การตรงกันของ schema ไม่ได้ยืนยันว่าเกมทุกระบบทำงานครบ -## บัคกล้องหลุด (SceneCastSkill) — สาเหตุและวิธีแก้ +## เริ่มใช้งานบน Windows -อาการ: กดตีใน overworld แล้วกล้องหลุดออกจากตัวละคร +1. ติดตั้ง **Go 1.27.0 ขึ้นไป** ตาม `go.mod` และตรวจด้วย `go version` +2. เตรียม Client, launcher/proxy และการ redirect dispatch ให้เชื่อมต่อเซิร์ฟเวอร์ที่กำหนดใน `server.json` +3. เก็บไฟล์ข้อมูลและโฟลเดอร์ `resources` ไว้ใน root ของโปรเจกต์ และตั้งค่า launcher ตามหัวข้อ config ด้านล่าง +4. เปิด `run.bat` แล้วเลือกเมนูที่ต้องการ -สาเหตุ (จาก `gameserver/src/net/handlers/battle.rs` เดิม): -handler ของ `SceneCastSkillCsReq` ตอบ `battle_info` + `monster_battle_info` ให้ client -เข้าสู่การต่อสู้กับ target ที่ผ่าน filter แบบหลวม: +| เมนู | การทำงาน | +| --- | --- | +| `1` หรือ Enter | Build ทั้งสองเซิร์ฟเวอร์ แล้วเปิดเซิร์ฟเวอร์และเกม | +| `2` | เปิดทั้งสองเซิร์ฟเวอร์จาก `.exe` ที่มีอยู่ | +| `3` | เปิดเฉพาะ SDK server | +| `4` | เปิดเฉพาะ game server | +| `5` | เปิดทั้งสองเซิร์ฟเวอร์จาก `.exe` ที่มีอยู่ และเรียก Gay Proxy | +| `6` | Build ทั้งสอง `.exe` อย่างเดียว | +| `7` | ออก | +| `8` หรือ `stop` | หยุดเซิร์ฟเวอร์ของโปรเจกต์นี้ | -``` -filter เดิม: id > 30_000 (monster) || id < 1_000 (avatar entity ของผู้เล่นเอง!) +เมนู 1 และ 6 หยุดเมื่อ build ไม่ผ่าน ส่วนเมนู 2–5 ใช้ binary ที่มีอยู่ หลังแก้ซอร์สจึงต้อง build ใหม่ก่อนใช้เมนูเหล่านั้น + +`run.bat` เรียก `run.ps1` ผ่าน PowerShell โดยอ่าน path เกม/Proxy จาก config ไม่มีการเดา Desktop, ไดรฟ์ หรือชื่อโฟลเดอร์ตามเวอร์ชัน ต้องตั้ง `launcher.client_path` เป็นไฟล์ launcher/เกมที่จะเปิด และ `launcher.proxy_path` เป็นไฟล์ Proxy ที่ต้องการ ถ้าเว้นว่างจะเปิดเซิร์ฟเวอร์และแสดงวิธีตั้งค่า + +SDK และ game server เปิดหน้าต่างคอนโซลแยกกันให้เห็น log โดยตรง หลังทำเมนูที่เลือกเสร็จ หน้าต่าง `run.bat` จะปิดเองใน 5 วินาที ส่วนหน้าต่างเซิร์ฟเวอร์ยังเปิดอยู่ ใช้ `run.bat stop` เพื่อหยุด หรือปิดหน้าต่างเซิร์ฟเวอร์ที่ต้องการ สคริปต์หยุดเฉพาะ process ที่ path executable ตรงกับ root นี้ ก่อนเริ่มใหม่หรือแทนที่ binary ที่ build สำเร็จ การเรียกด้วย argument เช่น `run.bat build` จะคืนค่าทันทีเมื่อทำงานเสร็จโดยไม่รอ 5 วินาที + +## Config และกฎห้าม hardcode + +`AGENTS.md` กำหนดให้ค่าที่เปลี่ยนตามเครื่อง/deployment/เวอร์ชันต้องมาจาก config, environment หรือ resource ห้ามฝัง path, IP, port หรือ fallback เฉพาะเครื่องใน source และ startup scripts + +ลำดับความสำคัญ: **environment > local config > config หลัก** + +- `server.json`: ค่าเครือข่ายและเวอร์ชันที่แชร์กันระหว่าง launcher, game server และ SDK server +- `server.local.json`: override เฉพาะเครื่อง ไฟล์นี้ถูก ignore โดย Git คัดลอก `server.local.example.json` แล้วใส่ path ของเครื่องได้ +- `GAC_CONFIG`: เลือกไฟล์หลักอื่น โดย local override ใช้ชื่อเดียวกันลงท้าย `.local.json` +- path แบบ relative ใน launcher อ้างอิง root ของโปรเจกต์ + +| ค่าใน config | Environment override | ใช้สำหรับ | +| --- | --- | --- | +| `client_version` | `GAC_CLIENT_VERSION` | เวอร์ชันที่แสดงใน log/API | +| `game_host`, `game_port` | `GAC_GAME_HOST`, `GAC_GAME_PORT` | ที่อยู่ bind ของ game server | +| `sdk_host`, `sdk_port` | `GAC_SDK_HOST`, `GAC_SDK_PORT` | ที่อยู่ bind ของ SDK server | +| `public_game_host` | `GAC_PUBLIC_GAME_HOST` | ที่อยู่ game server ที่ gateway แจ้ง Client | +| `public_sdk_url` | `GAC_PUBLIC_SDK_URL` | Base URL ที่ dispatch แจ้ง Client | +| `launcher.client_path` | `GAC_CLIENT_PATH` | ไฟล์ launcher/เกมที่จะเปิด | +| `launcher.proxy_path` | `GAC_PROXY_PATH` | ไฟล์ Proxy ที่จะเปิด | +| `launcher.go_command` | `GAC_GO_COMMAND` | คำสั่ง Go จาก PATH หรือ path ที่กำหนด | + +เมื่อเปลี่ยนเครือข่ายให้ตั้งทั้ง bind และค่าที่ประกาศให้ Client เข้าถึงได้ โดยเฉพาะ port ใน `public_sdk_url` เมื่อ SDK รับการเชื่อมต่อโดยตรง ค่าเหล่านี้มีค่าเริ่มต้นอยู่ในไฟล์ config และไม่มี fallback เครือข่ายในโค้ด ต้องรีสตาร์ตเซิร์ฟเวอร์หลังเปลี่ยน config การเปลี่ยน `client_version` ไม่ได้แปลง protocol หรือทรัพยากรของเกมให้อัตโนมัติ + +ตรวจ config โดยไม่เปิดเกมหรือเซิร์ฟเวอร์: + +```powershell +.\run.bat check ``` -เมื่อ client ส่ง hit target เป็น prop / NPC / ตัวผู้เล่นเอง server จะสั่งเข้า battle -กับ "monster" ที่ไม่มีอยู่จริง → battle transition โหลดไม่สำเร็จ → กล้อง battle -ไม่มี target ให้ตาม → กล้องหลุดจากตัวละคร นอกจากนี้ถ้าอยู่ใน arena -(entry >= 3000000) โค้ดเดิมยัง "หลอก" target เป็น 30001 เสมอ +## ทำไมมี executable สองตัว -วิธีแก้ (ใน `internal/gameserver/handlers_battle.go`): -1. ตอนโหลดฉาก server เก็บรายการ **monster entity จริง** ไว้ใน `Session.MonsterEntities` - (entity_id → event_id) -2. `SceneCastSkill` ยอมรับเฉพาะ hit target ที่เป็น monster จริงเท่านั้น -3. ถ้าไม่มี monster จริงถูกตี → ตอบ `retcode 0 + cast_entity_id` เฉยๆ **ไม่เข้า battle** - client เล่นแอนิเมชันตีปกติ กล้องค้างกับตัวละคร -4. ตีโดน monster จริง (รวมบอสใน arena 30001) → เข้า battle ตามปกติ +| ไฟล์ | หน้าที่ | พอร์ต | +| --- | --- | --- | +| `gameserver-go.exe` | รับการเชื่อมต่อเกมผ่าน KCP และจัดการผู้เล่น ฉาก และ battle | UDP ตาม `game_port` | +| `sdkserver-go.exe` | Dispatch, login, ข้อมูล hotfix และ REST API | HTTP ตาม `sdk_port` | -ยืนยันด้วย test อัตโนมัติ: `internal/gameserver/smoke_test.go` -(จำลอง client เต็มรูปแบบ: handshake → login → กดตี ทั้งกรณี ผ่าน UDP จริง) +การเปิดระบบครบต้องรันทั้งสองตัว `.exe` ที่ build เสร็จจะอยู่ใน root ของโปรเจกต์ ให้เปิดจาก root เพื่อให้เซิร์ฟเวอร์หาไฟล์ข้อมูลและบันทึก log ได้ถูกตำแหน่ง -## สถาปัตยกรรม +## Build และรันเอง -``` -cmd/gameserver เกมเซิร์ฟเวอร์ (UDP :23301, KCP) -cmd/sdkserver dispatch + login + SRTools (HTTP :21000) -protocol/proto StarRail.pb.go — generate จาก proto 4.6.51 (4,932 messages) -protocol/proto/cmdid.go — ตาราง CmdID (2,114 ids) generate จาก "// CmdID:" comments -pkg/kcp KCP สาย mihoyo (header 28 ไบต์: conv+token) — port จาก kcp.rs 1:1 -internal/common FreesrData / persistent / res.json / teleports.json + proto converters -internal/gameserver gateway, packet framing, session, dispatch, handlers ทั้งหมด -internal/sdkserver query_dispatch / query_gateway / auth / srtools / versions.json -``` +รันคำสั่งจาก root ของโปรเจกต์: -ไฟล์ข้อมูลใช้ชุดเดิมกับ Rust PS ทุกไฟล์ (freesr-data.json, persistent, res.json, -teleports.json, challenge_data.json, mission_unlocks.json, versions.json) — save เดิมใช้ได้ทันที - -## การใช้งาน - -``` -run_go.bat เมนู 1/2/3/4 เหมือนเดิม -gameserver-go.exe เกมเซิร์ฟเวอร์ -sdkserver-go.exe SDK/dispatch -``` - -ต้องแก้ hosts ของเครื่อง client แบบเดียวกับ Rust PS (หรือใช้ Fiddler/proxy เดิม) - -## Build จากซอร์ส - -ต้องมี Go 1.27+ และ protoc + protoc-gen-go (เฉพาะตอนแก้ proto): - -``` +```powershell go build -o gameserver-go.exe ./cmd/gameserver -go build -o sdkserver-go.exe ./cmd/sdkserver -go test ./... # รวม smoke test จำลอง client จริง +go build -o sdkserver-go.exe ./cmd/sdkserver ``` -## สิ่งที่พอร์ตครบแล้ว +เปิดคนละ terminal: -- Network: mihoyo KCP (conv+token), NetOperation handshake (0xFF/0x145/0x194), - packet framing (0x9D74C714/0xD7A152C8), freesr-data.json hot-reload watcher -- Login flow ครบ (GetToken/PlayerLogin/GetBasicInfo/PlayerLoginFinish + ContentPackage) -- ฉาก: GetCurSceneInfo/EnterScene (โหลด props/NPCs/monsters จาก res.json, teleport - resolution, fallback จุดเกิด), SceneEntityMove (บันทึกตำแหน่ง), InteractProp, - GetNpcTakenReward, GetSceneMapInfo, GetEnteredScene, GetUnlockTeleport -- ทีม: GetAllLineupData/GetCurLineupData/JoinLineup/ReplaceLineup/SwapLineup/ - SwitchLineupIndex/ChangeLineupLeader + SceneGroupRefresh/SyncLineup notify -- ตัวละคร/ไอเทม: GetAvatarData (+skin), สวม/ถอด lightcone/relic, RankUpAvatar, GetBag -- ภารกิจ: GetMissionStatus (curversion finished), GetMissionData, GetQuestData, - GetMainMissionCustomValue, UpdateTrackMainMission (จาก mission_unlocks.json) -- บทเรียน: ปดล็อคครบ 1249 tutorials + 855 guides (กันกล้อง/อินพุตค้าง) -- ต่อสู้: SceneCastSkill (**แก้บัคกล้องหลุดแล้ว**), StartCocoonStage, - StartQuickCocoonStage, SceneEnterStage, PVEBattleResult, GetCurBattleInfo, - SceneCastSkillCostMp, บัฟ global (Castorice/SW/Aha 151104) -- Challenge: GetChallenge, GetCurChallenge, GetChallengePeakData, GetCurChallengePeak, - GetActivityScheduleConfig, StartChallengePeak (cmd 8948 → arena 3000101), - StartChallenge → 1787 layout 4.6.51, Tierce (8973) + ทุก raw reply ที่ตอบค้าง - (1708/1716/1781/1704/8997/8999/8923/8920/... ครบตาม packet.rs เดิม) -- Dummy ปลอดภัย: ตอบว่างเฉพาะรายการที่ปลอดภัย ที่เหลือ drop เงียบ - (บทเรียนจาก Rust: ตอบว่างมั่วทำให้ client NRE จอดำ) +```powershell +.\sdkserver-go.exe +``` -## ต่างจาก Rust ที่ควรรู้ +```powershell +.\gameserver-go.exe +``` -- นาฬิกา KCP เป็นมิลลิวินาที (Rust ใช้วินาที — ทำให้ RTT/เวลา retransmit เพี้ยน) -- มี ticker ขับ KCP ทุก 50ms ต่อ session (retransmission ทำงานจริง) -- ชื่อ message ใช้ตาม proto 4.6.51 จริง (แก้ปัญหาชื่อเก่าที่หายไป) -- StartChallenge ยังไม่รองรับ challenge_id ที่อยู่นอก challenge_data.json - (fallback arena 3014101 ทำงานเหมือนเดิม) +ซอร์สมีไฟล์ protobuf ที่ generate แล้ว การ build ปกติไม่ต้องใช้ `protoc` หากแก้ schema จึงต้องติดตั้ง `protoc` และ `protoc-gen-go` เพื่อ generate ใหม่ + +สำหรับนักพัฒนาที่ต้องการรันชุดทดสอบ: + +```powershell +go test ./... +``` + +ผลทดสอบฝั่ง Go และการ build สำเร็จไม่ได้ยืนยันว่า Client โหลดฉากหรือเข้า battle สำเร็จ ต้องตรวจในเกมและอ่าน `Player.log` ประกอบด้วย + +## ไฟล์ข้อมูล + +| ไฟล์หรือโฟลเดอร์ | ใช้สำหรับ | +| --- | --- | +| `freesr-data.json` | ตัวละคร, lightcone, relic และ `battle_config` | +| `persistent` | ทีม, ตำแหน่ง, ฉาก และค่าผู้เล่นที่บันทึกไว้ | +| `res.json` | ข้อมูลฉากหลักในรูปแบบที่ Go loader ใช้ | +| `resources/` | ข้อมูลฉากและตารางประกอบ เช่น MapEntrance, MazePlane, Anchor และ StageConfig | +| `teleports.json` | ตำแหน่ง teleport แยกตาม floor | +| `challenge_data.json` | ข้อมูล challenge | +| `mission_unlocks.json` | ข้อมูลภารกิจและการปลดล็อก | +| `hotfix.json`, `versions.json` | ข้อมูลเวอร์ชันและทรัพยากร Client สำหรับ SDK server | +| `scripts/` | Lua ที่ส่งให้ Client ผ่าน heartbeat | + +สำรอง `freesr-data.json` และ `persistent` ก่อนเปลี่ยนชุดข้อมูล `res.json` ใน root กับ `resources/res.json` มีโครงสร้างต่างกันและ loader ใช้ร่วมกัน จึงควรเก็บไว้ตามตำแหน่งเดิม + +**Calyx ใช้ `battle_config` ใน `freesr-data.json` เป็นข้อมูลจำลองการต่อสู้** จึงอาจได้ StageID และมอนสเตอร์ต่างจากดอกไม้ที่กดในเกม + +## REST API / GaC-Srtool + +เมื่อ SDK server ทำงาน เปิด URL ที่ตั้งใน `public_sdk_url` เพื่อดูรายการ endpoint หน้า root และ `/dashboard` ตอบข้อมูล JSON ของ API gateway + +| Method | Endpoint | หน้าที่ | +| --- | --- | --- | +| GET | `/api/status` | อ่านสถานะและข้อมูลผู้เล่น | +| GET, POST | `/api/freesr` | อ่านหรืออัปเดต FreeSR data | +| GET, POST | `/api/freesr/avatars` | อ่านหรืออัปเดตข้อมูลตัวละคร | +| GET, POST | `/api/persistent` | อ่านหรืออัปเดตข้อมูล persistent | +| POST | `/api/exrank/toggle` | สลับการปลดล็อก EX rank | +| POST | `/api/avatar/rank` | เปลี่ยน rank ตัวละคร | +| POST | `/api/avatar/max_all` | ปรับข้อมูลตัวละครตามการทำงานของ handler | +| POST | `/api/teleport` | อัปเดตข้อมูลจุดหมาย teleport | + +รูปแบบ request และเงื่อนไขการอัปเดตอยู่ใน `internal/sdkserver/api.go` การอัปเดตข้อมูลผ่าน API ยังต้องตรวจผลการโหลดข้อมูลของ Client ด้วย + +## สถานะและปัญหาที่กำลังตรวจ + +มี handler สำหรับ login, ทีม, ตัวละคร, ไอเทม, ฉาก, teleport, ภารกิจ, Calyx และ challenge แต่ความครบของ handler ไม่ได้หมายถึงรองรับทุกกรณีใน Client + +| รายการ | สถานะ | +| --- | --- | +| Error 2000 ตอน login | แก้ `GetCurBattleInfo` ให้ตอบสำเร็จพร้อมข้อมูลว่างเมื่อไม่มี battle; รอยืนยันในเกม | +| Calyx เข้า fight ไม่ได้ | ยังอยู่ระหว่างตรวจ `NullReferenceException` ใน `CreateBattleServerCheckResult` | +| ตีมอนแล้วค้างหน้า Weakness | พบ stack trace ในฟังก์ชันตรวจ battle เดียวกับ Calyx; ยังไม่ยืนยันว่าแก้แล้ว | +| วาร์ปแมพใหม่ | ปรับลำดับ response/notify และข้อมูล scene ตาม upstream; ยังต้องยืนยันปลายทางในเกม | +| `SceneCastSkill` | ใช้รายการ monster entity ที่ server สร้างเพื่อตรวจเป้าหมายก่อนส่ง battle | + +ข้อมูลล่าสุดที่ตรวจในซอร์สและเอกสาร: **16 ตุลาคม 2026** + +## เก็บข้อมูลเมื่อพบปัญหา + +| ไฟล์ | ข้อมูลที่ช่วยวิเคราะห์ | +| --- | --- | +| `gameserver.log` | คำขอและแพ็กเก็ตตอบกลับ | +| `server_debug.log` | รายละเอียด battle, scene และ teleport | +| `battle_last.json` | Payload battle ล่าสุดที่สร้างสำหรับ Calyx หรือการตีมอน | +| `battle_last_response.bin`, `battle_last.bin` | ไบต์ response ที่ส่งจริง และ nested battle_info ที่ตัดจาก response นั้น | +| `battle_last_capture.json`, `battle_diagnostics/` | CmdID, เวลา, SHA-256 และ snapshot ของแต่ละรอบ | +| `Player.log` ของ Client | Exception และจุดที่ Client โหลดข้อมูลไม่สำเร็จ | + +ตำแหน่ง `Player.log` บน Windows สำหรับ Client ที่ใช้ในงานนี้: + +```text +%USERPROFILE%\AppData\LocalLow\Cognosphere\Star Rail\Player.log +``` + +เมื่อรายงานปัญหาให้ระบุเวอร์ชัน Client, เวลาที่เกิด, การกระทำก่อนค้าง และรหัส Cocoon/Entry/Teleport ถ้ามี พร้อม log ช่วงเดียวกันทั้ง server และ Client ข้อความ `sent packet` ยืนยันเพียงว่า server ส่งแพ็กเก็ตแล้ว ส่วนการโหลดสำเร็จต้องดูฝั่ง Client + +สำหรับการตรวจ battle ทีละส่วน ดู [บันทึกเทียบ upstream](docs/battle-investigation.md) ค่า `battle_diagnostics.client_battle_type` ใน local config เปิด probe ที่อ่าน payload ด้วย protobuf parser ของ Client และเขียน `[GAC_BATTLE_WIRE]` ลง Player.log ชื่อ class ต้องอ้างอิง stack trace ของ Client รุ่นนั้น ตั้งค่าว่างเพื่อปิด probe + +ผลตรวจล่าสุดพบ relic `63095` ของ Dan Heng IL ใช้ main affix `11` ที่ไม่มีในตารางของลูกแก้ว จึงแก้ inventory เป็น `10` (Imaginary DMG) ใน `freesr-data.json` แล้ว ต้องเปิดเกมใหม่เพื่อโหลดข้อมูลและยืนยันการเข้า battle ผล build ไม่ใช่การยืนยันว่าอาการค้างหายแล้ว + +## โครงสร้างซอร์ส + +```text +cmd/gameserver/ จุดเริ่ม game server +cmd/sdkserver/ จุดเริ่ม SDK/dispatch server +internal/common/ โมเดลข้อมูล, resource loader และ protobuf converters +internal/gameserver/ Gateway, session, dispatch และ game handlers +internal/sdkserver/ HTTP dispatch, login และ REST API +protocol/proto/ Schema, generated protobuf และตาราง CmdID +pkg/kcp/ KCP transport +resources/ ข้อมูลประกอบ +scripts/ Client Lua +``` + +## อัปเดต protocol และ handler + +ใช้ checkout ของ PearlSR ที่ตรวจ revision แล้วเป็นแหล่งข้อมูล ไม่แก้ generated Go ด้วยมือ: + +```powershell +python tools/sync_protocol_commands.py "" +protoc --go_out=. --go_opt=paths=source_relative protocol/proto/StarRail.proto protocol/proto/current_handlers.proto +python tools/audit_protocol_handlers.py "" +``` + +คำสั่ง sync อัปเดตตารางชื่อ/CmdID ส่วนการแก้ field ต้องตรวจจาก schema ต้นฉบับแล้วแก้ `.proto` ก่อน generate ใหม่ ตัว audit ตรวจ CmdID, คู่ request/response และชุดหมายเลข field ของ message ที่มีชื่อร่วมกัน ไม่ตรวจชนิด field, default values, asset หรือผลใน Client + +`ClientDownloadDataLuaVersion = 51` เป็นค่า version ของ payload ที่ upstream ปัจจุบันยังใช้ส่ง Lua จึงไม่เปลี่ยนเป็น `53` ตาม `client_version` โดยไม่มีหลักฐานรองรับ ส่วน Tutorial อ่าน ID จาก `resources/TutorialData.json` และ `resources/TutorialGuideGroup.json` หลังเปลี่ยน resource ให้รีสตาร์ต server + +## ที่มา + +พอร์ตจาก `GaC-hkrpg-ps` ภาษา Rust โดยใช้แนวทางและข้อมูลประกอบจาก [HonkaiSlopRail/pearl-sr](https://git.xeondev.com/HonkaiSlopRail/pearl-sr) ภาษา Zig การแก้ใน Go ต้องตรวจเทียบทั้ง handler, payload และเวอร์ชัน Client ที่ใช้งานจริง diff --git a/cmd/gameserver/main.go b/cmd/gameserver/main.go index f39f971..b7c7df1 100644 --- a/cmd/gameserver/main.go +++ b/cmd/gameserver/main.go @@ -2,23 +2,31 @@ package main import ( "fmt" + "os" + "gac-hkrpg-go/internal/common" "gac-hkrpg-go/internal/gameserver" ) func main() { + cfg, err := common.LoadRuntimeConfig() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } fmt.Println("============================================================") - fmt.Println(" [GaC hkrpg GO] Gameserver (4.6.51) - ONLINE") - fmt.Println(" Listening on UDP port 23301 (KCP Gateway)") + fmt.Printf(" [GaC hkrpg GO] Gameserver (Client %s)\n", cfg.ClientVersion) + fmt.Printf(" Listening on UDP %s:%d (KCP Gateway)\n", cfg.GameHost, cfg.GamePort) fmt.Println(" Ready for StarRail client connection...") fmt.Println("============================================================") - gw, err := gameserver.NewGateway("0.0.0.0", 23301) + gw, err := gameserver.NewGateway(cfg.GameHost, cfg.GamePort) if err != nil { - fmt.Println("failed to bind gateway:", err) - return + fmt.Fprintln(os.Stderr, "failed to bind gateway:", err) + os.Exit(1) } if err := gw.Listen(); err != nil { - fmt.Println("gateway error:", err) + fmt.Fprintln(os.Stderr, "gateway error:", err) + os.Exit(1) } } diff --git a/cmd/sdkserver/main.go b/cmd/sdkserver/main.go index c6d29e0..9fe7ff7 100644 --- a/cmd/sdkserver/main.go +++ b/cmd/sdkserver/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "gac-hkrpg-go/internal/sdkserver" ) @@ -9,10 +10,10 @@ import ( func main() { fmt.Println("============================================================") fmt.Println(" [GaC hkrpg GO] SDK/Dispatch server - ONLINE") - fmt.Println(" Listening on HTTP port 21000") fmt.Println("============================================================") if err := sdkserver.Listen(); err != nil { - fmt.Println("sdkserver error:", err) + fmt.Fprintln(os.Stderr, "sdkserver error:", err) + os.Exit(1) } } diff --git a/docs/battle-investigation.md b/docs/battle-investigation.md new file mode 100644 index 0000000..acc26c0 --- /dev/null +++ b/docs/battle-investigation.md @@ -0,0 +1,77 @@ +# Battle investigation — 2026-10-16 + +## หลักฐานที่ใช้อ้างอิง + +- Upstream: [HonkaiSlopRail/pearl-sr](https://git.xeondev.com/HonkaiSlopRail/pearl-sr) +- ตรวจ remote ใหม่แล้วที่ commit `005d2c12db4781071f4b5b68e8f364587886a710` +- ไฟล์หลัก: `gameserver/src/services/battle.zig`, `gameserver/src/manager/battle_mgr.zig`, `gameserver/src/data/game_config.zig`, `protocol/src/protocol.pb.zig` +- Client ล่าสุดยัง NRE ที่ `CreateBattleServerCheckResult` ผ่านตัวแปลง BattleAvatar/รายการ relic และ `NMBMHCNGCFG.BDNFEMGIEIH` +- Payload ที่บันทึกเวลา 21:34 มีตัวละคร 4 ตัว แต่ละตัวมี relic 6 ชิ้นและ sub-affix 24 ค่า แม้เลิกส่ง inventory UniqueId ใน BattleRelic แล้ว Client ก็ยังล้ม + +## เทียบทีละส่วน + +| ส่วน | สิ่งที่ตรวจพบ | ข้อสรุปที่ใช้ได้ | +| --- | --- | --- | +| Quick Calyx response | Field numbers: retcode=1, battle_info=5, wave=8, cocoon_id=11 ตรง upstream | ยังไม่พบ field-number mismatch ใน wrapper | +| SceneBattleInfo | ตรวจชุด field numbers ของ 20 fields ตรงกัน | ชื่อ `JAOIOKBBMFC` ของ Go คือ field 7 ซึ่ง upstream เรียก monster_wave_length | +| BattleAvatar | ตรวจชุด field numbers ของ 18 fields ตรงกัน | ยังต้องดูค่าที่ parser ของ Client ถอดได้จริง | +| Relic/lightcone | BattleRelic, RelicAffix และ BattleEquipment มีชุด field numbers ตรงกัน | จำนวน sub-affix ฝั่ง Go อย่างเดียวไม่ยืนยันว่า Client ถอดได้ครบ | +| Monster/wave | SceneMonster, SceneMonsterWave, SceneMonsterWaveParam มีชุด field numbers ตรงกัน | ยังไม่มีหลักฐานให้สลับ WaveParam เป็น nil อีก | +| Buff | Go ใช้ชื่อ OwnerId แต่ upstream ใช้ owner_index บน field 3 เหมือนกัน | ชื่อ Go ไม่ใช่หลักฐานว่า field บน wire ผิด | +| แหล่งข้อมูล | Go ใช้ freesr-data.json/persistent; PearlSR ใช้ config.json/selected avatars | Payload ทั้งสองยังไม่เท่ากันในระดับค่าข้อมูล | +| EX rank | Upstream append รายการ ExRankAvatarID ให้ทุก battle avatar; Go ส่งรายการตามตัวละครที่ปลดล็อก | เป็นข้อแตกต่างที่บันทึกไว้ ยังไม่ชี้ว่าเป็นสาเหตุ NRE | +| Global/technique buffs | เจ้าของบัฟ, target list และ dynamic values บางตัวต่างกัน | ต้องตรวจแยกหลังระบุตำแหน่งที่ parser/ตัวตรวจล้ม | + +การตรวจ field numbers ข้างต้นไม่ได้ยืนยัน default values, Client schema ของรุ่นที่รันจริง หรือผลการคำนวณค่าสถานะใน Client + +## ขั้นแรกที่เพิ่มในรอบนี้: เก็บ wire และอ่านกลับบน Client + +บันทึกหลังส่ง response สำเร็จจาก Session.Send แทนการบันทึกระหว่างสร้าง battle: + +- `battle_last_response.bin`: protobuf body ของ response ที่ส่งจริง +- `battle_last.bin`: bytes ของ nested battle_info ที่ตัดออกจาก response เดิมโดยตรง ไม่ marshal ใหม่ +- `battle_last.json`: ข้อมูลสำหรับอ่านด้วยตา +- `battle_last_capture.json`: CmdID, ชื่อ response, StageID, เวลา UTC และ SHA-256 +- `battle_diagnostics/`: snapshot ของแต่ละรอบ เก็บทั้งข้อมูลเดิมและรอบต่อมา + +เปิด read-only Client probe ด้วย `battle_diagnostics.client_battle_type` ใน local config ชื่อ class ต้องเอาจาก stack trace ของ Client รุ่นที่กำลังใช้งาน ไม่ฝังชื่อ class ที่เปลี่ยนตามเวอร์ชันใน Go/Lua + +Probe ถูกส่งครั้งเดียวใน heartbeat หลัง response battle โดยใช้ protobuf parser ของ Client อ่าน bytes เดียวกับ capture จากนั้นเขียน `[GAC_BATTLE_WIRE]` ลง Player.log พร้อม decoded JSON และ descriptor field numbers ไม่แก้ตัวละคร relic บัฟ หรือ callback การต่อสู้ + +หาก parser/Reflection ไม่ถูกเปิดให้ Lua ใช้ จะมี `probe_error` ใน Player.log แทน ห้ามตีความว่า capture ฝั่ง Go ผ่านแล้วแปลว่า Client decode ผ่าน + +## ข้อสรุปที่ถอนออก + +Stack trace เดิมเพียงอย่างเดียวไม่ได้พิสูจน์ว่า WaveParam=nil หรือ SceneMonster.ExtraInfo=nil เป็นสาเหตุ การใส่ ExtraInfo หรือการลบ UniqueId ยังไม่ได้ทำให้การเข้า battle สำเร็จ จึงไม่ถือเป็นการแก้ที่ยืนยันผลแล้ว + +## ผลตรวจรอบ 21:47: main affix ที่ไม่มีในตาราง + +Calyx และ overworld ตายที่ stack เดียวกัน ส่วน probe รุ่นแรกทำงานแล้วแต่รายงาน `protobuf parser property not found` จึงไม่มี Client readback และยังใช้ผลของ probe ยืนยันการ decode ไม่ได้ + +แก้การอ่านตำแหน่ง exception: `0xc4eb77c` เป็น return address หลังคำสั่ง `call` ที่ `0xc4eb777` เช่นเดียวกับ frame `0x166b23d2` ที่อยู่หลัง `call 0xc4eb0c0` ดังนั้นจุดนี้มาจาก branch หลัง lookup **main affix** ไม่พบ (`0xc4eb2b0`–`0xc4eb2b3`) ไม่ใช่ branch ตรวจ sub-affix array ที่กระโดดไป `0xc4eb77c` แล้วมี return address `0xc4eb781` การตีความ sub-affix array เป็น null ในรอบก่อนจึงถูกถอนออก + +ตรวจ payload จริงและ inventory พบรายการนี้: + +| Inventory UID | Avatar | Relic | main_affix_id เดิม | ผลตรวจ | +| --- | --- | --- | --- | --- | +| 483 | 1213 (Dan Heng IL) | 63095 | 11 | ไม่มีใน main affix group 55 ของลูกแก้ว | + +เทียบกับ [relics.json](https://github.com/Mar-7th/StarRailRes/blob/dbe8cdfcfb0bf657b9fe1cf92d5537f118ccb487/index_new/en/relics.json) และ [relic_main_affixes.json](https://github.com/Mar-7th/StarRailRes/blob/dbe8cdfcfb0bf657b9fe1cf92d5537f118ccb487/index_new/en/relic_main_affixes.json): group 55 มี ID 1–10 โดย ID 10 คือ `ImaginaryAddedRatio` และ `resources/AvatarConfig.json` ระบุ Avatar 1213 เป็น Imaginary + +แก้ **ข้อมูล inventory เท่านั้น** จาก 11 เป็น 10 ใน `freesr-data.json` ไม่มีเงื่อนไขพิเศษสำหรับ Avatar/Relic นี้ใน converter สำรองข้อมูลและ log ก่อนแก้ไว้ใน `battle_diagnostics/main-affix-repair/` แล้ว และปิด probe ใน local config ขณะลองผลการแก้ข้อมูล + +ข้อจำกัดของชุดข้อมูลอ้างอิง: ไม่พบ relic ใหม่ 63295/63296 ของ Avatar 1511 จึงไม่ได้รับรอง inventory ทุกชิ้น แต่ชิ้นที่อยู่ในทีมของ payload รอบนี้มีครบและพบ main affix ผิดหนึ่งชิ้นข้างต้น + +## ขั้นต่อไปหลังแก้ main affix + +1. เปิดเกมใหม่ให้ Client โหลด inventory ที่แก้แล้ว และลอง Calyx/overworld ด้วยทีมเดิม +2. หากยังล้ม ให้เก็บ stack ใหม่และเทียบ capture ว่าส่ง main affix 10 แล้ว ก่อนเปลี่ยนส่วนอื่น + +## แนวทางตรวจเพิ่มเติมหากยังล้ม + +1. เทียบ SHA-256 ให้ตรงกับรอบที่ Client ล้ม +2. เทียบ schema/ข้อมูล BattleAvatar และ nested relic ที่ Client ถอดได้กับ capture ฝั่ง Go +3. หากข้อมูลตรงกัน ให้แยกตัวละครและอุปกรณ์ที่ทำให้ตัวตรวจค่าสถานะล้มโดยมี control payload ก่อนแก้ resource หรือสูตรบัฟ +4. เปลี่ยนเฉพาะส่วนที่มีหลักฐาน แล้วตรวจ Calyx และ overworld battle แยกกัน + +สถานะ: ยังไม่มีการยืนยันว่าเข้า fight ได้ diff --git a/docs/protocol-update.md b/docs/protocol-update.md new file mode 100644 index 0000000..718922a --- /dev/null +++ b/docs/protocol-update.md @@ -0,0 +1,40 @@ +# Protocol และ handler — 2026-10-16 + +เป้าหมาย Client: 4.6.53 OS ตาม config ที่ใช้อยู่ + +แหล่งอ้างอิง: [PearlSR commit 005d2c12db4781071f4b5b68e8f364587886a710](https://git.xeondev.com/HonkaiSlopRail/pearl-sr/commit/005d2c12db4781071f4b5b68e8f364587886a710) โดยตรวจ `protocol/src/root.zig`, `protocol/src/protocol.pb.zig` และ `gameserver/src/services/{challenge,misc,avatar}.zig` หลัง fetch remote ใหม่ + +## สิ่งที่เปลี่ยน + +| ส่วน | การแก้ | +| --- | --- | +| ตารางคำสั่ง | Generate ชื่อ/CmdID 2,690 รายการจาก upstream เก็บ revision และตรวจ conflict กับ Go schema เมื่อเริ่มโปรแกรม | +| `handle` / raw dispatch | เลิกระบุเวอร์ชันเก่าในข้อความ log และให้ raw handler resolve request ตามชื่อจากตารางปัจจุบัน | +| alias เก่า | นำเลข request หลายเวอร์ชันและ fixed-body replies ที่ไม่มีชื่อปัจจุบันรองรับออก บางเลขเคยชี้ไปยัง response/notification เช่น 8909, 8924, 8935 | +| Tierce lineup | ใช้ request 8992 → response 8979; challenge_id field 4 และ stage_info_list field 5 ตาม upstream เพิ่ม generated message ที่ Go เดิมขาด | +| Tierce data | ตอบ GetChallengeTierceDataScRsp ครั้งเดียว เลิกส่งสำเนาไป cmd 8994 และเลิกรับ response 8979/8981 เป็น request | +| Challenge reward | รับ request 1747 → response 1748; อ่าน group_id field 10 และส่ง group_id field 12 / retcode field 9 ด้วย generated type แทน request 1739 → cmd 1704 ที่เป็นคนละระบบ | +| Challenge statistics | อ่าน request ด้วย generated type และตอบ group เดิมตาม upstream; ไม่ส่งคะแนนประวัติที่แต่งขึ้น ปรับ statistics สามชนิดเป็น oneof ตาม schema | +| ออกจาก Challenge | ส่ง response ตาม mode ที่ร้องขอเพียงชนิดเดียว ล้าง battle แล้วใช้ฉาก overworld ที่เก็บไว้ของผู้เล่น เลิกบังคับไปฉาก/พิกัดคงที่ | +| Peak | ลงทะเบียน StartChallengePeak ที่มี implementation อยู่แล้ว และ LeaveChallengePeak ด้วยชื่อปัจจุบัน; GetCurChallengePeak ตอบสถานะว่างแบบ upstream แทน PeakID 1 ที่แต่งขึ้น | +| Endgame / SwitchHand | ส่ง generated response โดย resolve CmdID ตามชื่อ แทน raw bytes และเลขที่ฝังใน handler | +| Avatar cmd 400 | อ่าน field จาก generated HIGLJHLNODG แทนการสแกนเลขทุก field และเลิกยิง response 5 หมายเลข; ใช้ LPOLGDLAADM ซึ่งอยู่คู่ติดกันใน schema ชื่อและความสัมพันธ์เชิงความหมายยังเป็น obfuscated จึงยังต้องยืนยัน callback ใน Client | +| ReplaceLineup | เพิ่ม field 14 `OMDLCGEMBLA` ที่ Go schema ขาดตาม upstream โดยไม่เดาค่าให้ field นี้ | +| Tutorial | เลิกใช้รายการ ID จาก Rust 4.6.51; อ่าน TutorialID/GroupID จาก resource JSON ที่ติดตั้งแทน และรายงาน error หากไฟล์หาย/ไม่มี ID | +| Activity | นำ generated list เก่าที่ไม่ได้ใช้แล้วออก ไม่เปิด activity panel เพิ่มก่อนมี implementation รองรับข้อมูลของมัน | +| Lua heartbeat | คงค่า version ของ ClientDownloadData ไว้ที่ 51 เพราะ `services/misc.zig` ของ upstream revision นี้ยังส่งค่านี้ ไม่อนุมานจากเลข client patch | + +Go message บางชื่อเป็น alias เก่าของชื่อที่ upstream ตั้งใหม่ เช่น StartQuickCocoonStage/QuickStartCocoonStage และ TakeChallengeReward/TakeChallengeCumulativeReward คงชนิด Go เดิมในจุดที่ wire layout ตรงกัน แล้วผูกหมายเลขคำสั่งกับตารางปัจจุบัน + +## ผล source audit + +- CmdID 2,690 รายการตรงกับ upstream +- คู่ request/response เดิม 865 คู่ตรงกับ upstream +- ชุดหมายเลข field ของ message ที่มีชื่อร่วมกัน 4,435 ชนิดตรงกันหลังนับ oneof ทั้งสองฝั่ง +- การตรวจนี้ไม่ครอบคลุมความหมายของชื่อ obfuscated, ชนิด/default ของทุก field, resource รุ่นที่ตรง Client หรือผลการเล่นจริง + +เพิ่ม `tools/sync_protocol_commands.py` สำหรับ generate ตารางคำสั่ง และ `tools/audit_protocol_handlers.py` สำหรับตรวจซอร์สซ้ำ โดยระบุ checkout ของ upstream เป็น argument ไม่ฝัง path เครื่อง + +## ขอบเขตที่ยังเหลือ + +การเปลี่ยนนี้อัปเดต dispatch/schema/handlers ข้างต้น ไม่ได้อ้างว่า implementation ของทุกระบบเสร็จแล้ว ข้อมูลปลดล็อก, สูตร fallback ด่าน, บัฟ และสถิติบางส่วนของโปรเจกต์ยังต้องตรวจแยกตาม resource จริง การ build หรือ source audit ผ่านไม่ยืนยันว่า Calyx, teleport หรือ Endgame ทำงานสำเร็จใน Client diff --git a/freesr-data.json b/freesr-data.json index 5ef2316..e44bea7 100644 --- a/freesr-data.json +++ b/freesr-data.json @@ -14531,7 +14531,7 @@ { "level": 15, "relic_id": 63095, - "main_affix_id": 11, + "main_affix_id": 10, "sub_affixes": [ { "sub_affix_id": 7, diff --git a/internal/common/models.go b/internal/common/models.go index b6eb770..b28ebd2 100644 --- a/internal/common/models.go +++ b/internal/common/models.go @@ -111,7 +111,7 @@ func (m *MultiPathAvatar) UnmarshalJSON(data []byte) error { return nil } -// MultiPathAvatarType in the 4.6.51 proto uses the avatar id itself as the +// MultiPathAvatarType in the loaded schema uses the avatar id itself as the // enum value (Mar7thKnightType=1001, BoyWarriorType=8001, ...), so the proto // value is simply the numeric id (0 for Unk). func (m MultiPathAvatar) ProtoType() uint32 { return uint32(m) } diff --git a/internal/common/proto_convert.go b/internal/common/proto_convert.go index 88c8c11..fa6bb06 100644 --- a/internal/common/proto_convert.go +++ b/internal/common/proto_convert.go @@ -44,11 +44,12 @@ func relicAffixes(subs []SubAffix) []*goproto.RelicAffix { } func (r *Relic) ToBattleRelic() *goproto.BattleRelic { + // Match PearlSR's standalone battle relic format. Inventory UniqueID is + // only sent by ToProtoRelic/ToEquipRelic; battle carries the full affixes. return &goproto.BattleRelic{ Id: r.RelicID, Level: r.Level, MainAffixId: r.MainAffixID, - UniqueId: r.UniqueID(), SubAffixList: relicAffixes(r.SubAffixes), } } @@ -316,19 +317,26 @@ func (m *Monster) ToSceneMonster() *goproto.SceneMonster { return &goproto.SceneMonster{ MonsterId: m.MonsterID, MaxHp: hp, - CurHp: hp, } } -func monsterWave(waveID uint32, monsters []Monster) *goproto.SceneMonsterWave { +func monsterWave(monsters []Monster) *goproto.SceneMonsterWave { + maxLevel := uint32(0) + for _, monster := range monsters { + if monster.Level > maxLevel { + maxLevel = monster.Level + } + } + if maxLevel == 0 { + maxLevel = 95 + } + wave := &goproto.SceneMonsterWave{ - WaveId: waveID, MonsterList: make([]*goproto.SceneMonster, 0, len(monsters)), - // NOTE: WaveParam MUST be nil! In client 4.6.53 GameAssembly.dll - // AdventureModule.CreateBattleServerCheckResult (RVA 0x166b2ef6), - // if WaveParam is non-nil it tries to dereference [wave_param + 0x10] - // and throws NullReferenceException. When nil, it safely jumps over. - WaveParam: nil, + // Include a level so the client can validate this wave directly instead + // of looking up monster data from the battle stage's static table. This + // matters when a Calyx request reuses a custom battle stage ID. + WaveParam: &goproto.SceneMonsterWaveParam{Level: maxLevel}, } for i := range monsters { wave.MonsterList = append(wave.MonsterList, monsters[i].ToSceneMonster()) @@ -338,8 +346,8 @@ func monsterWave(waveID uint32, monsters []Monster) *goproto.SceneMonsterWave { func ToSceneMonsterWaves(waves [][]Monster) []*goproto.SceneMonsterWave { out := make([]*goproto.SceneMonsterWave, 0, len(waves)) - for i, wave := range waves { - out = append(out, monsterWave(uint32(i+1), wave)) + for _, wave := range waves { + out = append(out, monsterWave(wave)) } return out } diff --git a/internal/common/proto_convert_test.go b/internal/common/proto_convert_test.go new file mode 100644 index 0000000..6af24c4 --- /dev/null +++ b/internal/common/proto_convert_test.go @@ -0,0 +1,35 @@ +package common + +import ( + "testing" + + goproto "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" +) + +func TestStandaloneBattleRelicWire(t *testing.T) { + relic := Relic{ + RelicID: 61151, Level: 15, MainAffixID: 1, InternalUID: 42, + SubAffixes: []SubAffix{{SubAffixID: 7, Count: 3, Step: 3}, {SubAffixID: 8, Count: 3, Step: 0}}, + } + encoded, err := proto.Marshal(relic.ToBattleRelic()) + if err != nil { + t.Fatal(err) + } + var decoded goproto.BattleRelic + if err := proto.Unmarshal(encoded, &decoded); err != nil { + t.Fatal(err) + } + if decoded.UniqueId != 0 { + t.Fatal("standalone battle relic must not use inventory ID") + } + if decoded.Id != 61151 || decoded.MainAffixId != 1 || len(decoded.SubAffixList) != 2 { + t.Fatalf("relic or affixes lost on wire: %v", &decoded) + } + if decoded.SubAffixList[0].Cnt != 3 || decoded.SubAffixList[0].Step != 3 || decoded.SubAffixList[1].Step != 0 { + t.Fatalf("sub-affix values changed on wire: %v", decoded.SubAffixList) + } + if relic.ToProtoRelic().UniqueId != 42 || relic.ToEquipRelic().RelicUniqueId != 42 { + t.Fatal("inventory and equipped relic references must retain their IDs") + } +} diff --git a/internal/common/runtime_config.go b/internal/common/runtime_config.go new file mode 100644 index 0000000..3a8bd35 --- /dev/null +++ b/internal/common/runtime_config.go @@ -0,0 +1,88 @@ +package common + +import ( + "bytes" + "encoding/json" + "fmt" + "net" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" +) + +// RuntimeConfig is shared by both server processes. Deployment values belong +// in configuration rather than in handlers or executable entry points. +type RuntimeConfig struct { + BattleDiagnostics BattleDiagnosticsConfig `json:"battle_diagnostics"` + ClientVersion string `json:"client_version"` + GameHost string `json:"game_host"` + GamePort uint16 `json:"game_port"` + SDKHost string `json:"sdk_host"` + SDKPort uint16 `json:"sdk_port"` + PublicGameHost string `json:"public_game_host"` + PublicSDKURL string `json:"public_sdk_url"` +} + +// The obfuscated protobuf class name is version-specific and comes from the +// client's stack trace. Empty disables the read-only client wire probe. +type BattleDiagnosticsConfig struct { + ClientBattleType string `json:"client_battle_type"` +} + +func LoadRuntimeConfig() (RuntimeConfig, error) { + path := os.Getenv("GAC_CONFIG") + if path == "" { + path = "server.json" + } + var cfg RuntimeConfig + for i, filename := range []string{path, filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))+".local.json")} { + raw, err := os.ReadFile(filename) + if i == 1 && os.IsNotExist(err) { + continue + } + if err != nil { + return cfg, fmt.Errorf("read runtime config %s: %w", filename, err) + } + if err := json.Unmarshal(bytes.TrimPrefix(raw, []byte{0xef, 0xbb, 0xbf}), &cfg); err != nil { + return cfg, fmt.Errorf("parse runtime config %s: %w", filename, err) + } + } + for name, dest := range map[string]*string{ + "GAC_CLIENT_VERSION": &cfg.ClientVersion, "GAC_GAME_HOST": &cfg.GameHost, + "GAC_SDK_HOST": &cfg.SDKHost, "GAC_PUBLIC_GAME_HOST": &cfg.PublicGameHost, + "GAC_PUBLIC_SDK_URL": &cfg.PublicSDKURL, + } { + if value, ok := os.LookupEnv(name); ok { + *dest = value + } + } + for name, dest := range map[string]*uint16{"GAC_GAME_PORT": &cfg.GamePort, "GAC_SDK_PORT": &cfg.SDKPort} { + if value, ok := os.LookupEnv(name); ok { + n, err := strconv.ParseUint(value, 10, 16) + if err != nil || n == 0 { + return cfg, fmt.Errorf("%s must be a port between 1 and 65535", name) + } + *dest = uint16(n) + } + } + if cfg.GamePort == 0 || cfg.SDKPort == 0 || strings.TrimSpace(cfg.ClientVersion) == "" { + return cfg, fmt.Errorf("runtime config requires client_version, game_port and sdk_port") + } + for name, host := range map[string]string{"game_host": cfg.GameHost, "sdk_host": cfg.SDKHost, "public_game_host": cfg.PublicGameHost} { + if strings.TrimSpace(host) == "" || strings.ContainsAny(host, "/\\ \t\r\n") { + return cfg, fmt.Errorf("invalid %s", name) + } + } + u, err := url.Parse(cfg.PublicSDKURL) + if err != nil || u.Hostname() == "" || (u.Scheme != "http" && u.Scheme != "https") || u.RawQuery != "" || u.Fragment != "" || u.User != nil { + return cfg, fmt.Errorf("public_sdk_url must be an HTTP(S) base URL") + } + cfg.PublicSDKURL = strings.TrimRight(cfg.PublicSDKURL, "/") + return cfg, nil +} + +func (c RuntimeConfig) SDKAddress() string { + return net.JoinHostPort(c.SDKHost, strconv.Itoa(int(c.SDKPort))) +} diff --git a/internal/common/runtime_config_test.go b/internal/common/runtime_config_test.go new file mode 100644 index 0000000..06ebb80 --- /dev/null +++ b/internal/common/runtime_config_test.go @@ -0,0 +1,58 @@ +package common + +import ( + "os" + "path/filepath" + "testing" +) + +// Addresses and ports below are deliberately different fixture values, not +// deployment defaults. These tests must never bind sockets or start servers. +func TestRuntimeConfigOverrides(t *testing.T) { + t.Chdir(t.TempDir()) + for _, name := range []string{"GAC_CONFIG", "GAC_CLIENT_VERSION", "GAC_GAME_HOST", "GAC_GAME_PORT", "GAC_SDK_HOST", "GAC_SDK_PORT", "GAC_PUBLIC_GAME_HOST", "GAC_PUBLIC_SDK_URL"} { + t.Setenv(name, "") + if err := os.Unsetenv(name); err != nil { + t.Fatal(err) + } + } + base := `{"client_version":"fixture","game_host":"localhost","game_port":32101,"sdk_host":"localhost","sdk_port":32102,"public_game_host":"example.invalid","public_sdk_url":"https://example.invalid/base/"}` + if err := os.WriteFile("server.json", []byte(base), 0600); err != nil { + t.Fatal(err) + } + local := append([]byte{0xef, 0xbb, 0xbf}, []byte(`{"game_port":32201,"launcher":{"client_path":"fixture"}}`)...) + if err := os.WriteFile("server.local.json", local, 0600); err != nil { + t.Fatal(err) + } + cfg, err := LoadRuntimeConfig() + if err != nil { + t.Fatal(err) + } + if cfg.GamePort != 32201 || cfg.SDKPort != 32102 || cfg.PublicSDKURL != "https://example.invalid/base" { + t.Fatalf("merge failed: %+v", cfg) + } + t.Setenv("GAC_GAME_PORT", "32301") + t.Setenv("GAC_PUBLIC_GAME_HOST", "gateway.example.invalid") + cfg, err = LoadRuntimeConfig() + if err != nil { + t.Fatal(err) + } + if cfg.GamePort != 32301 || cfg.PublicGameHost != "gateway.example.invalid" { + t.Fatalf("environment override failed: %+v", cfg) + } + t.Setenv("GAC_GAME_PORT", "65536") + if _, err := LoadRuntimeConfig(); err == nil { + t.Fatal("out-of-range port accepted") + } + t.Setenv("GAC_GAME_PORT", "0") + if _, err := LoadRuntimeConfig(); err == nil { + t.Fatal("zero port accepted") + } +} + +func TestRuntimeConfigRequiresFile(t *testing.T) { + t.Setenv("GAC_CONFIG", filepath.Join(t.TempDir(), "missing.json")) + if _, err := LoadRuntimeConfig(); err == nil { + t.Fatal("missing config silently accepted") + } +} diff --git a/internal/gameserver/battle_capture.go b/internal/gameserver/battle_capture.go new file mode 100644 index 0000000..7603743 --- /dev/null +++ b/internal/gameserver/battle_capture.go @@ -0,0 +1,140 @@ +package gameserver + +import ( + "crypto/sha256" + _ "embed" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "gac-hkrpg-go/internal/common" + goproto "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/proto" +) + +//go:embed battle_wire_probe.lua +var battleWireProbe string + +type battleCapture struct { + ResponseName string `json:"response_name"` + CmdID uint16 `json:"cmd_id"` + ResponseSHA256 string `json:"response_sha256"` + BattleSHA256 string `json:"battle_sha256"` + CapturedUTC time.Time `json:"captured_utc"` + StageID uint32 `json:"stage_id"` +} + +func saveBattleCapture(msg proto.Message, cmdID uint16, response []byte) ([]byte, *battleCapture, error) { + carrier, ok := msg.(interface { + GetBattleInfo() *goproto.SceneBattleInfo + }) + if !ok || carrier.GetBattleInfo() == nil { + return nil, nil, nil + } + battle := carrier.GetBattleInfo() + wire, err := extractBattleWire(msg, response) + if err != nil { + return nil, nil, err + } + pretty, err := (protojson.MarshalOptions{Indent: " "}).Marshal(battle) + if err != nil { + return nil, nil, err + } + meta := &battleCapture{ + ResponseName: string(msg.ProtoReflect().Descriptor().Name()), CmdID: cmdID, + ResponseSHA256: fmt.Sprintf("%x", sha256.Sum256(response)), + BattleSHA256: fmt.Sprintf("%x", sha256.Sum256(wire)), + CapturedUTC: time.Now().UTC(), StageID: battle.StageId, + } + info, err := json.MarshalIndent(meta, "", " ") + if err != nil { + return nil, nil, err + } + files := map[string][]byte{"battle_last_response.bin": response, "battle_last.bin": wire, "battle_last.json": pretty, "battle_last_capture.json": info} + // Keep earlier failures intact so consecutive attempts can be compared. + archive := filepath.Join("battle_diagnostics", meta.CapturedUTC.Format("20060102T150405.000000000Z")+"_"+meta.ResponseSHA256[:12]) + if err := os.MkdirAll(archive, 0700); err != nil { + return nil, nil, err + } + for name, data := range files { + if err := os.WriteFile(filepath.Join(archive, name), data, 0600); err != nil { + return nil, nil, err + } + if err := os.WriteFile(name, data, 0600); err != nil { + return nil, nil, err + } + } + return wire, meta, nil +} + +func extractBattleWire(msg proto.Message, response []byte) ([]byte, error) { + field := msg.ProtoReflect().Descriptor().Fields().ByName("battle_info") + if field == nil { + return nil, fmt.Errorf("response has no battle_info field") + } + for len(response) > 0 { + number, kind, n := protowire.ConsumeTag(response) + if n < 0 { + return nil, protowire.ParseError(n) + } + response = response[n:] + if number == protowire.Number(field.Number()) && kind == protowire.BytesType { + wire, n := protowire.ConsumeBytes(response) + if n < 0 { + return nil, protowire.ParseError(n) + } + return append([]byte(nil), wire...), nil + } + n = protowire.ConsumeFieldValue(number, kind, response) + if n < 0 { + return nil, protowire.ParseError(n) + } + response = response[n:] + } + return nil, fmt.Errorf("battle_info missing from serialized response") +} + +func makeBattleProbe(wire []byte, typeName, hash string) []byte { + // Values are quoted as Lua strings; base64 contains no executable syntax. + replacer := strings.NewReplacer("__TYPE_NAME__", strconv.Quote(typeName), "__PAYLOAD__", strconv.Quote(base64.StdEncoding.EncodeToString(wire)), "__CAPTURE_HASH__", strconv.Quote(hash)) + return []byte(replacer.Replace(battleWireProbe)) +} + +func (s *Session) captureBattleResponse(msg proto.Message, cmdID uint16, body []byte) { + wire, meta, err := saveBattleCapture(msg, cmdID, body) + if err != nil { + Log(fmt.Sprintf("[BATTLE CAPTURE] %v", err)) + return + } + if meta == nil { + return + } + Log(fmt.Sprintf("[BATTLE CAPTURE] cmd=%d stage=%d response_sha256=%s battle_sha256=%s", cmdID, meta.StageID, meta.ResponseSHA256, meta.BattleSHA256)) + cfg, err := common.LoadRuntimeConfig() + if err != nil { + Log(fmt.Sprintf("[BATTLE PROBE] config error: %v", err)) + return + } + if cfg.BattleDiagnostics.ClientBattleType == "" { + return + } + s.battleProbeMu.Lock() + s.battleProbe = makeBattleProbe(wire, cfg.BattleDiagnostics.ClientBattleType, meta.BattleSHA256) + s.battleProbeMu.Unlock() + Log("[BATTLE PROBE] queued read-only client parser check for next heartbeat") +} + +func (s *Session) takeBattleProbe() []byte { + s.battleProbeMu.Lock() + defer s.battleProbeMu.Unlock() + probe := s.battleProbe + s.battleProbe = nil + return probe +} diff --git a/internal/gameserver/battle_capture_test.go b/internal/gameserver/battle_capture_test.go new file mode 100644 index 0000000..d4f5e4a --- /dev/null +++ b/internal/gameserver/battle_capture_test.go @@ -0,0 +1,72 @@ +package gameserver + +import ( + "bytes" + "crypto/sha256" + "fmt" + "os" + "strings" + "testing" + + goproto "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" +) + +func TestCaptureUsesExactResponseBattleBytes(t *testing.T) { + t.Chdir(t.TempDir()) + // Fixture values exercise zero-step affixes and map ordering on the wire. + battle := &goproto.SceneBattleInfo{BattleAvatarList: []*goproto.BattleAvatar{{Id: 1204, RelicList: []*goproto.BattleRelic{{Id: 61151, SubAffixList: []*goproto.RelicAffix{{AffixId: 7, Cnt: 3, Step: 0}}}}}}, BuffList: []*goproto.BattleBuff{{DynamicValues: map[string]float32{"first": 1, "second": 2}}}} + rsp := &goproto.StartQuickCocoonStageRsp{BattleInfo: battle} + body, err := proto.Marshal(rsp) + if err != nil { + t.Fatal(err) + } + wire, meta, err := saveBattleCapture(rsp, 1371, body) + if err != nil { + t.Fatal(err) + } + if meta.ResponseSHA256 != fmt.Sprintf("%x", sha256.Sum256(body)) { + t.Fatal("response checksum mismatch") + } + var decoded goproto.SceneBattleInfo + if err := proto.Unmarshal(wire, &decoded); err != nil { + t.Fatal(err) + } + if !proto.Equal(battle, &decoded) { + t.Fatal("captured battle differs from outgoing battle") + } + written, err := os.ReadFile("battle_last_response.bin") + if err != nil || !bytes.Equal(written, body) { + t.Fatalf("response not preserved: %v", err) + } + written, err = os.ReadFile("battle_last.bin") + if err != nil || !bytes.Equal(written, wire) { + t.Fatalf("battle wire not preserved: %v", err) + } + probe := string(makeBattleProbe(wire, "FixtureBattleType", meta.BattleSHA256)) + if strings.Contains(probe, "__TYPE_NAME__") || !strings.Contains(probe, meta.BattleSHA256) { + t.Fatal("probe placeholders not populated") + } +} + +func TestProbeDoesNotRunWithoutBattle(t *testing.T) { + t.Chdir(t.TempDir()) + _, meta, err := saveBattleCapture(&goproto.GetCurBattleInfoScRsp{}, 171, nil) + if err != nil || meta != nil { + t.Fatalf("empty login response captured: %v", err) + } + if _, err := os.Stat("battle_last.bin"); !os.IsNotExist(err) { + t.Fatal("empty response wrote a capture") + } + s := &Session{} + s.battleProbe = []byte("fixture") + if string(s.takeBattleProbe()) != "fixture" || len(s.takeBattleProbe()) != 0 { + t.Fatal("probe was not consumed exactly once") + } +} + +func TestBattleWireRejectsTruncatedResponse(t *testing.T) { + if _, err := extractBattleWire(&goproto.StartQuickCocoonStageRsp{}, []byte{0x2a, 0x7f}); err == nil { + t.Fatal("truncated payload accepted") + } +} diff --git a/internal/gameserver/battle_wire_probe.lua b/internal/gameserver/battle_wire_probe.lua new file mode 100644 index 0000000..b4d36cc --- /dev/null +++ b/internal/gameserver/battle_wire_probe.lua @@ -0,0 +1,95 @@ +-- Read-only: decode the captured battle with the client's own protobuf parser. +-- This does not start battles, modify actors, replace handlers, or change saves. +do + local captureHash = __CAPTURE_HASH__ + if _G.__gacBattleWireProbeV2Hash ~= captureHash then + _G.__gacBattleWireProbeV2Hash = captureHash + local function log(text) + CS.UnityEngine.Debug.Log('[GAC_BATTLE_WIRE] ' .. captureHash .. ' ' .. tostring(text)) + end + local ok, err = pcall(function() + local typeName = __TYPE_NAME__ + local assemblies = CS.System.AppDomain.CurrentDomain:GetAssemblies() + local messageType = nil + for i = 0, assemblies.Length - 1 do + messageType = assemblies[i]:GetType(typeName) + if messageType ~= nil then break end + end + if messageType == nil then error('message type not found: ' .. typeName) end + -- IL2CPP builds can expose the parser as a non-public member. + -- Include both visibility levels instead of assuming a public getter. + local flags = CS.System.Reflection.BindingFlags.Public + + CS.System.Reflection.BindingFlags.NonPublic + + CS.System.Reflection.BindingFlags.Static + + CS.System.Reflection.BindingFlags.Instance + local properties = messageType:GetProperties(flags) + local parser, descriptor = nil, nil + log('message_type=' .. tostring(messageType.FullName) .. ' properties=' .. tostring(properties.Length)) + for i = 0, properties.Length - 1 do + local property = properties[i] + local getter = property:GetGetMethod(true) + if getter ~= nil and getter.IsStatic then + local propertyType = tostring(property.PropertyType.FullName) + log('static_property=' .. tostring(property.Name) .. ' type=' .. propertyType) + if string.find(propertyType, 'Google.Protobuf.MessageParser', 1, true) then + parser = property:GetValue(nil, nil) + elseif propertyType == 'Google.Protobuf.Reflection.MessageDescriptor' then + descriptor = property:GetValue(nil, nil) + end + end + end + local fields = messageType:GetFields(flags) + for i = 0, fields.Length - 1 do + local field = fields[i] + local fieldType = tostring(field.FieldType.FullName) + log('member=' .. tostring(field.Name) .. ' type=' .. fieldType .. ' static=' .. tostring(field.IsStatic)) + if field.IsStatic then + if parser == nil and string.find(fieldType, 'Google.Protobuf.MessageParser', 1, true) then + parser = field:GetValue(nil) + elseif descriptor == nil and fieldType == 'Google.Protobuf.Reflection.MessageDescriptor' then + descriptor = field:GetValue(nil) + end + end + end + local bytes = CS.System.Convert.FromBase64String(__PAYLOAD__) + local message = nil + if parser ~= nil then + message = parser:ParseFrom(bytes) + else + -- A generated message can decode through MergeFrom even when + -- its static parser metadata was stripped from reflection. + message = CS.System.Activator.CreateInstance(messageType) + local methods = messageType:GetMethods(flags) + local merge = nil + for i = 0, methods.Length - 1 do + local method = methods[i] + local parameters = method:GetParameters() + if not method.IsStatic and parameters.Length == 1 + and tostring(parameters[0].ParameterType.FullName) == 'Google.Protobuf.CodedInputStream' then + merge = method + break + end + end + if merge == nil then error('no parser or CodedInputStream decoder on ' .. typeName) end + local input = CS.Google.Protobuf.CodedInputStream(bytes) + local arguments = CS.System.Array.CreateInstance(typeof(CS.System.Object), 1) + arguments:SetValue(input, 0) + merge:Invoke(message, arguments) + end + log('type=' .. typeName .. ' bytes=' .. tostring(bytes.Length)) + log('decoded=' .. message:ToString()) + if descriptor ~= nil then + local fields = descriptor.Fields:InFieldNumberOrder() + local iterator = fields:GetEnumerator() + while iterator:MoveNext() do + local field = iterator.Current + local nested = field.MessageType + log('field=' .. tostring(field.FieldNumber) .. ' name=' .. tostring(field.Name) + .. ' type=' .. tostring(field.FieldType) + .. ' nested=' .. (nested ~= nil and tostring(nested.FullName) or '')) + end + end + end) + if not ok then log('probe_error=' .. tostring(err)) end + end +end diff --git a/internal/gameserver/dispatch.go b/internal/gameserver/dispatch.go index 340bca9..4a780c3 100644 --- a/internal/gameserver/dispatch.go +++ b/internal/gameserver/dispatch.go @@ -1,6 +1,7 @@ package gameserver import ( + "errors" "strings" "fmt" @@ -42,6 +43,10 @@ var BaseAvatarIDs = common.BaseAvatarIDs type handlerFunc func(s *Session, payload []byte) error +// errResponseAlreadySent tells the typed dispatcher that the handler has +// explicitly sent its response early to preserve packet ordering. +var errResponseAlreadySent = errors.New("response already sent") + var ( typedHandlers = map[uint16]handlerFunc{} dummyResponses = map[uint16]uint16{} // req cmd -> rsp cmd (empty body reply) @@ -59,12 +64,12 @@ func handle[ReqT any, RspT any](name string, fn func(s *Session, req *ReqT, rsp func handleNamed[ReqT any, RspT any](name, rspName string, fn func(s *Session, req *ReqT, rsp *RspT) error) { reqID, ok := goproto.CmdIDByName[name+"CsReq"] if !ok { - Log("[DISPATCH] proto 4.6.51 has no " + name + "CsReq; handler skipped") + Log("[DISPATCH] loaded protocol has no " + name + "CsReq; handler skipped") return } rspID, ok := goproto.CmdIDByName[rspName] if !ok { - Log("[DISPATCH] proto 4.6.51 has no " + rspName + "; handler skipped") + Log("[DISPATCH] loaded protocol has no " + rspName + "; handler skipped") return } typedHandlers[uint16(reqID)] = func(s *Session, payload []byte) error { @@ -78,7 +83,9 @@ func handleNamed[ReqT any, RspT any](name, rspName string, fn func(s *Session, r } } rsp := new(RspT) - if err := fn(s, req, rsp); err != nil { + if err := fn(s, req, rsp); errors.Is(err, errResponseAlreadySent) { + return nil + } else if err != nil { return err } return s.Send(any(rsp).(proto.Message)) @@ -91,12 +98,25 @@ func dummy(name string) { reqID, ok1 := goproto.CmdIDByName[name+"CsReq"] rspID, ok2 := goproto.CmdIDByName[name+"ScRsp"] if !ok1 || !ok2 { - Log("[DISPATCH] proto 4.6.51 has no dummy pair " + name + "; skipped") + Log("[DISPATCH] loaded protocol has no dummy pair " + name + "; skipped") return } dummyResponses[uint16(reqID)] = uint16(rspID) } +// rawNamed binds only the request declared by the current command table. +// Old numeric aliases can collide with responses after a protocol update. +func rawNamed(request string, fn func(*Session, []byte) error) { + id, ok := goproto.CmdIDByName[request] + if !ok { + panic("raw handler request missing from protocol: " + request) + } + if _, exists := rawHandlers[id]; exists { + panic("duplicate raw handler: " + request) + } + rawHandlers[id] = fn +} + // dangerFamilies are client modules that Sync(nil) -> NRE -> black screen when // they receive an empty ScRsp (lesson recorded in the Rust packet.rs / HANDOFF): // never auto-answer these, drop them silently instead. @@ -112,15 +132,8 @@ func isDangerCmd(cmdID uint16) bool { return true } } - // CycleScore cmd ids (4143/4146) and GridFight range carry generic names - switch cmdID { - case 4143, 4146, 8541, 5410, 5449, 7142, 3174, 9252, 8175, 6995, 6988: - return true - } - // GetMapRotationData: an empty reply NREs the client's map init - // (AdventurePhase._EnterStep1_InitializeMapInfo) - drop until real - // RotaterData is implemented - if cmdID == 6827 { + // This module requires populated rotation state before it can be answered. + if name == "GetMapRotationDataCsReq" || strings.HasPrefix(name, "ChimeraGet") { return true } return false diff --git a/internal/gameserver/handlers_activity_data.go b/internal/gameserver/handlers_activity_data.go deleted file mode 100644 index 1972c71..0000000 --- a/internal/gameserver/handlers_activity_data.go +++ /dev/null @@ -1,156 +0,0 @@ -package gameserver - -// Code generated from the March7th PS ActivityConfig (real 4.6.51 client -// activity ids). DO NOT EDIT manually. - -type activityScheduleEntry struct{ activityID, panelID uint32 } - -var realActivitySchedule = []activityScheduleEntry{ - {1001501, 10015}, - {1001701, 10017}, - {1002101, 10191}, - {1002501, 10025}, - {1002601, 10192}, - {1004101, 10041}, - {1006201, 10066}, - {1010801, 10187}, - {2000603, 20016}, - {2001101, 20011}, - {2001102, 20011}, - {2001103, 20011}, - {2001104, 20011}, - {2001105, 20011}, - {2001106, 20011}, - {2001107, 20011}, - {2001108, 20011}, - {2001109, 20011}, - {2002303, 20024}, - {2002502, 20027}, - {2006301, 20064}, - {2006302, 20064}, - {2006400, 20064}, - {2008201, 20091}, - {2008401, 20092}, - {2008601, 20087}, - {2008701, 20087}, - {2009100, 20091}, - {2009200, 20092}, - {2009301, 20093}, - {2101001, 21010}, - {2101101, 21011}, - {3000801, 30008}, - {4000208, 40002}, - {4000301, 40003}, - {4000302, 40003}, - {4000303, 40003}, - {4000304, 40003}, - {4000305, 40003}, - {4000306, 40003}, - {4000307, 40003}, - {5000002, 50000}, - {5000003, 50000}, - {5000004, 50000}, - {5000005, 50000}, - {5000006, 50000}, - {5000007, 50000}, - {5000008, 50000}, - {5000009, 50000}, - {5000010, 50000}, - {5000011, 50000}, - {5000013, 50000}, - {5000105, 50001}, - {5000106, 50001}, - {5000107, 50001}, - {5000201, 50002}, - {5000202, 50002}, - {5000203, 50002}, - {5000204, 50002}, - {5000301, 50003}, - {5000302, 50003}, - {5000303, 50003}, - {5000304, 50003}, - {5000408, 50004}, - {5000501, 50005}, - {5000502, 50005}, - {5000503, 50005}, - {5000504, 50005}, - {5000505, 50005}, - {5000601, 50006}, - {5000602, 50006}, - {5000603, 50006}, - {5000604, 50006}, - {5000605, 50006}, - {5000702, 50007}, - {5000902, 50009}, - {5001202, 50012}, - {5001401, 50014}, - {5001501, 50015}, - {5001802, 50018}, - {5002102, 50021}, - {5002202, 50022}, - {5002402, 50024}, - {5002602, 50026}, - {5002902, 50029}, - {5003402, 50034}, - {5003502, 50035}, - {5004102, 50041}, - {5004202, 50042}, - {5004502, 50045}, - {5006302, 50063}, - {5006802, 50068}, - {5007001, 50070}, - {5007002, 50070}, - {5007003, 50070}, - {5007004, 50070}, - {5007005, 50070}, - {5007007, 50070}, - {5007302, 50073}, - {5007402, 50074}, - {5009202, 50092}, - {5009402, 50094}, - {5010102, 50101}, - {5011102, 50111}, - {5011202, 50112}, - {5011402, 50114}, - {5011602, 50116}, - {5012501, 50125}, - {5012502, 50125}, - {5012503, 50125}, - {5012504, 50125}, - {5012505, 50125}, - {5012601, 50126}, - {5012701, 50127}, - {5012901, 50129}, - {5013201, 50132}, - {5013202, 50132}, - {5013301, 50133}, - {5013401, 50134}, - {5013402, 50134}, - {5013501, 50135}, - {5013502, 50135}, - {5013701, 50137}, - {5014201, 50142}, - {5014202, 50142}, - {5014301, 50143}, - {5014302, 50143}, - {5014303, 50143}, - {5014304, 50143}, - {5014305, 50143}, - {5014311, 50143}, - {5014312, 50143}, - {5014313, 50143}, - {5014314, 50143}, - {5014315, 50143}, - {5015401, 50154}, - {6000101, 60001}, - {6000302, 60003}, - {6000901, 60009}, - {6001402, 60014}, - {6001601, 60016}, - {6002301, 60023}, - {6002401, 60024}, - {7100001, 71000}, - {7100101, 71001}, - {7110001, 71100}, - {7110101, 71101}, -} diff --git a/internal/gameserver/handlers_battle.go b/internal/gameserver/handlers_battle.go index 03b354c..f585d14 100644 --- a/internal/gameserver/handlers_battle.go +++ b/internal/gameserver/handlers_battle.go @@ -13,33 +13,25 @@ import ( func init() { handle("StartCocoonStage", func(s *Session, req *goproto.StartCocoonStageCsReq, rsp *goproto.StartCocoonStageScRsp) error { Log(fmt.Sprintf("[BATTLE] StartCocoonStage: CocoonId=%d Wave=%d WorldLevel=%d PropEntityId=%d", req.CocoonId, req.Wave, req.WorldLevel, req.PropEntityId)) - battleInfo := createBattleInfo(s, 0, 0) - normalizeCalyxBattleInfo(battleInfo, req.Wave, req.WorldLevel) - CURRENT_BATTLE.Store(s.Token, battleInfo) - - challengeCnt := req.Wave - if challengeCnt == 0 { - challengeCnt = 1 - } rsp.Retcode = 0 rsp.PropEntityId = req.PropEntityId rsp.CocoonId = req.CocoonId rsp.Wave = req.Wave - rsp.ChallengeCnt = challengeCnt + battleInfo := createBattleInfo(s, 0, 0) + CURRENT_BATTLE.Store(s.Token, battleInfo) rsp.BattleInfo = battleInfo return nil }) - // NOTE: 4.6.51 proto names this message StartQuickCocoonStageCsReq/Rsp + // The Go schema names this message StartQuickCocoonStageCsReq/Rsp handleNamed("StartQuickCocoonStage", "StartQuickCocoonStageRsp", func(s *Session, req *goproto.StartQuickCocoonStageCsReq, rsp *goproto.StartQuickCocoonStageRsp) error { Log(fmt.Sprintf("[BATTLE] StartQuickCocoonStage: CocoonId=%d Wave=%d WorldLevel=%d", req.CocoonId, req.Wave, req.WorldLevel)) - battleInfo := createBattleInfo(s, 0, 0) - normalizeCalyxBattleInfo(battleInfo, req.Wave, req.WorldLevel) - CURRENT_BATTLE.Store(s.Token, battleInfo) - rsp.Retcode = 0 rsp.CocoonId = req.CocoonId rsp.Wave = req.Wave + battleInfo := createBattleInfo(s, 0, 0) + CURRENT_BATTLE.Store(s.Token, battleInfo) + rsp.BattleInfo = battleInfo return nil }) @@ -47,7 +39,6 @@ func init() { handleNamed("StartQuickFarmElement", "StartQuickFarmElementRsp", func(s *Session, req *goproto.StartQuickFarmElementCsReq, rsp *goproto.StartQuickFarmElementRsp) error { Log(fmt.Sprintf("[BATTLE] StartQuickFarmElement: FarmID=%d WorldLevel=%d", req.PAOFHFLFFHD, req.WorldLevel)) battleInfo := createBattleInfo(s, 0, 0) - normalizeCalyxBattleInfo(battleInfo, 1, req.WorldLevel) CURRENT_BATTLE.Store(s.Token, battleInfo) rsp.Retcode = 0 @@ -115,24 +106,24 @@ func init() { // // อาการ: กดโจมตีใน overworld แล้วกล้องหลุดออกจากตัวละคร // - // สาเหตุ (จากโค้ด Rust เดิม): handler ตอบ battle_info + monster_battle_info + // สาเหตุ (จากโค้ด Rust เดิม): handler ตอบ_info + monster_battle_info // ให้ client เข้าสู่การต่อสู้กับ target ที่ "ผ่าน filter แบบหลวมๆ" // filter เดิม: id > 30000 (monster ในฉาก) || id < 1000 (avatar entity ของผู้เล่น!) // ถ้า targets ว่างและอยู่ใน arena (entry >= 3000000) ยัง "หลอก" target // เป็น 30001 อีกชั้น - // พอ client ได้ battle_info กับ "monster" ที่จริงคือ prop/NPC/ตัวผู้เล่นเอง - // หรือ monster ที่ไม่มี event_id มันจะเริ่ม battle transition ที่โหลดไม่สำเร็จ - // → battle camera ไม่มี target ให้ตาม → กล้องหลุดจากตัวละคร + // พอ client ได้_info กับ "monster" ที่จริงคือ prop/NPC/ตัวผู้เล่นเอง + // หรือ monster ที่ไม่มี event_id มันจะเริ่ม transition ที่โหลดไม่สำเร็จ + // → camera ไม่มี target ให้ตาม → กล้องหลุดจากตัวละคร // // FIX: ยอมรับเฉพาะ entity ที่ server spawn เป็น "monster จริง" ในฉากปัจจุบัน // (เก็บไว้ใน Session.MonsterEntities ตอน load_scene) ถ้าไม่มี monster จริง - // ถูกโจมตี → ตอบ retcode 0 + cast_entity_id เฉยๆ ไม่เข้า battle + // ถูกโจมตี → ตอบ retcode 0 + cast_entity_id เฉยๆ ไม่เข้า // client จะเล่นแอนิเมชันตีปกติ กล้องค้างกับตัวละคร // ================================================================ handle("SceneCastSkill", func(s *Session, req *goproto.SceneCastSkillCsReq, rsp *goproto.SceneCastSkillScRsp) error { rsp.CastEntityId = req.CastEntityId - // เฉพาะ monster entity จริงเท่านั้นที่ trigger battle + // เฉพาะ monster entity จริงเท่านั้นที่ trigger var targets []uint32 seen := map[uint32]bool{} addTarget := func(id uint32) { @@ -149,14 +140,14 @@ func init() { } if len(targets) == 0 { - // ไม่มี monster จริงถูกตี — ไม่เข้า battle (นี่คือ fix หลัก) + // ไม่มี monster จริงถูกตี — ไม่เข้า (นี่คือ fix หลัก) SceneDebugLog("SCENE_CAST_SKILL cast_entity=%d caster=%d skill_index=%d -> no real monster target (battle skipped)", req.CastEntityId, req.CasterId, req.SkillIndex) rsp.Retcode = 0 return nil } - SceneDebugLog("SCENE_CAST_SKILL cast_entity=%d caster=%d -> battle with %d monster(s)", + SceneDebugLog("SCENE_CAST_SKILL cast_entity=%d caster=%d -> with %d monster(s)", req.CastEntityId, req.CasterId, len(targets)) battleInfo := createBattleInfo(s, req.CasterId, req.SkillIndex) @@ -179,39 +170,19 @@ func init() { }) handle("GetCurBattleInfo", func(s *Session, req *goproto.GetCurBattleInfoCsReq, rsp *goproto.GetCurBattleInfoScRsp) error { + // Login queries this even when no battle is active. An absent battle + // is a successful empty result, not RET_BATTLE_STAGE_NOT_MATCH. + rsp.Retcode = 0 if info, ok := CURRENT_BATTLE.Load(s.Token); ok { - rsp.Retcode = 0 rsp.BattleInfo = info return nil } - rsp.Retcode = 2000 // RET_BATTLE_STAGE_NOT_MATCH return nil }) } -func normalizeCalyxBattleInfo(battleInfo *goproto.SceneBattleInfo, reqWave uint32, worldLevel uint32) { - if worldLevel > 0 { - battleInfo.WorldLevel = worldLevel - } - if reqWave > 0 && int(reqWave) <= len(battleInfo.MonsterWaveList) { - battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:reqWave] - } else if len(battleInfo.MonsterWaveList) > 1 { - battleInfo.MonsterWaveList = battleInfo.MonsterWaveList[:1] - } - battleInfo.JAOIOKBBMFC = uint32(len(battleInfo.MonsterWaveList)) - for _, w := range battleInfo.MonsterWaveList { - // NOTE: In client 4.6.53 GameAssembly.dll 0x166b2fdd, client verifies: - // (wave.WaveId & ~8) == 1. If wave_id != 1, it jumps to 0x166b3161 - // and throws NullReferenceException in AdventureModule.CreateBattleServerCheckResult. - // Therefore every wave in Calyx / FarmElement MUST have WaveId = 1 and WaveParam = nil. - w.WaveId = 1 - w.WaveParam = nil - w.BattleStageId = battleInfo.StageId - } -} - -// createBattleInfo is the Go port of battle.rs create_battle_info. +// createBattleInfo is the Go port of.rs create_battle_info. func createBattleInfo(s *Session, casterID, skillIndex uint32) *goproto.SceneBattleInfo { player := s.JSON() if player == nil { @@ -431,9 +402,6 @@ func createBattleInfo(s *Session, casterID, skillIndex uint32) *goproto.SceneBat // monsters battleInfo.MonsterWaveList = common.ToSceneMonsterWaves(player.BattleConfig.Monsters) battleInfo.JAOIOKBBMFC = uint32(len(battleInfo.MonsterWaveList)) - for _, wave := range battleInfo.MonsterWaveList { - wave.BattleStageId = battleInfo.StageId - } // global buffs hasCastorice, hasSilverWolf := false, false diff --git a/internal/gameserver/handlers_challenge.go b/internal/gameserver/handlers_challenge.go index 3d8d5aa..fc4858e 100644 --- a/internal/gameserver/handlers_challenge.go +++ b/internal/gameserver/handlers_challenge.go @@ -274,12 +274,8 @@ func init() { }) handle("GetCurChallengePeak", func(s *Session, req *goproto.GetCurChallengePeakCsReq, rsp *goproto.GetCurChallengePeakScRsp) error { - // reverted to the state of the build that reached Pom-Pom: - // empty peak_id (a fabricated peak_id:1 crashed CreateMemberEntityLeader) + // Match upstream's empty current-state response until a Peak is active. rsp.Retcode = 0 - rsp.HasPassed = true - rsp.PeakId = 1 - rsp.BossBuffId = 0 return nil }) @@ -293,13 +289,9 @@ func init() { EndTime: 1924992000, }) } - // NOTE: the real 4.6.51 activity schedule (realActivitySchedule, from - // the March7th dump) BLACK-SCREENS the events page: the client renders - // real event panels, then requests each activity's data (GetActivity- - // HotData/GetActiveActivityData/...) which we cannot fill yet - NRE. - // The synthetic list below resolves to nothing and keeps the page open - // (empty but usable). Re-enable the real list only after implementing - // per-activity data handlers. + // Advertise only the supported Endgame schedules. Exposing the full + // ActivityConfig before implementing its activity-data handlers makes + // the Client request unavailable state and can stall the events page. // Apocalyptic Shadow tab requires an active schedule with panel 21001 for actID := uint32(21001); actID <= 21030; actID++ { diff --git a/internal/gameserver/handlers_challenge_flow.go b/internal/gameserver/handlers_challenge_flow.go index e0ed01f..6c98778 100644 --- a/internal/gameserver/handlers_challenge_flow.go +++ b/internal/gameserver/handlers_challenge_flow.go @@ -1,6 +1,7 @@ package gameserver import ( + "fmt" "sort" "google.golang.org/protobuf/proto" @@ -9,8 +10,8 @@ import ( goproto "gac-hkrpg-go/protocol/proto" ) -// Full Go port of challenge.rs: StartChallenge / Tierce / Peak flows with the -// hand-built 4.6.51 response layouts (cmd 1787 / 8973). +// Challenge flows use the generated request and response layouts. Command +// numbers are resolved through the current protocol table at registration/send. type decodedChallengeStart struct { challengeID uint32 @@ -106,433 +107,89 @@ func mapChallengeStageID(id uint32) uint32 { return id } -func readLenDelimAndAdvance(buf *[]byte) ([]byte, bool) { - b := *buf - l, n := readUvarint(b) - if n == 0 || int(l) > len(b[n:]) { - *buf = nil - return nil, false - } - sub := b[n : n+int(l)] - *buf = b[n+int(l):] - return sub, true -} - -func readLenDelim(buf []byte) ([]byte, bool) { - l, n := readUvarint(buf) - if n == 0 || int(l) > len(buf[n:]) { - return nil, false - } - return buf[n : n+int(l)], true -} - func decodeStartChallengeReq(payload []byte) decodedChallengeStart { - var req decodedChallengeStart - var pbReq goproto.StartChallengeCsReq - if err := proto.Unmarshal(payload, &pbReq); err == nil && pbReq.ChallengeId != 0 { - req.challengeID = pbReq.ChallengeId - req.firstAvatars = pbReq.FirstLineup - if len(req.firstAvatars) == 0 && len(pbReq.AvatarLineupFirst) > 0 { - for _, av := range pbReq.AvatarLineupFirst { - if av.Id != 0 { - req.firstAvatars = append(req.firstAvatars, av.Id) - } - } - } - req.secondAvatars = pbReq.SecondLineup - if len(req.secondAvatars) == 0 && len(pbReq.AvatarLineupSecond) > 0 { - for _, av := range pbReq.AvatarLineupSecond { - if av.Id != 0 { - req.secondAvatars = append(req.secondAvatars, av.Id) - } - } - } - if pbReq.StageInfo != nil { - if pbReq.StageInfo.BossInfo != nil && pbReq.StageInfo.BossInfo.BuffOne != 0 { - req.buffID = pbReq.StageInfo.BossInfo.BuffOne - } else if pbReq.StageInfo.StoryInfo != nil && pbReq.StageInfo.StoryInfo.BuffOne != 0 { - req.buffID = pbReq.StageInfo.StoryInfo.BuffOne - } - } - return req + if err := proto.Unmarshal(payload, &pbReq); err != nil { + return decodedChallengeStart{} } - - buf := payload - for len(buf) > 0 { - tag, n := readUvarint(buf) - if n == 0 { - break - } - buf = buf[n:] - fieldNumber := uint32(tag >> 3) - wireType := uint32(tag & 0x7) - - switch { - case (fieldNumber == 14 || fieldNumber == 12) && wireType == 0: - v, n2 := readUvarint(buf) - if n2 == 0 { - buf = nil - break + req := decodedChallengeStart{challengeID: pbReq.ChallengeId, + firstAvatars: pbReq.FirstLineup, secondAvatars: pbReq.SecondLineup} + if len(req.firstAvatars) == 0 { + for _, avatar := range pbReq.AvatarLineupFirst { + if avatar.Id != 0 { + req.firstAvatars = append(req.firstAvatars, avatar.Id) } - buf = buf[n2:] - if req.challengeID == 0 { - req.challengeID = uint32(v) - } - case fieldNumber == 6 && wireType == 0: - v, n2 := readUvarint(buf) - if n2 == 0 { - buf = nil - break - } - buf = buf[n2:] - req.firstAvatars = append(req.firstAvatars, uint32(v)) - case fieldNumber == 6 && wireType == 2: - sub, ok := readLenDelimAndAdvance(&buf) - if !ok { - break - } - for len(sub) > 0 { - v, n3 := readUvarint(sub) - if n3 == 0 { - break - } - sub = sub[n3:] - req.firstAvatars = append(req.firstAvatars, uint32(v)) - } - case fieldNumber == 8 && wireType == 0: - v, n2 := readUvarint(buf) - if n2 == 0 { - buf = nil - break - } - buf = buf[n2:] - req.secondAvatars = append(req.secondAvatars, uint32(v)) - case fieldNumber == 8 && wireType == 2: - sub, ok := readLenDelimAndAdvance(&buf) - if !ok { - break - } - for len(sub) > 0 { - v, n3 := readUvarint(sub) - if n3 == 0 { - break - } - sub = sub[n3:] - req.secondAvatars = append(req.secondAvatars, uint32(v)) - } - case (fieldNumber == 4 || fieldNumber == 15) && wireType == 2: - sub, ok := readLenDelimAndAdvance(&buf) - if !ok { - break - } - for len(sub) > 0 { - stag, n3 := readUvarint(sub) - if n3 == 0 { - break - } - sfn := uint32(stag >> 3) - swt := uint32(stag & 0x7) - if sfn == 1 && swt == 0 { - v, n4 := readUvarint(sub[n3:]) - if n4 == 0 { - break - } - sub = sub[n3+n4:] - if fieldNumber == 4 { - req.firstAvatars = append(req.firstAvatars, uint32(v)) - } else { - req.secondAvatars = append(req.secondAvatars, uint32(v)) - } - } else { - skipWireField(swt, &sub) - } - } - case fieldNumber == 10 && wireType == 2: - sub, ok := readLenDelimAndAdvance(&buf) - if !ok { - break - } - for len(sub) > 0 { - stag, n3 := readUvarint(sub) - if n3 == 0 { - break - } - sfn := uint32(stag >> 3) - swt := uint32(stag & 0x7) - sub = sub[n3:] - if swt == 2 && (sfn == 9 || sfn == 10) { - inner, ok2 := readLenDelimAndAdvance(&sub) - if !ok2 { - break - } - for len(inner) > 0 { - t2, n4 := readUvarint(inner) - if n4 == 0 { - break - } - f2 := uint32(t2 >> 3) - w2 := uint32(t2 & 0x7) - inner = inner[n4:] - if w2 == 0 && (f2 == 10 || f2 == 15) { - v, n5 := readUvarint(inner) - if n5 == 0 { - break - } - inner = inner[n5:] - if req.buffID == 0 { - req.buffID = uint32(v) - } - } else { - skipWireField(w2, &inner) - } - } - } else { - skipWireField(swt, &sub) - } - } - default: - skipWireField(wireType, &buf) } } - + if len(req.secondAvatars) == 0 { + for _, avatar := range pbReq.AvatarLineupSecond { + if avatar.Id != 0 { + req.secondAvatars = append(req.secondAvatars, avatar.Id) + } + } + } + if pbReq.StageInfo != nil { + if info := pbReq.StageInfo.BossInfo; info != nil { + req.buffID = info.BuffOne + } + if info := pbReq.StageInfo.StoryInfo; info != nil && req.buffID == 0 { + req.buffID = info.BuffOne + } + } return req } - -type tierceStageEntry struct { - buff uint32 - lineup []uint32 -} - func decodeStartChallengeTierceReq(payload []byte) decodedChallengeStart { - var req decodedChallengeStart - var stagesAvatars []tierceStageEntry - var pbReq goproto.StartChallengeTierceCsReq - if err := proto.Unmarshal(payload, &pbReq); err == nil && pbReq.ChallengeId != 0 { - req.challengeID = pbReq.ChallengeId - req.stageIndex = pbReq.FINOCEGDBDI - req.isSingleStage = pbReq.MPKMIKBFEHG - for _, lt := range pbReq.LineupTierceList { - var l []uint32 - for _, av := range lt.TierceAvatarIdList { - if av.Id != 0 { - l = append(l, av.Id) - } - } - stagesAvatars = append(stagesAvatars, tierceStageEntry{ - buff: lt.BuffId, - lineup: l, - }) - } - } else { - buf := payload - for len(buf) > 0 { - tag, n := readUvarint(buf) - if n == 0 { - break - } - buf = buf[n:] - fieldNumber := uint32(tag >> 3) - wireType := uint32(tag & 0x7) - - switch { - case (fieldNumber == 10 || fieldNumber == 1 || fieldNumber == 5) && wireType == 0: - v, n2 := readUvarint(buf) - if n2 == 0 { - buf = nil - break - } - buf = buf[n2:] - req.stageIndex = uint32(v) - case (fieldNumber == 15 || fieldNumber == 3) && wireType == 0: - v, n2 := readUvarint(buf) - if n2 == 0 { - buf = nil - break - } - buf = buf[n2:] - req.challengeID = uint32(v) - case (fieldNumber == 2 || fieldNumber == 13) && wireType == 0: - v, n2 := readUvarint(buf) - if n2 == 0 { - buf = nil - break - } - buf = buf[n2:] - req.isSingleStage = v != 0 - case (fieldNumber == 9 || fieldNumber == 12 || fieldNumber == 14) && wireType == 2: - sub, ok := readLenDelimAndAdvance(&buf) - if !ok { - break - } - - var stageBuff uint32 - var stageLineup []uint32 - for len(sub) > 0 { - subTag, n2 := readUvarint(sub) - if n2 == 0 { - break - } - subFn := uint32(subTag >> 3) - subWt := uint32(subTag & 0x7) - sub = sub[n2:] - switch { - case (subFn == 10 || subFn == 6 || subFn == 5) && subWt == 0: - v, n3 := readUvarint(sub) - if n3 == 0 { - sub = nil - break - } - sub = sub[n3:] - stageBuff = uint32(v) - case (subFn == 9 || subFn == 4) && subWt == 2: - abuf, ok2 := readLenDelimAndAdvance(&sub) - if !ok2 { - break - } - var aid uint32 - for len(abuf) > 0 { - atag, n4 := readUvarint(abuf) - if n4 == 0 { - break - } - afn := uint32(atag >> 3) - awt := uint32(atag & 0x7) - abuf = abuf[n4:] - if (afn == 1 || afn == 7 || afn == 9) && awt == 0 { - v, n5 := readUvarint(abuf) - if n5 == 0 { - break - } - abuf = abuf[n5:] - aid = uint32(v) - } else { - skipWireField(awt, &abuf) - } - } - if aid != 0 { - stageLineup = append(stageLineup, aid) - } - default: - skipWireField(subWt, &sub) - } - } - stagesAvatars = append(stagesAvatars, tierceStageEntry{buff: stageBuff, lineup: stageLineup}) - default: - skipWireField(wireType, &buf) + if err := proto.Unmarshal(payload, &pbReq); err != nil { + return decodedChallengeStart{} + } + req := decodedChallengeStart{challengeID: pbReq.ChallengeId, + stageIndex: pbReq.FINOCEGDBDI, isSingleStage: pbReq.MPKMIKBFEHG} + for index, stage := range pbReq.LineupTierceList { + var avatars []uint32 + for _, avatar := range stage.TierceAvatarIdList { + if avatar.Id != 0 { + avatars = append(avatars, avatar.Id) } } - } - - for _, st := range stagesAvatars { - req.stageInfoList = append(req.stageInfoList, st.lineup) - } - if len(stagesAvatars) > 0 { - req.firstAvatars = stagesAvatars[0].lineup - if req.stageIndex == 0 && stagesAvatars[0].buff != 0 { - req.buffID = stagesAvatars[0].buff + req.stageInfoList = append(req.stageInfoList, avatars) + if uint32(index) == req.stageIndex { + req.buffID = stage.BuffId } } - if len(stagesAvatars) > 1 { - req.secondAvatars = stagesAvatars[1].lineup - if req.stageIndex == 1 && stagesAvatars[1].buff != 0 { - req.buffID = stagesAvatars[1].buff - } + if len(req.stageInfoList) > 0 { + req.firstAvatars = req.stageInfoList[0] } - if len(stagesAvatars) > 2 { - req.thirdAvatars = stagesAvatars[2].lineup - if req.stageIndex == 2 && stagesAvatars[2].buff != 0 { - req.buffID = stagesAvatars[2].buff - } + if len(req.stageInfoList) > 1 { + req.secondAvatars = req.stageInfoList[1] + } + if len(req.stageInfoList) > 2 { + req.thirdAvatars = req.stageInfoList[2] } - return req } func decodeSetChallengeTierceLineupReq(payload []byte) (uint32, [][]uint32) { - var challengeID uint32 - var stages [][]uint32 - buf := payload - - for len(buf) > 0 { - tag, n := readUvarint(buf) - if n == 0 { - break - } - buf = buf[n:] - fieldNumber := uint32(tag >> 3) - wireType := uint32(tag & 0x7) - - switch { - case fieldNumber == 7 && wireType == 0: - v, n2 := readUvarint(buf) - if n2 == 0 { - buf = nil - break - } - buf = buf[n2:] - challengeID = uint32(v) - case fieldNumber == 12 && wireType == 2: - sub, ok := readLenDelim(buf) - if !ok { - buf = nil - break - } - buf = buf[len(sub):] - var stageLineup []uint32 - for len(sub) > 0 { - subTag, n2 := readUvarint(sub) - if n2 == 0 { - break - } - subFn := uint32(subTag >> 3) - subWt := uint32(subTag & 0x7) - if subFn == 9 && subWt == 2 { - abuf, ok2 := readLenDelim(sub[n2:]) - if !ok2 { - sub = nil - break - } - sub = sub[n2+len(abuf):] - var aid uint32 - for len(abuf) > 0 { - atag, n4 := readUvarint(abuf) - if n4 == 0 { - break - } - afn := uint32(atag >> 3) - awt := uint32(atag & 0x7) - if afn == 9 && awt == 0 { - v, n5 := readUvarint(abuf[n4:]) - if n5 == 0 { - break - } - abuf = abuf[n4+n5:] - aid = uint32(v) - } else { - skipWireField(awt, &abuf) - } - } - if aid != 0 { - stageLineup = append(stageLineup, aid) - } - } else { - skipWireField(subWt, &sub) - } - } - stages = append(stages, stageLineup) - default: - skipWireField(wireType, &buf) - } + var req goproto.SetChallengeTierceLineupCsReq + if err := proto.Unmarshal(payload, &req); err != nil { + return 0, nil } - - return challengeID, stages + var stages [][]uint32 + for _, stage := range req.StageInfoList { + var avatars []uint32 + for _, avatar := range stage.TierceAvatarIdList { + if avatar.Id != 0 { + avatars = append(avatars, avatar.Id) + } + } + stages = append(stages, avatars) + } + return req.ChallengeId, stages } // --------------------------------------------------------------------------- -// battle config setup shared by start handlers +// config setup shared by start handlers // --------------------------------------------------------------------------- func setChallengeBattleConfig(player *common.FreesrData, avatars []uint32, challengeID uint32, eventID uint32, buff uint32) { @@ -618,63 +275,12 @@ func dedupeTruncate4(in []uint32) []uint32 { return out } -// looksLikeChallengeID: known challenge-id ranges across endgame modes. -func looksLikeChallengeID(v uint64) bool { - switch { - case v >= 101 && v <= 119, v == 900, - v >= 1001 && v <= 1035, - v >= 2001 && v <= 2025, - v >= 3001 && v <= 3020, - v >= 5000 && v <= 5700, // MoC floors incl. 4.6 phases - v >= 20000 && v <= 20300, // Pure Fiction - v >= 30000 && v <= 30300: // Apocalyptic Shadow - return true - } - cd := getChallengeData() - _, okC := cd.Challenges[uint32(v)] - _, okT := cd.Tierce[uint32(v)] - return okC || okT -} - -// detectChallengeID: when the strict decoder misses the challenge id (field -// layout drift between client builds), scan top-level varint fields for the -// first value that looks like a challenge id. -func detectChallengeID(payload []byte) (uint32, int) { - buf := payload - for len(buf) > 0 { - tag, n := readUvarint(buf) - if n == 0 { - break - } - buf = buf[n:] - wireType := uint32(tag & 0x7) - if wireType == 0 { - v, n2 := readUvarint(buf) - if n2 == 0 { - break - } - buf = buf[n2:] - if looksLikeChallengeID(v) { - return uint32(v), int(tag >> 3) - } - } else { - skipWireField(wireType, &buf) - } - } - return 0, 0 -} - // handleStartChallenge ports handle_start_challenge: respond cmd 1787 with // retcode(3) + cur_challenge(5) + lineup_list(6) + scene(10) + stage_info(13). func handleStartChallenge(s *Session, payload []byte) error { req := decodeStartChallengeReq(payload) if req.challengeID == 0 { - if id, field := detectChallengeID(payload); id != 0 { - Log("handle_start_challenge: strict decode missed, detected challenge_id=" + itoa(int(id)) + " at field " + itoa(field)) - req.challengeID = id - } else { - PacketLog("[CHALLENGE] challenge_id=0 payload=" + hexDump(payload, 128)) - } + return s.Send(&goproto.StartChallengeScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)}) } Log("handle_start_challenge: challenge_id=" + itoa(int(req.challengeID))) @@ -742,12 +348,7 @@ func handleStartChallenge(s *Session, payload []byte) error { LineupList: lineups, Scene: sceneInfo, } - rspBuf, err := proto.Marshal(rsp) - if err != nil { - return err - } - - return s.SendRaw(&NetPacket{CmdType: 1787, Body: rspBuf}) + return s.Send(rsp) } func scoreIDFor(challengeID uint32) uint32 { @@ -757,9 +358,12 @@ func scoreIDFor(challengeID uint32) uint32 { return 0 } -// handleStartChallengeTierce ports handle_start_challenge_tierce -> cmd 8998 (4.6.51). +// handleStartChallengeTierce uses the current Tierce request schema. func handleStartChallengeTierce(s *Session, payload []byte) error { req := decodeStartChallengeTierceReq(payload) + if req.challengeID == 0 { + return s.Send(&goproto.StartChallengeTierceScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)}) + } baseID := mapChallengeStageID(req.challengeID) Log("handle_start_challenge_tierce: challenge_id=" + itoa(int(req.challengeID)) + " (mapped " + itoa(int(baseID)) + ")") @@ -910,8 +514,8 @@ func handleStartChallengeTierce(s *Session, payload []byte) error { tierceLineupList = append(tierceLineupList, lineupInfoFromAvatars(stageAvatars, sceneInfo.PlaneId, goproto.ExtraLineupType(1), uint32(stIdx))) } - // 4.6.51 StartChallengeTierceScRsp (CmdID 8998) - rsp46 := &goproto.StartChallengeTierceScRsp{ + // Generated response resolves its command ID through Session.Send. + rsp := &goproto.StartChallengeTierceScRsp{ Retcode: 0, Scene: sceneInfo, CurTierceChallenge: &goproto.CurTierceChallenge{ @@ -921,110 +525,61 @@ func handleStartChallengeTierce(s *Session, payload []byte) error { MPKMIKBFEHG: req.isSingleStage, }, } - rspBuf, err := proto.Marshal(rsp46) - if err != nil { - return err - } - return s.SendRaw(&NetPacket{CmdType: 8998, Body: rspBuf}) + return s.Send(rsp) } func handleSetChallengeTierceLineup(s *Session, payload []byte) error { challengeID, stages := decodeSetChallengeTierceLineupReq(payload) + if challengeID == 0 { + return s.Send(&goproto.SetChallengeTierceLineupScRsp{Retcode: uint32(goproto.Retcode_RET_CHALLENGE_NOT_EXIST)}) + } if len(stages) > 0 { tierceLineupsMu.Lock() tierceLineups[challengeID] = stages tierceLineupsMu.Unlock() } - // 4.6.51 SetChallengeTierceLineupScRsp (cmd 8985, retcode tag 1 = 0) - if err := s.SendRaw(&NetPacket{CmdType: 8985, Body: []byte{0x08, 0x00}}); err != nil { - return err - } - _ = s.SendRaw(&NetPacket{CmdType: 8999, Body: []byte{0x08, 0x00}}) - _ = s.SendRaw(&NetPacket{CmdType: 8995, Body: []byte{0x60, 0x00}}) - return nil + return s.Send(&goproto.SetChallengeTierceLineupScRsp{}) } func handleLeaveChallenge(s *Session, _ []byte) error { - var returnEntry uint32 = 102020107 - if player := s.JSON(); player != nil { - player.BattleConfig.CustomBattleLineup = nil - // Always return safely to Belobog Admin District 102020107 - player.Scene.EntryID = 102020107 - player.Scene.PlaneID = 10202 - player.Scene.FloorID = 10202001 - player.Position = common.Position{X: -92, Y: 4278, Z: 68083, RotY: 155566} - player.SavePersistent() - } - // 4.6.51 LeaveChallengeScRsp (cmd 1771, tag 10 retcode 0) - if err := s.SendRaw(&NetPacket{CmdType: 1771, Body: []byte{0x50, 0x00}}); err != nil { - return err - } - _ = s.SendRaw(&NetPacket{CmdType: 1716, Body: []byte{0x20, 0x00}}) - _ = s.SendRaw(&NetPacket{CmdType: 1781, Body: []byte{0x40, 0x00}}) - - // Restore the player back to the open world - _, _ = loadScene(s, returnEntry, true, nil, nil) - _ = refreshLineup(s) - return nil + return leaveChallengeScene(s, &goproto.LeaveChallengeScRsp{}) } func handleLeaveChallengeTierce(s *Session, _ []byte) error { - var returnEntry uint32 = 102020107 - if player := s.JSON(); player != nil { - player.BattleConfig.CustomBattleLineup = nil - // Always return safely to Belobog Admin District 102020107 - player.Scene.EntryID = 102020107 - player.Scene.PlaneID = 10202 - player.Scene.FloorID = 10202001 - player.Position = common.Position{X: -92, Y: 4278, Z: 68083, RotY: 155566} - player.SavePersistent() + return leaveChallengeScene(s, &goproto.LeaveChallengeTierceScRsp{}) +} + +func leaveChallengeScene(s *Session, response proto.Message) error { + player := s.JSON() + if player == nil || player.Scene.EntryID == 0 { + return fmt.Errorf("challenge return scene is unavailable") } - // 4.6.51 LeaveChallengeTierceScRsp (cmd 8983, tag 13 retcode 0) - if err := s.SendRaw(&NetPacket{CmdType: 8983, Body: []byte{0x68, 0x00}}); err != nil { + // loadChallengeScene preserves the player's saved overworld scene. + entryID := player.Scene.EntryID + player.BattleConfig.CustomBattleLineup = nil + CURRENT_BATTLE.Delete(s.Token) + if err := s.Send(&goproto.QuitBattleScNotify{}); err != nil { return err } - _ = s.SendRaw(&NetPacket{CmdType: 8997, Body: []byte{0x38, 0x00}}) - _ = s.SendRaw(&NetPacket{CmdType: 8982, Body: []byte{0x68, 0x00}}) - - // Restore the player back to the open world - _, _ = loadScene(s, returnEntry, true, nil, nil) - _ = refreshLineup(s) - return nil + if _, err := loadScene(s, entryID, true, true, nil, nil); err != nil { + return err + } + if err := refreshLineup(s); err != nil { + return err + } + return s.Send(response) } func handleTakeChallengeReward(s *Session, payload []byte) error { - var groupID uint32 - buf := payload - for len(buf) > 0 { - tag, n := readUvarint(buf) - if n == 0 { - break - } - buf = buf[n:] - fnNum := uint32(tag >> 3) - wt := uint32(tag & 0x7) - if fnNum == 4 && wt == 0 { - v, n2 := readUvarint(buf) - if n2 == 0 { - break - } - buf = buf[n2:] - groupID = uint32(v) - break - } - skipWireField(wt, &buf) + var req goproto.TakeChallengeRewardCsReq + if err := proto.Unmarshal(payload, &req); err != nil { + return err } - - var body []byte - body = append(body, 0x10) // tag 2: group_id - body = appendUvarint(body, uint64(groupID)) - body = append(body, 0x30, 0x00) // tag 6: retcode = 0 - - return s.SendRaw(&NetPacket{CmdType: 1704, Body: body}) + return s.Send(&goproto.TakeChallengeRewardScRsp{GroupId: req.GroupId}) } -// buildGetChallengeTierceDataScRsp — verbatim port of the Rust builder. -func buildGetChallengeTierceDataScRspReal() []byte { +// buildGetChallengeTierceDataScRspReal builds the generated current response. +func buildGetChallengeTierceDataScRspReal() *goproto.GetChallengeTierceDataScRsp { cd := getChallengeData() type tierceEntry struct { @@ -1066,17 +621,8 @@ func buildGetChallengeTierceDataScRspReal() []byte { }) } - rspMsg := &goproto.GetChallengeTierceDataScRsp{ + return &goproto.GetChallengeTierceDataScRsp{ ChallengeTierceGroups: groups, Retcode: 0, } - body, err := proto.Marshal(rspMsg) - if err == nil { - return body - } - - var rsp []byte - rsp = append(rsp, 0x40, 0x00) // tag 8: retcode = 0 - rsp = append(rsp, 0x60, 0x00) // legacy tag 12: retcode = 0 - return rsp } diff --git a/internal/gameserver/handlers_misc.go b/internal/gameserver/handlers_misc.go index 66df486..c4a73ac 100644 --- a/internal/gameserver/handlers_misc.go +++ b/internal/gameserver/handlers_misc.go @@ -161,7 +161,7 @@ func init() { } rsp.RecommendType = &goproto.GetBigDataAllRecommendScRsp_RelicAvatar{RelicAvatar: inner} case goproto.BigDataRecommendType_BIG_DATA_RECOMMEND_TYPE_AVATAR_RELIC: - // 4.6.51 proto: recomended_relic_info_list is repeated GDFKJKDLEPM + // Loaded schema: recomended_relic_info_list is repeated GDFKJKDLEPM // (obfuscated, contents unused by this PS) — reply with empty entries inner := &goproto.BigDataRecommendAvatarRelic{} for range BaseAvatarIDs { diff --git a/internal/gameserver/handlers_player.go b/internal/gameserver/handlers_player.go index 68f7e27..38d7fe6 100644 --- a/internal/gameserver/handlers_player.go +++ b/internal/gameserver/handlers_player.go @@ -11,7 +11,7 @@ import ( func init() { // The three optional PlayerSettingInfo sub-messages are required by the - // 4.6.51 client: leaving them unset NREs inside _CmdGetBasicInfoScRsp. + // Client requires these when processing _CmdGetBasicInfoScRsp. handle("GetBasicInfo", func(s *Session, req *goproto.GetBasicInfoCsReq, rsp *goproto.GetBasicInfoScRsp) error { rsp.Retcode = 0 rsp.Gender = 2 @@ -40,6 +40,10 @@ func init() { var finalData []byte finalData = append(finalData, heartbeatLua...) + if probe := s.takeBattleProbe(); len(probe) > 0 { + finalData = append(finalData, '\n') + finalData = append(finalData, probe...) + } // Load all .lua files from scripts/ directory in deterministic order if entries, err := os.ReadDir("scripts"); err == nil { @@ -62,7 +66,7 @@ func init() { } rsp.DownloadData = &goproto.ClientDownloadData{ - Version: 51, + Version: goproto.ClientDownloadDataLuaVersion, Time: int64(rsp.ServerTimeMs), Data: finalData, } diff --git a/internal/gameserver/handlers_raw.go b/internal/gameserver/handlers_raw.go index 804a9c6..7c7192d 100644 --- a/internal/gameserver/handlers_raw.go +++ b/internal/gameserver/handlers_raw.go @@ -39,94 +39,43 @@ func init() { 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}) - } - } - } + // 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) - 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 + rawNamed("GetSwitchHandDataCsReq", func(s *Session, payload []byte) error { + return s.Send(&goproto.GetSwitchHandDataScRsp{}) + }) - // ---- 1775 | 1711 -> GetChallengeGroupStatisticsScRsp (cmd 1708) ---- - rawHandlers[1775] = challengeGroupStatsReply - rawHandlers[1711] = challengeGroupStatsReply + handle("GetChallengeTierceController", func(s *Session, req *goproto.GetChallengeTierceControllerCsReq, rsp *goproto.GetChallengeTierceControllerScRsp) error { + rsp.Retcode = 0 + return nil + }) + handle("StartChallengePeak", handleStartChallengePeak) + rawNamed("LeaveChallengePeakCsReq", func(s *Session, payload []byte) error { + return leaveChallengeScene(s, &goproto.LeaveChallengePeakScRsp{}) + }) - // ---- 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 req goproto.HIGLJHLNODG + if err := proto.Unmarshal(payload, &req); err != nil { + return err } + parsedVal := req.OJFIGABCKLG var avatarID uint32 if parsedVal >= 100000 { @@ -162,99 +111,23 @@ func handleExRankUnlock(s *Session, payload []byte) error { 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 + // The matching response is declared by the current upstream schema. + return s.Send(&goproto.LPOLGDLAADM{}) } -// challengeGroupStatsReply handles CmdGetChallengeGroupStatisticsCsReq (1775/1711) -// and returns GetChallengeGroupStatisticsScRsp (1708) using standard 4.6.51 proto. +// challengeGroupStatsReply uses the current typed statistics response. 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 { + if err := proto.Unmarshal(payload, &req); err != nil { return err } - return s.SendRaw(&NetPacket{CmdType: 1708, Body: body}) + // Match upstream: echo the group without fabricating historical scores. + return s.Send(&goproto.GetChallengeGroupStatisticsScRsp{GroupId: req.GroupId}) } -// challengeTierceReply ports the 8995/8979/8981 arm: cmd 8996 (4.6.51) plus -// legacy cmd 8994 with the same tierce body. +// challengeTierceReply sends only the response paired with the current request. 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() + return s.Send(buildGetChallengeTierceDataScRspReal()) } func readUvarint(b []byte) (uint64, int) { diff --git a/internal/gameserver/handlers_scene.go b/internal/gameserver/handlers_scene.go index 89f5b3c..40fa992 100644 --- a/internal/gameserver/handlers_scene.go +++ b/internal/gameserver/handlers_scene.go @@ -52,6 +52,57 @@ func firstSceneOfEntry(res *common.JsonConfig, entryID uint32) (string, *common. return "", nil, false } +// sceneOfEntryForTeleport selects the floor config that actually owns the +// requested teleport. Some resource sets put several floors under one entry; +// choosing the lexicographically first config can then send a valid scene +// transition with the wrong floor and spawn position. +func sceneOfEntryForTeleport(res *common.JsonConfig, entryID, floorID, teleportID uint32) (string, *common.LevelOutputConfig, bool) { + configs := res.LevelOutputConfigs[entryID] + names := make([]string, 0, len(configs)) + for name := range configs { + names = append(names, name) + } + sort.Strings(names) + if floorID != 0 { + want := fmt.Sprintf("_F%d", floorID) + for _, name := range names { + cfg := configs[name] + if len(name) > len(want) && name[len(name)-len(want):] == want && sceneHasTeleport(cfg, teleportID) { + return name, cfg, true + } + } + } + if teleportID != 0 { + for _, name := range names { + cfg := configs[name] + if sceneHasTeleport(cfg, teleportID) { + return name, cfg, true + } + } + } + if floorID != 0 { + want := fmt.Sprintf("_F%d", floorID) + for _, name := range names { + if len(name) > len(want) && name[len(name)-len(want):] == want { + return name, configs[name], true + } + } + } + return firstSceneOfEntry(res, entryID) +} + +func sceneHasTeleport(cfg *common.LevelOutputConfig, teleportID uint32) bool { + if cfg == nil || teleportID == 0 { + return false + } + for _, group := range cfg.Scenes { + if _, ok := group.Teleports[teleportID]; ok { + return true + } + } + return false +} + // parsePlaneFloor splits "P_F". func parsePlaneFloor(name string) (uint32, uint32, error) { if len(name) < 2 || name[0] != 'P' { @@ -108,7 +159,7 @@ func init() { defaultScene.FloorId = player.Scene.FloorID } - scene, err := loadScene(s, entryID, false, nil, nil) + scene, err := loadScene(s, entryID, false, false, nil, nil) rsp.Retcode = 0 if err == nil { rsp.Scene = scene @@ -120,7 +171,7 @@ func init() { handle("EnterScene", func(s *Session, req *goproto.EnterSceneCsReq, rsp *goproto.EnterSceneScRsp) error { Log(fmt.Sprintf("on_enter_scene_cs_req: entry_id=%d, teleport_id=%d, interact_id=%d", req.EntryId, req.TeleportId, req.InteractId)) - SceneDebugLog("ENTER_SCENE entry_id=%d teleport_id=%d interact_id=%d", req.EntryId, req.TeleportId, req.InteractId) + SceneDebugLog("ENTER_SCENE entry_id=%d teleport_id=%d interact_id=%d scene_identifier=%v", req.EntryId, req.TeleportId, req.InteractId, req.SceneIdentifier) res, err := common.GameRes() if err != nil { @@ -222,7 +273,7 @@ func init() { teleportID = &v } - sceneInfo, err := loadScene(s, resolvedEntry, true, teleportID, sceneIdent) + sceneInfo, err := loadScene(s, resolvedEntry, true, false, teleportID, sceneIdent) if err != nil { SceneDebugLog("ENTER_SCENE_FAILED requested_entry=%d resolved_entry=%d err=%v", req.EntryId, resolvedEntry, err) Log(fmt.Sprintf("Failed to enter scene %d: %v", resolvedEntry, err)) @@ -230,11 +281,21 @@ func init() { return nil } - SceneDebugLog("ENTER_SCENE_RESOLVED requested_entry=%d resolved_entry=%d", req.EntryId, resolvedEntry) + SceneDebugLog("ENTER_SCENE_RESOLVED requested_entry=%d resolved_entry=%d floor_id=%d plane_id=%d teleport_id=%d", req.EntryId, resolvedEntry, sceneInfo.FloorId, sceneInfo.PlaneId, teleportIDValue(teleportID)) rsp.Retcode = 0 rsp.SceneIdentifier = sceneInfo.SceneIdentifier + rsp.IsOverMap = true rsp.IsCloseMap = req.IsCloseMap - return nil + if err := s.Send(rsp); err != nil { + return err + } + if err := s.Send(&goproto.EnterSceneByServerScNotify{ + Scene: sceneInfo, + Lineup: common.ToLineupInfo(player.Lineups), + }); err != nil { + return err + } + return errResponseAlreadySent }) handle("InteractProp", func(s *Session, req *goproto.InteractPropCsReq, rsp *goproto.InteractPropScRsp) error { @@ -268,14 +329,14 @@ func init() { return nil }) - // decodeSceneIdentifiersLenient: the 4.6.51 client's cmd-1461 payload can + // decodeSceneIdentifiersLenient: some Client scene-map payloads can // fail strict proto parsing (field layout drift between builds) — walk the // wire format manually and pull floor_id/content_id out of field 2. - // GetSceneMapInfo as a raw handler: the 4.6.51 client's cmd-1461 payload + // GetSceneMapInfo as a raw handler: some Client scene-map payloads // can fail strict proto parsing (layout drift), so fall back to a manual // wire walk instead of dropping the request (the map UI stalls without // this reply - no pillars, "navigation loading"). - rawHandlers[1461] = func(s *Session, payload []byte) error { + rawNamed("GetSceneMapInfoCsReq", func(s *Session, payload []byte) error { res, err := common.GameRes() if err != nil { return err @@ -352,7 +413,7 @@ func init() { } return s.Send(rsp) - } + }) handle("SceneEntityMove", func(s *Session, req *goproto.SceneEntityMoveCsReq, rsp *goproto.SceneEntityMoveScRsp) error { player := s.JSON() @@ -696,9 +757,9 @@ func sortedU32Keys2[V any](m map[uint32]V) []uint32 { func sortedU32Keys[V any](m map[uint32]V) []uint32 { return sortedU32Keys2(m) } -// loadScene is the Go port of load_scene. When isEnterScene is true it also -// sends EnterSceneByServerScNotify and persists the new position. -func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32, reqSceneIdent *goproto.SceneIdentifier) (*goproto.SceneInfo, error) { +// loadScene is the Go port of load_scene. When isEnterScene is true it updates +// the player's scene; sendNotify controls whether it also emits the notify. +func loadScene(s *Session, entryID uint32, isEnterScene, sendNotify bool, teleportID *uint32, reqSceneIdent *goproto.SceneIdentifier) (*goproto.SceneInfo, error) { player := s.JSON() if player == nil { return nil, fmt.Errorf("data is not set!") @@ -708,7 +769,15 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32 return nil, err } - name, scene, ok := firstSceneOfEntry(res, entryID) + floorHint := uint32(0) + if reqSceneIdent != nil { + floorHint = reqSceneIdent.FloorId + } + requestedTeleport := uint32(0) + if teleportID != nil { + requestedTeleport = *teleportID + } + name, scene, ok := sceneOfEntryForTeleport(res, entryID, floorHint, requestedTeleport) if !ok { Log(fmt.Sprintf("Map Entrance Not Found %d", entryID)) return nil, fmt.Errorf("Map Entrance Not Found %d", entryID) @@ -750,6 +819,7 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32 } } } + SceneDebugLog("LOAD_SCENE_TELEPORT entry_id=%d config=%s floor_id=%d teleport_id=%d matched=%t pos=(%d,%d,%d)", entryID, name, floorID, *teleportID, found, jsonPos.X, jsonPos.Y, jsonPos.Z) } else if isEnterScene || jsonPos.Y < -5000 { // first teleport in the scene, else the teleport DB, else spawn near the first prop var firstTP *common.TeleportInfo @@ -861,7 +931,7 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32 for _, groupID := range sortedU32Keys2(scene.Scenes) { group := scene.Scenes[groupID] - groupInfo := &goproto.SceneEntityGroupInfo{GroupId: groupID} + groupInfo := &goproto.SceneEntityGroupInfo{State: 1, GroupId: groupID} // props for i := range group.Props { @@ -924,6 +994,7 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32 }) } + sceneInfo.EntityList = append(sceneInfo.EntityList, groupInfo.EntityList...) if len(group.Props) == 0 && len(group.Npcs) == 0 && len(group.Monsters) == 0 { continue } @@ -959,17 +1030,26 @@ func loadScene(s *Session, entryID uint32, isEnterScene bool, teleportID *uint32 player.Scene.PlaneID = planeID player.Position = jsonPos - if err := s.Send(&goproto.EnterSceneByServerScNotify{ - Scene: sceneInfo, - Lineup: common.ToLineupInfo(player.Lineups), - }); err != nil { - return nil, err + if sendNotify { + if err := s.Send(&goproto.EnterSceneByServerScNotify{ + Scene: sceneInfo, + Lineup: common.ToLineupInfo(player.Lineups), + }); err != nil { + return nil, err + } } } return sceneInfo, nil } +func teleportIDValue(id *uint32) uint32 { + if id == nil { + return 0 + } + return *id +} + func hasAvatar(player *common.FreesrData, id uint32) bool { _, ok := player.Avatars[id] return ok diff --git a/internal/gameserver/handlers_tutorial.go b/internal/gameserver/handlers_tutorial.go index 0c2a618..bc4db3e 100644 --- a/internal/gameserver/handlers_tutorial.go +++ b/internal/gameserver/handlers_tutorial.go @@ -8,6 +8,10 @@ import ( // tutorial modal (which locks camera + input). func init() { handle("GetTutorial", func(s *Session, req *goproto.GetTutorialCsReq, rsp *goproto.GetTutorialScRsp) error { + tutorialIDs, _, err := getTutorialResourceIDs() + if err != nil { + return err + } rsp.Retcode = 0 for _, id := range tutorialIDs { rsp.TutorialList = append(rsp.TutorialList, &goproto.Tutorial{ @@ -19,6 +23,10 @@ func init() { }) handle("GetTutorialGuide", func(s *Session, req *goproto.GetTutorialGuideCsReq, rsp *goproto.GetTutorialGuideScRsp) error { + _, tutorialGuideIDs, err := getTutorialResourceIDs() + if err != nil { + return err + } rsp.Retcode = 0 for _, id := range tutorialGuideIDs { rsp.TutorialGuideList = append(rsp.TutorialGuideList, &goproto.TutorialGuide{ diff --git a/internal/gameserver/handlers_tutorial_ids.go b/internal/gameserver/handlers_tutorial_ids.go index 8cc9f70..4e671c9 100644 --- a/internal/gameserver/handlers_tutorial_ids.go +++ b/internal/gameserver/handlers_tutorial_ids.go @@ -1,115 +1,75 @@ package gameserver -// Generated from the Rust tutorial.rs lists (client 4.6.51). +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "sync" +) -var tutorialIDs = []uint32{ - 1001, 1002, 1003, 1004, 1005, 1007, 1008, 1010, 2000, 2001, 2002, 2003, 2004, 2008, 2010, 2011, 2012, 2013, 3001, 3002, - 3003, 3004, 3005, 3006, 3008, 3009, 3010, 3011, 3012, 3013, 3014, 3202, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, - 4010, 4011, 4013, 4014, 4015, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, - 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049, 4050, 4051, - 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061, 4063, 4066, 4067, 4068, 4070, 4071, 4072, 4073, 4074, 4075, - 4076, 4077, 4078, 4079, 4081, 4083, 4084, 4085, 4097, 4098, 4112, 5001, 5002, 5003, 5004, 5005, 5006, 5007, 5008, 5009, - 5010, 5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5021, 5022, 5023, 5024, 5025, 5026, 5027, 5028, 5029, 5030, - 5031, 5032, 5033, 5034, 5035, 5036, 5038, 5039, 5041, 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, - 5055, 5056, 5057, 5058, 5059, 5060, 5061, 5062, 5063, 5064, 5065, 5070, 5071, 5072, 5073, 5074, 5075, 5077, 5078, 5079, - 5081, 5082, 5083, 5084, 5085, 5086, 5087, 5088, 5089, 5090, 5091, 5092, 5093, 5094, 5095, 5096, 5097, 5098, 5099, 5100, - 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108, 5109, 5110, 5111, 5112, 5113, 5114, 5115, 5116, 5130, 5131, 5132, 5133, - 5134, 5135, 5140, 5141, 5142, 5143, 5144, 5145, 5146, 5150, 5151, 5152, 5153, 5154, 5155, 5156, 5157, 5158, 5159, 5160, - 5161, 5162, 5163, 5164, 5172, 5173, 5174, 5175, 5176, 5177, 5178, 5179, 5180, 5181, 5182, 5183, 5184, 5185, 5186, 5187, - 5188, 5189, 5190, 5191, 5192, 5193, 5194, 5195, 5196, 5197, 5198, 5199, 5200, 5201, 5202, 5210, 5211, 5212, 5213, 5214, - 5215, 5216, 5217, 5218, 5219, 5220, 5221, 5222, 5301, 5302, 5303, 5304, 5305, 5306, 5307, 5308, 5309, 5310, 5311, 5312, - 5313, 5314, 5316, 5317, 5318, 5319, 5320, 5321, 5322, 5323, 5324, 5325, 5326, 5350, 5351, 5352, 5355, 5356, 5357, 5358, - 5359, 5360, 5361, 5365, 5366, 5367, 5368, 5369, 5370, 5371, 5372, 5373, 5374, 5375, 5376, 5377, 5378, 5380, 5381, 5382, - 5383, 5384, 5385, 5386, 5387, 5388, 5389, 5390, 5391, 5392, 5393, 5394, 5395, 5397, 5398, 5399, 5400, 5401, 5402, 5403, - 5404, 5405, 5406, 5407, 5408, 5409, 5410, 5450, 5451, 5452, 5453, 5454, 5455, 5456, 5457, 5458, 5459, 5460, 5461, 5462, - 5463, 5464, 5465, 5510, 5511, 5512, 5513, 5514, 5515, 5517, 5518, 5520, 5521, 5522, 5523, 5524, 5525, 5530, 5531, 5532, - 5533, 5534, 5535, 5536, 5537, 5540, 5550, 5551, 5552, 5553, 5554, 5555, 5556, 5560, 5561, 5562, 5563, 5564, 5565, 5566, - 5567, 5568, 5569, 5600, 5601, 5602, 5603, 5604, 5605, 5606, 5607, 5608, 5609, 5610, 5611, 5612, 5613, 5614, 5615, 5700, - 5701, 5702, 5703, 5704, 5705, 5706, 5707, 5708, 5709, 5710, 5711, 5712, 5713, 5714, 5715, 5716, 5717, 5718, 5750, 5751, - 5752, 5753, 5754, 5755, 5756, 5757, 5758, 5759, 5760, 5761, 5762, 5763, 5764, 5765, 5766, 5767, 5768, 5769, 5770, 5771, - 5772, 5773, 5774, 5775, 5801, 5802, 5803, 5804, 5805, 5806, 5810, 5811, 5812, 5813, 5814, 5815, 5816, 5817, 5901, 5902, - 5903, 5904, 5905, 5906, 5907, 5908, 5909, 5910, 5911, 5912, 5913, 5914, 5915, 5916, 5917, 5918, 5921, 5922, 5923, 5924, - 5925, 5926, 5927, 5928, 5929, 5930, 5931, 5933, 5934, 5936, 5937, 5938, 5939, 5940, 5950, 5961, 5962, 5963, 5964, 5965, - 5966, 5967, 5968, 5969, 5970, 5971, 5972, 5973, 5974, 5975, 5976, 5977, 5978, 5979, 5980, 6000, 6001, 6002, 6003, 6004, - 6005, 6006, 6007, 6008, 6009, 6010, 6011, 6012, 6013, 6014, 6015, 6016, 6017, 6018, 6019, 6020, 6021, 6022, 6023, 6024, - 6025, 6026, 6027, 6028, 6029, 6030, 6031, 6050, 6051, 6052, 6053, 6054, 6055, 6100, 6102, 6103, 6104, 6105, 6106, 6107, - 6108, 6109, 6110, 6111, 6112, 6113, 6114, 6115, 6116, 6117, 6118, 6119, 6120, 6121, 6122, 6123, 6124, 6125, 6126, 6127, - 6128, 6129, 6130, 6131, 6132, 6133, 6134, 6135, 6136, 6137, 6138, 6139, 6141, 6142, 6193, 6194, 6195, 6196, 6197, 6198, - 6199, 6200, 6300, 6301, 6302, 6303, 6304, 6305, 6306, 6307, 6308, 6309, 6310, 6311, 6312, 6313, 6314, 6315, 6316, 6317, - 6318, 6319, 6320, 6321, 6322, 6323, 6324, 6325, 6326, 6327, 6328, 6329, 6330, 6331, 6332, 6333, 6334, 6335, 6336, 6337, - 6338, 6339, 6340, 6341, 6342, 6343, 6400, 6401, 6402, 6403, 6404, 6405, 6406, 6408, 6409, 6410, 6411, 6412, 6450, 6451, - 6452, 6453, 6454, 6455, 6456, 6500, 6501, 6503, 6505, 6506, 6507, 6508, 6509, 6510, 6511, 6512, 6513, 6514, 6515, 6516, - 6517, 6518, 6519, 6601, 6602, 6603, 6604, 6605, 6606, 6607, 6608, 6609, 6610, 6611, 6612, 6613, 6614, 6700, 6701, 6702, - 6703, 6704, 6705, 6706, 6707, 6708, 6709, 6710, 6711, 6712, 6713, 6714, 6715, 6716, 6717, 6718, 6719, 6750, 6751, 6752, - 6753, 6754, 6755, 6756, 6757, 6758, 6759, 7001, 7002, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7011, 7012, 7013, - 7014, 7015, 7016, 7017, 7023, 7024, 7025, 7026, 7027, 7028, 7029, 7030, 7031, 7032, 7033, 7034, 7035, 8001, 8002, 8003, - 8005, 8006, 8007, 8009, 8012, 8013, 8014, 8015, 8016, 8017, 8018, 8019, 8020, 8021, 8101, 8102, 8103, 8104, 8105, 8106, - 8107, 8108, 8109, 8110, 8111, 8112, 8113, 8114, 8115, 8116, 8117, 8118, 8119, 8120, 8121, 8122, 8123, 8124, 8125, 8126, - 8127, 8128, 8129, 8130, 8201, 8202, 8203, 8204, 8205, 8206, 8301, 8302, 8303, 8304, 8305, 8306, 8307, 8308, 8309, 8310, - 8311, 8312, 8313, 8314, 8315, 8316, 8317, 8318, 8319, 8320, 8321, 8322, 8323, 8324, 8325, 8326, 8327, 8328, 8401, 8404, - 8405, 8406, 8501, 8502, 8503, 8504, 8505, 8506, 8508, 8509, 8601, 8602, 8603, 8604, 8605, 8606, 8607, 8608, 8609, 8611, - 8612, 8613, 8614, 8621, 8625, 8626, 8630, 8631, 8632, 8633, 8634, 8635, 8636, 8637, 8638, 8639, 8640, 8641, 8700, 8701, - 8702, 8703, 8704, 8705, 8706, 8707, 8708, 8709, 8710, 8711, 8712, 8713, 8714, 8715, 8716, 8717, 8718, 8719, 8720, 8721, - 8722, 8723, 8724, 8725, 8802, 8803, 8806, 8807, 8808, 8809, 8811, 8812, 8813, 8905, 8906, 8907, 8908, 8909, 8910, 8911, - 8931, 8932, 9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009, 9010, 9011, 9012, 9013, 9014, 9015, 9016, 9017, 9018, - 9019, 9020, 9021, 9022, 9023, 9024, 9025, 9026, 9027, 9028, 9029, 9030, 9031, 9032, 9033, 9034, 9035, 9036, 9037, 9038, - 9039, 9040, 9041, 9042, 9043, 9044, 9045, 9046, 9047, 9048, 9049, 9050, 9051, 9052, 9053, 9054, 9055, 9056, 9057, 9058, - 9059, 9060, 9061, 9062, 9063, 9065, 9066, 9067, 9068, 9070, 9071, 9072, 9073, 9074, 9075, 9076, 9077, 9078, 9079, 9080, - 9081, 9082, 9083, 9084, 9085, 9086, 9087, 9088, 9090, 9091, 9092, 9093, 9094, 9095, 9096, 9097, 9098, 9099, 9100, 9110, - 9111, 9112, 9113, 9114, 9115, 9116, 9117, 9118, 9119, 9120, 9121, 9122, 9123, 9124, 9125, 9126, 9127, 9128, 9129, 9130, - 9131, 9132, 9133, 9134, 9135, 9136, 9137, 9201, 9202, 9203, 9204, 9205, 9206, 9207, 9208, 9209, 9210, 9211, 9212, 9213, - 9214, 9301, 9302, 9303, 9304, 9305, 9306, 9307, 9308, 9309, 9310, 9311, 9313, 9314, 9315, 9316, 9317, 9318, 9319, 9321, - 9322, 9323, 9324, 9325, 9401, 9402, 9403, 9404, 9405, 9501, 9502, 9503, 9504, 9505, 9506, 9507, 9508, 9509, 9510, 9511, - 9512, 9513, 9514, 9515, 9598, 9599, 9610, 9611, 9612, 9613, 9614, 9615, 9616, 9617, 9618, 9619, 9620, 9621, 9622, 9623, - 9624, 9625, 9626, 9627, 9628, 9629, 9630, 9631, 9650, 9651, 9701, 9702, 9703, 9704, 9801, 9802, 9803, 9804, 9805, 9806, - 9807, 9808, 9809, 9810, 9811, 9812, 9813, 9814, 9815, 9816, 9817, 9818, 9819, 9820, 9901, 9903, 9904, 9905, 9906, 9908, - 10001, 10002, 10003, 10004, 10005, 10006, 10007, 10008, 10009, 10031, 10032, 10033, 10034, 10036, 10037, 10038, 10039, 10040, 10041, 10042, - 10043, 10045, 43000, 43001, 43002, 43003, 43004, 43005, 43006, 43007, 43008, 43009, 43010, 43011, 44001, 44002, 44003, 44004, 44005, 44006, - 44007, 44008, 44009, 44010, 44011, 44012, 44013, 44014, 45001, +// Tutorial IDs come from the installed Client resource tables, not a list +// copied from an older server build. Restart after replacing these resources. +var tutorialResourceCache struct { + sync.Once + tutorials []uint32 + guides []uint32 + err error } -var tutorialGuideIDs = []uint32{ - 1101, 1102, 1103, 1104, 1105, 1108, 1109, 1110, 1116, 1117, 1118, 1119, 2006, 2007, 2008, 2105, 2106, 2107, 2200, 2201, - 2202, 2203, 2204, 2205, 2206, 2207, 2208, 2209, 2210, 2211, 2212, 2213, 2214, 2215, 2216, 2217, 2218, 2219, 2220, 2221, - 2222, 2223, 2224, 2225, 2226, 2227, 2228, 2229, 2230, 2231, 2232, 2233, 2234, 2235, 2236, 2237, 2238, 2242, 2243, 2244, - 2245, 2246, 2247, 2249, 2251, 2252, 2253, 2254, 2260, 3007, 3105, 3106, 3107, 3108, 3109, 3201, 3202, 3203, 3204, 3205, - 3206, 3207, 3208, 3209, 3210, 3211, 3212, 3213, 3214, 3215, 3216, 3217, 3218, 3219, 3221, 3222, 3223, 3225, 3227, 3228, - 3229, 3230, 3231, 3232, 4001, 4101, 4102, 4103, 4104, 4105, 4106, 4107, 4109, 4110, 5101, 5102, 5103, 5104, 5105, 5106, - 6001, 6002, 6003, 6004, 6008, 6009, 6010, 6011, 6012, 6014, 6015, 6018, 6020, 6021, 6023, 6024, 6025, 6026, 6027, 6028, - 6029, 6030, 6031, 6032, 6033, 6034, 6035, 6036, 6037, 6038, 6039, 6040, 6041, 6042, 6043, 6044, 6045, 6046, 6047, 6048, - 6049, 6050, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6060, 6061, 6062, 6063, 6064, 6065, 6066, 6067, 6068, - 6069, 6070, 6071, 6072, 6073, 6074, 6075, 6076, 6077, 6078, 6079, 6080, 6081, 6082, 6083, 6085, 6086, 6087, 6088, 6089, - 6090, 6091, 6092, 6093, 6094, 6095, 6096, 6097, 6098, 6099, 6100, 6101, 6102, 6103, 6104, 6105, 6106, 6107, 6108, 6109, - 6110, 6111, 6112, 6113, 6114, 6116, 6117, 6118, 6119, 6120, 6122, 6124, 6125, 6126, 6127, 6128, 6129, 6130, 6301, 6302, - 6303, 6304, 6305, 6306, 6307, 6308, 6309, 6310, 6311, 6312, 6314, 6315, 6316, 6317, 6318, 6319, 6320, 6321, 6322, 6323, - 6324, 6325, 6326, 6327, 6328, 6329, 6330, 6331, 6332, 6333, 6334, 6335, 6336, 6337, 6338, 6339, 6340, 6341, 6342, 6343, - 6345, 6346, 6347, 6348, 6349, 6350, 6351, 6352, 6354, 6355, 6356, 6357, 6358, 6359, 6360, 6361, 6362, 6363, 6364, 6365, - 6366, 6367, 6368, 6369, 6371, 6372, 6374, 6375, 6376, 6377, 6378, 6379, 6380, 7090, 7091, 7092, 7100, 7101, 7501, 7502, - 7503, 7504, 7506, 7507, 7508, 7509, 7511, 7514, 7515, 8001, 8002, 8003, 8004, 8006, 8007, 8008, 8010, 8011, 8012, 8013, - 8014, 8015, 8016, 8017, 8018, 8019, 8020, 8021, 8022, 8023, 8024, 8025, 8026, 8027, 8028, 8038, 8039, 8047, 8050, 8051, - 8052, 8055, 8056, 8057, 8058, 8059, 8061, 8062, 8063, 8064, 8065, 8066, 8067, 8069, 8070, 8072, 8073, 8074, 8075, 8076, - 8078, 8079, 8080, 8090, 8091, 8092, 8093, 8094, 8095, 8096, 8100, 8101, 8102, 8103, 8104, 8105, 8106, 8107, 8108, 8109, - 8110, 8111, 8112, 8113, 8122, 8123, 8124, 8130, 8131, 8132, 8133, 8134, 8135, 8136, 8137, 8138, 8139, 8140, 8141, 8142, - 8143, 8144, 8145, 8146, 8147, 8148, 8149, 8150, 8151, 8152, 8153, 8154, 8155, 8156, 8157, 8158, 8160, 8161, 8162, 8163, - 8164, 8165, 8166, 8167, 8168, 8170, 8171, 8172, 8173, 8174, 8175, 8176, 8180, 8181, 8182, 8183, 8184, 8185, 8186, 8187, - 8188, 8189, 8190, 8191, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8203, 8204, 8205, 8206, 8207, - 8208, 8209, 8211, 8212, 8213, 8301, 8302, 8303, 8304, 8305, 8306, 8310, 8340, 8341, 8342, 8343, 8344, 8345, 8346, 8701, - 8702, 8703, 8704, 8802, 8803, 8804, 8805, 9101, 9102, 9103, 9104, 9105, 9107, 9108, 9109, 9110, 9111, 9112, 9113, 9114, - 9115, 9116, 9117, 9118, 9119, 9120, 9201, 9202, 9203, 9204, 9205, 9206, 9207, 9208, 9209, 9210, 9211, 9212, 9301, 9303, - 9304, 9305, 9306, 9308, 9309, 9310, 9312, 9313, 9314, 9315, 9316, 9317, 9318, 9319, 9320, 9321, 9323, 9324, 9325, 9326, - 9327, 9330, 9331, 9332, 9335, 9336, 9337, 9338, 9339, 9340, 9341, 9342, 9343, 9344, 9345, 9601, 9602, 9603, 9604, 9605, - 9701, 9702, 9801, 9802, 9803, 9804, 9811, 9812, 9813, 9814, 9815, 9816, 9817, 9818, 9819, 9820, 9821, 9822, 9823, 9824, - 9825, 9826, 9827, 9829, 9830, 9831, 9832, 9833, 9834, 9835, 9836, 9837, 9838, 9839, 9840, 9841, 9842, 9843, 9844, 9845, - 9846, 9847, 9848, 9849, 9850, 9851, 9861, 9862, 9863, 9864, 9871, 9872, 9901, 9902, 9903, 9904, 9905, 9911, 9913, 9915, - 9920, 9921, 9922, 9923, 9925, 9926, 9931, 9932, 9933, 9935, 9936, 9937, 9938, 9941, 9942, 9943, 9944, 9945, 9946, 9947, - 9951, 9952, 9953, 9954, 9955, 9956, 9957, 9958, 9959, 9960, 9961, 9962, 9963, 9964, 9965, 9967, 9968, 9969, 9970, 9971, - 9973, 9974, 9975, 9976, 9978, 9979, 9980, 9981, 9982, 9983, 9984, 9985, 9986, 9987, 9988, 9989, 9990, 9991, 9992, 9993, - 9994, 9995, 10000, 10001, 10002, 10003, 10005, 10010, 10011, 10012, 10013, 10014, 10015, 10016, 10017, 10020, 10021, 10022, 10030, 10031, - 10032, 10033, 10034, 10040, 10041, 10042, 10043, 10044, 10045, 10050, 10051, 10052, 10053, 10054, 10055, 10060, 10061, 10062, 10070, 10071, - 10072, 10073, 10080, 10081, 10082, 10083, 10084, 10085, 10086, 10087, 10088, 10090, 10091, 10092, 10093, 10096, 10100, 10101, 10102, 10103, - 10111, 10112, 10113, 10201, 10202, 10203, 10204, 10310, 10311, 10312, 10313, 10314, 10315, 10401, 10502, 16505, 16506, 16507, 16508, 16509, - 31001, 31102, 31103, 31105, 31106, 31109, 31204, 31206, 45001, 100001, 100002, 100003, 100004, 100005, 100006, 100007, 100008, 100009, 100010, 100011, - 100012, 100013, 100014, 100015, 100016, 100017, 100021, 100022, 100023, 100024, 100025, 100026, 100027, 100028, 100029, 100030, 100031, 100032, 100033, 100034, - 100035, 100036, 100037, 100038, 100039, 100040, 100041, 100042, 100043, 100044, 100045, 100046, 100047, 100048, 100049, 100050, 100051, 100052, 100053, 100054, - 100055, 100056, 100057, 100058, 100059, 100060, 100061, 100062, 100063, 100064, 100065, 100066, 100067, 100068, 100069, +func getTutorialResourceIDs() ([]uint32, []uint32, error) { + tutorialResourceCache.Do(func() { + var tutorialFile struct { + Entries []struct { + ID uint32 `json:"TutorialID"` + } `json:"tutorial_config"` + } + var guideFile struct { + Entries []struct { + ID uint32 `json:"GroupID"` + } `json:"tutorial_guide_config"` + } + load := func(name string, dst any) error { + path := filepath.Join("resources", name) + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("tutorial resource %s: %w", path, err) + } + if err := json.Unmarshal(data, dst); err != nil { + return fmt.Errorf("tutorial resource %s: %w", path, err) + } + return nil + } + if err := load("TutorialData.json", &tutorialFile); err != nil { + tutorialResourceCache.err = err + return + } + if err := load("TutorialGuideGroup.json", &guideFile); err != nil { + tutorialResourceCache.err = err + return + } + collect := func(ids []uint32) []uint32 { + sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) + out := ids[:0] + for _, id := range ids { + if id != 0 && (len(out) == 0 || id != out[len(out)-1]) { + out = append(out, id) + } + } + return out + } + for _, entry := range tutorialFile.Entries { + tutorialResourceCache.tutorials = append(tutorialResourceCache.tutorials, entry.ID) + } + for _, entry := range guideFile.Entries { + tutorialResourceCache.guides = append(tutorialResourceCache.guides, entry.ID) + } + tutorialResourceCache.tutorials = collect(tutorialResourceCache.tutorials) + tutorialResourceCache.guides = collect(tutorialResourceCache.guides) + if len(tutorialResourceCache.tutorials) == 0 || len(tutorialResourceCache.guides) == 0 { + tutorialResourceCache.err = fmt.Errorf("tutorial resource tables contain no IDs") + } + }) + return tutorialResourceCache.tutorials, tutorialResourceCache.guides, tutorialResourceCache.err } diff --git a/internal/gameserver/handlers_ui.go b/internal/gameserver/handlers_ui.go index 7960213..50034a9 100644 --- a/internal/gameserver/handlers_ui.go +++ b/internal/gameserver/handlers_ui.go @@ -4,9 +4,7 @@ import ( goproto "gac-hkrpg-go/protocol/proto" ) -// Handlers for cmds the real 4.6.51 client sends during normal play that were -// dropped in the first test session (2026-10-07 01:00) — several of them gate -// UI state (pause menu, Endgame entry, map rotation) on the server's reply. +// UI requests bound to the command names in the loaded protocol. func init() { // client pauses itself when opening menus/map — must be acked @@ -16,12 +14,11 @@ func init() { return nil }) - // the Endgame entrance waits for this before opening its UI - // (4.6.51: CsReq 4190 -> ScRsp 4112, retcode = tag 14 — the Rust build - // answered the wrong id 4130 and the client stalled) - rawHandlers[4190] = func(s *Session, payload []byte) error { - return s.SendRaw(&NetPacket{CmdType: 4112, Body: []byte{0x70, 0x00}}) - } + // The Endgame entrance waits for this response before opening its UI. + handle("GetUnreleasedBlockInfo", func(s *Session, req *goproto.GetUnreleasedBlockInfoCsReq, rsp *goproto.GetUnreleasedBlockInfoScRsp) error { + rsp.Retcode = 0 + return nil + }) handle("TakeQuestReward", func(s *Session, req *goproto.TakeQuestRewardCsReq, rsp *goproto.TakeQuestRewardScRsp) error { rsp.Retcode = 0 diff --git a/internal/gameserver/session.go b/internal/gameserver/session.go index 99059b8..1b6e08c 100644 --- a/internal/gameserver/session.go +++ b/internal/gameserver/session.go @@ -37,6 +37,8 @@ type Session struct { startTime time.Time nextSceneSave int64 + battleProbeMu sync.Mutex + battleProbe []byte PropEntities map[uint32]PropEntry // MonsterEntities tracks real overworld monster entities (entity_id -> event_id) // spawned in the current scene. Used by SceneCastSkill to decide whether an @@ -127,7 +129,7 @@ func (s *Session) Consume(data []byte) error { s.kcpMu.Unlock() for _, pkt := range packets { - if pkt.CmdType == 31 { // CmdPlayerLogoutCsReq + if pkt.CmdType == goproto.CmdIDByName["PlayerLogoutCsReq"] { Log("[PACKET] Player logged out") s.Close() return nil @@ -156,7 +158,11 @@ func (s *Session) Send(msg proto.Message) error { PacketLog(fmt.Sprintf("[PACKET SEND] cmd_id: %d %s (len: %d)", cmdID, name, len(body))) Log(fmt.Sprintf("sent packet with CmdID: %d (%s)", cmdID, name)) - return s.SendRaw(&NetPacket{CmdType: cmdID, Body: body}) + if err := s.SendRaw(&NetPacket{CmdType: cmdID, Body: body}); err != nil { + return err + } + s.captureBattleResponse(msg, cmdID, body) + return nil } // SendRaw writes an already-framed packet into the KCP stream. diff --git a/internal/sdkserver/api.go b/internal/sdkserver/api.go index 3221185..10082ed 100644 --- a/internal/sdkserver/api.go +++ b/internal/sdkserver/api.go @@ -50,20 +50,25 @@ func apiRoot(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) return } + cfg, err := common.LoadRuntimeConfig() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } writeJSON(w, map[string]any{ "name": "GaC-hkrpg-go API Gateway", - "version": "4.6.53", + "version": cfg.ClientVersion, "status": 200, "description": "Backend connection endpoints for freesr-data, persistent, and GaC-Srtool", "endpoints": map[string]string{ - "status": "GET /api/status", - "freesr": "GET, POST /api/freesr", - "avatars": "GET, POST /api/freesr/avatars", - "persistent": "GET, POST /api/persistent", - "exrank_toggle": "POST /api/exrank/toggle", - "avatar_rank": "POST /api/avatar/rank", + "status": "GET /api/status", + "freesr": "GET, POST /api/freesr", + "avatars": "GET, POST /api/freesr/avatars", + "persistent": "GET, POST /api/persistent", + "exrank_toggle": "POST /api/exrank/toggle", + "avatar_rank": "POST /api/avatar/rank", "avatar_max_all": "POST /api/avatar/max_all", - "teleport": "POST /api/teleport", + "teleport": "POST /api/teleport", }, }) } diff --git a/internal/sdkserver/runtime_config_test.go b/internal/sdkserver/runtime_config_test.go new file mode 100644 index 0000000..f950934 --- /dev/null +++ b/internal/sdkserver/runtime_config_test.go @@ -0,0 +1,43 @@ +package sdkserver + +import ( + "encoding/base64" + "net/http/httptest" + "testing" + + "gac-hkrpg-go/internal/common" + goproto "gac-hkrpg-go/protocol/proto" + "google.golang.org/protobuf/proto" +) + +func TestConfiguredDispatchAndGateway(t *testing.T) { + // Fixture endpoints must appear on the wire instead of any local defaults. + cfg := common.RuntimeConfig{PublicSDKURL: "https://sdk.example.invalid/prefix", PublicGameHost: "game.example.invalid", GamePort: 32123} + w := httptest.NewRecorder() + queryDispatch(w, httptest.NewRequest("GET", "/query_dispatch", nil), cfg) + raw, err := base64.StdEncoding.DecodeString(w.Body.String()) + if err != nil { + t.Fatal(err) + } + var dispatch goproto.Dispatch + if err := proto.Unmarshal(raw, &dispatch); err != nil { + t.Fatal(err) + } + if len(dispatch.RegionList) != 1 || dispatch.RegionList[0].DispatchUrl != cfg.PublicSDKURL+"/query_gateway" { + t.Fatalf("wrong dispatch: %v", &dispatch) + } + state := &appState{runtime: cfg, hotfixMap: map[string]VersionConfig{"fixture": {}}} + w = httptest.NewRecorder() + queryGateway(state)(w, httptest.NewRequest("GET", "/query_gateway?version=fixture", nil)) + raw, err = base64.StdEncoding.DecodeString(w.Body.String()) + if err != nil { + t.Fatal(err) + } + var gateway goproto.Gateserver + if err := proto.Unmarshal(raw, &gateway); err != nil { + t.Fatal(err) + } + if gateway.Ip != cfg.PublicGameHost || gateway.Port != uint32(cfg.GamePort) { + t.Fatalf("wrong gateway: %v", &gateway) + } +} diff --git a/internal/sdkserver/server.go b/internal/sdkserver/server.go index 8e66ce3..75eb3bd 100644 --- a/internal/sdkserver/server.go +++ b/internal/sdkserver/server.go @@ -1,5 +1,5 @@ // Package sdkserver is the Go port of the Rust sdkserver: HTTP dispatch + -// auth + SRTools save upload, listening on :21000. +// auth + SRTools save upload, using the shared runtime configuration. package sdkserver import ( @@ -11,13 +11,12 @@ import ( "os" "sync" + "gac-hkrpg-go/internal/common" google "google.golang.org/protobuf/proto" goproto "gac-hkrpg-go/protocol/proto" ) -const Port = 21000 - // VersionConfig is one entry of versions.json. type VersionConfig struct { AssetBundleURL string `json:"asset_bundle_url"` @@ -27,6 +26,7 @@ type VersionConfig struct { } type appState struct { + runtime common.RuntimeConfig mu sync.Mutex hotfixMap map[string]VersionConfig } @@ -129,7 +129,7 @@ func fetchHotfix(version, dispatchSeed string) (VersionConfig, error) { }, nil } -// parseVersionString extracts region/branch, e.g. "OSBETAWin4.6.51" -> OS,BETA. +// parseVersionString extracts region/branch, e.g. a version with the OSBETAWin prefix -> OS,BETA. func parseVersionString(s string) (string, string) { branches := []string{"PREbeta", "BETA", "PROD", "DEV", "PRE", "GM", "CECREATION"} if len(s) < 2 { @@ -154,14 +154,14 @@ func writeJSON(w http.ResponseWriter, v any) { _ = json.NewEncoder(w).Encode(v) } -func queryDispatch(w http.ResponseWriter, _ *http.Request) { +func queryDispatch(w http.ResponseWriter, _ *http.Request, runtime common.RuntimeConfig) { rsp := &goproto.Dispatch{ Retcode: 0, RegionList: []*goproto.RegionInfo{{ Name: "GaC hkrpg GO", Title: "GaC hkrpg GO", EnvType: "9", - DispatchUrl: fmt.Sprintf("http://127.0.0.1:%d/query_gateway", Port), + DispatchUrl: runtime.PublicSDKURL + "/query_gateway", }}, } bin, err := google.Marshal(rsp) @@ -186,8 +186,8 @@ func queryGateway(state *appState) http.HandlerFunc { cfg := state.getOrInsertHotfix(version, seed) rsp := &goproto.Gateserver{ - Ip: "127.0.0.1", - Port: 23301, + Ip: state.runtime.PublicGameHost, + Port: uint32(state.runtime.GamePort), AssetBundleUrl: cfg.AssetBundleURL, AssetBundleUrlB: cfg.AssetBundleURL, ExResourceUrl: cfg.ExResourceURL, @@ -344,11 +344,15 @@ func srToolsSave(w http.ResponseWriter, r *http.Request) { // Listen starts the HTTP sdkserver. func Listen() error { - state := &appState{hotfixMap: loadHotfix()} + runtime, err := common.LoadRuntimeConfig() + if err != nil { + return err + } + state := &appState{hotfixMap: loadHotfix(), runtime: runtime} Log(fmt.Sprintf("loaded %d hotfix versions", len(state.hotfixMap))) mux := http.NewServeMux() - mux.HandleFunc("/query_dispatch", queryDispatch) + mux.HandleFunc("/query_dispatch", func(w http.ResponseWriter, r *http.Request) { queryDispatch(w, r, runtime) }) mux.HandleFunc("/query_gateway", queryGateway(state)) mux.HandleFunc("/{product_name}/mdk/shield/api/login", func(w http.ResponseWriter, r *http.Request) { loginReply(w) }) mux.HandleFunc("/{product_name}/mdk/shield/api/verify", func(w http.ResponseWriter, r *http.Request) { loginReply(w) }) @@ -366,9 +370,9 @@ func Listen() error { notFound(w, r) }) - addr := fmt.Sprintf("0.0.0.0:%d", Port) + addr := runtime.SDKAddress() Log("sdkserver is listening at " + addr) - Log(fmt.Sprintf("REST API Gateway is live at http://localhost:%d/ (for GaC-Srtool)", Port)) + Log(fmt.Sprintf("REST API Gateway is live at %s/ (for GaC-Srtool)", runtime.PublicSDKURL)) return http.ListenAndServe(addr, mux) } diff --git a/protocol/proto/StarRail.pb.go b/protocol/proto/StarRail.pb.go index 6544370..19fead5 100644 --- a/protocol/proto/StarRail.pb.go +++ b/protocol/proto/StarRail.pb.go @@ -93771,15 +93771,18 @@ func (x *GetChallengeGroupStatisticsCsReq) GetGroupId() uint32 { // CmdID: 1708 type GetChallengeGroupStatisticsScRsp struct { - state protoimpl.MessageState `protogen:"open.v1"` - GroupId uint32 `protobuf:"varint,5,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` - Retcode uint32 `protobuf:"varint,9,opt,name=retcode,proto3" json:"retcode,omitempty"` - MEGBIPBAFBP *BBBIAPEHABA `protobuf:"bytes,1461,opt,name=MEGBIPBAFBP,proto3" json:"MEGBIPBAFBP,omitempty"` - ChallengeBoss *ChallengeBossStatistics `protobuf:"bytes,1,opt,name=challenge_boss,json=challengeBoss,proto3" json:"challenge_boss,omitempty"` - ChallengeStory *ChallengeStoryStatistics `protobuf:"bytes,3,opt,name=challenge_story,json=challengeStory,proto3" json:"challenge_story,omitempty"` - ChallengeDefault *ChallengeStatistics `protobuf:"bytes,11,opt,name=challenge_default,json=challengeDefault,proto3" json:"challenge_default,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + GroupId uint32 `protobuf:"varint,5,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + Retcode uint32 `protobuf:"varint,9,opt,name=retcode,proto3" json:"retcode,omitempty"` + MEGBIPBAFBP *BBBIAPEHABA `protobuf:"bytes,1461,opt,name=MEGBIPBAFBP,proto3" json:"MEGBIPBAFBP,omitempty"` + // Types that are valid to be assigned to EDKOHAAMONH: + // + // *GetChallengeGroupStatisticsScRsp_ChallengeBoss + // *GetChallengeGroupStatisticsScRsp_ChallengeStory + // *GetChallengeGroupStatisticsScRsp_ChallengeDefault + EDKOHAAMONH isGetChallengeGroupStatisticsScRsp_EDKOHAAMONH `protobuf_oneof:"EDKOHAAMONH"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetChallengeGroupStatisticsScRsp) Reset() { @@ -93833,27 +93836,65 @@ func (x *GetChallengeGroupStatisticsScRsp) GetMEGBIPBAFBP() *BBBIAPEHABA { return nil } +func (x *GetChallengeGroupStatisticsScRsp) GetEDKOHAAMONH() isGetChallengeGroupStatisticsScRsp_EDKOHAAMONH { + if x != nil { + return x.EDKOHAAMONH + } + return nil +} + func (x *GetChallengeGroupStatisticsScRsp) GetChallengeBoss() *ChallengeBossStatistics { if x != nil { - return x.ChallengeBoss + if x, ok := x.EDKOHAAMONH.(*GetChallengeGroupStatisticsScRsp_ChallengeBoss); ok { + return x.ChallengeBoss + } } return nil } func (x *GetChallengeGroupStatisticsScRsp) GetChallengeStory() *ChallengeStoryStatistics { if x != nil { - return x.ChallengeStory + if x, ok := x.EDKOHAAMONH.(*GetChallengeGroupStatisticsScRsp_ChallengeStory); ok { + return x.ChallengeStory + } } return nil } func (x *GetChallengeGroupStatisticsScRsp) GetChallengeDefault() *ChallengeStatistics { if x != nil { - return x.ChallengeDefault + if x, ok := x.EDKOHAAMONH.(*GetChallengeGroupStatisticsScRsp_ChallengeDefault); ok { + return x.ChallengeDefault + } } return nil } +type isGetChallengeGroupStatisticsScRsp_EDKOHAAMONH interface { + isGetChallengeGroupStatisticsScRsp_EDKOHAAMONH() +} + +type GetChallengeGroupStatisticsScRsp_ChallengeBoss struct { + ChallengeBoss *ChallengeBossStatistics `protobuf:"bytes,1,opt,name=challenge_boss,json=challengeBoss,proto3,oneof"` +} + +type GetChallengeGroupStatisticsScRsp_ChallengeStory struct { + ChallengeStory *ChallengeStoryStatistics `protobuf:"bytes,3,opt,name=challenge_story,json=challengeStory,proto3,oneof"` +} + +type GetChallengeGroupStatisticsScRsp_ChallengeDefault struct { + ChallengeDefault *ChallengeStatistics `protobuf:"bytes,11,opt,name=challenge_default,json=challengeDefault,proto3,oneof"` +} + +func (*GetChallengeGroupStatisticsScRsp_ChallengeBoss) isGetChallengeGroupStatisticsScRsp_EDKOHAAMONH() { +} + +func (*GetChallengeGroupStatisticsScRsp_ChallengeStory) isGetChallengeGroupStatisticsScRsp_EDKOHAAMONH() { +} + +func (*GetChallengeGroupStatisticsScRsp_ChallengeDefault) isGetChallengeGroupStatisticsScRsp_EDKOHAAMONH() { +} + type ChallengeBossSingleNodeInfo struct { state protoimpl.MessageState `protogen:"open.v1"` MaxScore uint32 `protobuf:"varint,4,opt,name=max_score,json=maxScore,proto3" json:"max_score,omitempty"` @@ -208294,6 +208335,7 @@ func (x *ReplaceLineupCsReq) GetLineupSlotList() []*LineupSlotData { type ReplaceLineupScRsp struct { state protoimpl.MessageState `protogen:"open.v1"` Retcode uint32 `protobuf:"varint,8,opt,name=retcode,proto3" json:"retcode,omitempty"` + OMDLCGEMBLA uint32 `protobuf:"varint,14,opt,name=OMDLCGEMBLA,proto3" json:"OMDLCGEMBLA,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -208335,6 +208377,13 @@ func (x *ReplaceLineupScRsp) GetRetcode() uint32 { return 0 } +func (x *ReplaceLineupScRsp) GetOMDLCGEMBLA() uint32 { + if x != nil { + return x.OMDLCGEMBLA + } + return 0 +} + // CmdID: 723 type ExtraLineupDestroyNotify struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -345159,14 +345208,15 @@ const file_protocol_proto_StarRail_proto_rawDesc = "" + "\vEEJCPNAEKLJ\x18\f \x01(\rR\vEEJCPNAEKLJ\x12\x19\n" + "\bbuff_two\x18\x0f \x01(\rR\abuffTwo\"=\n" + " GetChallengeGroupStatisticsCsReq\x12\x19\n" + - "\bgroup_id\x18\x05 \x01(\rR\agroupId\"\xd0\x02\n" + + "\bgroup_id\x18\x05 \x01(\rR\agroupId\"\xe5\x02\n" + " GetChallengeGroupStatisticsScRsp\x12\x19\n" + "\bgroup_id\x18\x05 \x01(\rR\agroupId\x12\x18\n" + "\aretcode\x18\t \x01(\rR\aretcode\x12/\n" + - "\vMEGBIPBAFBP\x18\xb5\v \x01(\v2\f.BBBIAPEHABAR\vMEGBIPBAFBP\x12?\n" + - "\x0echallenge_boss\x18\x01 \x01(\v2\x18.ChallengeBossStatisticsR\rchallengeBoss\x12B\n" + - "\x0fchallenge_story\x18\x03 \x01(\v2\x19.ChallengeStoryStatisticsR\x0echallengeStory\x12A\n" + - "\x11challenge_default\x18\v \x01(\v2\x14.ChallengeStatisticsR\x10challengeDefault\"\x8c\x01\n" + + "\vMEGBIPBAFBP\x18\xb5\v \x01(\v2\f.BBBIAPEHABAR\vMEGBIPBAFBP\x12A\n" + + "\x0echallenge_boss\x18\x01 \x01(\v2\x18.ChallengeBossStatisticsH\x00R\rchallengeBoss\x12D\n" + + "\x0fchallenge_story\x18\x03 \x01(\v2\x19.ChallengeStoryStatisticsH\x00R\x0echallengeStory\x12C\n" + + "\x11challenge_default\x18\v \x01(\v2\x14.ChallengeStatisticsH\x00R\x10challengeDefaultB\r\n" + + "\vEDKOHAAMONH\"\x8c\x01\n" + "\x1bChallengeBossSingleNodeInfo\x12\x1b\n" + "\tmax_score\x18\x04 \x01(\rR\bmaxScore\x12 \n" + "\vMBHCJPONJOM\x18\x05 \x01(\bR\vMBHCJPONJOM\x12\x15\n" + @@ -353098,9 +353148,10 @@ const file_protocol_proto_StarRail_proto_rawDesc = "" + "\vleader_slot\x18\r \x01(\rR\n" + "leaderSlot\x12 \n" + "\vPLIEODDEEHG\x18\x0e \x01(\bR\vPLIEODDEEHG\x129\n" + - "\x10lineup_slot_list\x18\x0f \x03(\v2\x0f.LineupSlotDataR\x0elineupSlotList\".\n" + + "\x10lineup_slot_list\x18\x0f \x03(\v2\x0f.LineupSlotDataR\x0elineupSlotList\"P\n" + "\x12ReplaceLineupScRsp\x12\x18\n" + - "\aretcode\x18\b \x01(\rR\aretcode\"X\n" + + "\aretcode\x18\b \x01(\rR\aretcode\x12 \n" + + "\vOMDLCGEMBLA\x18\x0e \x01(\rR\vOMDLCGEMBLA\"X\n" + "\x18ExtraLineupDestroyNotify\x12<\n" + "\x11extra_lineup_type\x18\a \x01(\x0e2\x10.ExtraLineupTypeR\x0fextraLineupType\"\xa9\x01\n" + "&VirtualLineupTrialAvatarChangeScNotify\x12 \n" + @@ -379094,6 +379145,11 @@ func file_protocol_proto_StarRail_proto_init() { (*CakeRaceBetCoinPVECsReq_BOEJNBKDJLM)(nil), (*CakeRaceBetCoinPVECsReq_BCFPLAPCFPL)(nil), } + file_protocol_proto_StarRail_proto_msgTypes[849].OneofWrappers = []any{ + (*GetChallengeGroupStatisticsScRsp_ChallengeBoss)(nil), + (*GetChallengeGroupStatisticsScRsp_ChallengeStory)(nil), + (*GetChallengeGroupStatisticsScRsp_ChallengeDefault)(nil), + } file_protocol_proto_StarRail_proto_msgTypes[908].OneofWrappers = []any{ (*DOPHNDHILPA_IIHFEDLHOPI)(nil), (*DOPHNDHILPA_DFNNKENBOEN)(nil), diff --git a/protocol/proto/StarRail.proto b/protocol/proto/StarRail.proto index bb1ab2b..d75f8c7 100644 --- a/protocol/proto/StarRail.proto +++ b/protocol/proto/StarRail.proto @@ -13437,9 +13437,11 @@ message GetChallengeGroupStatisticsScRsp { uint32 group_id = 5; uint32 retcode = 9; BBBIAPEHABA MEGBIPBAFBP = 1461; - ChallengeBossStatistics challenge_boss = 1; - ChallengeStoryStatistics challenge_story = 3; - ChallengeStatistics challenge_default = 11; + oneof EDKOHAAMONH { + ChallengeBossStatistics challenge_boss = 1; + ChallengeStoryStatistics challenge_story = 3; + ChallengeStatistics challenge_default = 11; + } } message ChallengeBossSingleNodeInfo { @@ -25147,6 +25149,7 @@ message ReplaceLineupCsReq { // CmdID: 715 message ReplaceLineupScRsp { uint32 retcode = 8; + uint32 OMDLCGEMBLA = 14; } // CmdID: 723 diff --git a/protocol/proto/current_handlers.pb.go b/protocol/proto/current_handlers.pb.go new file mode 100644 index 0000000..da579b2 --- /dev/null +++ b/protocol/proto/current_handlers.pb.go @@ -0,0 +1,184 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc v7.36.0 +// source: protocol/proto/current_handlers.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// PearlSR 005d2c12db4781071f4b5b68e8f364587886a710: +// protocol/src/protocol.pb.zig, SetChallengeTierceLineup messages. +// These named messages are missing from the original Go schema. +type SetChallengeTierceLineupCsReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + ChallengeId uint32 `protobuf:"varint,4,opt,name=challenge_id,json=challengeId,proto3" json:"challenge_id,omitempty"` + StageInfoList []*LineupTierce `protobuf:"bytes,5,rep,name=stage_info_list,json=stageInfoList,proto3" json:"stage_info_list,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetChallengeTierceLineupCsReq) Reset() { + *x = SetChallengeTierceLineupCsReq{} + mi := &file_protocol_proto_current_handlers_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetChallengeTierceLineupCsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetChallengeTierceLineupCsReq) ProtoMessage() {} + +func (x *SetChallengeTierceLineupCsReq) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_current_handlers_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetChallengeTierceLineupCsReq.ProtoReflect.Descriptor instead. +func (*SetChallengeTierceLineupCsReq) Descriptor() ([]byte, []int) { + return file_protocol_proto_current_handlers_proto_rawDescGZIP(), []int{0} +} + +func (x *SetChallengeTierceLineupCsReq) GetChallengeId() uint32 { + if x != nil { + return x.ChallengeId + } + return 0 +} + +func (x *SetChallengeTierceLineupCsReq) GetStageInfoList() []*LineupTierce { + if x != nil { + return x.StageInfoList + } + return nil +} + +type SetChallengeTierceLineupScRsp struct { + state protoimpl.MessageState `protogen:"open.v1"` + Retcode uint32 `protobuf:"varint,1,opt,name=retcode,proto3" json:"retcode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetChallengeTierceLineupScRsp) Reset() { + *x = SetChallengeTierceLineupScRsp{} + mi := &file_protocol_proto_current_handlers_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetChallengeTierceLineupScRsp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetChallengeTierceLineupScRsp) ProtoMessage() {} + +func (x *SetChallengeTierceLineupScRsp) ProtoReflect() protoreflect.Message { + mi := &file_protocol_proto_current_handlers_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetChallengeTierceLineupScRsp.ProtoReflect.Descriptor instead. +func (*SetChallengeTierceLineupScRsp) Descriptor() ([]byte, []int) { + return file_protocol_proto_current_handlers_proto_rawDescGZIP(), []int{1} +} + +func (x *SetChallengeTierceLineupScRsp) GetRetcode() uint32 { + if x != nil { + return x.Retcode + } + return 0 +} + +var File_protocol_proto_current_handlers_proto protoreflect.FileDescriptor + +const file_protocol_proto_current_handlers_proto_rawDesc = "" + + "\n" + + "%protocol/proto/current_handlers.proto\x1a\x1dprotocol/proto/StarRail.proto\"y\n" + + "\x1dSetChallengeTierceLineupCsReq\x12!\n" + + "\fchallenge_id\x18\x04 \x01(\rR\vchallengeId\x125\n" + + "\x0fstage_info_list\x18\x05 \x03(\v2\r.LineupTierceR\rstageInfoList\"9\n" + + "\x1dSetChallengeTierceLineupScRsp\x12\x18\n" + + "\aretcode\x18\x01 \x01(\rR\aretcodeB\x1dZ\x1bgac-hkrpg-go/protocol/protob\x06proto3" + +var ( + file_protocol_proto_current_handlers_proto_rawDescOnce sync.Once + file_protocol_proto_current_handlers_proto_rawDescData []byte +) + +func file_protocol_proto_current_handlers_proto_rawDescGZIP() []byte { + file_protocol_proto_current_handlers_proto_rawDescOnce.Do(func() { + file_protocol_proto_current_handlers_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_protocol_proto_current_handlers_proto_rawDesc), len(file_protocol_proto_current_handlers_proto_rawDesc))) + }) + return file_protocol_proto_current_handlers_proto_rawDescData +} + +var file_protocol_proto_current_handlers_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_protocol_proto_current_handlers_proto_goTypes = []any{ + (*SetChallengeTierceLineupCsReq)(nil), // 0: SetChallengeTierceLineupCsReq + (*SetChallengeTierceLineupScRsp)(nil), // 1: SetChallengeTierceLineupScRsp + (*LineupTierce)(nil), // 2: LineupTierce +} +var file_protocol_proto_current_handlers_proto_depIdxs = []int32{ + 2, // 0: SetChallengeTierceLineupCsReq.stage_info_list:type_name -> LineupTierce + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_protocol_proto_current_handlers_proto_init() } +func file_protocol_proto_current_handlers_proto_init() { + if File_protocol_proto_current_handlers_proto != nil { + return + } + file_protocol_proto_StarRail_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_protocol_proto_current_handlers_proto_rawDesc), len(file_protocol_proto_current_handlers_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_protocol_proto_current_handlers_proto_goTypes, + DependencyIndexes: file_protocol_proto_current_handlers_proto_depIdxs, + MessageInfos: file_protocol_proto_current_handlers_proto_msgTypes, + }.Build() + File_protocol_proto_current_handlers_proto = out.File + file_protocol_proto_current_handlers_proto_goTypes = nil + file_protocol_proto_current_handlers_proto_depIdxs = nil +} diff --git a/protocol/proto/current_handlers.proto b/protocol/proto/current_handlers.proto new file mode 100644 index 0000000..cb43f02 --- /dev/null +++ b/protocol/proto/current_handlers.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +option go_package = "gac-hkrpg-go/protocol/proto"; +import "protocol/proto/StarRail.proto"; + +// PearlSR 005d2c12db4781071f4b5b68e8f364587886a710: +// protocol/src/protocol.pb.zig, SetChallengeTierceLineup messages. +// These named messages are missing from the original Go schema. +message SetChallengeTierceLineupCsReq { + uint32 challenge_id = 4; + repeated LineupTierce stage_info_list = 5; +} + +message SetChallengeTierceLineupScRsp { + uint32 retcode = 1; +} diff --git a/protocol/proto/download_format.go b/protocol/proto/download_format.go new file mode 100644 index 0000000..0bc7022 --- /dev/null +++ b/protocol/proto/download_format.go @@ -0,0 +1,7 @@ +package proto + +// ClientDownloadDataLuaVersion is the version value used for Lua downloads by +// PearlSR services/misc.zig at source revision +// 005d2c12db4781071f4b5b68e8f364587886a710 (client 4.6.53). +// Do not derive it from the configured client patch number. +const ClientDownloadDataLuaVersion uint32 = 51 diff --git a/protocol/proto/upstream_cmdids.go b/protocol/proto/upstream_cmdids.go new file mode 100644 index 0000000..a7bb4bf --- /dev/null +++ b/protocol/proto/upstream_cmdids.go @@ -0,0 +1,2709 @@ +// Code generated by tools/sync_protocol_commands.py. DO NOT EDIT. +// Source: https://git.xeondev.com/HonkaiSlopRail/pearl-sr protocol/src/root.zig +// Revision: 005d2c12db4781071f4b5b68e8f364587886a710 +package proto + +const CommandSourceRevision = "005d2c12db4781071f4b5b68e8f364587886a710" + +var upstreamCmdIDByName = map[string]uint16{ + "GateServerScNotify": 1, + "ForceSyncGameStateFinishCSScRsp": 3, + "SetNicknameCsReq": 4, + "PlayerGetTokenCsReq": 5, + "PlayerSqueezedScNotify": 6, + "RegionStopScNotify": 8, + "PlayerLoginFinishCsReq": 9, + "UpdatePlayerSettingScRsp": 11, + "PlayerLoginFinishScRsp": 13, + "GetLevelRewardCsReq": 15, + "PlayerHeartBeatScRsp": 16, + "GetVideoVersionKeyCsReq": 17, + "JKJMAKHANPB": 18, + "PlayerLoginScRsp": 19, + "GetBasicInfoScRsp": 20, + "StaminaInfoScNotify": 21, + "SetGenderCsReq": 22, + "GetLevelRewardScRsp": 23, + "GGIHCKPGACF": 24, + "ClientObjUploadScRsp": 25, + "SetRedPointStatusScNotify": 26, + "EnterStageScRsp": 27, + "UpdateFeatureSwitchScNotify": 29, + "GetSecretKeyInfoCsReq": 30, + "PlayerLogoutCsReq": 31, + "ReserveStaminaExchangeScRsp": 32, + "ReserveStaminaExchangeCsReq": 33, + "UpdatePlayWithPsnOnlySettingCsReq": 34, + "GetSecretKeyInfoScRsp": 38, + "SetGameplayBirthdayCsReq": 39, + "MHMFPLEKOIL": 41, + "QueryProductInfoCsReq": 43, + "PlayerHeartBeatCsReq": 45, + "GetBasicInfoCsReq": 46, + "ExchangeStaminaScRsp": 47, + "GetAuthkeyCsReq": 48, + "SetGenderScRsp": 49, + "QueryProductInfoScRsp": 53, + "FLMODPLLMNF": 55, + "GetLevelRewardTakenListCsReq": 57, + "ClientDownloadDataScNotify": 60, + "SetLanguageScRsp": 62, + "SetLanguageReq": 63, + "SetGameplayBirthdayScRsp": 64, + "SetPlayerInfoCsReq": 66, + "GetVideoVersionKeyScRsp": 69, + "EnterStageCsReq": 70, + "PlayerGetTokenScRsp": 71, + "PlayerLoginCsReq": 74, + "GetAuthkeyScRsp": 75, + "UpdatePlayerSettingCsReq": 76, + "ForceSyncGameStateFinishCSReq": 77, + "DailyRefreshNotify": 78, + "UpdatePlayWithPsnOnlySettingScRsp": 79, + "ServerAnnounceNotify": 80, + "GetLevelRewardTakenListScRsp": 82, + "FeatureSwitchClosedScNotify": 85, + "ClientObjDownloadDataScNotify": 86, + "SetPlayerInfoScRsp": 90, + "AntiAddictScNotify": 92, + "SetNicknameScRsp": 94, + "IAOCHINIPGK": 95, + "ClientObjUploadCsReq": 96, + "ExchangeStaminaCsReq": 98, + "EMFKFGCBJBP": 99, + "MonthCardRewardNotify": 100, + "GetCurBattleInfoCsReq": 105, + "QuitBattleScNotify": 106, + "SyncClientResVersionScRsp": 118, + "PVEBattleResultScRsp": 119, + "QuitBattleCsReq": 131, + "BattleReplayScNotify": 147, + "SyncClientResVersionCsReq": 159, + "GetCurBattleInfoScRsp": 171, + "PVEBattleResultCsReq": 174, + "QuitBattleScRsp": 187, + "RebattleByClientCsNotify": 198, + "TakePromotionRewardCsReq": 304, + "UnlockSkillTreeCsReq": 305, + "DressAvatarCsReq": 306, + "TakeOffRelicCsReq": 308, + "SetPlayerOutfitScRsp": 312, + "TakeOffAvatarSkinCsReq": 315, + "PromoteAvatarScRsp": 318, + "GetAvatarDataScRsp": 319, + "SetMultipleAvatarPathsCsReq": 320, + "SetAvatarEnhancedIdScRsp": 322, + "TakeOffAvatarSkinScRsp": 323, + "TakeOffEquipmentCsReq": 327, + "UnlockAvatarSkinScNotify": 328, + "UnlockAvatarPathCsReq": 329, + "AvatarExpUpCsReq": 331, + "AddAvatarScNotify": 335, + "AvatarSpecialSkilltreeUnlockScNotify": 339, + "PMEMIDKDAFG": 341, + "SetAvatarPathCsReq": 343, + "UnlockAvatarPathScRsp": 346, + "RankUpAvatarScRsp": 347, + "DressRelicAvatarCsReq": 348, + "GetPreAvatarActivityListCsReq": 349, + "GetPreAvatarGrowthInfoScRsp": 350, + "SetGrowthTargetAvatarScRsp": 351, + "TakeOffEquipmentScRsp": 352, + "SetAvatarPathScRsp": 353, + "CFGGOBEDJOK": 354, + "EKLIDIDKFHC": 355, + "DressAvatarSkinCsReq": 357, + "PromoteAvatarCsReq": 359, + "AvatarPathChangedNotify": 360, + "AddMultiPathAvatarScNotify": 362, + "MarkAvatarScRsp": 363, + "LENDBHPIOJK": 364, + "GetPreAvatarActivityListScRsp": 366, + "MarkAvatarCsReq": 367, + "DressAvatarScRsp": 370, + "UnlockSkillTreeScRsp": 371, + "GetAvatarDataCsReq": 374, + "DressRelicAvatarScRsp": 375, + "SetMultipleAvatarPathsScRsp": 378, + "SetGrowthTargetAvatarCsReq": 380, + "SetAvatarEnhancedIdCsReq": 381, + "DressAvatarSkinScRsp": 382, + "AvatarExpUpScRsp": 387, + "GetPreAvatarGrowthInfoCsReq": 389, + "SetPlayerOutfitCsReq": 390, + "TakeOffRelicScRsp": 392, + "TakePromotionRewardScRsp": 394, + "LPOLGDLAADM": 395, + "RankUpAvatarCsReq": 398, + "HIGLJHLNODG": 400, + "JBAEGHHHDNH": 405, + "GMCAGPOHKJJ": 419, + "MKEPKKAJONE": 471, + "LKJFAOIDNNF": 474, + "RelicFilterPlanClearNameScNotify": 501, + "SellItemScRsp": 504, + "CmdLockEquipmentCsReq": 505, + "RankUpEquipmentCsReq": 506, + "LockRelicScRsp": 508, + "BatchRankUpEquipmentScRsp": 509, + "DiscardRelicCsReq": 512, + "EquipmentQuickUpCsReq": 513, + "ComposeSelectedRelicCsReq": 515, + "MarkRelicFilterPlanScRsp": 516, + "EquipmentQuickUpScRsp": 517, + "UseItemScRsp": 518, + "GetBagScRsp": 519, + "GetRelicFilterPlanCsReq": 520, + "SyncTurnFoodNotify": 522, + "ComposeSelectedRelicScRsp": 523, + "RelicCraftScRsp": 526, + "ExpUpEquipmentCsReq": 527, + "AddEquipmentScRsp": 528, + "RelicReforgeConfirmScRsp": 530, + "PromoteEquipmentCsReq": 531, + "ComposeItemCsReq": 535, + "RelicReforgeConfirmCsReq": 536, + "BatchRankUpEquipmentCsReq": 538, + "AddRelicFilterPlanCsReq": 539, + "ModifyRelicFilterPlanScRsp": 541, + "DiscardRelicScRsp": 543, + "MarkItemScRsp": 544, + "MarkRelicFilterPlanCsReq": 545, + "ExpUpRelicCsReq": 547, + "ExpUpRelicScRsp": 548, + "SetTurnFoodSwitchCsReq": 549, + "GetMarkItemListScRsp": 550, + "DestroyItemCsReq": 551, + "ExpUpEquipmentScRsp": 552, + "DestroyItemScRsp": 554, + "ModifyRelicFilterPlanCsReq": 555, + "ExchangeHcoinCsReq": 557, + "UseItemCsReq": 559, + "ComposeLimitNumCompleteScNotify": 562, + "GetRecyleTimeScRsp": 563, + "AddRelicFilterPlanScRsp": 564, + "SetTurnFoodSwitchScRsp": 566, + "GetRecycleTimeCsReq": 567, + "MarkItemCsReq": 568, + "RelicCraftCsReq": 569, + "RankUpEquipmentScRsp": 570, + "CmdLockEquipmentScRsp": 571, + "GetBagCsReq": 574, + "LockRelicCsReq": 575, + "GetRelicFilterPlanScRsp": 578, + "ComposeLimitNumUpdateScNotify": 580, + "CancelMarkItemNotify": 581, + "ExchangeHcoinScRsp": 582, + "RelicReforgeScRsp": 584, + "RelicReforgeCsReq": 585, + "PromoteEquipmentScRsp": 587, + "GetMarkItemListCsReq": 589, + "GeneralVirtualItemDataNotify": 590, + "SellItemCsReq": 592, + "RechargeSuccNotify": 594, + "DeleteRelicFilterPlanScRsp": 595, + "ComposeItemScRsp": 598, + "DeleteRelicFilterPlanCsReq": 600, + "PlayerSyncScNotify": 674, + "GetAllLineupDataCsReq": 704, + "JoinLineupCsReq": 705, + "SwapLineupCsReq": 706, + "SetLineupNameCsReq": 708, + "ReplaceLineupScRsp": 715, + "QuitLineupScRsp": 718, + "ExtraLineupDestroyNotify": 723, + "SyncLineupNotify": 727, + "VirtualLineupTrialAvatarChangeScNotify": 728, + "GetCurLineupDataCsReq": 731, + "GetLineupAvatarDataScRsp": 735, + "ChangeLineupLeaderScRsp": 747, + "SwitchLineupIndexCsReq": 748, + "GetLineupAvatarDataCsReq": 752, + "VirtualLineupDestroyNotify": 757, + "QuitLineupCsReq": 759, + "MarkPresetLineupCsReq": 762, + "CNCFCDJAFOJ": 763, + "SyncAllPresetLineupNotify": 767, + "SwapLineupScRsp": 770, + "JoinLineupScRsp": 771, + "SwitchLineupIndexScRsp": 775, + "MarkPresetLineupScRsp": 780, + "ReplaceLineupCsReq": 782, + "GetCurLineupDataScRsp": 787, + "SetLineupNameScRsp": 792, + "GetAllLineupDataScRsp": 794, + "ChangeLineupLeaderCsReq": 798, + "DelMailCsReq": 805, + "NewMailScNotify": 806, + "TakeMailAttachmentScRsp": 818, + "GetMailScRsp": 819, + "MarkReadMailCsReq": 831, + "TakeMailAttachmentCsReq": 859, + "DelMailScRsp": 871, + "GetMailCsReq": 874, + "MarkReadMailScRsp": 887, + "QuestRecordScNotify": 906, + "GetQuestRecordScRsp": 918, + "GetQuestDataScRsp": 919, + "FinishQuestScRsp": 927, + "TakeQuestRewardCsReq": 931, + "TakeQuestOptionalRewardScRsp": 935, + "BatchGetQuestDataCsReq": 947, + "BatchGetQuestDataScRsp": 948, + "TakeQuestOptionalRewardCsReq": 952, + "GetQuestRecordCsReq": 959, + "FinishQuestCsReq": 970, + "GetQuestDataCsReq": 974, + "TakeQuestRewardScRsp": 987, + "FightGiveUpCsReq": 1005, + "NENHKMEFAEC": 1006, + "HFFCEACMEIG": 1018, + "FightGameStateScRsp": 1019, + "MultiplayerGetMatchPlayDataScRsp": 1027, + "PlayerGetFightGateCsReq": 1031, + "GetFriendRankingInfoCsReq": 1035, + "SelfRankChangeNtf": 1047, + "MultiplayerMatchPlayDataChangeScNotify": 1052, + "FightGameStartScNotify": 1059, + "MultiplayerGetMatchPlayDataCsReq": 1070, + "FightGiveUpScRsp": 1071, + "FightGameStateCsReq": 1074, + "PlayerGetFightGateScRsp": 1087, + "GetFriendRankingInfoRsp": 1098, + "FinishPlotScRsp": 1119, + "FinishPlotCsReq": 1174, + "GetMissionStatusCsReq": 1204, + "MissionRewardScNotify": 1205, + "StarPerformanceRelayCsReq": 1212, + "GetMissionDataScRsp": 1219, + "UpdateTrackMainMissionScRsp": 1222, + "SubMissionRewardScNotify": 1228, + "FinishTalkMissionCsReq": 1231, + "FinishCosumeItemMissionCsReq": 1235, + "StarPerformanceRelayScRsp": 1243, + "FinishedMissionScNotify": 1249, + "GetMainMissionCustomValueScRsp": 1250, + "AcceptMainMissionCsReq": 1251, + "MissionGroupWarnScNotify": 1252, + "AcceptMainMissionScRsp": 1254, + "SyncTaskScRsp": 1259, + "StartFinishSubMissionScNotify": 1262, + "TeleportToMissionResetPointScRsp": 1263, + "UpdateMainMissionCustomValueCsReq": 1266, + "TeleportToMissionResetPointCsReq": 1267, + "MainMissionAcceptNotify": 1268, + "SyncTaskCsReq": 1271, + "GetMissionDataCsReq": 1274, + "StartFinishMainMissionScNotify": 1280, + "UpdateTrackMainMissionCsReq": 1281, + "FinishTalkMissionScRsp": 1287, + "GetMainMissionCustomValueCsReq": 1289, + "UpdateMainMissionCustomValueScRsp": 1290, + "GetMissionStatusScRsp": 1294, + "FinishCosumeItemMissionScRsp": 1298, + "QuickStartCocoonStageCsReq": 1305, + "CocoonSweepCsReq": 1306, + "QuickStartFarmElementScRsp": 1318, + "FarmElementSweepCsReq": 1327, + "GetFarmStageGachaInfoCsReq": 1331, + "FarmElementSweepScRsp": 1352, + "QuickStartFarmElementCsReq": 1359, + "CocoonSweepScRsp": 1370, + "QuickStartCocoonStageScRsp": 1371, + "GetFarmStageGachaInfoScRsp": 1387, + "SetGroupCustomSaveDataScRsp": 1401, + "GroupStateChangeScRsp": 1402, + "SetTrainWorldIdCsReq": 1403, + "HNGLCIGBNGB": 1404, + "SceneCastSkillCsReq": 1405, + "ChangePropTimelineInfoCsReq": 1406, + "UnlockTeleportNotify": 1407, + "SceneCheckpointRefreshMonsterReq": 1408, + "SceneEntityTeleportScRsp": 1409, + "UpdateGroupPropertyCsReq": 1410, + "UpdateFloorSavedValueNotify": 1411, + "EnterSceneCsReq": 1413, + "EnteredSceneChangeScNotify": 1414, + "SceneEnterStageScRsp": 1415, + "SetGroupCustomSaveDataCsReq": 1416, + "EnterSceneScRsp": 1417, + "GetCurSceneInfoScRsp": 1418, + "SceneEntityMoveScRsp": 1419, + "GetUnlockTeleportScRsp": 1421, + "SceneReviveAfterRebattleCsReq": 1423, + "GameplayCounterUpdateScNotify": 1425, + "ScenePlaneEventScNotify": 1426, + "SceneEntityMoveScNotify": 1427, + "SceneReviveAfterRebattleScRsp": 1428, + "ActiveFarmElementScRsp": 1429, + "ReEnterLastElementStageScRsp": 1430, + "InteractPropCsReq": 1431, + "GetUnlockTeleportCsReq": 1432, + "SyncServerSceneChangeNotify": 1433, + "SceneGroupRefreshScNotify": 1434, + "SyncEntityBuffChangeListScNotify": 1435, + "ReEnterLastElementStageCsReq": 1436, + "RefreshTriggerByClientCsReq": 1437, + "SceneEntityTeleportCsReq": 1438, + "RefreshTriggerByClientScNotify": 1440, + "GetEnteredSceneCsReq": 1442, + "DeactivateFarmElementCsReq": 1443, + "StartCocoonStageCsReq": 1444, + "SceneCastSkillCostMpScRsp": 1447, + "SceneCastSkillMpUpdateScNotify": 1448, + "KCFMJCBJGMN": 1450, + "FDGOBNJPKJG": 1452, + "DeactivateFarmElementScRsp": 1453, + "SetTrainWorldIdScRsp": 1456, + "MNEPAIGBPGM": 1457, + "GetSceneMapInfoScRsp": 1458, + "GetCurSceneInfoCsReq": 1459, + "ActiveFarmElementCsReq": 1460, + "GetSceneMapInfoCsReq": 1461, + "OCHPFJEKNGM": 1462, + "EnterSectionScRsp": 1463, + "UpdateGroupPropertyScRsp": 1465, + "SetClientPausedCsReq": 1466, + "EnterSectionCsReq": 1467, + "SavePointsInfoNotify": 1468, + "EnterSceneByServerScNotify": 1469, + "ChangePropTimelineInfoScRsp": 1470, + "SceneCastSkillScRsp": 1471, + "CounterRecoverScRsp": 1473, + "SceneEntityMoveCsReq": 1474, + "OpenChestScNotify": 1476, + "TrainWorldIdChangeScNotify": 1477, + "CounterRecoverCsReq": 1479, + "StartCocoonStageScRsp": 1481, + "SceneEnterStageCsReq": 1482, + "RefreshTriggerByClientScRsp": 1483, + "GetCurSceneStablePosScRsp": 1484, + "GetCurSceneStablePosCsReq": 1485, + "CounterDownCsReq": 1486, + "InteractPropScRsp": 1487, + "DeleteSummonUnitCsReq": 1488, + "PDHBDPNPMPI": 1489, + "SetClientPausedScRsp": 1490, + "GroupStateChangeCsReq": 1491, + "SceneCheckpoingRefreshScRsp": 1492, + "OOFDADLENDH": 1494, + "CounterDownScRsp": 1496, + "GetEnteredSceneScRsp": 1497, + "SceneCastSkillCostMpCsReq": 1498, + "DeleteSummonUnitScRsp": 1499, + "TakeCityShopRewardCsReq": 1505, + "GetShopListScRsp": 1519, + "BuyGoodsCsReq": 1531, + "CityShopInfoScNotify": 1559, + "TakeCityShopRewardScRsp": 1571, + "GetShopListCsReq": 1574, + "BuyGoodsScRsp": 1587, + "UnlockTutorialCsReq": 1605, + "FinishTutorialCsReq": 1606, + "UnlockTutorialGuideScRsp": 1618, + "GetTutorialScRsp": 1619, + "FinishTutorialGuideCsReq": 1627, + "GetTutorialGuideCsReq": 1631, + "FinishTutorialGuideScRsp": 1652, + "UnlockTutorialGuideCsReq": 1659, + "FinishTutorialScRsp": 1670, + "UnlockTutorialScRsp": 1671, + "GetTutorialCsReq": 1674, + "GetTutorialGuideScRsp": 1687, + "StartPartialChallengeScRsp": 1704, + "LeaveChallengeCsReq": 1705, + "GetChallengeGroupStatisticsScRsp": 1708, + "EnterChallengeNextPhaseCsReq": 1715, + "GetChallengeScRsp": 1719, + "EnterChallengeNextPhaseScRsp": 1723, + "GetCurChallengeScRsp": 1727, + "ChallengeBossPhaseSettleNotify": 1728, + "StartChallengeCsReq": 1731, + "TakeChallengeCumulativeRewardCsReq": 1747, + "TakeChallengeCumulativeRewardScRsp": 1748, + "ChallengeLineupNotify": 1752, + "RestartChallengePhaseCsReq": 1757, + "ChallengeSettleNotify": 1759, + "GetCurChallengeCsReq": 1770, + "LeaveChallengeScRsp": 1771, + "GetChallengeCsReq": 1774, + "GetChallengeGroupStatisticsCsReq": 1775, + "RestartChallengePhaseScRsp": 1782, + "StartChallengeScRsp": 1787, + "StartPartialChallengeCsReq": 1792, + "ReviveRogueAvatarScRsp1": 1804, + "EnterRogueCsReq": 1805, + "CDOKCLHEDBO": 1812, + "FinishAeonDialogueGroupScRsp": 1814, + "GetRogueBuffEnhanceInfoScRsp1": 1815, + "ExchangeRogueRewardKeyScRsp": 1816, + "LeaveRogueScRsp": 1818, + "GetRogueInfoScRsp": 1819, + "EnhanceRogueBuffCsReq1": 1823, + "SyncRogueAreaUnlockScNotify": 1826, + "EnhanceRogueBuffScRsp1": 1828, + "StartRogueCsReq": 1831, + "TakeRogueAeonLevelRewardScRsp": 1833, + "GetRogueTalentInfoCsReq": 1837, + "EnableRogueTalentCsReq": 1840, + "GetRogueAeonInfoScRsp": 1842, + "SyncRogueMapRoomScNotify": 1843, + "SyncRogueSeasonFinishScNotify": 1844, + "ExchangeRogueRewardKeyCsReq": 1845, + "SyncRogueFinishScNotify": 1847, + "PickRogueAvatarCsReq1": 1848, + "EnterRogueMapRoomCsReq": 1849, + "SyncRogueReviveInfoScNotify": 1851, + "QuitRogueCsReq": 1854, + "SyncRogueVirtualItemInfoScNotify": 1856, + "TakeRogueAeonLevelRewardCsReq": 1858, + "LeaveRogueCsReq": 1859, + "SyncRogueGetItemScNotify": 1861, + "EnterRogueMapRoomScRsp": 1866, + "SyncRogueExploreWinScNotify": 1868, + "SyncRogueAeonScNotify": 1869, + "EnterRogueScRsp": 1871, + "SyncRogueStatusScNotify": 1872, + "GetRogueInfoCsReq": 1874, + "PickRogueAvatarScRsp1": 1875, + "GetRogueBuffEnhanceInfoCsReq1": 1882, + "GetRogueTalentInfoScRsp": 1883, + "StartRogueScRsp": 1887, + "EnableRogueTalentScRsp": 1888, + "QuitRogueScRsp": 1889, + "KLFPHOOPELB": 1890, + "ReviveRogueAvatarCsReq1": 1892, + "GetRogueAeonInfoCsReq": 1893, + "OpenRogueChestScRsp": 1895, + "FinishAeonDialogueGroupCsReq": 1897, + "OpenRogueChestCsReq": 1900, + "GetGachaCeilingCsReq": 1905, + "SetGachaDecideItemCsReq": 1906, + "ExchangeGachaCeilingScRsp": 1918, + "GetGachaInfoScRsp": 1919, + "DoGachaCsReq": 1931, + "ExchangeGachaCeilingCsReq": 1959, + "SetGachaDecideItemScRsp": 1970, + "GetGachaCeilingScRsp": 1971, + "GetGachaInfoCsReq": 1974, + "DoGachaScRsp": 1987, + "GetFirstTalkNpcCsReq": 2105, + "SelectInclinationTextCsReq": 2106, + "FinishFirstTalkNpcScRsp": 2118, + "GetNpcTakenRewardScRsp": 2119, + "GetFirstTalkByPerformanceNpcCsReq": 2127, + "GetTalkEventRewardCsReq": 2131, + "FinishFirstTalkByPerformanceNpcCsReq": 2135, + "GetFirstTalkByPerformanceNpcScRsp": 2152, + "FinishFirstTalkNpcCsReq": 2159, + "SelectInclinationTextScRsp": 2170, + "GetFirstTalkNpcScRsp": 2171, + "GetNpcTakenRewardCsReq": 2174, + "TakeTalkRewardScRsp": 2187, + "FinishFirstTalkByPerformanceNpcScRsp": 2198, + "RaidKickByServer": 2209, + "SetClientRaidTargetCountScRsp": 2210, + "ChallengeRaidNotify": 2211, + "DelSaveRaidScNotify": 2215, + "GetAllSaveRaidScRsp": 2216, + "TakeChallengeRaidRewardCsReq": 2224, + "GetSaveRaidScRsp": 2225, + "GetRaidInfoScRsp": 2226, + "RaidInfoNotify": 2230, + "SetClientRaidTargetCountCsReq": 2232, + "GetChallengeRaidInfoScRsp": 2234, + "GetChallengeRaidInfoCsReq": 2235, + "TakeChallengeRaidRewardScRsp": 2237, + "StartRaidScRsp": 2238, + "GetSaveRaidCsReq": 2241, + "StartRaidCsReq": 2242, + "GetAllSaveRaidCsReq": 2244, + "GetRaidInfoCsReq": 2245, + "LeaveRaidCsReq": 2248, + "LeaveRaidScRsp": 2250, + "GetFiveDimGameDataScRsp": 2254, + "UpdateFiveDimGameDataScRsp": 2255, + "LeaveFiveDimGameScRsp": 2257, + "UpdateFiveDimGameDataCsReq": 2258, + "FiveDimJumpEnergyChangeScNotify": 2259, + "GetFiveDimMoneyDataScRsp": 2260, + "EnterFiveDimGameCsReq": 2261, + "GetFiveDimGameDataCsReq": 2262, + "GetFiveDimMoneyScRsp": 2263, + "EnterFiveDimGameScRsp": 2265, + "LeaveFiveDimGameCsReq": 2266, + "FiveDimGameDataUpdateScNotify": 2269, + "FiveDimGameTransferCsReq": 2270, + "FiveDimMoneyChangeScNotify": 2271, + "FiveDimGameTransferScRsp": 2272, + "GetFiveDimMoneyCsReq": 2273, + "GetFiveDimMoneyDataCsReq": 2275, + "GetArchiveDataScRsp": 2319, + "GetUpdatedArchiveDataCsReq": 2331, + "GetArchiveDataCsReq": 2374, + "GetUpdatedArchiveDataScRsp": 2387, + "GetBigDataAllRecommendScRsp": 2411, + "GetBigDataRecommendScRsp": 2424, + "GetBigDataRecommendCsReq": 2434, + "GetBigDataAllRecommendCsReq": 2437, + "TakeMultipleExpeditionRewardScRsp": 2515, + "TakeMultipleExpeditionRewardCsReq": 2516, + "AcceptMultipleExpeditionCsReq": 2525, + "ExpeditionDataChangeScNotify": 2537, + "GetExpeditionDataScRsp": 2538, + "GetExpeditionDataCsReq": 2542, + "AcceptMultipleExpeditionScRsp": 2544, + "ElfRestaurantBuyShopItemScRsp": 2553, + "ElfRestaurantUpgradeRecipeLevelScRsp": 2556, + "ElfRestaurantUpgradeRecipeLevelCsReq": 2558, + "ElfRestaurantUpgradeFacilityLevelScRsp": 2559, + "ElfRestaurantRecycleSeedScRsp": 2560, + "ElfRestaurantPlantSeedScRsp": 2561, + "SetElfRestaurantPlayRecipeScRsp": 2563, + "ElfRestaurantTakeVillagerRewardScRsp": 2564, + "ElfRestaurantUpgradeFacilityLevelCsReq": 2565, + "ElfRestaurantUpgradeEmployeeLevelScRsp": 2566, + "ElfRestaurantClientStatusCsReq": 2567, + "ElfRestaurantFinishTradeOrderScRsp": 2568, + "SettleElfRestaurantPlayScRsp": 2572, + "SetElfRestaurantPlayRecipeCsReq": 2573, + "ElfRestaurantBuyFieldScRsp": 2574, + "ElfRestaurantHarvestCropScRsp": 2576, + "ElfRestaurantTakeVillagerRewardCsReq": 2577, + "ElfRestaurantFinishTradeOrderCsReq": 2579, + "SettleElfRestaurantPlayCsReq": 2581, + "ElfRestaurantRecycleSeedCsReq": 2582, + "ElfRestaurantBuyFieldCsReq": 2584, + "ElfRestaurantDataChangeNotify": 2585, + "ElfRestaurantPlantSeedCsReq": 2587, + "GetElfRestaurantDataScRsp": 2588, + "ElfRestaurantClientStatusScRsp": 2590, + "GetElfRestaurantDataCsReq": 2592, + "ElfRestaurantUpgradeEmployeeLevelCsReq": 2594, + "ElfRestaurantHarvestCropCsReq": 2595, + "ElfRestaurantBuyShopItemCsReq": 2596, + "EnterElfRestaurantNextDayCsReq": 2598, + "EnterElfRestaurantNextDayScRsp": 2600, + "StartTrialActivityScRsp": 2601, + "SubmitMaterialSubmitActivityMaterialScRsp": 2604, + "GetActivityScheduleConfigCsReq": 2605, + "GetMaterialSubmitActivityDataScRsp": 2608, + "ChooseAvatarCsReq": 2609, + "ChooseAvatarScRsp": 2613, + "StartTrialActivityCsReq": 2616, + "TakeRewardCsReq": 2617, + "GetLoginActivityScRsp": 2619, + "GetDataCsReq": 2630, + "TakeLoginActivityRewardCsReq": 2631, + "CurTrialActivityScNotify": 2636, + "GetDataScRsp": 2638, + "GetTrialActivityDataCsReq": 2639, + "EnterTrialActivityStageCsReq": 2641, + "TakeTrialActivityRewardScRsp": 2645, + "TrialActivityDataChangeScNotify": 2655, + "TakeMaterialSubmitActivityRewardScRsp": 2657, + "GetTrialActivityDataScRsp": 2664, + "TakeRewardScRsp": 2669, + "GetActivityScheduleConfigScRsp": 2671, + "GetLoginActivityCsReq": 2674, + "GetMaterialSubmitActivityDataCsReq": 2675, + "LeaveTrialActivityScRsp": 2684, + "LeaveTrialActivityCsReq": 2685, + "TakeLoginActivityRewardScRsp": 2687, + "SubmitMaterialSubmitActivityMaterialCsReq": 2692, + "TakeMaterialSubmitActivityRewardCsReq": 2694, + "TakeTrialActivityRewardCsReq": 2695, + "EnterTrialActivityStageScRsp": 2700, + "FinishItemIdCsReq": 2705, + "FinishPerformSectionIdCsReq": 2706, + "FinishSectionIdScRsp": 2718, + "GetNpcMessageGroupScRsp": 2719, + "GetMissionMessageCsReq": 2727, + "GetNpcStatusCsReq": 2731, + "FinishMultiPerformSectionIdCsReq": 2735, + "UnfrozenContactScNotify": 2747, + "GetMissionMessageScRsp": 2752, + "FinishSectionIdCsReq": 2759, + "FinishPerformSectionIdScRsp": 2770, + "FinishItemIdScRsp": 2771, + "GetNpcMessageGroupCsReq": 2774, + "GetNpcStatusScRsp": 2787, + "FinishMultiPerformSectionIdScRsp": 2798, + "SetDisplayAvatarCsReq": 2805, + "SetIsDisplayAvatarInfoScRsp": 2818, + "GetPlayerBoardDataScRsp": 2819, + "SetSignatureScRsp": 2827, + "SetHeadIconCsReq": 2831, + "SetAssistAvatarScRsp": 2835, + "SetPersonalCardScRsp": 2847, + "SetAssistAvatarCsReq": 2852, + "SetIsDisplayAvatarInfoCsReq": 2859, + "SetSignatureCsReq": 2870, + "SetDisplayAvatarScRsp": 2871, + "GetPlayerBoardDataCsReq": 2874, + "SetHeadIconScRsp": 2887, + "SetPersonalCardCsReq": 2898, + "GetFriendDevelopmentInfoScRsp": 2901, + "GetFriendRecommendListInfoScRsp": 2904, + "GetFriendApplyListInfoCsReq": 2905, + "SyncApplyFriendScNotify": 2906, + "SyncAddBlacklistScNotify": 2908, + "GetPlatformPlayerInfoScRsp": 2912, + "ReportPlayerScRsp": 2915, + "GetFriendDevelopmentInfoCsReq": 2916, + "ApplyFriendScRsp": 2918, + "GetFriendListInfoScRsp": 2919, + "SetFriendMarkScRsp": 2920, + "TakeAssistRewardCsReq": 2922, + "DeleteBlacklistCsReq": 2923, + "HandleFriendScRsp": 2927, + "DeleteBlacklistScRsp": 2928, + "SetForbidOtherApplyFriendScRsp": 2929, + "GetFriendRecommendLineupDetailScRsp": 2930, + "GetPlayerDetailInfoCsReq": 2931, + "DeleteFriendCsReq": 2935, + "GetFriendRecommendLineupDetailCsReq": 2936, + "GetFriendAssistListScRsp": 2939, + "GetFriendLoginInfoCsReq": 2943, + "GetAssistHistoryScRsp": 2944, + "GetFriendBattleRecordDetailScRsp": 2945, + "SetFriendMarkCsReq": 2946, + "SyncDeleteFriendScNotify": 2947, + "AddBlacklistCsReq": 2948, + "TakeAssistRewardScRsp": 2949, + "GetCurAssistScRsp": 2950, + "SetAssistCsReq": 2951, + "SyncHandleFriendScNotify": 2952, + "GetFriendLoginInfoScRsp": 2953, + "SetAssistScRsp": 2954, + "SetFriendRemarkNameScRsp": 2957, + "ApplyFriendCsReq": 2959, + "SetAllowOtherApplyFriendCsReq": 2960, + "GetAssistListCsReq": 2962, + "SearchPlayerScRsp": 2963, + "CurAssistChangedNotify": 2966, + "SearchPlayerCsReq": 2967, + "GetAssistHistoryCsReq": 2968, + "HandleFriendCsReq": 2970, + "GetFriendApplyListInfoScRsp": 2971, + "GetFriendListInfoCsReq": 2974, + "AddBlacklistScRsp": 2975, + "GetFriendAssistListCsReq": 2978, + "GetAssistListScRsp": 2980, + "NewAssistHistoryNotify": 2981, + "ReportPlayerCsReq": 2982, + "GetFriendRecommendLineupScRsp": 2984, + "GetFriendRecommendLineupCsReq": 2985, + "GetPlayerDetailInfoScRsp": 2987, + "GetCurAssistCsReq": 2989, + "GetPlatformPlayerInfoCsReq": 2990, + "GetFriendRecommendListInfoCsReq": 2992, + "SetFriendRemarkNameCsReq": 2994, + "GetFriendBattleRecordDetailCsReq": 2995, + "DeleteFriendScRsp": 2998, + "TakeBpRewardScRsp": 3005, + "TakeAllRewardScRsp": 3006, + "TakeAllRewardCsReq": 3018, + "BuyBpLevelScRsp": 3059, + "BuyBpLevelCsReq": 3071, + "BattlePassInfoNotify": 3074, + "TakeBpRewardCsReq": 3087, + "UnlockBackGroundMusicCsReq": 3105, + "EGDFPDHNOKO": 3118, + "GetJukeboxDataScRsp": 3119, + "PlayBackGroundMusicCsReq": 3131, + "FKLMGEFILDI": 3159, + "UnlockBackGroundMusicScRsp": 3171, + "GetJukeboxDataCsReq": 3174, + "PlayBackGroundMusicScRsp": 3187, + "SharePunkLordMonsterCsReq": 3205, + "GetKilledPunkLordMonsterDataCsReq": 3215, + "SummonPunkLordMonsterScRsp": 3218, + "GetPunkLordMonsterDataScRsp": 3219, + "GetKilledPunkLordMonsterDataScRsp": 3223, + "StartPunkLordRaidCsReq": 3231, + "TakePunkLordPointRewardCsReq": 3235, + "PunkLordMonsterInfoScNotify": 3247, + "GetPunkLordDataCsReq": 3248, + "PunkLordDataChangeNotify": 3251, + "GetPunkLordBattleRecordCsReq": 3254, + "SummonPunkLordMonsterCsReq": 3259, + "TakeKilledPunkLordMonsterScoreCsReq": 3262, + "PunkLordMonsterKilledNotify": 3263, + "SharePunkLordMonsterScRsp": 3271, + "GetPunkLordMonsterDataCsReq": 3274, + "GetPunkLordDataScRsp": 3275, + "TakeKilledPunkLordMonsterScoreScRsp": 3280, + "PunkLordBattleResultScNotify": 3282, + "StartPunkLordRaidScRsp": 3287, + "GetPunkLordBattleRecordScRsp": 3289, + "PunkLordRaidTimeOutScNotify": 3292, + "TakePunkLordPointRewardScRsp": 3298, + "DailyActiveInfoNotify": 3305, + "TakeApRewardScRsp": 3319, + "GetDailyActiveInfoCsReq": 3331, + "TakeAllApRewardScRsp": 3359, + "TakeAllApRewardCsReq": 3371, + "TakeApRewardCsReq": 3374, + "GetDailyActiveInfoScRsp": 3387, + "GetRndOptionScRsp": 3419, + "DailyFirstMeetPamCsReq": 3431, + "GetRndOptionCsReq": 3474, + "DailyFirstMeetPamScRsp": 3487, + "GetReplayTokenScRsp": 3519, + "GetReplayTokenCsReq": 3574, + "EnterFightActivityStageScRsp": 3605, + "GetFightActivityDataScRsp": 3619, + "FightActivityDataChangeScNotify": 3631, + "TakeFightActivityRewardScRsp": 3659, + "TakeFightActivityRewardCsReq": 3671, + "GetFightActivityDataCsReq": 3674, + "EnterFightActivityStageCsReq": 3687, + "TrainRefreshTimeNotify": 3705, + "TakeTrainVisitorBehaviorRewardCsReq": 3706, + "GetTrainVisitorRegisterScRsp": 3718, + "TrainVisitorBehaviorFinishScRsp": 3719, + "NewSupplementVisitorCsReq": 3727, + "NPEBOLNHFAL": 3731, + "NewSupplementVisitorScRsp": 3752, + "GetTrainVisitorRegisterCsReq": 3759, + "TakeTrainVisitorBehaviorRewardScRsp": 3770, + "TrainVisitorRewardSendNotify": 3771, + "TrainVisitorBehaviorFinishCsReq": 3774, + "EIDKOEHEAAA": 3787, + "TextJoinBatchSaveCsReq": 3805, + "OGFBEAPMGJM": 3819, + "TextJoinQueryCsReq": 3831, + "TextJoinBatchSaveScRsp": 3871, + "KIJJCOBOJFM": 3874, + "TextJoinQueryScRsp": 3887, + "GetPrivateChatHistoryCsReq": 3905, + "GetChatEmojiListCsReq": 3906, + "GetChatFriendHistoryScRsp": 3918, + "SendMsgScRsp": 3919, + "MarkChatEmojiCsReq": 3927, + "RevcMsgScNotify": 3931, + "BatchMarkChatEmojiCsReq": 3935, + "GetLoginChatInfoCsReq": 3947, + "GetLoginChatInfoScRsp": 3948, + "MarkChatEmojiScRsp": 3952, + "GetChatFriendHistoryCsReq": 3959, + "GetChatEmojiListScRsp": 3970, + "GetPrivateChatHistoryScRsp": 3971, + "SendMsgCsReq": 3974, + "BatchMarkChatEmojiScRsp": 3998, + "AcceptedPamMissionExpireScRsp": 4019, + "SyncAcceptedPamMissionNotify": 4031, + "AcceptedPamMissionExpireCsReq": 4074, + "LIOGDHEFOGD": 4105, + "CancelAutoConversionCacheCsReq": 4106, + "GetOrigamiPropInfoScRsp": 4108, + "GetUnreleasedBlockInfoScRsp": 4112, + "UpdateMovieRacingDataScRsp": 4115, + "ShareScRsp": 4119, + "CycleScoreRewardDestroyExceedRewardScRsp": 4120, + "GetSwitchMascotDataCsReq": 4122, + "GetWolfBroShootingDataCsReq": 4123, + "IGBJDKIEDJL": 4127, + "GetGunPlayDataScRsp": 4128, + "CycleScoreRewardTakeRewardScRsp": 4129, + "GetShareDataCsReq": 4131, + "TriggerVoiceCsReq": 4135, + "GetPlayerForbidCsReq": 4139, + "CycleScoreRewardGetDataCsReq": 4143, + "CancelDirectDeliveryNoticeCsReq": 4144, + "CycleScoreRewardDestroyExceedRewardCsReq": 4146, + "SubmitOrigamiItemCsReq": 4147, + "SubmitOrigamiItemScRsp": 4148, + "GetSwitchMascotDataScRsp": 4149, + "MazeKillDirectScRsp": 4150, + "DifficultyAdjustmentUpdateDataCsReq": 4151, + "IDENFKIJICM": 4152, + "CycleScoreRewardGetDataScRsp": 4153, + "DifficultyAdjustmentUpdateDataScRsp": 4154, + "PlayerForbidScNotify": 4155, + "GetMovieRacingDataScRsp": 4157, + "CycleScoreRewardTakeRewardCsReq": 4160, + "DifficultyAdjustmentGetDataCsReq": 4162, + "UpdateGunPlayDataScRsp": 4163, + "GetPlayerForbidScRsp": 4164, + "SwitchMascotUpdateScNotify": 4166, + "UpdateWolfBroShootingDataCsReq": 4167, + "DirectDeliveryScNotify": 4168, + "CancelAutoConversionCacheScRsp": 4170, + "EHNLGMEGIJP": 4171, + "ShareCsReq": 4174, + "GetOrigamiPropInfoCsReq": 4175, + "EABIMAPLLLL": 4178, + "DifficultyAdjustmentGetDataScRsp": 4180, + "CancelDirectDeliveryNoticeScRsp": 4181, + "UpdateMovieRacingDataCsReq": 4182, + "GetShareDataScRsp": 4187, + "MazeKillDirectCsReq": 4189, + "GetUnreleasedBlockInfoCsReq": 4190, + "GetMovieRacingDataCsReq": 4194, + "TriggerVoiceScRsp": 4198, + "StartBoxingClubBattleCsReq": 4205, + "BoxingClubRewardScNotify": 4206, + "GiveUpBoxingClubChallengeScRsp": 4218, + "GetBoxingClubInfoScRsp": 4219, + "ChooseBoxingClubResonanceCsReq": 4227, + "MatchBoxingClubOpponentCsReq": 4231, + "SetBoxingClubResonanceCsReq": 4235, + "ChooseBoxingClubStageOptionalBuffCsReq": 4247, + "ChooseBoxingClubStageOptionalBuffScRsp": 4248, + "ChooseBoxingClubResonanceScRsp": 4252, + "GiveUpBoxingClubChallengeCsReq": 4259, + "BoxingClubChallengeUpdateScNotify": 4270, + "StartBoxingClubBattleScRsp": 4271, + "GetBoxingClubInfoCsReq": 4274, + "MatchBoxingClubOpponentScRsp": 4287, + "SetBoxingClubResonanceScRsp": 4298, + "MuseumRandomEventQueryCsReq": 4304, + "SetStuffToAreaCsReq": 4305, + "SyncGetStuffScNotify": 4306, + "SyncMuseumFundsChangedScNotify": 4308, + "RemoveStuffFromAreaScRsp": 4318, + "GetMuseumInfoScRsp": 4319, + "SyncMuseumTargetStartNotify": 4323, + "FinishCurTurnCsReq": 4327, + "SyncMuseumTargetMissionFinishNotify": 4328, + "BuyNpcStuffCsReq": 4331, + "UpgradeAreaCsReq": 4335, + "UpgradeAreaStatCsReq": 4347, + "UpgradeAreaStatScRsp": 4348, + "FinishCurTurnScRsp": 4352, + "MuseumRandomEventSelectCsReq": 4357, + "RemoveStuffFromAreaCsReq": 4359, + "TakeCollectRewardRsp": 4362, + "TakeCollectRewardCsReq": 4363, + "SyncMuseumTargetRewardNotify": 4367, + "SyncGetExhibitScNotify": 4370, + "SetStuffToAreaScRsp": 4371, + "GetMuseumInfoCsReq": 4374, + "SyncMuseumInfoChangedScNotify": 4375, + "MuseumRandomEventSelectScRsp": 4382, + "BuyNpcStuffScRsp": 4387, + "SyncMuseumRandomEventStartScNotify": 4392, + "MuseumRandomEventQueryScRsp": 4394, + "UpgradeAreaScRsp": 4398, + "InteractTreasureDungeonGridScRsp": 4404, + "FightTreasureDungeonMonsterScRsp": 4408, + "QuitTreasureDungeonScRsp": 4415, + "TreasureDungeonFinishScNotify": 4419, + "GetTreasureDungeonActivityDataCsReq": 4427, + "EnterTreasureDungeonCsReq": 4435, + "OpenTreasureDungeonGridCsReq": 4447, + "OpenTreasureDungeonGridScRsp": 4448, + "GetTreasureDungeonActivityDataScRsp": 4452, + "UseTreasureDungeonItemScRsp": 4457, + "TreasureDungeonDataScNotify": 4474, + "FightTreasureDungeonMonsterCsReq": 4475, + "QuitTreasureDungeonCsReq": 4482, + "InteractTreasureDungeonGridCsReq": 4492, + "UseTreasureDungeonItemCsReq": 4494, + "EnterTreasureDungeonScRsp": 4498, + "TakeReturnExtraHcoinRewardScRsp": 4504, + "PlayerReturnTakePointRewardCsReq": 4505, + "PlayerReturnInfoQueryCsReq": 4506, + "PlayerReturnTakeRewardScRsp": 4518, + "PlayerReturnSignCsReq": 4519, + "PlayerReturnForceFinishScNotify": 4527, + "PlayerReturnSignScRsp": 4531, + "PlayerReturnTakeRelicScRsp": 4535, + "PlayerReturnTakeCustomRelicCsReq": 4547, + "PlayerReturnTakeCustomRelicScRsp": 4548, + "PlayerReturnTakeRelicCsReq": 4552, + "CEINLELLFLF": 4557, + "PlayerReturnTakeRewardCsReq": 4559, + "PlayerReturnInfoQueryScRsp": 4570, + "PlayerReturnTakePointRewardScRsp": 4571, + "FEIHGOEPANI": 4574, + "KFFEEDHEOOH": 4582, + "PlayerReturnPointChangeScNotify": 4587, + "TakeReturnExtraHcoinRewardCsReq": 4592, + "PlayerReturnExtraHcoinProgressScNotify": 4594, + "PlayerReturnTakeExtraHcoinScNotify": 4598, + "GetPlayerReturnMultiDropInfoScRsp": 4605, + "GetMultipleDropInfoScRsp": 4619, + "MultipleDropInfoScNotify": 4631, + "MultipleDropInfoNotify": 4671, + "GetMultipleDropInfoCsReq": 4674, + "GetPlayerReturnMultiDropInfoCsReq": 4687, + "AlleyEventChangeNotify": 4706, + "StartAlleyEventScRsp": 4718, + "GetAlleyInfoScRsp": 4719, + "LogisticsDetonateStarSkiffScRsp": 4722, + "DPFGBJFOFCE": 4723, + "TakePrestigeRewardCsReq": 4727, + "SaveLogisticsCsReq": 4728, + "LogisticsGameCsReq": 4731, + "AlleyPlacingGameCsReq": 4735, + "AlleyTakeEventRewardScRsp": 4744, + "LogisticsScoreRewardSyncInfoScNotify": 4749, + "GetSaveLogisticsMapScRsp": 4751, + "TakePrestigeRewardScRsp": 4752, + "PrestigeLevelUpScRsp": 4757, + "StartAlleyEventCsReq": 4759, + "AlleyShipmentEventEffectsScNotify": 4762, + "LogisticsInfoScNotify": 4763, + "ActivityRaidPlacingGameCsReq": 4766, + "SaveLogisticsScRsp": 4767, + "AlleyTakeEventRewardCsReq": 4768, + "MGMDNBOLBMO": 4770, + "GetAlleyInfoCsReq": 4774, + "GetSaveLogisticsMapCsReq": 4780, + "LogisticsDetonateStarSkiffCsReq": 4781, + "AlleyFundsScNotify": 4782, + "LogisticsGameScRsp": 4787, + "ActivityRaidPlacingGameScRsp": 4790, + "AlleyOrderChangedScNotify": 4792, + "PrestigeLevelUpCsReq": 4794, + "AlleyPlacingGameScRsp": 4798, + "AetherDivideRefreshEndlessScNotify": 4801, + "GetAetherDivideChallengeInfoCsReq": 4803, + "StartAetherDivideStageBattleScRsp": 4806, + "StartAetherDivideStageBattleCsReq": 4808, + "SwitchAetherDivideLineUpSlotScRsp": 4809, + "SetAetherDivideLineUpScRsp": 4810, + "AetherDivideLineupScNotify": 4813, + "AetherDivideSkillItemScNotify": 4814, + "SwitchAetherDivideLineUpSlotCsReq": 4815, + "TakeOffSkillCoreScRsp": 4816, + "AetherDivideRefreshEndlessCsReq": 4817, + "AetherDivideFinishChallengeScNotify": 4818, + "AetherDivideTakeChallengeRewardScRsp": 4821, + "AetherDivideSpiritExpUpScRsp": 4822, + "StartAetherDivideChallengeBattleScRsp": 4824, + "EquipSkillCoreScRsp": 4825, + "GetAetherDivideInfoScRsp": 4826, + "AetherDivideTainerInfoScNotify": 4827, + "GetAetherDivideChallengeInfoScRsp": 4829, + "StartAetherDivideSceneBattleCsReq": 4830, + "AetherDivideSpiritExpUpCsReq": 4831, + "SetAetherDivideLineUpCsReq": 4832, + "StartAetherDivideChallengeBattleCsReq": 4834, + "StartAetherDivideSceneBattleScRsp": 4835, + "DMAMEFJBCLE": 4838, + "AetherDivideRefreshEndlessScRsp": 4840, + "EquipAetherDividePassiveSkillCsReq": 4841, + "BGPNJKENONA": 4842, + "AetherDivideTakeChallengeRewardCsReq": 4843, + "ClearAetherDividePassiveSkillCsReq": 4844, + "GetAetherDivideInfoCsReq": 4845, + "AetherDivideSpiritInfoScNotify": 4846, + "LeaveAetherDivideSceneCsReq": 4848, + "LeaveAetherDivideSceneScRsp": 4850, + "JoinBenefitActivityCsReq": 4880, + "JoinBenefitActivityScRsp": 4885, + "GetBenefitActivityInfo": 4888, + "GetBenefitActivityInfoCsReq": 4892, + "TakeBenefitActivityRewardCsReq": 4898, + "TakeBenefitActivityRewardScRsp": 4900, + "EnterFantasticStoryActivityStageScRsp": 4905, + "GetFantasticStoryActivityDataScRsp": 4919, + "EPMJEKOHNME": 4931, + "FantasticStoryActivityBattleEndScNotify": 4971, + "GetFantasticStoryActivityDataCsReq": 4974, + "EnterFantasticStoryActivityStageCsReq": 4987, + "UnlockChatBubbleScNotify": 5105, + "SelectPhoneCaseCsReq": 5106, + "UnlockPhoneThemeScNotify": 5118, + "GetPhoneDataScRsp": 5119, + "UnlockPhoneCaseScNotify": 5127, + "SelectChatBubbleCsReq": 5131, + "SelectPhoneThemeScRsp": 5159, + "SelectPhoneCaseScRsp": 5170, + "SelectPhoneThemeCsReq": 5171, + "GetPhoneDataCsReq": 5174, + "SelectChatBubbleScRsp": 5187, + "RogueModifierSelectCellScRsp": 5305, + "RogueModifierDelNotify": 5306, + "RogueModifierUpdateNotify": 5318, + "RogueModifierAddNotify": 5331, + "RogueModifierStageStartNotify": 5370, + "RogueModifierSelectCellCsReq": 5387, + "ChessRogueSelectCellCsReq": 5402, + "ChessRogueSkipTeachingLevelCsReq": 5403, + "ChessRogueConfirmRollScRsp": 5405, + "ChessRogueEnterNextLayerScRsp": 5407, + "GetChessRogueNousStoryInfoCsReq": 5410, + "ChessRogueNousDiceSurfaceUnlockNotify": 5412, + "ChessRogueUpdateLevelBaseInfoScNotify": 5415, + "SyncChessRogueNousMainStoryScNotify": 5418, + "EnhanceRogueBuffCsReq": 5424, + "ChessRogueNousGetRogueTalentInfoCsReq": 5426, + "ChessRogueSkipTeachingLevelScRsp": 5429, + "ChessRogueCheatRollScRsp": 5432, + "ChessRogueUpdateAeonModifierValueScNotify": 5433, + "ChessRogueNousGetRogueTalentInfoScRsp": 5434, + "ChessRogueNousDiceUpdateNotify": 5436, + "ChessRogueCheatRollCsReq": 5437, + "ChessRogueEnterNextLayerCsReq": 5441, + "ReviveRogueAvatarScRsp": 5444, + "ChessRogueLeaveCsReq": 5445, + "ChessRogueLeaveScRsp": 5446, + "ChessRogueQueryCsReq": 5449, + "SyncChessRogueNousValueScNotify": 5452, + "ChessRogueGiveUpCsReq": 5457, + "PickRogueAvatarCsReq": 5458, + "ChessRogueGiveUpScRsp": 5459, + "ChessRogueQueryScRsp": 5460, + "ChessRogueEnterCsReq": 5461, + "GetChessRogueNousStoryInfoScRsp": 5462, + "GetRogueBuffEnhanceInfoCsReq": 5470, + "ChessRogueReRollDiceScRsp": 5490, + "ChessRogueStartScRsp": 5491, + "PickRogueAvatarScRsp": 5494, + "GetRogueBuffEnhanceInfoScRsp": 5495, + "ChessRogueQuestFinishNotify": 5496, + "ChessRogueNousEditDiceScRsp": 5497, + "EnterChessRogueAeonRoomScRsp": 5499, + "ChessRogueUpdateMoneyInfoScNotify": 5502, + "ChessRogueSelectCellScRsp": 5503, + "ChessRogueQuitCsReq": 5510, + "ChessRogueEnterScRsp": 5511, + "SyncChessRogueMainStoryFinishScNotify": 5514, + "ChessRogueUpdateDicePassiveAccumulateValueScNotify": 5515, + "ChessRogueUpdateReviveInfoScNotify": 5517, + "ChessRogueChangeAeonDimensionNotify": 5521, + "ChessRogueLayerSettlement": 5526, + "ChessRogueUpdateUnlockLevelScNotify": 5527, + "ChessRogueRollDiceScRsp": 5530, + "ChessRogueUpdateDiceInfoScNotify": 5532, + "ChessRogueCellUpdateNotify": 5536, + "ChessRogueNousEnableRogueTalentScRsp": 5537, + "FinishChessRogueSubStoryCsReq": 5538, + "ChessRogueReRollDiceCsReq": 5542, + "ChessRogueNousEnableRogueTalentCsReq": 5547, + "ChessRogueEnterCellCsReq": 5550, + "GetChessRogueStoryAeonTalkInfoScRsp": 5554, + "ChessRogueEnterCellScRsp": 5555, + "ChessRogueConfirmRollCsReq": 5559, + "FinishChessRogueSubStoryScRsp": 5564, + "SyncChessRogueNousSubStoryScNotify": 5565, + "GetChessRogueStoryInfoCsReq": 5567, + "ChessRogueUpdateActionPointScNotify": 5568, + "ReviveRogueAvatarCsReq": 5570, + "EnhanceRogueBuffScRsp": 5571, + "ChessRogueRollDiceCsReq": 5573, + "ChessRogueUpdateAllowedSelectCellScNotify": 5574, + "SelectChessRogueSubStoryCsReq": 5580, + "GetChessRogueStoryAeonTalkInfoCsReq": 5583, + "ChessRogueStartCsReq": 5588, + "GetChessRogueStoryInfoScRsp": 5589, + "ChessRogueNousEditDiceCsReq": 5590, + "ChessRogueQuitScRsp": 5592, + "EnterChessRogueAeonRoomCsReq": 5595, + "SelectChessRogueSubStoryScRsp": 5600, + "GetRogueCommonDialogueDataScRsp": 5601, + "RogueGetGambleInfoScRsp": 5602, + "RogueShopBeginBattleScRsp": 5603, + "GetRogueShopMiracleInfoScRsp": 5605, + "BuyRogueShopMiracleScRsp": 5606, + "SyncRogueCommonDialogueOptionFinishScNotify": 5609, + "TakeEventHandbookRewardCsReq": 5612, + "GetRogueCommonDialogueDataCsReq": 5616, + "RogueWorkbenchGetInfoCsReq": 5617, + "MKNBFMJGKBM": 5618, + "PrepareRogueAdventureRoomCsReq": 5619, + "CommonRogueVirtualItemInfoScNotify": 5620, + "SetRogueExhibitionScRsp": 5621, + "GetRogueHandbookDataScRsp": 5622, + "StopRogueAdventureRoomCsReq": 5623, + "BuyRogueShopFormulaScRsp": 5624, + "RogueWorkBenchLevelUpMagicScepterReq": 5626, + "BuyRogueShopBuffScRsp": 5627, + "StopRogueAdventureRoomScRsp": 5628, + "UpdateRogueAdventureRoomScoreCsReq": 5629, + "FinishRogueCommonDialogueScRsp": 5630, + "PrepareRogueAdventureRoomScRsp": 5631, + "SetRogueExhibitionCsReq": 5632, + "SetRogueCollectionScRsp": 5633, + "RogueNpcDisappearScRsp": 5635, + "FinishRogueCommonDialogueCsReq": 5636, + "SyncRogueCommonDialogueDataScNotify": 5638, + "GetRogueShopFormulaInfoCsReq": 5640, + "HandleRogueCommonPendingActionScRsp": 5641, + "RogueDoGambleScRsp": 5642, + "TakeEventHandbookRewardScRsp": 5643, + "CommonRogueUpdateScNotify": 5645, + "UpdateRogueAdventureRoomScoreScRsp": 5646, + "GetRogueAdventureRoomInfoScRsp": 5647, + "SyncRogueHandbookDataUpdateScNotify": 5649, + "RogueNpcDisappearCsReq": 5652, + "SyncRogueCommonActionResultScNotify": 5653, + "RogueBuffDropReq": 5655, + "SetRogueCollectionCsReq": 5658, + "GetRogueShopBuffInfoScRsp": 5659, + "SyncRogueCommonPendingActionScNotify": 5660, + "RogueWorkbenchHandleFuncScRsp": 5661, + "TakeRogueMiracleHandbookRewardCsReq": 5666, + "RogueWorkbenchGetInfoScRsp": 5669, + "LECIGPPCMAB": 5670, + "GetRogueShopBuffInfoCsReq": 5671, + "PJFPIPIPOJN": 5674, + "RogueShopBeginBattleCsReq": 5677, + "GetRogueHandbookDataCsReq": 5681, + "SelectRogueCommonDialogueOptionScRsp": 5684, + "SelectRogueCommonDialogueOptionCsReq": 5685, + "GetRogueShopMiracleInfoCsReq": 5687, + "GetRogueShopFormulaInfoScRsp": 5688, + "TakeRogueMiracleHandbookRewardScRsp": 5690, + "RogueGetGambleInfoCsReq": 5691, + "RogueDoGambleCsReq": 5693, + "CommonRogueQueryScRsp": 5695, + "GetRogueAdventureRoomInfoCsReq": 5698, + "BuyRogueShopMiracleCsReq": 5699, + "CommonRogueQueryCsReq": 5700, + "StartBattleCollegeScRsp": 5705, + "GetBattleCollegeDataScRsp": 5719, + "SyncBattleCollegeDataChangeScNotify": 5731, + "GetBattleCollegeDataCsReq": 5774, + "StartBattleCollegeCsReq": 5787, + "HeliobusEnterBattleCsReq": 5804, + "HeliobusSnsPostCsReq": 5805, + "HeliobusSnsCommentCsReq": 5806, + "HeliobusChallengeUpdateScNotify": 5815, + "HeliobusSnsLikeScRsp": 5818, + "HeliobusActivityDataScRsp": 5819, + "HeliobusLineupUpdateScNotify": 5823, + "HeliobusSnsUpdateScNotify": 5827, + "HeliobusSnsReadCsReq": 5831, + "HeliobusUpgradeLevelCsReq": 5835, + "HeliobusUnlockSkillScNotify": 5847, + "HeliobusSelectSkillCsReq": 5848, + "HeliobusInfoChangedScNotify": 5852, + "HeliobusStartRaidCsReq": 5857, + "HeliobusSnsLikeCsReq": 5859, + "HeliobusSnsCommentScRsp": 5870, + "HeliobusSnsPostScRsp": 5871, + "HeliobusActivityDataCsReq": 5874, + "HeliobusSelectSkillScRsp": 5875, + "HeliobusStartRaidScRsp": 5882, + "HeliobusSnsReadScRsp": 5887, + "HeliobusEnterBattleScRsp": 5894, + "HeliobusUpgradeLevelScRsp": 5898, + "ILDOMDOFEPK": 5930, + "LLCOHLNBNGM": 5935, + "GetAllRedDotDataScRsp": 5938, + "GetAllRedDotDataCsReq": 5942, + "UpdateRedDotDataCsReq": 5948, + "UpdateRedDotDataScRsp": 5950, + "FateTakeExpRewardCsReq": 5953, + "FateSyncPendingActionScNotify": 5956, + "FateSyncActionResultScNotify": 5958, + "FateHandlePendingActionScRsp": 5959, + "FateShopRefreshGoodsScRsp": 5960, + "FateShopBuyGoodsScRsp": 5961, + "FateHouguSelectReq": 5965, + "FateShopLeaveScRsp": 5966, + "JOLGFGCFMAL": 5972, + "FateChangeLineupScRsp": 5974, + "FateShopLockGoodsScRsp": 5975, + "FateShopSellBuffScRsp": 5976, + "FateTakeExpRewardScRsp": 5979, + "FateBattleStartCsReq": 5980, + "FGEMJLFIHHL": 5981, + "FateShopRefreshGoodsCsReq": 5982, + "FateChangeLineupCsReq": 5984, + "FateBattleStartScRsp": 5985, + "FateShopBuyGoodsCsReq": 5987, + "FateQueryScRsp": 5988, + "FateShopLockGoodsCsReq": 5991, + "FateQueryCsReq": 5992, + "FateShopLeaveCsReq": 5994, + "FateShopSellBuffCsReq": 5995, + "FateSettleScNotify": 5996, + "FateStartCsReq": 5998, + "FateStartScRsp": 6000, + "TakeRogueEndlessActivityAllBonusRewardCsReq": 6001, + "TakeRogueEndlessActivityAllBonusRewardScRsp": 6002, + "GetRogueEndlessActivityDataCsReq": 6003, + "TakeRogueEndlessActivityPointRewardCsReq": 6004, + "TakeRogueEndlessActivityPointRewardScRsp": 6005, + "GetRogueEndlessActivityDataScRsp": 6006, + "EnterRogueEndlessActivityStageCsReq": 6007, + "RogueEndlessActivityBattleEndScNotify": 6008, + "EnterRogueEndlessActivityStageScRsp": 6010, + "RogueTournWeekChallengeUpdateScNotify": 6011, + "RogueTournPersonaRoomCardZoneUpdateScNotify": 6012, + "RogueTournAreaUpdateScNotify": 6014, + "RogueTournReviveCostUpdateScNotify": 6015, + "RogueTournTakeExpRewardCsReq": 6016, + "RogueTournEnterCsReq": 6018, + "RogueTournGetSeasonTalentInfoCsReq": 6019, + "RogueTournUseSuperRewardKeyCsReq": 6021, + "RogueTournLeaveRogueCocoonSceneCsReq": 6022, + "RogueTournSaveBuildRefCsReq": 6023, + "RogueTournEnablePermanentTalentCsReq": 6025, + "RogueTournConfirmSettleScRsp": 6026, + "RogueTournEnterRogueCocoonSceneScRsp": 6027, + "RogueTournEnterScRsp": 6028, + "RogueTournLeaveCsReq": 6029, + "RogueTournReEnterRogueCocoonStageScRsp": 6030, + "RogueTournRenameBuildRefScRsp": 6031, + "RogueTournRenameArchiveScRsp": 6032, + "RogueTournEnterLayerCsReq": 6033, + "RogueTournTriggerRoomSelectCsReq": 6035, + "RogueTournSettleCsReq": 6036, + "RogueTournGetAllArchiveScRsp": 6037, + "RogueTournReviveAvatarCsReq": 6038, + "RogueTournReEnterRogueCocoonStageCsReq": 6039, + "RogueTournGetArchiveRepositoryScRsp": 6040, + "RogueTournGetAllBuildRefScRsp": 6041, + "RougeTournRenameBuildRefCsReq": 6042, + "RogueTournDeleteBuildRefScRsp": 6043, + "RogueTournTriggerRoomSelectScRsp": 6044, + "RogueTournGetAllBuildRefCsReq": 6045, + "RogueTournGetArchiveRepositoryCsReq": 6046, + "RogueTournClearArchiveNameScNotify": 6048, + "RogueTournHandBookNotify": 6049, + "RogueTournGetCurRogueCocoonInfoScRsp": 6051, + "RogueTournDeleteArchiveCsReq": 6053, + "RogueTournReviveAvatarScRsp": 6054, + "RogueTournConfirmSettleCsReq": 6055, + "RogueTournGetCurRogueCocoonInfoCsReq": 6056, + "RogueTournGetPermanentTalentInfoScRsp": 6057, + "RogueTournGetAllArchiveCsReq": 6058, + "RogueTournEnterRoomCsReq": 6059, + "RogueTournEnableSeasonTalentCsReq": 6060, + "RogueTournSettleScRsp": 6061, + "RogueTournGetSeasonTalentInfoScRsp": 6062, + "RogueTournGetPermanentTalentInfoCsReq": 6063, + "RogueTournStartCsReq": 6064, + "RogueTournResetPermanentTalentScRsp": 6065, + "RogueTournDeleteArchiveScRsp": 6067, + "RogueTournDeleteBuildRefCsReq": 6068, + "RogueTournEnableSeasonTalentScRsp": 6069, + "RogueTournResetPermanentTalentCsReq": 6070, + "RogueTournSaveBuildRefScRsp": 6071, + "RogueTournQueryCsReq": 6072, + "RogueTournGetSettleInfoCsReq": 6073, + "RogueTournStartScRsp": 6074, + "RogueTournLevelInfoUpdateScNotify": 6076, + "RogueTournLeaveScRsp": 6077, + "RogueTournTakeExpRewardScRsp": 6078, + "RogueTournRenameArchiveCsReq": 6079, + "RogueTournExpNotify": 6080, + "JGFPIJKOFCC": 6083, + "RogueTournTitanUpdateTitanBlessProgressScNotify": 6084, + "RogueTournEnterLayerScRsp": 6085, + "RogueTournGetMiscRealTimeDataScRsp": 6086, + "RogueTournEnablePermanentTalentScRsp": 6088, + "RogueTournEnterRoomScRsp": 6089, + "PALAOPCBAPC": 6090, + "RogueTournEnterRogueCocoonSceneCsReq": 6091, + "RogueTournQueryScRsp": 6092, + "RogueTournGetMiscRealTimeDataCsReq": 6094, + "RogueTournGetSettleInfoScRsp": 6095, + "RogueTournUseSuperRewardKeyScRsp": 6096, + "RogueTournBattleFailSettleInfoScNotify": 6098, + "RogueTournLeaveRogueCocoonSceneScRsp": 6100, + "UpdateServerPrefsCsReq": 6105, + "GetAllServerPrefsDataScRsp": 6119, + "GetServerPrefsDataCsReq": 6131, + "UpdateServerPrefsScRsp": 6171, + "GetAllServerPrefsDataCsReq": 6174, + "GetServerPrefsDataScRsp": 6187, + "GetStoryLineInfoScRsp": 6219, + "StoryLineInfoScNotify": 6231, + "StoryLineTrialAvatarChangeScNotify": 6259, + "ChangeStoryLineFinishScNotify": 6271, + "GetStoryLineInfoCsReq": 6274, + "SubmitEmotionItemCsReq": 6305, + "HeartDialScriptChangeScNotify": 6306, + "FinishEmotionDialoguePerformanceScRsp": 6318, + "GetHeartDialInfoScRsp": 6319, + "HeartDialTraceScriptScRsp": 6327, + "ChangeScriptEmotionCsReq": 6331, + "FinishEmotionDialoguePerformanceCsReq": 6359, + "HeartDialTraceScriptCsReq": 6370, + "SubmitEmotionItemScRsp": 6371, + "GetHeartDialInfoCsReq": 6374, + "ChangeScriptEmotionScRsp": 6387, + "TravelBrochurePageResetScRsp": 6404, + "TravelBrochureSelectMessageCsReq": 6405, + "TravelBrochureRemovePasterCsReq": 6406, + "TravelBrochureSetPageDescStatusScRsp": 6408, + "TravelBrochureGetDataScRsp": 6419, + "TravelBrochureUpdatePasterPosCsReq": 6427, + "TravelBrochurePageUnlockScNotify": 6431, + "DDHIIJAJIOO": 6435, + "TravelBrochureSetCustomValueCsReq": 6447, + "TravelBrochureSetCustomValueScRsp": 6448, + "TravelBrochureUpdatePasterPosScRsp": 6452, + "TravelBrochureApplyPasterListScRsp": 6457, + "TravelBrochureRemovePasterScRsp": 6470, + "TravelBrochureSelectMessageScRsp": 6471, + "TravelBrochureGetDataCsReq": 6474, + "TravelBrochureSetPageDescStatusCsReq": 6475, + "TravelBrochurePageResetCsReq": 6492, + "TravelBrochureApplyPasterListCsReq": 6494, + "AJDPPONBABG": 6510, + "MDDFDNHFCIL": 6511, + "HFJCGEMMPKB": 6515, + "LBMFCJLHNAA": 6516, + "KDMANLNDIOA": 6524, + "PHPEPPKFMJO": 6525, + "OKEFGCNNEKI": 6526, + "LJFEONHOKAG": 6530, + "NHIGCJFJGPF": 6532, + "DKJBGINLBPP": 6534, + "BPPICAMLGNB": 6535, + "LIKMPNFOKFF": 6537, + "ENPJFKDKLOB": 6538, + "DEAHIAKBGMJ": 6541, + "NHLGEDLKOKI": 6542, + "HKPFPMJBNEP": 6544, + "LJEFFBCJJEM": 6545, + "JOHDLJLGNHL": 6548, + "CPKCMNJKPOE": 6550, + "GetEraFlipperDataScRsp": 6554, + "EnterEraFlipperDataScRsp": 6555, + "ResetEraFlipperDataScRsp": 6557, + "EnterEraFlipperDataCsReq": 6558, + "ChangeEraFlipperDataCsReq": 6561, + "GetEraFlipperDataCsReq": 6562, + "ChangeEraFlipperDataScRsp": 6565, + "ResetEraFlipperDataCsReq": 6566, + "DMICEOCOGAD": 6570, + "GetLocalLegendDataScRsp": 6579, + "StartLocalLegendLevelCsReq": 6586, + "GetLocalLegendDataCsReq": 6587, + "EnterLocalLegendLevelScRsp": 6590, + "LocalLegendDataChangeNotify": 6591, + "CBFOJHEPNHE": 6605, + "CEJMCEEBCOG": 6619, + "EnterStrongChallengeActivityStageCsReq": 6631, + "FPMFICMFNGJ": 6674, + "EnterStrongChallengeActivityStageScRsp": 6687, + "SpaceZooMutateCsReq": 6705, + "SpaceZooDeleteCatCsReq": 6706, + "SpaceZooOpCatteryScRsp": 6718, + "SpaceZooDataScRsp": 6719, + "SpaceZooCatUpdateNotify": 6727, + "SpaceZooBornCsReq": 6731, + "SpaceZooExchangeItemScRsp": 6735, + "SpaceZooTakeScRsp": 6747, + "SpaceZooExchangeItemCsReq": 6752, + "SpaceZooOpCatteryCsReq": 6759, + "SpaceZooDeleteCatScRsp": 6770, + "SpaceZooMutateScRsp": 6771, + "SpaceZooDataCsReq": 6774, + "SpaceZooBornScRsp": 6787, + "SpaceZooTakeCsReq": 6798, + "DNNLHFKKNLM": 6804, + "DeployRotaterCsReq": 6805, + "LeaveMapRotationRegionCsReq": 6806, + "RemoveRotaterCsReq": 6808, + "RotateMapScRsp": 6818, + "EnterMapRotationRegionScRsp": 6819, + "GetMapRotationDataCsReq": 6827, + "InteractChargerCsReq": 6831, + "ResetMapRotationRegionCsReq": 6835, + "UpdateEnergyScNotify": 6848, + "GetMapRotationDataScRsp": 6852, + "RotateMapCsReq": 6859, + "LeaveMapRotationRegionScRsp": 6870, + "DeployRotaterScRsp": 6871, + "EnterMapRotationRegionCsReq": 6874, + "UpdateMapRotationDataScNotify": 6875, + "InteractChargerScRsp": 6887, + "RemoveRotaterScRsp": 6892, + "ResetMapRotationRegionScRsp": 6898, + "DoGachaInRollShopCsReq": 6904, + "GetRollShopInfoScRsp": 6908, + "TakeRollShopRewardScRsp": 6909, + "GetRollShopInfoCsReq": 6915, + "TakeRollShopRewardCsReq": 6916, + "DoGachaInRollShopScRsp": 6920, + "SubmitOfferingItemCsReq": 6924, + "OfferingInfoScNotify": 6927, + "GetOfferingInfoScRsp": 6928, + "TakeOfferingRewardScRsp": 6929, + "GetOfferingInfoCsReq": 6935, + "TakeOfferingRewardCsReq": 6936, + "SubmitOfferingItemScRsp": 6940, + "RaidCollectionDataScRsp": 6948, + "RaidCollectionDataCsReq": 6955, + "RaidCollectionEnterNextRaidScRsp": 6956, + "RaidCollectionEnterNextRaidCsReq": 6960, + "GetTelevisionActivityDataScRsp": 6968, + "TelevisionActivityBattleEndScNotify": 6969, + "GetTelevisionActivityDataCsReq": 6975, + "EnterTelevisionActivityStageScRsp": 6976, + "EnterTelevisionActivityStageCsReq": 6980, + "DrinkMakerCheersMakeDrinkScRsp": 6981, + "DrinkMakerCheersEnterNextGroupScRsp": 6982, + "MakeDrinkCsReq": 6984, + "DrinkMakerChallengeScRsp": 6985, + "MakeMissionDrinkScRsp": 6986, + "MakeMissionDrinkCsReq": 6987, + "GetDrinkMakerDataScRsp": 6988, + "EndDrinkMakerSequenceScRsp": 6989, + "DrinkMakerCheersMakeDrinkCsReq": 6990, + "DrinkMakerUpdateTipsNotify": 6991, + "DrinkMakerChallengeCsReq": 6992, + "GetDrinkMakerDayEndScNotify": 6993, + "DrinkMakerCheersEnterNextGroupCsReq": 6994, + "GetDrinkMakerDataCsReq": 6995, + "EndDrinkMakerSequenceCsReq": 6996, + "DrinkMakerCheersGetDataCsReq": 6998, + "DrinkMakerCheersGetDataScRsp": 6999, + "MakeDrinkScRsp": 7000, + "MonopolyEventSelectFriendCsReq": 7001, + "GetMonopolyDailyReportScRsp": 7002, + "MonopolyConfirmRandomScRsp": 7004, + "MonopolyMoveScRsp": 7006, + "MonopolyReRollRandomScRsp": 7008, + "DeleteSocialEventServerCacheScRsp": 7009, + "MonopolyGetRafflePoolInfoScRsp": 7011, + "MonopolyGuessBuyInformationCsReq": 7012, + "MonopolyGetDailyInitItemScRsp": 7014, + "MonopolyUpgradeAssetScRsp": 7015, + "GetMbtiReportScRsp": 7016, + "MonopolyGetRaffleTicketCsReq": 7017, + "MonopolyMoveCsReq": 7018, + "GetMonopolyInfoScRsp": 7019, + "MonopolyGetRegionProgressScRsp": 7021, + "MonopolyAcceptQuizScRsp": 7022, + "MonopolyGiveUpCurContentCsReq": 7023, + "MonopolyEventLoadUpdateScNotify": 7025, + "MonopolyTakeRaffleTicketRewardCsReq": 7026, + "MonopolySelectOptionScRsp": 7027, + "MonopolyGiveUpCurContentScRsp": 7028, + "GetSocialEventServerCacheScRsp": 7030, + "MonopolyActionResultScNotify": 7031, + "MonopolyGetRegionProgressCsReq": 7032, + "MonopolyScrachRaffleTicketScRsp": 7033, + "MonopolySttUpdateScNotify": 7034, + "DailyFirstEnterMonopolyActivityCsReq": 7035, + "GetSocialEventServerCacheCsReq": 7036, + "MonopolyConditionUpdateScNotify": 7037, + "DeleteSocialEventServerCacheCsReq": 7038, + "MonopolyDailySettleScNotify": 7039, + "MonopolyClickMbtiReportScRsp": 7040, + "MonopolyLikeCsReq": 7041, + "MonopolyClickCellScRsp": 7042, + "MonopolyGuessBuyInformationScRsp": 7043, + "MonopolyGameBingoFlipCardScRsp": 7044, + "GetMbtiReportCsReq": 7045, + "MonopolyRollRandomCsReq": 7047, + "MonopolyRollRandomScRsp": 7048, + "MonopolyGameCreateScNotify": 7049, + "MonopolyGameGachaScRsp": 7050, + "MonopolyGameRaiseRatioScRsp": 7051, + "MonopolyGuessDrawScNotify": 7053, + "MonopolyGameSettleScNotify": 7054, + "GetMonopolyFriendRankingListScRsp1": 7055, + "MonopolyBuyGoodsScRsp": 7057, + "MonopolyScrachRaffleTicketCsReq": 7058, + "MonopolyRollDiceScRsp": 7059, + "MonopolyQuizDurationChangeScNotify": 7060, + "MonopolyTakeRaffleTicketRewardScRsp": 7061, + "MonopolyContentUpdateScNotify": 7062, + "MonopolyCheatDiceScRsp": 7063, + "GetMonopolyFriendRankingListScRsp": 7064, + "MonopolyGuessChooseCsReq": 7066, + "MonopolyCheatDiceCsReq": 7067, + "MonopolyGameBingoFlipCardCsReq": 7068, + "MonopolyGetRaffleTicketScRsp": 7069, + "MonopolySelectOptionCsReq": 7070, + "MonopolyRollDiceCsReq": 7071, + "GetMonopolyMbtiReportRewardScRsp": 7073, + "GetMonopolyInfoCsReq": 7074, + "MonopolyReRollRandomCsReq": 7075, + "MonopolyGetRafflePoolInfoCsReq": 7076, + "GetMonopolyMbtiReportRewardCsReq": 7079, + "MonopolyGameRaiseRatioCsReq": 7080, + "MonopolyAcceptQuizCsReq": 7081, + "MonopolyUpgradeAssetCsReq": 7082, + "MonopolyClickMbtiReportCsReq": 7083, + "MonopolySocialEventEffectScNotify": 7084, + "MonopolyEventSelectFriendScRsp": 7085, + "MonopolyTakePhaseRewardCsReq": 7086, + "MonopolyCellUpdateNotify": 7087, + "MonopolyGameGachaCsReq": 7089, + "MonopolyGuessChooseScRsp": 7090, + "GetMonopolyDailyReportCsReq": 7091, + "MonopolyConfirmRandomCsReq": 7092, + "MonopolyClickCellCsReq": 7093, + "MonopolyBuyGoodsCsReq": 7094, + "MonopolyLikeScNotify": 7095, + "MonopolyTakePhaseRewardScRsp": 7096, + "MonopolyGetDailyInitItemCsReq": 7097, + "DailyFirstEnterMonopolyActivityScRsp": 7098, + "MonopolyLikeScRsp": 7100, + "EvolveBuildSkipTeachLevelCsReq": 7103, + "GetEvolveBuildShopAbilityResetCsReq": 7106, + "GetEvolveBuildShopAbilityDownScRsp": 7115, + "GetEvolveBuildShopAbilityDownCsReq": 7116, + "GetEvolveBuildCoinNotify": 7122, + "GetEvolveBuildGiveupScRsp": 7124, + "GetEvolveBuildShopAbilityUpCsReq": 7125, + "GetEvolveBuildReRandomStageCsReq": 7126, + "EvolveBuildSkipTeachLevelScRsp": 7129, + "EvolveBuildStartStageCsReq": 7130, + "GetEvolveBuildShopAbilityResetScRsp": 7131, + "GetEvolveBuildReRandomStageScRsp": 7132, + "GetEvolveBuildGiveupCsReq": 7134, + "EvolveBuildStartStageScRsp": 7135, + "GetEvolveBuildQueryInfoScRsp": 7138, + "GetEvolveBuildQueryInfoCsReq": 7142, + "GetEvolveBuildShopAbilityUpScRsp": 7144, + "GetEvolveBuildFinishScNotify": 7145, + "EvolveBuildStartLevelCsReq": 7148, + "EvolveBuildStartLevelScRsp": 7150, + "EnterFeverTimeActivityStageCsReq": 7153, + "EnterFeverTimeActivityStageScRsp": 7156, + "FeverTimeActivityBattleEndScNotify": 7157, + "GetFeverTimeActivityDataScRsp": 7158, + "GetFeverTimeActivityDataCsReq": 7160, + "GetStarFightDataCsReq": 7163, + "GetStarFightDataScRsp": 7166, + "StartStarFightLevelCsReq": 7167, + "StarFightDataChangeNotify": 7168, + "StartStarFightLevelScRsp": 7170, + "GetBadgeInfoCsReq": 7173, + "GetBadgeInfoScRsp": 7176, + "BadgeInfoScNotify": 7177, + "ClockParkFinishScriptScNotify": 7208, + "ClockParkUseBuffScRsp": 7209, + "ClockParkQuitScriptScRsp": 7210, + "ClockParkGetOngoingScriptInfoScRsp": 7211, + "ClockParkUseBuffCsReq": 7215, + "ClockParkStartScriptScRsp": 7224, + "ClockParkHandleWaitOperationScRsp": 7226, + "ClockParkUnlockTalentCsReq": 7230, + "ClockParkQuitScriptCsReq": 7232, + "ClockParkStartScriptCsReq": 7234, + "ClockParkUnlockTalentScRsp": 7235, + "ClockParkGetOngoingScriptInfoCsReq": 7237, + "ClockParkGetInfoScRsp": 7238, + "ClockParkGetInfoCsReq": 7242, + "OMPEMGHMDPM": 7244, + "ClockParkHandleWaitFinishStoryOperationCsReq": 7245, + "FightFestUpdateCoinNotify": 7274, + "FightFestScoreUpdateNotify": 7280, + "FightFestUpdateChallengeRecordNotify": 7284, + "FightFestUnlockSkillNotify": 7285, + "GetFightFestDataScRsp": 7288, + "GetFightFestDataCsReq": 7292, + "StartFightFestCsReq": 7298, + "StartFightFestScRsp": 7300, + "GetCrossInfoScRsp": 7334, + "GetCrossInfoCsReq": 7335, + "StartMatchScRsp": 7338, + "StartMatchCsReq": 7342, + "CancelMatchCsReq": 7348, + "CancelMatchScRsp": 7350, + "ANFMHBGNNEG": 7356, + "LobbyInteractScNotify": 7358, + "AGDPIOAKFGB": 7359, + "LobbyQuitCsReq": 7360, + "LobbyKickOutScRsp": 7361, + "OPKKJFEFEIL": 7365, + "LobbyGetInfoScRsp": 7366, + "LobbyModifyPlayerInfoScRsp": 7374, + "LobbyInviteCsReq": 7376, + "DACEFJFGLGM": 7380, + "KIGJGMLAMGH": 7381, + "LobbyInviteScRsp": 7382, + "LobbyModifyPlayerInfoCsReq": 7384, + "GJBGNMGGDLE": 7385, + "LobbyKickOutCsReq": 7387, + "LobbyCreateScRsp": 7388, + "LobbyQuitScRsp": 7391, + "LobbyCreateCsReq": 7392, + "LobbyGetInfoCsReq": 7394, + "LobbySyncInfoScNotify": 7395, + "LobbyJoinCsReq": 7398, + "LobbyJoinScRsp": 7400, + "MatchThreeV2SetBirdPosCsReq": 7410, + "MatchThreeV2BattleItemLevelUpCsReq": 7411, + "EEHLDBOEFGB": 7416, + "GetDataCsReq2": 7424, + "MatchThreeV2ScNotify": 7425, + "MatchThreeV2LevelEndCsReq": 7426, + "MatchThreeSyncDataScNotify": 7430, + "MatchThreeV2LevelEndScRsp": 7432, + "MatchThreeSetBirdPosScRsp": 7434, + "MatchThreeSetBirdPosCsReq": 7435, + "GetDataScRsp4": 7437, + "MatchThreeGetDataDataScRsp": 7438, + "MatchThreeV2SetBirdPosScRsp": 7441, + "MatchThreeGetDataDataCsReq": 7442, + "MatchThreeV2PvpFinishScNotify": 7444, + "MatchThreeV2BattleItemLevelUpScRsp": 7445, + "MatchThreeLevelEndCsReq": 7448, + "MatchThreeLevelEndScRsp": 7450, + "SwordTrainingActionTurnSettleScNotify": 7451, + "SwordTrainingUnlockSyncScNotify": 7453, + "SwordTrainingMarkEndingViewedCsReq": 7454, + "SwordTrainingMarkEndingViewedScRsp": 7455, + "SwordTrainingGiveUpGameCsReq": 7456, + "SwordTrainingStoryConfirmScRsp": 7458, + "SwordTrainingStoryConfirmCsReq": 7459, + "EnterSwordTrainingExamCsReq": 7460, + "SwordTrainingResumeGameScRsp": 7462, + "SwordTrainingSelectEndingScRsp": 7464, + "SwordTrainingStartGameScRsp": 7465, + "SwordTrainingStartGameCsReq": 7466, + "SwordTrainingRestoreGameCsReq": 7467, + "SwordTrainingStoryBattleCsReq": 7469, + "SwordTrainingStoryBattleScRsp": 7470, + "SwordTrainingExamResultConfirmScRsp": 7471, + "SwordTrainingDialogueSelectOptionCsReq": 7474, + "SwordTrainingLearnSkillCsReq": 7475, + "SwordTrainingSelectEndingCsReq": 7477, + "SwordTrainingSetSkillTraceCsReq": 7478, + "SwordTrainingTurnActionScRsp": 7480, + "SwordTrainingGiveUpGameScRsp": 7481, + "SwordTrainingDailyPhaseConfirmScRsp": 7484, + "SwordTrainingDailyPhaseConfirmCsReq": 7485, + "SwordTrainingSetSkillTraceScRsp": 7486, + "SwordTrainingDialogueSelectOptionScRsp": 7487, + "GetSwordTrainingDataCsReq": 7488, + "SwordTrainingRestoreGameScRsp": 7490, + "EnterSwordTrainingExamScRsp": 7491, + "SwordTrainingGameSyncChangeScNotify": 7492, + "SwordTrainingExamResultConfirmCsReq": 7493, + "SwordTrainingLearnSkillScRsp": 7494, + "SwordTrainingGameSettleScNotify": 7496, + "GetSwordTrainingDataScRsp": 7498, + "SwordTrainingResumeGameCsReq": 7499, + "SwordTrainingTurnActionCsReq": 7500, + "ContentPackageTransferScNotify": 7535, + "ContentPackageGetDataScRsp": 7538, + "ContentPackageGetDataCsReq": 7542, + "ContentPackageSyncDataScNotify": 7548, + "QuitTrackPhotoStageScRsp": 7551, + "GetTrackPhotoActivityDataCsReq": 7553, + "StartTrackPhotoStageScRsp": 7554, + "QuitTrackPhotoStageCsReq": 7555, + "GetTrackPhotoActivityDataScRsp": 7556, + "SettleTrackPhotoStageCsReq": 7557, + "StartTrackPhotoStageCsReq": 7558, + "SettleTrackPhotoStageScRsp": 7560, + "GetSummonActivityDataCsReq": 7563, + "GetSummonActivityDataScRsp": 7566, + "EnterSummonActivityStageCsReq": 7567, + "SummonActivityBattleEndScNotify": 7568, + "EnterSummonActivityStageScRsp": 7570, + "MusicRhythmSaveSongConfigDataCsReq": 7577, + "MusicRhythmUnlockTrackScNotify": 7578, + "MusicRhythmDataScRsp": 7579, + "MusicRhythmMaxDifficultyLevelsUnlockNotify": 7582, + "MusicRhythmUnlockSongNotify": 7583, + "MusicRhythmFinishLevelCsReq": 7585, + "MusicRhythmStartLevelCsReq": 7586, + "MusicRhythmSaveSongConfigDataScRsp": 7589, + "MusicRhythmDataCsReq": 7592, + "MusicRhythmUnlockSongSfxScNotify": 7594, + "MusicRhythmStartLevelScRsp": 7598, + "MusicRhythmFinishLevelScRsp": 7600, + "GetPetDataScRsp": 7604, + "SetBlindBoxPetLineupCsReq": 7605, + "RecallPetScRsp": 7607, + "SwitchBlindBoxPetChromaScRsp": 7609, + "SummonPetCsReq": 7611, + "GetPetDataCsReq": 7612, + "RecycleBlindBoxPetScRsp": 7613, + "SummonPetScRsp": 7615, + "RecallPetCsReq": 7616, + "SwitchBlindBoxPetChromaCsReq": 7619, + "SetBlindBoxPetLineupScRsp": 7620, + "PetDataChangeScNotify": 7622, + "RecycleBlindBoxPetCsReq": 7623, + "WorldUnlockScRsp": 7626, + "WorldUnlockCsReq": 7627, + "RogueArcadeRestartCsReq": 7663, + "RogueArcadeLeaveScRsp": 7667, + "RogueArcadeGetInfoCsReq": 7675, + "RogueArcadeStartScRsp": 7684, + "RogueArcadeGetInfoScRsp": 7685, + "RogueArcadeStartCsReq": 7690, + "RogueArcadeLeaveCsReq": 7696, + "RogueArcadeRestartScRsp": 7698, + "RogueMagicReviveAvatarCsReq": 7704, + "RogueMagicLeaveCsReq": 7705, + "RogueMagicEnterRoomCsReq": 7706, + "RogueMagicBattleFailSettleInfoScNotify": 7708, + "RogueMagicAutoDressInUnitScRsp": 7712, + "RogueMagicSettleScRsp": 7718, + "RogueMagicStartScRsp": 7719, + "RogueMagicSetAutoDressInMagicUnitScRsp": 7722, + "RogueMagicScepterDressInUnitCsReq": 7723, + "RogueMagicEnterLayerCsReq": 7727, + "RogueMagicScepterDressInUnitScRsp": 7728, + "RogueMagicEnterCsReq": 7731, + "RogueMagicLevelInfoUpdateScNotify": 7735, + "RogueMagicStoryInfoUpdateScNotify": 7743, + "RogueMagicEnableTalentScRsp": 7744, + "RogueMagicGetMiscRealTimeDataCsReq": 7749, + "RogueMagicEnterLayerScRsp": 7752, + "RogueMagicAutoDressInMagicUnitChangeScNotify": 7753, + "RogueMagicQueryCsReq": 7757, + "RogueMagicSettleCsReq": 7759, + "RogueMagicUnitComposeCsReq": 7762, + "RogueMagicScepterTakeOffUnitScRsp": 7763, + "RogueMagicGetMiscRealTimeDataScRsp": 7766, + "RogueMagicScepterTakeOffUnitCsReq": 7767, + "RogueMagicEnableTalentCsReq": 7768, + "RogueMagicEnterRoomScRsp": 7770, + "RogueMagicLeaveScRsp": 7771, + "RogueMagicStartCsReq": 7774, + "RogueMagicUnitComposeScRsp": 7780, + "RogueMagicSetAutoDressInMagicUnitCsReq": 7781, + "RogueMagicQueryScRsp": 7782, + "RogueMagicEnterScRsp": 7787, + "RogueMagicAutoDressInUnitCsReq": 7790, + "RogueMagicReviveCostUpdateScNotify": 7792, + "RogueMagicReviveAvatarScRsp": 7794, + "RogueMagicAreaUpdateScNotify": 7798, + "FDDJJCFNFOJ": 8004, + "TrainPartyMoveScNotify": 8005, + "TrainPartySyncUpdateScNotify": 8006, + "TrainPartyGetDataScRsp": 8019, + "TrainPartyHandlePendingActionCsReq": 8027, + "TrainPartySetBadgeAutoFillCsReq": 8029, + "TrainPartyUseCardCsReq": 8031, + "TrainPartyBuildStartStepCsReq": 8035, + "TrainPartyBuildSelfDisplayAreaDiyCsReq": 8043, + "DNIAFCGKEOP": 8044, + "TrainPartySetBadgeAutoFillScRsp": 8046, + "TrainPartyBuildDiyCsReq": 8047, + "TrainPartyBuildDiyScRsp": 8048, + "TrainPartyTakeBuildLevelAwardScRsp": 8050, + "TrainPartyHandlePendingActionScRsp": 8052, + "TrainPartyBuildSelfDisplayAreaDiyScRsp": 8053, + "DBGHCHPONOH": 8057, + "TrainPartySettleNotify": 8059, + "TrainPartyBuildSelfDisplayAreaDiyScNotify": 8060, + "TrainPartyGamePlaySettleNotify": 8062, + "TrainPartyGamePlayStartScRsp": 8063, + "TrainPartyGamePlayStartCsReq": 8067, + "IBNCBEPCAPF": 8068, + "TrainPartyGetDataCsReq": 8074, + "TrainPartyBuildingUpdateNotify": 8075, + "TrainPartyBuildRoomScNotify": 8081, + "TrainPartyUseCardScRsp": 8087, + "TrainPartyTakeBuildLevelAwardCsReq": 8089, + "FHLHOEPCKAG": 8092, + "AMDOMPIGBPG": 8094, + "TrainPartyBuildStartStepScRsp": 8098, + "SwitchHandStartCsReq": 8104, + "SwitchHandResetTransformCsReq": 8105, + "GetSwitchHandUpdateScRsp": 8106, + "SwitchHandCatchItemCsReq": 8107, + "GetSwitchHandDataScRsp": 8108, + "SwitchHandFinishScRsp": 8109, + "GetSwitchHandResetHandPosScRsp": 8111, + "SwitchHandCoinUpdateScRsp": 8112, + "SwitchHandCoinUpdateCsReq": 8113, + "GetSwitchHandDataCsReq": 8115, + "SwitchHandFinishCsReq": 8116, + "GetSwitchHandResetGameCsReq": 8118, + "GetSwitchHandResetGameScRsp": 8119, + "SwitchHandStartScRsp": 8120, + "SelectPamSkinCsReq": 8124, + "GetPamSkinDataScRsp": 8128, + "GetPamSkinDataCsReq": 8135, + "UnlockPamSkinScNotify": 8136, + "SelectPamSkinScRsp": 8140, + "TarotBookMultiOpenPackAndUnlockStoryScRsp": 8141, + "TarotBookOpenPackCsReq": 8144, + "TarotBookFinishInteractionScRsp": 8145, + "TarotBookFinishStoryScRsp": 8146, + "TarotBookFinishStoryCsReq": 8147, + "TarotBookGetDataScRsp": 8148, + "TarotBookUnlockStoryScRsp": 8149, + "TarotBookMultiOpenPackAndUnlockStoryCsReq": 8150, + "TarotBookUnlockInteractionCsReq": 8151, + "TarotBookFinishInteractionCsReq": 8152, + "TarotBookModifyEnergyScNotify": 8153, + "TarotBookGetDataCsReq": 8155, + "TarotBookUnlockStoryCsReq": 8156, + "TarotBookUnlockInteractionScRsp": 8158, + "TarotBookSyncDataScNotify": 8159, + "TarotBookOpenPackScRsp": 8160, + "ChimeraRoundWorkStartScRsp": 8161, + "EIALJHKFNKG": 8164, + "ChimeraQuitEndlessCsReq": 8165, + "ChimeraStartEndlessScRsp": 8166, + "ChimeraStartEndlessCsReq": 8167, + "GetDataScRsp1": 8168, + "ChimeraFinishRound": 8169, + "ChimeraRoundWorkStartCsReq": 8170, + "ChimeraQuitEndlessScRsp": 8171, + "ChimeraFinishEndlessRoundScRsp": 8172, + "ChimeraFinishEndlessRoundCsReq": 8173, + "ChimeraGetDataCsReq": 8175, + "ChimeraFinishRoundCsReq": 8176, + "ChimeraDoFinalRoundCsReq": 8178, + "ChimeraDoFinalRoundScRsp": 8179, + "MFFFEOKDOAE": 8180, + "UpdateMarkChestCsReq": 8184, + "GetMarkChestScRsp": 8188, + "GetMarkChestCsReq": 8195, + "MarkChestChangedScNotify": 8196, + "UpdateMarkChestScRsp": 8200, + "PlanetFesStartMiniGameScRsp": 8201, + "PlanetFesSetCustomKeyValueCsReq": 8202, + "PlanetFesChooseAvatarEventOptionCsReq": 8203, + "PlanetFesUpgradeFesLevelScRsp": 8204, + "PlanetFesGetAvatarStatCsReq": 8205, + "PlanetFesBusinessDayRefreshEventCsReq": 8206, + "PlanetFesGameBingoFlipScRsp": 8207, + "PlanetFesGetBusinessDayInfoScRsp": 8208, + "PlanetFesGetBusinessDayInfoCsReq": 8209, + "PlanetFesDoGachaCsReq": 8210, + "PlanetFesClientStatusCsReq": 8211, + "PlanetFesGetFriendRankingInfoListScRsp": 8212, + "PlanetFesTakeRegionPhaseRewardCsReq": 8213, + "PlanetFesUpgradeSkillLevelCsReq": 8214, + "PlanetFesTakeQuestRewardScRsp": 8215, + "PlanetFesTakeQuestRewardCsReq": 8216, + "PlanetFesTakeRegionPhaseRewardScRsp": 8217, + "PlanetFesDealAvatarEventOptionItemCsReq": 8218, + "PlanetFesBonusEventInteractCsReq": 8219, + "PlanetFesBonusEventInteractScRsp": 8220, + "PlanetFesUseItemScRsp": 8221, + "PlanetFesDeliverPamCargoCsReq": 8222, + "PlanetFesSkillLevelUpScRsp": 8223, + "PlanetFesBuyLandCsReq": 8224, + "PlanetFesAvatarLevelUpCsReq": 8225, + "PlanetFesCollectAllIncomeCsReq": 8226, + "PlanetFesDealAvatarEventOptionItemScRsp": 8227, + "PlanetFesSetCustomKeyValueScRsp": 8228, + "PlanetFesChooseAvatarEventOptionScRsp": 8229, + "PlanetFesCollectIncomeScRsp": 8230, + "PlanetFesBusinessDayRefreshEventScRsp": 8231, + "PlanetFesCollectAllIncomeScRsp": 8232, + "PlanetFesGetAvatarStatScRsp": 8233, + "PlanetFesSetAvatarWorkScRsp": 8234, + "PlanetFesSetAvatarWorkCsReq": 8235, + "PlanetFesUpgradeFesLevelCsReq": 8236, + "PlanetFesBuyLandScRsp": 8237, + "GetPlanetFesDataScRsp": 8238, + "PlanetFesFriendRankingInfoChangeScNotify": 8239, + "PlanetFesStartMiniGameCsReq": 8240, + "PlanetFesDoGachaScRsp": 8241, + "GetPlanetFesDataCsReq": 8242, + "PlanetFesUseItemCsReq": 8243, + "PlanetFesAvatarLevelUpScRsp": 8244, + "PlanetFesClientStatusScRsp": 8245, + "PlanetFesDeliverPamCargoScRsp": 8246, + "PlanetFesGameBingoFlipCsReq": 8247, + "PlanetFesSyncChangeScNotify": 8248, + "PlanetFesGetFriendRankingInfoListCsReq": 8249, + "PlanetFesCollectIncomeCsReq": 8250, + "RelicSmartWearUpdatePinRelicCsReq": 8251, + "RelicSmartWearDeletePinRelicCsReq": 8252, + "RelicSmartWearDeletePinRelicScRsp": 8253, + "RelicSmartWearAddPlanCsReq": 8254, + "RelicSmartWearGetPinRelicCsReq": 8255, + "RelicSmartWearDeletePlanScRsp": 8256, + "RelicSmartWearDeletePlanCsReq": 8257, + "RelicSmartWearGetPlanScRsp": 8258, + "RelicSmartWearUpdatePlanScRsp": 8259, + "RelicSmartWearGetPinRelicScRsp": 8261, + "RelicSmartWearUpdatePinRelicScRsp": 8264, + "RelicSmartWearGetPlanCsReq": 8265, + "RelicSmartWearUpdatePlanCsReq": 8266, + "RelicSmartWearUpdatePinRelicScNotify": 8268, + "RelicSmartWearAddPlanScRsp": 8270, + "HAJKEPNEJED": 8274, + "MarblePvpDataUpdateScNotify": 8276, + "MarbleUnlockSealScNotify": 8277, + "MarbleGetDataScRsp": 8278, + "MarbleShopBuyScRsp": 8279, + "MarbleUpdateShownSealScRsp": 8282, + "MarbleUpdateShownSealCsReq": 8283, + "MarbleGetDataCsReq": 8285, + "MarbleShopBuyCsReq": 8286, + "KBECKAMICJF": 8290, + "PlanetFesGiveCardPieceScRsp": 8300, + "PlanetFesHandleCardPieceApplyScRsp": 8301, + "PlanetFesEnterNextBusinessDayScRsp": 8306, + "PlanetFesApplyCardPieceScRsp": 8314, + "PlanetFesLargeBonusInteractScRsp": 8315, + "PlanetFesGetOfferedCardPieceScRsp": 8316, + "PlanetFesChangeCardPieceApplyPermissionCsReq": 8320, + "PlanetFesGiveCardPieceCsReq": 8322, + "PlanetFesApplyCardPieceCsReq": 8324, + "PlanetFesChangeCardPieceApplyPermissionScRsp": 8325, + "PlanetFesHandleCardPieceApplyCsReq": 8327, + "PlanetFesGetExtraCardPieceInfoScRsp": 8328, + "PlanetFesLargeBonusInteractCsReq": 8331, + "PlanetFesGetExtraCardPieceInfoCsReq": 8332, + "PlanetFesEnterNextBusinessDayCsReq": 8334, + "PlanetFesGetOfferedCardPieceCsReq": 8335, + "PlanetFesGetFriendCardPieceCsReq": 8338, + "PlanetFesGetFriendCardPieceScRsp": 8340, + "TakeRechargeGiftRewardCsReq": 8364, + "TakeRechargeBenefitRewardCsReq": 8366, + "SyncRechargeBenefitInfoScNotify": 8367, + "GetRechargeGiftInfoScRsp": 8368, + "GetRechargeBenefitInfoScRsp": 8369, + "TakeRechargeBenefitRewardScRsp": 8373, + "GetRechargeGiftInfoCsReq": 8375, + "GetRechargeBenefitInfoCsReq": 8376, + "TakeRechargeGiftRewardScRsp": 8380, + "ParkourGetRankingInfoCsReq": 8384, + "ParkourEndLevelScRsp": 8386, + "ParkourEndLevelCsReq": 8387, + "ParkourGetDataScRsp": 8388, + "ParkourStartLevelScRsp": 8389, + "ParkourGetDataCsReq": 8395, + "ParkourStartLevelCsReq": 8396, + "ParkourGetRankingInfoScRsp": 8400, + "FDJCIHMFOMC": 8405, + "GridFightDestroyWeeklyRewardScRsp": 8409, + "GridFightSpotLightEnterScRsp": 8417, + "GridFightEquipDressScRsp": 8420, + "GridFightEnterBattleStageCsReq": 8429, + "OENNAOAFAMA": 8433, + "CheckTextRsp": 8440, + "GridFightBuyExpCsReq": 8441, + "GridFightPermanentTalentEnableScRsp": 8442, + "MOIONLDJGJI": 8445, + "JOEHCLLKPIP": 8448, + "GridFightResumeGamePlayScRsp": 8452, + "GridFightSyncTutorialProgressIdScNotify": 8457, + "GridFightSeasonTalentEnableScRsp": 8459, + "EMFJDODNGIF": 8460, + "IIFPPBCFBBC": 8463, + "GridFightUpdateEquipTrackPriorityScRsp": 8464, + "GridFightHandlePendingActionScRsp": 8466, + "GridFightPermanentTalentResetScRsp": 8479, + "GridFightHandleBonfireHealHpCsReq": 8485, + "UpdateGameFormationCsReq": 8486, + "GridFightEquipCraftCsReq": 8488, + "GridFightGetDataScRsp": 8503, + "GridFightSpotLightEnterCsReq": 8508, + "GridFightQuitLeaveGamePlayScRsp": 8509, + "GridFightStartGamePlayScRsp": 8511, + "GridFightSummonProjectionCsReq": 8514, + "GridFightSyncKeepWinCntNotify": 8515, + "BuyGoodRsp": 8516, + "GridFightSpotLightReplaceScRsp": 8518, + "GridFightBackToPrepareScRsp": 8532, + "GridFightBuyGoodsCsReq": 8533, + "GridFightUpdateEquipTrackCsReq": 8537, + "GridFightSeasonTalentResetCsReq": 8540, + "GridFightGetDataCsReq": 8541, + "GEBBMDCOLCH": 8543, + "CCGJDDBKIDE": 8546, + "GridFightUseForgeScRsp": 8551, + "GridFightRecycleItemCsReq": 8553, + "GridFightUpdateEliteBranchSelectCsReq": 8554, + "KNDOLAFNJED": 8556, + "GridFightEnterBonfireCsReq": 8559, + "GridFightFinishTutorialCsReq": 8561, + "GridFightLockShopCsReq": 8572, + "GridFightUseForgeCsReq": 8573, + "NLNGEPOOMJJ": 8576, + "GridFightSummonProjectionScRsp": 8577, + "GridFightStartGamePlayCsReq": 8579, + "CNOAPAMNDOA": 8580, + "GridFightEnterBonfireScRsp": 8582, + "GetGameFormation": 8584, + "GridFightWeeklyExtraSeasonExpScRsp": 8591, + "GetGameFormationCsReq": 8596, + "GridFightPermanentTalentResetCsReq": 8603, + "GridFightBuyExpScRsp": 8607, + "GridFightLockShopScRsp": 8608, + "GridFightTakeWeeklyRewardScRsp": 8618, + "IKMOCJBMGOJ": 8623, + "GridFightSeasonTalentEnableCsReq": 8626, + "GridFightPermanentTalentEnableCsReq": 8629, + "ALNFEBKHCFC": 8630, + "GridFightResumeGamePlayCsReq": 8631, + "GridFightRecycleRoleScRsp": 8633, + "JKGILKGILAI": 8634, + "GridFightUpdateSpotLightTrackCsReq": 8636, + "AGOBLKJFJDL": 8639, + "HKOAOCKBBJA": 8642, + "FavourArchiveScRsp": 8644, + "GridFightUpdatePosScRsp": 8648, + "GridFightTraitUpdateCsReq": 8657, + "PNHDGCBLPGD": 8659, + "KAHAOOJHJPK": 8660, + "MCIPOMDGBCA": 8665, + "GridFightTakeWeeklyRewardCsReq": 8667, + "FavourArchiveCsReq": 8668, + "GridFightGetArchiveScRsp": 8679, + "GridFightSeasonHandBookNotify": 8680, + "GridFightSyncUpdateResultScNotify": 8683, + "GridFightQuitLeaveGamePlayCsReq": 8691, + "GridFightRecycleItemScRsp": 8695, + "GridFightEquipDressCsReq": 8698, + "GridFightRefreshShopScRsp": 8699, + "GridFightRefreshShopCsReq": 8701, + "GridFightSettleNotify": 8706, + "GridFightTraitUpdateScRsp": 8708, + "GridFightUpdateEquipTrackPriorityCsReq": 8710, + "GridFightRecycleRoleCsReq": 8712, + "GridFightSyncVirtualItemNotify": 8716, + "UpdateGameFormation": 8717, + "GridFightBackToPrepareReq": 8720, + "GridFightUpdateEliteBranchSelectScRsp": 8724, + "GridFightUpdateSpotLightTrackScRsp": 8727, + "GridFightSpotLightReplaceCsReq": 8740, + "ENDFBBDFFDH": 8741, + "GDHFMHKFEAL": 8745, + "GridFightSeasonTalentResetScRsp": 8747, + "MCDBJHOCGHI": 8748, + "GridFightLastTutorialFinishScNotify": 8751, + "GridFightQuitSettleScRsp": 8755, + "PHGFFOEJEBM": 8760, + "GridFightFinishTutorialScRsp": 8762, + "GridFightDestroyWeeklyRewardCsReq": 8766, + "GridFightUpdateEquipTrackScRsp": 8769, + "GridFightEnterBattleStageScRsp": 8777, + "GridFightQuitSettleCsReq": 8779, + "CheckTextCsReq": 8781, + "GridFightEndBattleStageNotify": 8782, + "GridFightGetArchiveCsReq": 8784, + "GridFightEquipCraftScRsp": 8790, + "GridFightUpdatePosCsReq": 8795, + "SetChallengePeakMobLineupAvatarScRsp": 8909, + "SetChallengePeakBossHardModeScRsp": 8910, + "TakeChallengePeakRewardScRsp": 8911, + "SetChallengePeakMobLineupAvatarCsReq": 8915, + "ConfirmChallengePeakSettleScRsp": 8922, + "ChallengePeakGroupDataUpdateScNotify": 8924, + "ReStartChallengePeakScRsp": 8925, + "GetCurChallengePeakScRsp": 8926, + "LeaveChallengePeakCsReq": 8930, + "ConfirmChallengePeakSettleCsReq": 8931, + "SetChallengePeakBossHardModeCsReq": 8932, + "ChallengePeakSettleScNotify": 8934, + "LeaveChallengePeakScRsp": 8935, + "TakeChallengePeakRewardCsReq": 8937, + "GetChallengePeakDataScRsp": 8938, + "ReStartChallengePeakCsReq": 8941, + "GetChallengePeakDataCsReq": 8942, + "GetCurChallengePeakCsReq": 8945, + "StartChallengePeakCsReq": 8948, + "StartChallengePeakScRsp": 8950, + "RelicBoxRegularScNotify": 8951, + "SetRelicBoxCustomCsReq": 8954, + "ConfirmRelicBoxCsReq": 8955, + "GetRelicBoxDataScRsp": 8958, + "SetRelicBoxTargetScRsp": 8959, + "ReportRelicBoxActionScRsp": 8960, + "ConfirmRelicBoxScRsp": 8961, + "OpenRelicBoxScRsp": 8962, + "OpenRelicBoxCsReq": 8963, + "GetRelicBoxDataCsReq": 8965, + "SetRelicBoxTargetCsReq": 8966, + "ReportRelicBoxActionCsReq": 8969, + "SetRelicBoxCustomScRsp": 8970, + "DOPHNDHILPA": 8975, + "LHAKNKHBLBI": 8977, + "MHJNOBBOFOC": 8978, + "SetChallengeTierceLineupScRsp": 8979, + "GetChallengeTierceControllerScRsp": 8981, + "MENLHHCJNCE": 8982, + "LeaveChallengeTierceScRsp": 8983, + "PKKFNODINEM": 8985, + "StartChallengeTierceCsReq": 8986, + "LeaveChallengeTierceCsReq": 8989, + "SetChallengeTierceLineupCsReq": 8992, + "GetChallengeTierceControllerCsReq": 8993, + "OKEDFNJJGHB": 8994, + "GetChallengeTierceDataCsReq": 8995, + "GetChallengeTierceDataScRsp": 8996, + "StartChallengeTierceScRsp": 8998, + "JMPJCAPKNGI": 9000, + "HipplenChangeScNotify": 9009, + "SetHipplenOutfitScRsp": 9010, + "HipplenCycleResultScNotify": 9011, + "TakeHipplenEndingRewardScRsp": 9015, + "TakeHipplenEndingRewardCsReq": 9016, + "SettleHipplenWorkScRsp": 9024, + "OpenHipplenCycleScRsp": 9026, + "SetHipplenAgendaCsReq": 9030, + "SetHipplenOutfitCsReq": 9032, + "SettleHipplenWorkCsReq": 9034, + "SetHipplenAgendaScRsp": 9035, + "HipplenAgendaResultScNotify": 9037, + "GetHipplenDataScRsp": 9038, + "GetHipplenDataCsReq": 9042, + "HipplenTraitUnlockScNotify": 9044, + "OpenHipplenCycleCsReq": 9045, + "GetHipplenInheritCsReq": 9048, + "GetHipplenInheritScRsp": 9050, + "NCGFPOKHMBL": 9056, + "AIPNFBBDNEO": 9058, + "EGJJHFIPCNB": 9059, + "GetFiveDimMiniGameDataScRsp": 9060, + "CDJKLJDMJHO": 9061, + "BAGCLEDHOBD": 9065, + "GetFiveDimFluteDataScRsp": 9066, + "MazeCrossFloorConditionChangeScNotify": 9072, + "ChenLingGameBoyChangeSettingsScRsp": 9074, + "FinishFiveDimMiniGameScRsp": 9075, + "JHEHACPKKBL": 9076, + "ChenLingGameBoyGetFriendRankingInfoCsReq": 9080, + "FiveDimFluteDataChangeNotify": 9081, + "GetFiveDimMiniGameDataCsReq": 9082, + "ChenLingGameBoyChangeSettingsCsReq": 9084, + "ChenLingGameBoyGetFriendRankingInfoScRsp": 9085, + "ANDPLLAKCLE": 9087, + "GetChenLingGameBoyDataScRsp": 9088, + "FinishFiveDimMiniGameCsReq": 9091, + "GetChenLingGameBoyDataCsReq": 9092, + "GetFiveDimFluteDataCsReq": 9094, + "EMDKLICDPKK": 9095, + "FinishChenLingGameBoyCsReq": 9098, + "FinishChenLingGameBoyScRsp": 9100, + "GetElationActivityDataCsReq": 9103, + "GetElationActivityDataScRsp": 9106, + "EnterElationActivityStageCsReq": 9107, + "ElationActivityBattleEndScNotify": 9108, + "EnterElationActivityStageScRsp": 9110, + "GetActivityHotDataCsReq": 9113, + "GetActivityRewardCountDataScRsp": 9114, + "GetActivityHotDataScRsp": 9116, + "GetAllActivityRewardCountDataCsReq": 9117, + "GetActivityRewardCountDataCsReq": 9118, + "GetAllActivityRewardCountDataScRsp": 9120, + "CakeRaceLikeFriendRankingInfoScRsp": 9124, + "CakeRaceCoinScoreChangeScNotify": 9125, + "CakeRaceGetDataScRsp": 9129, + "CakeRaceGetDailyLikeScRsp": 9130, + "CakeRaceGetFriendRankingInfoListScRsp": 9131, + "CakeRaceFinishPveCsReq": 9132, + "CakeRaceUpdatePveScRsp": 9133, + "CakeRaceStartPveCsReq": 9135, + "CakeRaceLoanCsReq": 9136, + "CakeRaceBetCoinPveCsReq": 9139, + "CakeRaceGetDailyLikeCsReq": 9141, + "CakeRaceGetDataCsReq": 9142, + "CakeRaceGetFriendRankingInfoListCsReq": 9143, + "CakeRaceFinishPveScRsp": 9144, + "CakeRaceGetHandbookCsReq": 9145, + "CakeRaceGetHandbookScRsp": 9146, + "CakeRaceLikeFriendRankingInfoCsReq": 9147, + "CakeRaceLoanScRsp": 9148, + "CakeRaceFinishFieldRewardScNotify": 9149, + "CakeRaceStartPveScRsp": 9150, + "GHBKJLIHHGJ": 9151, + "KBAKBMGBNOL": 9152, + "JABBBMCMKAI": 9154, + "GNPIEPGLNPC": 9155, + "OFNBDEKJMON": 9156, + "LJCBIAEMLDN": 9157, + "ICCEJJFNNBD": 9159, + "HJBGBDKMNNE": 9160, + "MAGMMMINOAJ": 9161, + "DDAPICMFDAB": 9162, + "HOALCJNIJJC": 9163, + "PNOAIGFGNKE": 9165, + "IBPHNIBGMPH": 9166, + "CPGFNBBIGLH": 9167, + "DGFHEGJKHFO": 9169, + "KELNHIOMCNF": 9170, + "EPMONPAABKJ": 9171, + "NFBHBDICGOG": 9177, + "PDPKFDOJOFK": 9178, + "EMOPGLBEHPA": 9186, + "JEGNOIJMBCA": 9187, + "DOEOMHPIPKE": 9188, + "PLADCLDJGKK": 9189, + "DPAJAECBCPA": 9190, + "GHBBMIJFIAD": 9192, + "ACGBGKFFAOL": 9194, + "OFDMHDODALP": 9196, + "OLNDMBMBNDC": 9198, + "LDCEDOGDNDC": 9200, + "HBOOIEJHKOD": 9203, + "JKHGDJGBHCD": 9204, + "IGIBNBCMEAG": 9205, + "PNALNDONMCK": 9206, + "JGHOCEGBDCC": 9207, + "LFGKIJMBKJG": 9209, + "Receive": 9211, + "ChimeraDuelSellChimeraCsReq": 9213, + "ChimeraDuelShopRefreshScRsp": 9216, + "ChimeraDuelShopRefreshCsReq": 9218, + "ChimeraDuelShopLockScRsp": 9219, + "ChimeraDuelEndGameScRsp": 9220, + "ChimeraDuelEndRoundShopStageScRsp": 9221, + "ChimeraDuelSetFriendDefendLineupCsReq": 9223, + "ChimeraDuelSaveFriendPvpLineupCsReq": 9224, + "ChimeraDuelShopLockCsReq": 9225, + "ChimeraDuelShopBuyItemScRsp": 9226, + "ChimeraDuelSetFriendDefendLineupScRsp": 9227, + "ChimeraDuelSelectGameCsReq": 9228, + "ChimeraDuelFinishMasterChallengeScRsp": 9231, + "ChimeraDuelChangeLineupCsReq": 9232, + "ChimeraDuelSaveFriendPvpLineupScRsp": 9233, + "ChimeraDuelStartGameScRsp": 9234, + "ChimeraDuelShopBuyChimeraScRsp": 9235, + "ChimeraDuelEndRoundBattleStageScRsp": 9236, + "ChimeraDuelSelectGameScRsp": 9237, + "ChimeraDuelSellChimeraScRsp": 9239, + "ChimeraDuelUnlockMasterCsReq": 9240, + "ChimeraDuelSyncChangeScNotify": 9241, + "ChimeraDuelEndGameCsReq": 9242, + "ChimeraDuelStartGameCsReq": 9244, + "ChimeraDuelUnlockMasterScRsp": 9245, + "ChimeraDuelEndRoundShopStageCsReq": 9247, + "ChimeraDuelGetDataScRsp": 9248, + "ChimeraDuelGetFriendListCsReq": 9250, + "ChimeraDuelShopBuyChimeraCsReq": 9251, + "ChimeraDuelGetDataCsReq": 9252, + "ChimeraDuelFinishMasterChallengeCsReq": 9253, + "ChimeraDuelShopBuyItemCsReq": 9254, + "ChimeraDuelEndRoundBattleStageCsReq": 9255, + "ChimeraDuelChangeLineupScRsp": 9256, + "BLBHACNKKFC": 9263, + "V2FinishPveStageScRsp": 9267, + "BuyShopGoodScRsp": 9268, + "DDKNIMHHNPA": 9269, + "GetSystemDataRsp": 9273, + "V2FinishPveStageCsReq": 9274, + "DiceCombatBuyShopGoodReq": 9275, + "DiceCombatUpgradeAvatarCsReq": 9276, + "GetDiceCombatShopDataReq": 9277, + "AddItemRsp": 9282, + "DiceCombatFinishPveStageCsReq": 9284, + "DiceCombatMainPageRollDiceCsReq": 9286, + "ModifyAvatarDiceRsp": 9289, + "DiceCombatMainPageRollDiceScRsp": 9291, + "FinishPveStageScRsp": 9292, + "UpgradeAvatarRsp": 9294, + "GetShopDataScRsp": 9295, + "GetDiceCombatSystemDataReq": 9297, + "ModifyAvatarDiceCsReq": 9300, + "OONNIGNNMGL": 9327, + "AAOADDHLOJE": 9328, + "EEOMNBBJDOG": 9329, + "ALLNMAAOELO": 9335, + "MIGBFGJMAPA": 9336, + "FJKEGMEPLDI": 9339, + "MFBKEMKDKLO": 9342, + "NIEPBHBOCBI": 9348, + "AAKFAEEILGA": 9350, + "FinishQuestionRsp": 9351, + "IdleLiveGetPendingEquipScRsp": 9354, + "IdleLiveStartNodeBattleCsReq": 9355, + "IdleLiveFinishNodeEventCsReq": 9356, + "IdleLiveCollectIncomeScRsp": 9358, + "IdleLiveEndDungeonCsReq": 9362, + "DressSpecialEquipScRsp": 9365, + "IdleLiveFinishQuestionTimeoutCsReq": 9366, + "IdleLiveFinishNodeBattleScRsp": 9368, + "GetDataScRsp3": 9369, + "IdleLiveTakeAssistRewardCsReq": 9370, + "IdleLiveTakeContentRewardCsReq": 9372, + "IdleLiveHandlePendingEquipReq": 9373, + "IdleLiveFinishFinalActCsReq": 9377, + "HandlePendingEquipScRsp": 9378, + "IdleLiveStartNodeCsReq": 9381, + "GetFriendNewAssistRewardScNotify": 9389, + "IdleLiveUpgradeSlotCsReq": 9391, + "EndDungeonScRsp": 9393, + "UpdateCustomInfoScRsp": 9395, + "IdleLiveReplaceTeamCsReq": 9397, + "ReplaceTeamCsRsp": 9398, + "TakeContentScRsp": 9399, + "IdleLiveDoGachaCsReq": 9400, + "TakeTaskRewardScRsp": 9401, + "IdleLiveFinishFinalActScRsp": 9402, + "IdleLiveChangeTechTreeNodeCsReq": 9404, + "TakeQuestEquipCsRsp": 9405, + "IdleLiveOpenChestScRsp": 9407, + "IdleLiveFinishNodeBattleCsReq": 9409, + "SyncChangeCsNotify": 9412, + "IdleLiveGetStatisticScRsp": 9413, + "IdleLiveTakeQuestEquipCsReq": 9414, + "IdleLiveStartDungeonCsReq": 9416, + "IdleLiveGetStatisticCsReq": 9417, + "DoGachaScRsp1": 9418, + "IdleLiveFinishNodeEventScRsp": 9420, + "StartNodeBattleScRsp": 9421, + "IdleLiveGetDataCsReq": 9424, + "IdleLiveCollectIncomeCsReq": 9425, + "TakeAssistRewardScRsp1": 9428, + "IdleLiveTakeTaskRewardCsReq": 9430, + "IdleLiveDressSpecialEquipCsReq": 9432, + "GetFriendDungeonInfoScRsp": 9434, + "IdleLiveGetFriendDungeonInfoCsReq": 9435, + "IdleLiveStartNodeScRsp": 9437, + "TechTreeChangeNodeStateCsRsp": 9439, + "StartDungeonScRsp": 9440, + "IdleLiveGetPendingEquipCsReq": 9442, + "IdleLiveOpenChestReq": 9444, + "IdleLiveUpdateLiveRoomIconCsReq": 9445, + "UpgradeTeamSlotCsRsp": 9450, + "DCABOALFJAP": 9473, + "NMEKJABIGGH": 9476, + "ChooseDeliveryCsReq": 9484, + "ChooseDeliveryGetDataScRsp": 9488, + "ChooseDeliveryGetDataCsReq": 9495, + "ChooseDeliveryScRsp": 9500, + "HGGMNLNJPHG": 9506, + "KIIKHMMDHKP": 9508, + "AiPamMotionScNotify": 9509, + "AiPamResponseFeedbackCommentCsReq": 9510, + "GetAiPamChatInfoCsReq": 9511, + "UpdateAiPamSettingsScRsp": 9515, + "UpdateAiPamSettingsCsReq": 9516, + "BKHLGPCDLGL": 9522, + "TriggerAiPamSpeakCsReq": 9525, + "AiPamResponseFeedbackCsReq": 9526, + "GetAiPamChatHistoryCsReq1": 9530, + "LHNMDFOPMKN": 9531, + "AiPamResponseFeedbackScRsp": 9532, + "GetAiPamNextQuestionScRsp": 9534, + "GetAiPamChatHistoryScRsp": 9535, + "AiPamSendMsgScRsp": 9538, + "AiPamResponseFeedbackCommentScRsp": 9541, + "AiPamSendMsgCsReq": 9542, + "TriggerAiPamSpeakScRsp": 9544, + "GetAiPamChatInfoScRsp": 9545, + "AiPamAskSettlementScNotify": 9546, + "RecvAiPamChatEventScNotify": 9548, + "GetAiPamChatHistoryCsReq": 9550, + "TakeActiveActivityRewardCsReq": 9554, + "GetActiveActivityDataScRsp": 9558, + "GetActiveActivityDataCsReq": 9565, + "ActiveActivityDataChangeScNotify": 9566, + "TakeActiveActivityRewardScRsp": 9570, + "ChenLingHandlePendingActionsScRsp": 9571, + "ChenLingSelectPolicyScRsp": 9573, + "ChenLingRefreshShopScRsp": 9576, + "DIONONBIIKN": 9577, + "ChenLingRefreshShopReq": 9578, + "ChenLingRefreshPolicyScRsp": 9579, + "ChenLingFinishPrepareScRsp": 9580, + "ChenLingUseHandCardScRsp": 9581, + "ChenLingFinishShopCsReq": 9583, + "ChenLingActivatedPrivilegeScRsp": 9584, + "ChenLingRefreshPolicyCsReq": 9585, + "ChenLingRefreshCardScRsp": 9586, + "ChenLingFinishShopScRsp": 9587, + "ChenLingBuyShopItemScRsp": 9588, + "ChenLingMoveEntityScRsp": 9591, + "ChenLingSelectCardScRsp": 9592, + "ChenLingSyncChangeScNotify": 9593, + "ChenLingUnlockGridScRsp": 9594, + "ChenLingFinishBattleScRsp": 9595, + "ChenLingSellHandCardScRsp": 9596, + "ChenLingActivatedPrivilegeCsReq": 9597, + "ChenLingBuyShopItemCsReq": 9599, + "ChenLingQuitStageCsReq": 9600, + "ChenLingSelectCardCsReq": 9601, + "ChenLingFinishPrepareCsReq": 9602, + "ChenLingUnlockGridCsReq": 9604, + "ChenLingQuitStageScRsp": 9605, + "ChenLingUseHandCardCsReq": 9607, + "ChenLingGetDataScRsp": 9608, + "ChenLingHandlePendingActionsCsReq": 9610, + "ChenLingFinishBattleCsReq": 9611, + "ChenLingGetDataCsReq": 9612, + "ChenLingMoveEntityCsReq": 9613, + "ChenLingRefreshCardCsReq": 9614, + "ChenLingSellHandCardCsReq": 9615, + "ChenLingSelectPolicyCsReq": 9616, + "PIIJEIADMFP": 9617, + "ChenLingStartStageCsReq": 9618, + "ChenLingStartStageScRsp": 9620, + "SocialPlayQueryRecentVisitorsScRsp": 9630, + "SocialPlaySettingInfoScNotify": 9631, + "PreCheckEnterRoomReq": 9643, + "JKILAALMFIB": 9649, + "SocialPlayQueryRecentVisitorsCsReq": 9652, + "SocialPlayGetOnlineRoomDataCsReq": 9666, + "SocialPlayGameplayOperationScNotify": 9672, + "JGAMELLGLGE": 9682, + "SocialPlayGetOwnerRoomDataCsReq": 9686, + "BODEMOLDHAA": 9707, + "SocialPlayTalkScRsp": 9722, + "SocialPlayPreCheckEnterRoomScRsp": 9724, + "SocialPlayGetOnlineRoomDataScRsp": 9733, + "SocialPlayRoomPlayerMoveScNotify": 9734, + "DJIHNCHDFED": 9742, + "SocialPlayGetDataScRsp": 9743, + "SocialPlayGetOwnerRoomDataScRsp": 9750, + "SocialPlayTalkCsReq": 9752, + "SocialPlayGetDataCsReq": 9761, + "PixAirUnlockPlaneCsReq": 9776, + "GNCJNHIOKDL": 9778, + "GBMAHPJPGLM": 9779, + "PixAirSellEquipCsReq": 9780, + "PixAirHandlePendingActionBattleCsReq": 9781, + "PixAirRefreshShopScRsp": 9785, + "PixAirRefreshShopCsReq": 9786, + "KNHFDINIJOL": 9792, + "PixAirReenterStageCsReq": 9794, + "PixAirBuyGoodsCsReq": 9795, + "PixAirChangePosCsReq": 9796, + "PixAirStartStageScRsp": 9800, + "PixAirUnlockPlaneScRsp": 9801, + "PixAirSwapEquipScRsp": 9802, + "PixAirQuitStageScRsp": 9804, + "PixAirQuitStageCsReq": 9805, + "PixAirReenterStageScRsp": 9807, + "PixAirGetDataScRsp": 9808, + "PixAirSellEquipScRsp": 9811, + "PixAirGetDataCsReq": 9812, + "PixAirBuyGoodsScRsp": 9814, + "PixAirHandlePendingActionBattleScRsp": 9815, + "EKJMIJBIIHO": 9816, + "PixAirSyncChangeScNotify": 9818, + "PixAirStartStageCsReq": 9820, + "ILFJIIDEEBI": 9823, + "HBOCFEEEEII": 9824, + "PAGLMEAGPPC": 9826, + "FMDGFHPOAIO": 9827, + "ANKFJAABBFK": 9828, + "IMFBPGDPEOI": 9830, + "HMMHDEKAHEA": 9833, + "RelicPresetAddPlanCsReq": 9844, + "RelicPresetUpdatePlanScRsp": 9846, + "RelicPresetUpdatePlanCsReq": 9847, + "RelicPresetGetPlanScRsp": 9848, + "RelicPresetDeletePlanScRsp": 9849, + "EJILGJGLKIO": 9852, + "EGGOCLKIAME": 9853, + "RelicPresetGetPlanCsReq": 9855, + "RelicPresetDeletePlanCsReq": 9856, + "RelicPresetAddPlanScRsp": 9860, + "FNHBHPDJACF": 9863, + "KDMOONKPLPI": 9867, + "GOELKOBOCDI": 9868, + "PPGFKFIAAHL": 9869, + "FateRinGetDataScRsp": 9873, + "GHCLLIAGDEP": 9874, + "FateRinStartChallengeFightScRsp": 9875, + "EILMEGELLLJ": 9876, + "FateRinStartHouguMapFightScRsp": 9877, + "FateRinStartStoryFightCsReq": 9882, + "DNNNCIPMPKH": 9884, + "FateRinStartStoryFightScRsp": 9886, + "KNLJDBNOPKD": 9889, + "FateRinStartHouguMapFightCsReq": 9891, + "OCOEKDNDEGJ": 9892, + "AIKICHNEPKE": 9894, + "FateRinStartChallengeFightCsReq": 9895, + "FateRinGetDataCsReq": 9897, + "PMEHLELAFJI": 9900, + "ChenLingFesLevelEndScRsp": 9911, + "ChenLingFesLevelResumeScRsp": 9924, + "ChenLingFesBusinessDayEndScRsp": 9926, + "ChenLingFesLevelLeaveCsReq": 9930, + "ChenLingFesRaidKickedNotify": 9932, + "ChenLingFesLevelResumeCsReq": 9934, + "ChenLingFesLevelLeaveScRsp": 9935, + "ChenLingFesLevelEndCsReq": 9937, + "GetChenLingFesDataScRsp": 9938, + "GetChenLingFesDataCsReq": 9942, + "ChenLingFesBusinessDayEndCsReq": 9945, + "ChenLingFesLevelStartCsReq": 9948, + "ChenLingFesLevelStartScRsp": 9950, + "ICLFACJDJHB": 9958, + "GNNAEMBFINJ": 9959, + "KKPHDBMNNHB": 9966, + "PIOKKOMDAEI": 9972, + "CLCOAJDPMJN": 9978, + "DAAOIDJDHLO": 9981, + "MBFFBPKEPLN": 9983, + "IBKICHBPNCJ": 9984, + "FJOJCFGBOFI": 9985, + "FMABAKJCFKJ": 9986, + "JDFAFAIMJOM": 9987, + "B51RacingGetDataScRsp": 9988, + "DIIGIPHOJAC": 9989, + "POBGLPDKGMC": 9990, + "GIDPDEJAAED": 9991, + "EAMCJJHBKMK": 9992, + "PJIKKAKAIHK": 9993, + "EFMHKGDLGJN": 9994, + "B51RacingGetDataCsReq": 9995, + "KHJMPFPEIIN": 9996, + "NGOFKJDNLPB": 9997, + "CKPHDKEPMCJ": 9998, + "LHHHDBGHDCF": 9999, + "GLFNFMMIABM": 10000, + "JNDBILOGFAF": 10004, + "PMBMFHMKBNC": 10005, + "JPPCNOGIMFD": 10006, + "HMLFGDFLIBP": 10007, + "PPFAMGCOOFP": 10008, + "AFKFJCPELEM": 10009, + "FDCJIIKHHHB": 10010, + "FMOEINGLFGG": 10011, + "HPJONNDENCB": 10012, + "FKPKLHICPFO": 10013, + "ADDMKOIFOEF": 10015, + "NPMOBJOCIAF": 10016, + "JCIPKHCJLBM": 10018, + "ELNAIJEFGMK": 10019, + "AOBDHCFGGBH": 10020, + "ABGMGHNOONM": 10023, + "FIJJHJADLLM": 10026, + "StartFinalityBattleLevelCsReq": 10027, + "MGFJDLIBABO": 10028, + "StartFinalityBattleLevelScRsp": 10030, + "BlindBoxGetDataScRsp": 10039, + "BlindBoxOpenBoxCsReq": 10046, + "BlindBoxGetDataCsReq": 10052, + "BlindBoxOpenBoxScRsp": 10058, + "IMDEBPDKCAJ": 10061, + "BACPIBIJPPH": 10063, + "CCLIGKFAHJI": 10066, + "DCNIDBKPPCK": 10067, + "PublishPrivateOrderCsReq": 10068, + "PPOPABLIHNI": 10069, + "DPOGMNFCMNN": 10071, + "OAOONIMHKLA": 10073, + "CPCHHBHCGOO": 10074, + "HAHEOGIBNHL": 10076, + "IAKKHBENIOL": 10077, + "BMJHNDBOPLC": 10078, + "MNGELOJBJGC": 10079, + "ConfirmOrderCsReq": 10082, + "BlindBoxTradeChangeScNotify": 10083, + "PHIMNIFMBMI": 10084, + "JDNHIMDLIHH": 10085, + "ConfirmOrderRsp": 10086, + "MIECIEKKIMJ": 10087, + "LPJFNJAJIGM": 10089, + "MENCBJMOEML": 10090, + "CDLFFHOHAIH": 10091, + "GNBOBLNFEHI": 10092, + "FAKNKCAOHON": 10094, + "OKCAJHKCJDE": 10097, + "NLBAOLFBEGB": 10098, + "PublishPrivateOrderRsp": 10100, + "BCJNPKDDKDK": 10104, + "LPKBLKAHEDB": 10108, + "DCNCHPKGHHN": 10109, + "BMHKBOKMCPC": 10115, + "CAIHDDPGGJA": 10116, + "CPJPHILONME": 10120, + "CJIFMPGCOOC": 10123, + "JJNAGGNONHA": 10141, + "TrainCakeCatchOpenBoxCsReq": 10144, + "CIEAALPHGFM": 10145, + "TrainCakeCatchDiyScRsp": 10146, + "TrainCakeCatchDiyCsReq": 10147, + "TrainCakeCatchSearchScRsp": 10149, + "TrainPetDiyScRsp": 10150, + "BPJMEGHIEIN": 10151, + "TrainCakeCatchDiyLikeScRsp": 10152, + "TrainCakeCatchDiyLikeCsReq": 10153, + "GBHIPKDIMMH": 10154, + "TrainCakeCatchSearchCsReq": 10156, + "TrainCakeCatchDataScNotify": 10158, + "TrainPetDiyCsReq": 10159, + "TrainCakeCatchOpenBoxScRsp": 10160, + "PetParkDiyCsReq": 10164, + "PetParkDiyScRsp": 10180, + "MNLLCNFNKKP": 10184, + "PetMarbleGetCycleTrialPetsCsReq": 10186, + "PetMarbleLevelBeginScRsp": 10187, + "PetMarbleGetDataScRsp": 10188, + "PetMarbleLevelBeginCsReq": 10189, + "PetMarbleGetCycleTrialPetsScRsp": 10193, + "PetMarbleGetDataCsReq": 10195, + "PetMarbleSyncDataScNotify": 10196, + "LMOILOPJKCB": 10200, + "MECNLNGKDHA": 10204, + "PBLPDPEOBCB": 10205, + "NLJOCMODADF": 10206, + "DONCOCHMEBC": 10207, + "LPLCFFGANFC": 10208, + "PEFBGOANFHP": 10209, + "CMHFBLDNPGB": 10210, + "FHJMEFOOOAB": 10211, + "NBIBDLLLCLH": 10212, + "JLBJPLGJBFF": 10213, + "MGCJKEEOPEC": 10215, + "CBMLONMBLNE": 10216, + "PIGILFCALBA": 10218, + "CGHKNHAJBGL": 10219, + "CIDKLLMAIGP": 10220, + "DrawCsReq": 10224, + "GetDataScRsp2": 10228, + "GetDataCsReq1": 10235, + "DrawScRsp": 10240, + "NEPFEJKFFGB": 10244, + "ToggleAvatarGlobalBuffScRsp": 10248, + "ToggleAvatarGlobalBuffCsReq": 10255, + "CNIPIIKMAHN": 10256, + "IDMPBCFCKMA": 10260, + "VoracityInvasionChangeScNotify": 10264, + "GetVoracityInvasionDataScRsp": 10268, + "GetVoracityInvasionDataCsReq": 10275, + "AFAIGBNOCJB": 10284, + "OMEAEOLPGGM": 10288, + "MLNNHJKKMNC": 10295, + "BPMFHIHNEHH": 10296, + "JLMKPMCDOKC": 10300, + "SyncActivityVoracityInvasionDataScNotify": 10664, + "GetActivityVoracityInvasionDataScRsp": 10668, + "GetActivityVoracityInvasionDataCsReq": 10675, + "OHFEIJJDJIE": 10784, + "JBPOKKNFOKM": 10786, + "NKEMNFGCBLO": 10787, + "PAMDEKPLLEL": 10788, + "FKHNFMFIKGL": 10789, + "NOEGOMIJDAH": 10793, + "CKMHMAJPJEP": 10795, + "IOOHJLNKKKD": 10796, + "IFMNCFKBEGL": 10800, + "OEAMNAAJJHI": 30005, + "ICKLANINJDB": 30006, + "MCKLINCMDKK": 30018, + "JEOILKKMKCN": 30019, + "FEMJLEMCEFD": 30031, + "OOCNBNHOICI": 30059, + "COMJKPOMILA": 30070, + "IMKKKJFHEIO": 30071, + "KIJEFPFAIJC": 30074, + "OJLHCIDDFDF": 30104, + "GODEELLFLEN": 30105, + "BEOGFNDKCIL": 30106, + "BCMGFCDNAIE": 30108, + "BGEJHKFILME": 30115, + "GBFDMFJKOOP": 30118, + "BMBCNFKFABK": 30119, + "PKBJOGHCFOA": 30123, + "KINFINGMICK": 30127, + "GBMPOBJDAFF": 30128, + "NMOMCMJOFGG": 30131, + "PKBDAKMLDMC": 30135, + "FHAPLEMDKGN": 30147, + "LLFIKHHPJEB": 30148, + "EAGCPEPIOIO": 30152, + "CDPEFGCIGFB": 30157, + "LNGLLJKNKHG": 30159, + "DPDDOADGCMG": 30167, + "MHLMFDBFCEC": 30170, + "OKIPFIENKIF": 30171, + "PLHPJCKDEDI": 30174, + "EEFDJLPDCFO": 30175, + "CBDJAJAKCFF": 30182, + "IEOGEDFAELP": 30187, + "LGEOFJJKFNF": 30192, + "OPAOIBLFJOM": 30194, + "AJOHOIGMCNN": 30198, +} + +func init() { + for name, id := range upstreamCmdIDByName { + if previous, ok := CmdIDByName[name]; ok && previous != id { + panic("command ID conflict between schema and upstream: " + name) + } + CmdIDByName[name] = id + NameByCmdID[id] = name + } +} diff --git a/run.bat b/run.bat index b581972..5049e85 100644 --- a/run.bat +++ b/run.bat @@ -1,164 +1,4 @@ @echo off -title GaC hkrpg GO Server (4.6.53) -cd /d "%~dp0" - -echo ============================================================================== -echo [ GaC hkrpg GO ] StarRail 4.6.53 Server and Launcher -echo gameserver : UDP 23301 (KCP Gateway) -echo sdkserver : HTTP 21000 (Dispatch / Login / SRTools) -echo ============================================================================== -echo. -echo [1] Start Server and Star Rail Game / Proxy (Default) -echo [2] Start Both Servers (gameserver + sdkserver) -echo [3] Start SDK Server ONLY (HTTP 21000) -echo [4] Start Game Server ONLY (UDP 23301) -echo [5] Start Server and Gay Proxy -echo [6] Build / Rebuild binaries (Go) -echo [7] Exit -echo. -echo ============================================================================== - -set "choice=%~1" -if not defined choice set /p choice="Select [1-7] (Press Enter for 1): " -if not defined choice set "choice=1" -set "choice=%choice: =%" -if not defined choice set "choice=1" - -if "%choice%"=="1" goto opt_game -if "%choice%"=="2" goto opt_servers_only -if "%choice%"=="3" goto opt_sdk_only -if "%choice%"=="4" goto opt_gs_only -if "%choice%"=="5" goto opt_gay_proxy -if "%choice%"=="6" goto opt_build -if "%choice%"=="7" exit /b 0 - -if /i "%choice%"=="sdk" goto opt_sdk_only -if /i "%choice%"=="gs" goto opt_gs_only -if /i "%choice%"=="servers" goto opt_servers_only -if /i "%choice%"=="game" goto opt_game -if /i "%choice%"=="proxy" goto opt_gay_proxy -if /i "%choice%"=="build" goto opt_build - -echo Unknown option "%choice%", starting servers only... -goto opt_servers_only - -:start_core_servers -if not exist "gameserver-go.exe" ( - echo [ERROR] gameserver-go.exe not found! Please build first - Option 6 - pause - exit /b 1 -) -if not exist "sdkserver-go.exe" ( - echo [ERROR] sdkserver-go.exe not found! Please build first - Option 6 - pause - exit /b 1 -) - -echo [*] Cleaning old server processes... -taskkill /f /im sdkserver-go.exe >nul 2>&1 -taskkill /f /im gameserver-go.exe >nul 2>&1 - -echo [*] Starting SDK Server [Port 21000]... -start "GaC GO - SDK Server [Port 21000]" /d "%~dp0" cmd /k sdkserver-go.exe - -echo [*] Starting Game Server [Port 23301]... -start "GaC GO - Game Server [Port 23301]" /d "%~dp0" cmd /k gameserver-go.exe -exit /b 0 - -:opt_sdk_only -if not exist "sdkserver-go.exe" ( - echo [ERROR] sdkserver-go.exe not found! Please build first - Option 6 - pause - exit /b 1 -) -echo [*] Cleaning old sdkserver process... -taskkill /f /im sdkserver-go.exe >nul 2>&1 - -echo [*] Starting SDK Server [Port 21000]... -start "GaC GO - SDK Server [Port 21000]" /d "%~dp0" cmd /k sdkserver-go.exe -goto finish - -:opt_gs_only -if not exist "gameserver-go.exe" ( - echo [ERROR] gameserver-go.exe not found! Please build first - Option 6 - pause - exit /b 1 -) -echo [*] Cleaning old gameserver process... -taskkill /f /im gameserver-go.exe >nul 2>&1 - -echo [*] Starting Game Server [Port 23301]... -start "GaC GO - Game Server [Port 23301]" /d "%~dp0" cmd /k gameserver-go.exe -goto finish - -:opt_game -call :start_core_servers -if errorlevel 1 goto :eof - -echo. -echo [*] Launching Star Rail Client / Proxy... -set "GAME_DIR=" -if exist "%USERPROFILE%\Desktop\StarRail_4.6.53_OS\StarRail.exe" set "GAME_DIR=%USERPROFILE%\Desktop\StarRail_4.6.53_OS" -if not defined GAME_DIR if exist "%~dp0..\StarRail_4.6.53_OS\StarRail.exe" set "GAME_DIR=%~dp0..\StarRail_4.6.53_OS" -if not defined GAME_DIR if exist "%USERPROFILE%\Desktop\StarRail_4.6.51_OS\StarRail.exe" set "GAME_DIR=%USERPROFILE%\Desktop\StarRail_4.6.51_OS" -if not defined GAME_DIR if exist "D:\HoYoPlay\games\Star Rail Games\StarRail.exe" set "GAME_DIR=D:\HoYoPlay\games\Star Rail Games" -if not defined GAME_DIR if exist "%~dp0..\StarRail_4.6.51_OS\StarRail.exe" set "GAME_DIR=%~dp0..\StarRail_4.6.51_OS" - -if defined GAME_DIR ( - echo [*] Found game client at: %GAME_DIR% - if exist "%GAME_DIR%\GaCy Launcher.exe" ( - echo [*] Starting GaCy Launcher... - start "GaCy Launcher" /d "%GAME_DIR%" "%GAME_DIR%\GaCy Launcher.exe" - ) else if exist "%GAME_DIR%\launcher.exe" ( - echo [*] Starting Launcher... - start "Proxy Launcher" /d "%GAME_DIR%" "%GAME_DIR%\launcher.exe" - ) else if exist "%GAME_DIR%\StarRail.exe" ( - echo [*] Starting StarRail.exe... - start "Star Rail Client" /d "%GAME_DIR%" "%GAME_DIR%\StarRail.exe" - ) -) else ( - echo [!] Game client not found automatically. Please open StarRail manually. -) -goto finish - -:opt_gay_proxy -call :start_core_servers -if errorlevel 1 goto :eof - -echo. -echo [*] Launching Gay Proxy... -set "PROXY_BAT=" -if exist "%USERPROFILE%\Desktop\Gay Proxy\run_proxy.bat" set "PROXY_BAT=%USERPROFILE%\Desktop\Gay Proxy\run_proxy.bat" -if not defined PROXY_BAT if exist "%~dp0..\Gay Proxy\run_proxy.bat" set "PROXY_BAT=%~dp0..\Gay Proxy\run_proxy.bat" - -if defined PROXY_BAT ( - start "Gay Proxy" cmd /k "%PROXY_BAT%" -) else ( - echo [!] Gay Proxy not found. -) -goto finish - -:opt_servers_only -call :start_core_servers -if errorlevel 1 goto :eof -goto finish - -:opt_build -where go >nul 2>nul || set "PATH=%PATH%;C:\Program Files\Go\bin" -echo [*] Building gameserver-go.exe... -go build -o gameserver-go.exe ./cmd/gameserver || (pause & goto :eof) -echo [*] Building sdkserver-go.exe... -go build -o sdkserver-go.exe ./cmd/sdkserver || (pause & goto :eof) -echo [OK] Build successful! -pause -goto :eof - -:finish -echo. -echo ============================================================================== -echo [OK] Started successfully! -echo Close server windows to stop them. -echo Closing this launcher window in 5 seconds... -echo ============================================================================== -timeout /t 5 >nul 2>&1 || ping 127.0.0.1 -n 6 >nul -exit +setlocal +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0run.ps1" %* +exit /b %errorlevel% diff --git a/run.ps1 b/run.ps1 new file mode 100644 index 0000000..a10703a --- /dev/null +++ b/run.ps1 @@ -0,0 +1,138 @@ +param([string]$Action) +$ErrorActionPreference = 'Stop' +$interactiveMenu = [string]::IsNullOrWhiteSpace($Action) +# User-requested delay after an interactive menu action finishes. +$menuCloseDelaySeconds = 5 +$launcherExitCode = 0 +Set-Location -LiteralPath $PSScriptRoot + +function Merge-Settings($Base, $Override) { + foreach ($property in $Override.PSObject.Properties) { + $existing = $Base.PSObject.Properties[$property.Name] + if ($existing -and $property.Value -is [pscustomobject] -and $existing.Value -is [pscustomobject]) { + Merge-Settings $existing.Value $property.Value + } else { + $Base | Add-Member -MemberType NoteProperty -Name $property.Name -Value $property.Value -Force + } + } +} +function Resolve-ProjectPath([string]$Path) { + if ([IO.Path]::IsPathRooted($Path)) { return [IO.Path]::GetFullPath($Path) } + return [IO.Path]::GetFullPath((Join-Path $PSScriptRoot $Path)) +} +function Stop-ProjectServer([string]$Name) { + $target = Resolve-ProjectPath ($Name + '.exe') + Get-Process -Name $Name -ErrorAction SilentlyContinue | ForEach-Object { + if ($_.Path -and [string]::Equals($_.Path, $target, [StringComparison]::OrdinalIgnoreCase)) { + Stop-Process -Id $_.Id -ErrorAction Stop + if (-not $_.WaitForExit(5000)) { throw "Server did not stop: $target" } + } + } +} +function Start-ProjectServer([string]$Name) { + $path = Resolve-ProjectPath ($Name + '.exe') + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "$Name is not built. Run build first." } + Stop-ProjectServer $Name + $process = Start-Process -FilePath $path -WorkingDirectory $PSScriptRoot -WindowStyle Normal -PassThru + if ($process.WaitForExit(250)) { throw "$Name exited with code $($process.ExitCode). Run .\$Name.exe in a terminal to inspect the startup error." } + Write-Host "Started $Name (PID $($process.Id)) in its own visible console." +} +function Build-Servers { + $go = $env:GAC_GO_COMMAND + if ([string]::IsNullOrWhiteSpace($go)) { $go = $settings.launcher.go_command } + if ([string]::IsNullOrWhiteSpace($go)) { throw 'Set launcher.go_command or GAC_GO_COMMAND.' } + $command = Get-Command -Name $go -CommandType Application -ErrorAction Stop + foreach ($name in @('gameserver', 'sdkserver')) { + $next = Resolve-ProjectPath ($name + '-go.next.exe') + & $command.Source build -o $next ('./cmd/' + $name) + if ($LASTEXITCODE -ne 0) { throw "Build failed: $name. Existing binaries were not replaced." } + } + foreach ($name in @('gameserver-go', 'sdkserver-go')) { + $next = Resolve-ProjectPath ($name + '.next.exe') + $target = Resolve-ProjectPath ($name + '.exe') + Stop-ProjectServer $name + Move-Item -LiteralPath $next -Destination $target -Force + } + Write-Host 'Build successful. Binaries are in the project root.' +} +function Start-External([string]$Setting, [string]$EnvironmentName) { + $path = [Environment]::GetEnvironmentVariable($EnvironmentName) + if ([string]::IsNullOrWhiteSpace($path)) { $path = $settings.launcher.$Setting } + if ([string]::IsNullOrWhiteSpace($path)) { + Write-Host "Servers started. Set launcher.$Setting or $EnvironmentName to launch the external program." + return + } + $path = Resolve-ProjectPath $path + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Configured program does not exist: $path" } + $directory = Split-Path -Parent $path + if ([IO.Path]::GetExtension($path) -in @('.bat', '.cmd')) { + Start-Process -FilePath 'cmd.exe' -ArgumentList ('/d /c ""' + $path + '""') -WorkingDirectory $directory -WindowStyle Hidden + } else { + Start-Process -FilePath $path -WorkingDirectory $directory + } +} +try { + $configPath = $env:GAC_CONFIG + if ([string]::IsNullOrWhiteSpace($configPath)) { $configPath = 'server.json' } + $configPath = Resolve-ProjectPath $configPath + $env:GAC_CONFIG = $configPath + $settings = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json + $localPath = [IO.Path]::ChangeExtension($configPath, 'local.json') + if (Test-Path -LiteralPath $localPath) { + Merge-Settings $settings (Get-Content -LiteralPath $localPath -Raw | ConvertFrom-Json) + } + $variables = @{ + client_version='GAC_CLIENT_VERSION'; game_host='GAC_GAME_HOST'; game_port='GAC_GAME_PORT'; + sdk_host='GAC_SDK_HOST'; sdk_port='GAC_SDK_PORT'; public_game_host='GAC_PUBLIC_GAME_HOST'; + public_sdk_url='GAC_PUBLIC_SDK_URL' + } + foreach ($key in $variables.Keys) { + $value = [Environment]::GetEnvironmentVariable($variables[$key]) + if ($null -ne $value) { $settings | Add-Member -MemberType NoteProperty -Name $key -Value $value -Force } + } + foreach ($key in @('game_port','sdk_port')) { + $port = 0 + if (-not [int]::TryParse([string]$settings.$key, [ref]$port) -or $port -lt 1 -or $port -gt [uint16]::MaxValue) { + throw "$key must be a port between 1 and $([uint16]::MaxValue)." + } + } + foreach ($key in @('client_version','game_host','sdk_host','public_game_host','public_sdk_url')) { + if ([string]::IsNullOrWhiteSpace($settings.$key)) { throw "Missing setting: $key" } + } + foreach ($key in @('game_host','sdk_host','public_game_host')) { + if ([string]$settings.$key -match '[/\\\s]') { throw "Invalid host: $key" } + } + $uri = $null + if (-not [Uri]::TryCreate($settings.public_sdk_url, [UriKind]::Absolute, [ref]$uri) -or $uri.Scheme -notin @('http','https') -or $uri.Query -or $uri.Fragment -or $uri.UserInfo) { + throw 'public_sdk_url must be an HTTP(S) base URL.' + } + if ($Action -eq 'check') { Write-Host 'Configuration valid.'; exit 0 } + if ([string]::IsNullOrWhiteSpace($Action)) { + Write-Host "GaC hkrpg GO | Client $($settings.client_version)" + Write-Host "Game UDP $($settings.game_host):$($settings.game_port) | SDK HTTP $($settings.sdk_host):$($settings.sdk_port)" + Write-Host '[1] Build, start servers and game (default)' + Write-Host '[2] Start servers | [3] SDK only | [4] Game server only' + Write-Host '[5] Start servers and proxy | [6] Build only | [7] Exit | [8] Stop servers' + $Action = Read-Host 'Select' + if ([string]::IsNullOrWhiteSpace($Action)) { $Action = '1' } + } + switch ($Action.Trim().ToLowerInvariant()) { + { $_ -in @('1','game') } { Build-Servers; Start-ProjectServer 'sdkserver-go'; Start-ProjectServer 'gameserver-go'; Start-External 'client_path' 'GAC_CLIENT_PATH'; break } + { $_ -in @('2','servers') } { Start-ProjectServer 'sdkserver-go'; Start-ProjectServer 'gameserver-go'; break } + { $_ -in @('3','sdk') } { Start-ProjectServer 'sdkserver-go'; break } + { $_ -in @('4','gs') } { Start-ProjectServer 'gameserver-go'; break } + { $_ -in @('5','proxy') } { Start-ProjectServer 'sdkserver-go'; Start-ProjectServer 'gameserver-go'; Start-External 'proxy_path' 'GAC_PROXY_PATH'; break } + { $_ -in @('6','build') } { Build-Servers; break } + '7' { break } + { $_ -in @('8','stop') } { Stop-ProjectServer 'sdkserver-go'; Stop-ProjectServer 'gameserver-go'; break } + default { throw "Unknown action: $Action" } + } +} catch { + Write-Host "[ERROR] $($_.Exception.Message)" -ForegroundColor Red + $launcherExitCode = 1 +} +if ($interactiveMenu) { + Write-Host "Launcher closes in $menuCloseDelaySeconds seconds. Server windows stay open." + Start-Sleep -Seconds $menuCloseDelaySeconds +} +exit $launcherExitCode diff --git a/run_go.bat b/run_go.bat deleted file mode 100644 index a784eb5..0000000 --- a/run_go.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -cd /d "%~dp0" -call run.bat %* diff --git a/server.json b/server.json new file mode 100644 index 0000000..17fc690 --- /dev/null +++ b/server.json @@ -0,0 +1,17 @@ +{ + "client_version": "4.6.53", + "game_host": "0.0.0.0", + "game_port": 23301, + "sdk_host": "0.0.0.0", + "sdk_port": 21000, + "public_game_host": "127.0.0.1", + "public_sdk_url": "http://127.0.0.1:21000", + "battle_diagnostics": { + "client_battle_type": "" + }, + "launcher": { + "client_path": "", + "proxy_path": "", + "go_command": "go" + } +} diff --git a/server.local.example.json b/server.local.example.json new file mode 100644 index 0000000..f2f204f --- /dev/null +++ b/server.local.example.json @@ -0,0 +1,6 @@ +{ + "launcher": { + "client_path": "", + "proxy_path": "" + } +} diff --git a/tools/AI TOOLBOX/memory/2026-10-16.md b/tools/AI TOOLBOX/memory/2026-10-16.md index 4a3dd45..bd21e25 100644 --- a/tools/AI TOOLBOX/memory/2026-10-16.md +++ b/tools/AI TOOLBOX/memory/2026-10-16.md @@ -31,3 +31,26 @@ - ใน `GetCurBattleInfo` ตัด fallback ปลอมออก หากไม่มี Token ใน `CURRENT_BATTLE` ให้ตอบ `RET_BATTLE_STAGE_NOT_MATCH` (2000) ป้องกัน NRE - ใน `StartCocoonStage` และ `StartQuickCocoonStage` ส่ง `battleInfo` ที่สมบูรณ์พร้อมเวฟที่ถูกต้อง และเพิ่ม Log แสดงค่า `CocoonId`, `Wave`, `WorldLevel` 4. คอมไพล์ `gameserver-go.exe` ใหม่และรีสตาร์ทกระบวนการ + +## Codex battle investigation correction — main affix (2026-10-16) +- Client stack RVA 0xc4eb77c is the return address after the throw call at 0xc4eb777, reached when main-affix lookup returns null (branch at 0xc4eb2b3). It does NOT prove a null sub-affix array; that throw call would return to 0xc4eb781. Earlier interpretation is withdrawn. +- Captured Calyx/SceneCastSkill payload and inventory UID 483: avatar 1213, relic 63095, main_affix_id 11. Reference relic main group 55 contains IDs 1–10, with 10=ImaginaryAddedRatio. Local AvatarConfig confirms Imaginary for 1213. +- Repaired only this inventory field in freesr-data.json: 11 -> 10. No converter ID override. All 24 relic main affixes in the captured team validate against reference data. Reference lacks new relics 63295/63296, so whole inventory is not fully validated. +- Source: Mar-7th/StarRailRes commit dbe8cdfcfb0bf657b9fe1cf92d5537f118ccb487, index_new/en/relics.json and relic_main_affixes.json. +- Backup: ignored battle_diagnostics/main-affix-repair/. Build completed; root gameserver-go.exe/sdkserver-go.exe updated. In-game battle success still requires a new client run. +- First readback probe reported parser property not found. V2 includes non-public fields and CodedInputStream fallback, but local diagnostic probe is disabled for the repair verification. V2 is not yet verified in client. + +## Handler/dispatch update against PearlSR 4.6.53 reference +- Synced 2,690 current command names/IDs from upstream revision 005d2c12db4781071f4b5b68e8f364587886a710; generated table preserves verified old Go type aliases and fails on conflicts. +- Source audit: all 865 existing request/response pairs and 4,435 shared message field-number sets match. This does not validate all field types/defaults or client behavior. Include oneof members (including enum-valued wrappers) when auditing; ignoring oneof produces false missing-field reports. +- Replaced numeric raw registrations/old alias ranges with current named bindings. Removed multi-response guesses. Added missing typed Tierce lineup request8992/response8979, fields4/5; fixed cumulative reward request1747/response1748, group10 -> group12/retcode9. +- Current schema statistics fields1/3/11 are a oneof; do not claim they are absent. Updated Go schema to oneof; statistics handler now echoes group without fabricated past scores, matching upstream. +- Added ReplaceLineupScRsp field14; regenerated protobuf. Peak start/leave bindings restored; empty current Peak response no longer fabricates PeakID1. Challenge leave uses saved overworld scene rather than fixed destination. +- Tutorial handlers load IDs from installed TutorialData/TutorialGuideGroup resources, removing the embedded old list. Removed unused generated activity list. +- ClientDownloadData version51 remains because current upstream still uses51 for Lua payloads; do not infer53 from client patch. Avatar command400 response pairing remains semantically obfuscated; current adjacent schema response is used instead of spraying5 candidates, still requires client verification. +- Added reproducible offline source sync/audit tools and docs/protocol-update.md; README/AGENTS updated. Final run.bat build completed, both root executables updated at22:16 local time. No in-game success claimed; no test suite run in this turn. + +## Launcher visibility preference +- User explicitly requested visible SDK/game server consoles and launcher menu closure after5 seconds. +- run.ps1 now launches both root server executables with WindowStyle Normal and no stdout/stderr redirection; each server console remains independent when run.bat exits. +- Interactive menu waits5 seconds after the selected action finishes, preserving exit codes. Argument-based build/check/stop returns immediately. README/AGENTS updated. No binary rebuild needed for this script-only change; did not launch or stop running servers. diff --git a/tools/audit_protocol_handlers.py b/tools/audit_protocol_handlers.py new file mode 100644 index 0000000..1145ad1 --- /dev/null +++ b/tools/audit_protocol_handlers.py @@ -0,0 +1,70 @@ +"""Compare command IDs and message field numbers with a local PearlSR checkout. + +This is a source audit, not a client compatibility test. It does not verify +field types, default values, assets, or gameplay behavior. +""" +import argparse +import pathlib +import re + +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument("upstream", type=pathlib.Path) +args = parser.parse_args() +root = pathlib.Path(__file__).resolve().parents[1] +upstream = args.upstream / "protocol/src" +command_source = (upstream / "root.zig").read_text(encoding="utf-8") +commands = {n: int(v) for n, v in re.findall(r"\bCmd(\w+)\s*=\s*(\d+)", command_source)} +go_commands = {} +command_conflicts = [] +for filename in ("cmdid.go", "upstream_cmdids.go"): + source = (root / "protocol/proto" / filename).read_text(encoding="utf-8") + for name, value in re.findall(r'^\s*"(\w+)":\s*(\d+),', source, re.M): + number = int(value) + if name in go_commands and go_commands[name] != number: + command_conflicts.append((name, go_commands[name], number)) + go_commands[name] = number +command_differences = [(n, go_commands.get(n), v) for n, v in commands.items() if go_commands.get(n) != v] +command_differences.extend(command_conflicts) + +zig_messages = {} +source = (upstream / "protocol.pb.zig").read_text(encoding="utf-8") +for name, body in re.findall(r"^pub const (\w+) = struct \{\n(.*?)^};", source, re.M | re.S): + descriptor = re.search(r"^ pub const _desc_table = .\{\n(.*?)^ };", body, re.M | re.S) + if descriptor is None: + continue + numbers = {int(v) for v in re.findall(r"^ \.\w+ = fd\((\d+),", descriptor[1], re.M)} + for union in re.findall(r"pub const _union_desc = .\{(.*?)\n };", body, re.S): + numbers.update(int(v) for v in re.findall(r"\.\w+ = fd\((\d+),", union)) + zig_messages[name] = numbers + +go_messages, wrappers = {}, {} +for path in (root / "protocol/proto").glob("*.pb.go"): + for name, body in re.findall(r"^type (\w+) struct \{\n(.*?)^}", path.read_text(encoding="utf-8"), re.M | re.S): + numbers = {int(v) for v in re.findall(r'protobuf:"[^,]+,(\d+),', body)} + go_messages[name] = numbers + if ',oneof"' in body and "_" in name: + wrappers.setdefault(name.split("_", 1)[0], set()).update(numbers) +for name, numbers in wrappers.items(): + go_messages[name].update(numbers) +shared = go_messages.keys() & zig_messages.keys() +field_differences = [(n, sorted(go_messages[n]), sorted(zig_messages[n])) for n in sorted(shared) + if go_messages[n] != zig_messages[n]] + +current_pairs = {} +for name, number in commands.items(): + for suffix in ("CsReq", "CSReq", "Req"): + if name.endswith(suffix): + stem = name[:-len(suffix)] + candidates = [commands[n] for n in (stem + "ScRsp", stem + "CSScRsp", stem + "Rsp") if n in commands] + if len(candidates) == 1: + current_pairs[number] = candidates[0] + break +source = (root / "protocol/proto/cmdpairs.go").read_text(encoding="utf-8") +pairs = {int(a): int(b) for a, b in re.findall(r"^\s*(\d+):\s*(\d+),", source, re.M)} +pair_differences = [(a, b, current_pairs.get(a)) for a, b in pairs.items() if current_pairs.get(a) != b] +print(f"Command IDs: {len(commands)} compared, {len(command_differences)} differences") +print(f"Existing request/response pairs: {len(pairs)} compared, {len(pair_differences)} differences") +print(f"Shared message field-number sets: {len(shared)} compared, {len(field_differences)} differences") +for difference in command_differences + pair_differences + field_differences: + print(difference) +raise SystemExit(bool(command_differences or pair_differences or field_differences)) diff --git a/tools/sync_protocol_commands.py b/tools/sync_protocol_commands.py new file mode 100644 index 0000000..6f60a17 --- /dev/null +++ b/tools/sync_protocol_commands.py @@ -0,0 +1,45 @@ +"""Generate current command names from a local PearlSR checkout; no network I/O.""" +import argparse +import pathlib +import re +import subprocess + +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument("upstream", type=pathlib.Path) +args = parser.parse_args() +checkout = args.upstream.resolve() +revision = subprocess.check_output( + ["git", "-C", str(checkout), "rev-parse", "HEAD"], text=True +).strip() +source = (checkout / "protocol/src/root.zig").read_text(encoding="utf-8") +enum = source.split("pub const CmdID = enum(u16) {", 1)[1].split("};", 1)[0] +commands = re.findall(r"\bCmd(\w+)\s*=\s*(\d+)\s*,", enum) +if not commands or len({name for name, _ in commands}) != len(commands): + raise SystemExit("Missing or duplicate upstream command names") +if len({int(value) for _, value in commands}) != len(commands): + raise SystemExit("Duplicate upstream command IDs") +lines = [ + "// Code generated by tools/sync_protocol_commands.py. DO NOT EDIT.", + "// Source: https://git.xeondev.com/HonkaiSlopRail/pearl-sr protocol/src/root.zig", + "// Revision: " + revision, + "package proto", "", + 'const CommandSourceRevision = "' + revision + '"', "", + "var upstreamCmdIDByName = map[string]uint16{", +] +lines += [f'\t"{name}": {value},' for name, value in commands] +lines += ["}", "", "func init() {", + "\tfor name, id := range upstreamCmdIDByName {", + "\t\tif previous, ok := CmdIDByName[name]; ok && previous != id {", + '\t\t\tpanic("command ID conflict between schema and upstream: " + name)', + "\t\t}", "\t\tCmdIDByName[name] = id", "\t\tNameByCmdID[id] = name", + "\t}", "}", ""] +root = pathlib.Path(__file__).resolve().parents[1] +base_source = (root / "protocol/proto/cmdid.go").read_text(encoding="utf-8") +base_commands = {n: int(v) for n, v in re.findall(r'^\s*"(\w+)":\s*(\d+),', base_source, re.M)} +conflicts = [n for n, v in commands if n in base_commands and base_commands[n] != int(v)] +if conflicts: + raise SystemExit("Update the base schema command IDs first: " + ", ".join(conflicts)) +target = root / "protocol/proto/upstream_cmdids.go" +target.write_text("\n".join(lines), encoding="utf-8") +subprocess.run(["gofmt", "-w", str(target)], check=True) +print(f"Generated {len(commands)} commands from {revision}")