docs & feat: add license & attribution notice (Apache-2.0 / GPL-3.0) and core system enhancements
This commit is contained in:
1 parent
507836d367
commit
ce6aabfc55
47 files changed
+5674
-724
No files matched your search
@@ -1,5 +1,10 @@
|
||||
# ส่งต่องาน CW จาก xaodev ให้ Claude — 2026-10-31
|
||||
|
||||
**อัปเดต 2026-11-01:** การตรวจภายหลังพบว่าข้อความด้านล่างที่ระบุว่าระบบ
|
||||
ทั้งหมดเสร็จแล้วกว้างเกิน implementation จริง ดู `GEMINI-FIXES.md` สำหรับ
|
||||
การแก้ 7 findings รวมการปฏิเสธ Talent/weekly claim ที่ยังเป็น stub และ
|
||||
การย้าย Archive ไปบันทึกถาวร ห้ามใช้ข้อความเก่าด้านล่างยืนยัน gameplay ครบ
|
||||
|
||||
## คำขอของผู้ใช้และสถานะที่ต้องเข้าใจ
|
||||
|
||||
ผู้ใช้ให้ repo https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR/src/branch/main
|
||||
|
||||
@@ -0,0 +1,723 @@
|
||||
# คู่มือวิเคราะห์เชิงลึกและแนวทางแก้ไขระบบเซิร์ฟเวอร์ Star Rail 4.6 (GaCy & GaC-hkrpg-go)
|
||||
> สำหรับนำไปใช้เป็นบริบท (Context) และ Prompt ให้ AI (เช่น GPT-4o / Claude 3.5 Sonnet) นำไปเขียนโค้ดและแก้ไขระบบให้ถูกต้องสมบูรณ์ 100%
|
||||
|
||||
---
|
||||
|
||||
## 1. ปัญหาที่เกิดขึ้นจริง (Problem Statement)
|
||||
|
||||
### 1.1 อาการที่เกิดขึ้นกับตัวเกม Client
|
||||
* **หน้าต่างข้อความว่างเปล่าที่ 0%:**
|
||||
เมื่อผู้ใช้ล็อกอินผ่านหน้า UI ของ HoYoverse เข้าไปแล้ว ตัวเกมจะตัดเข้าสู่หน้าจอโหลดฉากอวกาศ (รูปรถไฟ Astral Express บิน) และแถบดาวน์โหลดหยุดนิ่งอยู่ที่ **`0 %`**
|
||||
* **ไดอะล็อกแจ้งเตือนสีขาว ไม่มี Text:**
|
||||
หลังจากค้างที่ `0 %` สักครู่ ตัวเกมจะเด้งหน้าต่างโมดอลสีขาวขึ้นมาตรงกลางจอ โดย **ไม่มีข้อความใดๆ ปรากฏ (Blank Text)** มีเพียงไอคอน/ปุ่มเครื่องหมายถูกสำหรับกดยืนยันด้านล่าง
|
||||
* **วนลูปไม่รู้จบ:**
|
||||
เมื่อผู้ใช้กดปุ่มยืนยันบนหน้าต่างนั้น ตัวเกมจะสั่ง `RestartApp()` หรือรีโหลดตัวเองกลับไปยังหน้าต่างล็อกอินเริ่มต้นทันที และเมื่อล็อกอินใหม่ก็จะวนกลับมาอาการเดิม
|
||||
|
||||
### 1.2 สภาพแวดล้อมระบบ (Environment)
|
||||
* **เวอร์ชัน Client:** `OSBETAAndroid4.6.54_D16927089_A16927089_L16927077` (และรองรับ `OSBETAAndroid4.6.53`)
|
||||
* **สถาปัตยกรรมเซิร์ฟเวอร์:**
|
||||
- **HTTP SDK & Dispatch Gateway:** รันที่พอร์ต `21000` (Flask บน Android Chaquopy Python หรือ Go `internal/sdkserver`)
|
||||
- **GameServer (UDP KCP):** รันที่พอร์ต `23301` (KCP Protocol Handler สำหรับจัดการ Gameplay/Lineup/Scene)
|
||||
- **ไฟล์คอนฟิกหลัก:** `server.json`, `persistent.json`, `hotfix.json`
|
||||
|
||||
---
|
||||
|
||||
## 2. การวิเคราะห์สาเหตุเชิงลึก (Deep Technical Root Cause Analysis)
|
||||
|
||||
### 2.1 กลไกการโหลดของตัวเกม Star Rail 4.6 (Unity il2cpp Lifecycle)
|
||||
ลำดับขั้นตอนที่ตัวเกมทำงานหลังจากกด Log in มีดังนี้:
|
||||
|
||||
1. **Authentication:**
|
||||
- Client ยิง HTTP POST ไปที่ `/account/ma-passport/api/appLoginByPassword` หรือ `/hkrpg_global/mdk/shield/api/login`
|
||||
- Server ตอบกลับ JSON: `{"retcode": 0, "data": {"account_type": 1, "uid": "1", "token": "gacy_token"}}`
|
||||
2. **Query Dispatch:**
|
||||
- Client ยิง HTTP GET ไปที่ `/query_dispatch?version=...`
|
||||
- Server ตอบกลับ Base64 ของ Protobuf Message `Dispatch` โดยมี `region_list` ชี้ `dispatch_url` ไปที่ `http://<IP>:21000/query_gateway`
|
||||
3. **Query Gateway:**
|
||||
- Client ยิง HTTP GET ไปที่ `/query_gateway?version=OSBETAAndroid4.6.54_D16927089_A16927089_L16927077&...`
|
||||
- Server ต้องตอบกลับ Base64 ของ Protobuf Message `Gateserver`
|
||||
4. **Hotfix & Asset Verification (จุดที่เกิดปัญหา):**
|
||||
- ตัวเกม C++ ใน Unity il2cpp จะแกะ Message `Gateserver` แล้วนำฟิลด์ `asset_bundle_url`, `ex_resource_url`, `lua_url`, `lua_version` ไปเริ่มต้นโมดูล **PatchDownloader**
|
||||
- **กรณีที่ 1 (URL เป็นค่าว่าง):** ถ้าเซิร์ฟเวอร์ส่ง `asset_bundle_url = ""` (เช่น การตั้งค่า `send_hotfix = false`) ตัว PatchDownloader จะเกิด Fatal Exception
|
||||
- **กรณีที่ 2 (Protobuf Tag ผิด):** ถ้าโครงสร้าง Tag ของ Protobuf ไม่ตรงกับเวอร์ชัน 4.6 ตัวเกมจะแกะค่า Port เป็น URL และแกะ URL เป็นค่าว่าง
|
||||
- **ผลลัพธ์ของ Exception:** ในโค้ด Native C++ ของเกม Error ในจังหวะนี้ไม่มีการผูก Key ข้อความแปลภาษา (Missing Localized String) กล่องโมดอลแจ้งเตือนจึงแสดงเป็น **หน้าต่างว่างเปล่าสีขาว** และปุ่มยืนยันจะผูกคำสั่ง `RestartGame()` ทำให้เกิดอาการค้างลูป
|
||||
5. **UDP KCP Connection (พอร์ต 23301):**
|
||||
- เมื่อผ่านการตรวจ Hotfix ไปได้ ตัวเกมจะสร้าง Socket UDP KCP ไปยัง `ip:port` ที่ระบุใน `Gateserver`
|
||||
- แพ็กเก็ตแรกที่ส่งคือ `PlayerGetTokenCsReq` (CmdID = 5 ในเวอร์ชัน 4.6)
|
||||
- หาก Server แมป CmdID ผิดเป็นเวอร์ชันเก่า (เช่น 88) Server จะขึ้น `Unhandled cmd_id 5` และตัดการเชื่อมต่อ
|
||||
|
||||
---
|
||||
|
||||
### 2.2 ตารางเปรียบเทียบ Protobuf Wire Format: `Gateserver` (4.3 vs 4.6)
|
||||
|
||||
นี่คือจุดที่ทำให้ระบบพังอย่างเงียบๆ หากใช้ไฟล์ Schema เก่า:
|
||||
|
||||
| ฟิลด์ข้อมูล | ชนิดข้อมูล (Type) | 4.3 Tag (Schema เก่าของ GaCy) | 4.6 Tag (Schema แท้ของ Star Rail 4.6) | ผลกระทบเมื่อส่งผิดไปให้ Client 4.6 |
|
||||
|---|---|---|---|---|
|
||||
| `ip` | string (wire 2) | Tag 2 | **Tag 3** | Client 4.6 ไม่สนใจ Tag 2 |
|
||||
| `ex_resource_url` | string (wire 2) | Tag 5 | **Tag 5** | ค่าตรงกัน |
|
||||
| `asset_bundle_url` | string (wire 2) | Tag 15 | **Tag 6** | **CRITICAL:** Client 4.6 อ่าน Tag 15 เป็น boolean `unk2` ทำให้มองไม่เห็น `asset_bundle_url`! |
|
||||
| `lua_url` | string (wire 2) | Tag 3 | **Tag 7** | **CRITICAL:** Client 4.6 อ่าน Tag 3 เป็น `ip`! |
|
||||
| `port` | uint32 (wire 0) | Tag 6 | **Tag 9** | **CRITICAL:** Client 4.6 อ่าน Tag 6 เป็น `asset_bundle_url` (เอาเลขพอร์ต 23301 ไปเป็น URL ดาวน์โหลด)! |
|
||||
| `unk1` | bool (wire 0) | - | **Tag 10** | ค่าคงที่ `true` |
|
||||
| `unk2` | bool (wire 0) | - | **Tag 15** | ค่าคงที่ `true` |
|
||||
| `unk3` | bool (wire 0) | - | **Tag 77** | ค่าคงที่ `true` |
|
||||
| `unk4` | bool (wire 0) | - | **Tag 454** | ค่าคงที่ `true` |
|
||||
| `ifix_url` | string (wire 2) | - | **Tag 694** | URL ifix |
|
||||
| `unk5` | bool (wire 0) | - | **Tag 802** | ค่าคงที่ `true` |
|
||||
| `unk6` | bool (wire 0) | - | **Tag 918** | ค่าคงที่ `true` |
|
||||
| `ifix_version` | string (wire 2) | - | **Tag 1286** | ค่าคงที่ `"0"` (ต้องเป็น string ห้ามส่ง int) |
|
||||
| `asset_bundle_url_b`| string (wire 2) | - | **Tag 1379**| URL สำรอง (ใส่ค่าเดียวกับ `asset_bundle_url`) |
|
||||
| `lua_version` | string (wire 2) | - | **Tag 1516** | ค่าเวอร์ชัน Lua (เช่น `"16927297"`, ต้องเป็น string) |
|
||||
| `unk7` | bool (wire 0) | - | **Tag 1559**| ค่าคงที่ `true` |
|
||||
| `unk8` | bool (wire 0) | - | **Tag 1975**| ค่าคงที่ `true` |
|
||||
|
||||
---
|
||||
|
||||
### 2.3 การตัด Hash ท้ายสตริงเวอร์ชัน (Version Query String Mismatch)
|
||||
* ตัวเกมยิงมาที่ URL:
|
||||
`GET /query_gateway?version=OSBETAAndroid4.6.54_D16927089_A16927089_L16927077`
|
||||
* แต่ในไฟล์ `hotfix.json` บันทึกคีย์ไว้แบบสั้น:
|
||||
`"OSBETAAndroid4.6.54"`
|
||||
* **ข้อผิดพลาดเดิม:** โค้ดใช้ `hotfix.get(version)` ตรงๆ ทำให้ได้ค่า `None` ส่งผลให้ URL ใน `Gateserver` กลายเป็นค่าว่าง
|
||||
* **การแก้ไขที่ถูกต้อง:** ต้องตัดเอาเฉพาะส่วนหน้าก่อนเครื่องหมาย `_`:
|
||||
```python
|
||||
clean_version = version.split('_')[0] if version else version
|
||||
hotfix_entry = hotfix_dict.get(version) or hotfix_dict.get(clean_version)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 ตาราง CmdID บน UDP KCP ในเวอร์ชัน 4.6 (เปรียบเทียบกับ 4.3)
|
||||
|
||||
เมื่อตัวเกมผ่าน 0% ไปแล้ว จะเริ่มยิง KCP ถ้า CmdID ไม่ตรง Server จะพิมพ์ `Unhandled cmd_id` และ Client จะหลุด:
|
||||
|
||||
| Command Name | CmdID ในเวอร์ชัน 4.3 | CmdID ในเวอร์ชัน 4.6 | ประเภท |
|
||||
|---|---|---|---|
|
||||
| `PlayerGetTokenCsReq` | 88 | **5** | Client -> Server (คำสั่งแรกสุดของ KCP) |
|
||||
| `PlayerGetTokenScRsp` | 44 | **71** | Server -> Client |
|
||||
| `PlayerLoginCsReq` | 94 | **74** | Client -> Server |
|
||||
| `PlayerLoginScRsp` | 9 | **19** | Server -> Client |
|
||||
| `PlayerLoginFinishCsReq` | 84 | **9** | Client -> Server |
|
||||
| `PlayerLoginFinishScRsp` | 13 | **13** | Server -> Client |
|
||||
| `PlayerHeartBeatCsReq` | 98 | **45** | Heartbeat |
|
||||
| `PlayerHeartBeatScRsp` | 99 | **16** | Heartbeat Response |
|
||||
| `GetCurSceneInfoCsReq` | 1488 | **1459** | โหลดข้อมูลฉาก |
|
||||
| `GetCurSceneInfoScRsp` | 1444 | **1418** | ตอบข้อมูลฉาก |
|
||||
| `SceneCastSkillCsReq` | 1424 | **1405** | กดใช้สกิลในฉาก |
|
||||
| `SceneCastSkillScRsp` | 1403 | **1471** | ตอบรับสกิล |
|
||||
| `SceneEntityMoveCsReq` | 1494 | **1474** | เดินในฉาก |
|
||||
| `SceneEntityMoveScRsp` | 1409 | **1419** | ตำแหน่งการเดิน |
|
||||
| `GetCurLineupDataCsReq`| 788 | **731** | ข้อมูลทีมปัจจุบัน |
|
||||
| `GetCurLineupDataScRsp`| 744 | **787** | ตอบข้อมูลทีมปัจจุบัน |
|
||||
| `GetAllLineupDataCsReq`| 794 | **704** | ข้อมูลทีมทั้งหมด |
|
||||
| `GetAllLineupDataScRsp`| 709 | **794** | ตอบข้อมูลทีมทั้งหมด |
|
||||
| `ChangeLineupLeaderCsReq`| 784 | **798** | เปลี่ยนตัวละครหลัก |
|
||||
| `ChangeLineupLeaderScRsp`| 713 | **747** | ตอบรับเปลี่ยนตัวละคร |
|
||||
| `GetBagCsReq` | 588 | **574** | ข้อมูลกระเป๋า |
|
||||
| `GetBagScRsp` | 544 | **519** | ตอบข้อมูลกระเป๋า |
|
||||
| `GetAvatarDataCsReq` | 388 | **374** | ข้อมูลตัวละคร |
|
||||
| `GetAvatarDataScRsp` | 344 | **319** | ตอบข้อมูลตัวละคร |
|
||||
| `PVEBattleResultCsReq` | 188 | **174** | ผลการต่อสู้ |
|
||||
| `PVEBattleResultScRsp` | 144 | **119** | ตอบรับผลการต่อสู้ |
|
||||
| `StartCocoonStageCsReq`| 1438 | **1444** | เริ่มดันเจี้ยน |
|
||||
| `StartCocoonStageScRsp`| 1416 | **1481** | ตอบเริ่มดันเจี้ยน |
|
||||
|
||||
---
|
||||
|
||||
### 2.5 ทำไมถึงห้ามนำ binary ของ ppSR มาใส่ตรงๆ?
|
||||
ในไฟล์ `ppsr.pyc` ของ ppSR มีการเขียนฟังก์ชันป้องกันการแจกจ่าย (`check_access` / `qyqwiourtyqot`) โดยจะนำ Timestamp มา Hash ร่วมกับคีย์ลับ (HMAC-SHA256) เพื่อสร้าง Password รายวัน/สัปดาห์ หากรหัสใน `persistent["key"]` ไม่ตรง ตัวโปรแกรมจะเรียก `input("Enter the correct password: ")` ซึ่งสำหรับแอพแบบ Embedded บน Android จะทำให้เซิร์ฟเวอร์หยุดรอ Input และไม่สามารถทำงานต่อได้
|
||||
|
||||
**ดังนั้น:** เราต้องสร้างตัว Encode และ Logic 4.6 ภายในโค้ดของเราเอง (ทั้งใน Python runner และ Go backend) โดยไม่ต้องพึ่งพาหรือก๊อปปี้ binary ของ ppSR มาใช้งาน
|
||||
|
||||
---
|
||||
|
||||
## 3. ข้อมูล CDN Hotfix 4.6 ที่แท้จริง (Official 4.6 CDN URLs)
|
||||
|
||||
ให้ใช้ชุดข้อมูล JSON นี้สำหรับบรรจุลงใน `hotfix.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"OSBETAAndroid4.6.54": {
|
||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_16927089_a94e4821d0e7_f07f57da7e245a",
|
||||
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_16927089_a94e4821d0e7_f07f57da7e245a",
|
||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_16932918_4a2e3f79111d_3e2af64ea2b108",
|
||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_16927297_c46b951e256b_0abc31ea529d71",
|
||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4",
|
||||
"lua_version": "16927297",
|
||||
"ifix_version": "0"
|
||||
},
|
||||
"OSBETAAndroid4.6.53": {
|
||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_16818953_d4345bbd54a2_ae00cce42191d1",
|
||||
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_16818953_d4345bbd54a2_ae00cce42191d1",
|
||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_16833202_959c316ef547_335871eaabab71",
|
||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_16819203_679727d45bd7_d086e33f0af119",
|
||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4",
|
||||
"lua_version": "16819203",
|
||||
"ifix_version": "0"
|
||||
},
|
||||
"OSBETAWin4.6.54": {
|
||||
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_16927089_a94e4821d0e7_f07f57da7e245a",
|
||||
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_16927089_a94e4821d0e7_f07f57da7e245a",
|
||||
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_16932918_4a2e3f79111d_3e2af64ea2b108",
|
||||
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_16927297_c46b951e256b_0abc31ea529d71",
|
||||
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4",
|
||||
"lua_version": "16927297",
|
||||
"ifix_version": "0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. โค้ดตัวอย่างสำหรับแก้ปัญหา (Ready-to-use Implementation)
|
||||
|
||||
### 4.1 ตัวสร้าง Wire Format Protobuf `Gateserver` 4.6 (Python Standalone Serializer)
|
||||
ฟังก์ชันนี้ทำงานโดยเขียน Byte Stream ตามสเปก Protobuf Wire Format โดยตรง ไม่ต้องพึ่งพา `classes_pb2.pyc` เก่า:
|
||||
|
||||
```python
|
||||
import base64
|
||||
|
||||
def encode_varint(value: int) -> bytes:
|
||||
bits = value & 0x7fffffffffffffff
|
||||
buf = bytearray()
|
||||
while True:
|
||||
b = bits & 0x7f
|
||||
bits >>= 7
|
||||
if bits:
|
||||
buf.append(b | 0x80)
|
||||
else:
|
||||
buf.append(b)
|
||||
break
|
||||
return bytes(buf)
|
||||
|
||||
def encode_field(tag: int, wire_type: int, payload: bytes) -> bytes:
|
||||
key = (tag << 3) | wire_type
|
||||
return encode_varint(key) + payload
|
||||
|
||||
def build_gateserver_4_6(gate_ip: str, game_port: int, hf_data: dict) -> bytes:
|
||||
parts = []
|
||||
|
||||
# Tag 3: ip (string)
|
||||
ip_bytes = gate_ip.encode('utf-8')
|
||||
parts.append(encode_field(3, 2, encode_varint(len(ip_bytes)) + ip_bytes))
|
||||
|
||||
# Tag 5: ex_resource_url (string)
|
||||
ex_res = hf_data.get('ex_resource_url', '').encode('utf-8')
|
||||
if ex_res:
|
||||
parts.append(encode_field(5, 2, encode_varint(len(ex_res)) + ex_res))
|
||||
|
||||
# Tag 6: asset_bundle_url (string)
|
||||
asb = hf_data.get('asset_bundle_url', '').encode('utf-8')
|
||||
if asb:
|
||||
parts.append(encode_field(6, 2, encode_varint(len(asb)) + asb))
|
||||
|
||||
# Tag 7: lua_url (string)
|
||||
lua_url = hf_data.get('lua_url', '').encode('utf-8')
|
||||
if lua_url:
|
||||
parts.append(encode_field(7, 2, encode_varint(len(lua_url)) + lua_url))
|
||||
|
||||
# Tag 9: port (uint32)
|
||||
parts.append(encode_field(9, 0, encode_varint(game_port)))
|
||||
|
||||
# Boolean flags (unk1 .. unk8)
|
||||
bool_tags = [10, 15, 77, 454, 802, 918, 1559, 1975]
|
||||
for tag in bool_tags:
|
||||
parts.append(encode_field(tag, 0, encode_varint(1)))
|
||||
|
||||
# Tag 694: ifix_url (string)
|
||||
ifix_url = hf_data.get('ifix_url', '').encode('utf-8')
|
||||
if ifix_url:
|
||||
parts.append(encode_field(694, 2, encode_varint(len(ifix_url)) + ifix_url))
|
||||
|
||||
# Tag 1286: ifix_version (string)
|
||||
ifix_ver = str(hf_data.get('ifix_version', '0')).encode('utf-8')
|
||||
parts.append(encode_field(1286, 2, encode_varint(len(ifix_ver)) + ifix_ver))
|
||||
|
||||
# Tag 1379: asset_bundle_url_b (string)
|
||||
asb_b = (hf_data.get('asset_bundle_url_b') or hf_data.get('asset_bundle_url', '')).encode('utf-8')
|
||||
if asb_b:
|
||||
parts.append(encode_field(1379, 2, encode_varint(len(asb_b)) + asb_b))
|
||||
|
||||
# Tag 1516: lua_version (string)
|
||||
lua_ver = str(hf_data.get('lua_version', '16927297')).encode('utf-8')
|
||||
parts.append(encode_field(1516, 2, encode_varint(len(lua_ver)) + lua_ver))
|
||||
|
||||
raw_protobuf = b''.join(parts)
|
||||
return base64.b64encode(raw_protobuf)
|
||||
```
|
||||
|
||||
### 4.2 โค้ด Handler สำหรับ `query_gateway` (Python Flask)
|
||||
```python
|
||||
@app.route('/query_gateway', methods=['GET', 'HEAD'])
|
||||
def query_gateway():
|
||||
from flask import request
|
||||
if request.method == 'HEAD':
|
||||
return '', 200
|
||||
|
||||
version = request.args.get('version', '')
|
||||
if not version:
|
||||
return '', 200
|
||||
|
||||
# 1. ตัด build hash ออก เช่น OSBETAAndroid4.6.54_D16927089... -> OSBETAAndroid4.6.54
|
||||
clean_ver = version.split('_')[0]
|
||||
|
||||
# 2. ค้นหา Hotfix Data
|
||||
hf = hotfix_dict.get(version) or hotfix_dict.get(clean_ver)
|
||||
if not hf:
|
||||
# Fallback หาคีย์ 4.6 ตัวที่ใกล้เคียงที่สุด
|
||||
for k in hotfix_dict:
|
||||
if '4.6' in k and 'Android' in k:
|
||||
hf = hotfix_dict[k]
|
||||
break
|
||||
|
||||
# 3. กำหนด Target IP (127.0.0.1 หากเล่นบนเครื่องเดียวกัน หรือ LAN IP)
|
||||
client_ip = request.remote_addr
|
||||
target_ip = "127.0.0.1" if client_ip in ("127.0.0.1", "localhost") else get_local_lan_ip()
|
||||
|
||||
# 4. ตรวจสอบว่าเปิดส่ง Hotfix หรือไม่ (ต้องเป็น True เสมอสำหรับ 4.6)
|
||||
should_send_hotfix = Manager.persistent.get("send_hotfix", True)
|
||||
hotfix_payload = hf if should_send_hotfix else {}
|
||||
|
||||
# 5. สร้าง Base64 Protobuf 4.6 แท้
|
||||
response_b64 = build_gateserver_4_6(target_ip, 23301, hotfix_payload)
|
||||
return response_b64
|
||||
```
|
||||
|
||||
### 4.3 โค้ดสำหรับเซิร์ฟเวอร์ Go (`internal/sdkserver/server.go`)
|
||||
ใน Go backend ของ `GaC-hkrpg-go`:
|
||||
```go
|
||||
func (a *appState) getOrInsertHotfix(version, dispatchSeed string) VersionConfig {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
// 1. Direct match
|
||||
if cfg, ok := a.hotfixMap[version]; ok {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// 2. Stripped prefix match (สำคัญมาก: ตัด _D... ออก)
|
||||
cleanVer := strings.Split(version, "_")[0]
|
||||
if cfg, ok := a.hotfixMap[cleanVer]; ok {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// 3. Fallback to OSBETAAndroid4.6.54 if version contains 4.6
|
||||
if strings.Contains(version, "4.6") {
|
||||
if cfg, ok := a.hotfixMap["OSBETAAndroid4.6.54"]; ok {
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
|
||||
return VersionConfig{}
|
||||
}
|
||||
```
|
||||
|
||||
และใน struct `Gateserver` ของ Protobuf ใน Go (`StarRail.pb.go`) ให้แน่ใจว่า Tag ตรงกับสเปก 4.6:
|
||||
```protobuf
|
||||
message Gateserver {
|
||||
string ip = 3;
|
||||
string ex_resource_url = 5;
|
||||
string asset_bundle_url = 6;
|
||||
string lua_url = 7;
|
||||
uint32 port = 9;
|
||||
bool unk1 = 10;
|
||||
bool unk2 = 15;
|
||||
bool unk3 = 77;
|
||||
bool unk4 = 454;
|
||||
string ifix_url = 694;
|
||||
bool unk5 = 802;
|
||||
bool unk6 = 918;
|
||||
string ifix_version = 1286;
|
||||
string asset_bundle_url_b = 1379;
|
||||
string lua_version = 1516;
|
||||
bool unk7 = 1559;
|
||||
bool unk8 = 1975;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. สรุปเช็คลิสต์สำหรับการทดสอบ (Verification Checklist)
|
||||
|
||||
- [ ] **เปิดใช้งาน `send_hotfix: true`:** ตรวจสอบว่าใน `persistent.json` และโค้ดตัวอ่าน persistent ไม่มีส่วนใดไปบังคับเซฟทับเป็น `false`
|
||||
- [ ] **Hotfix URLs ไม่ว่างเปล่า:** เมื่อ Client ยิง `/query_gateway` ต้องได้ URL ที่ขึ้นต้นด้วย `https://autopatchos.starrails.com/...` เสมอ
|
||||
- [ ] **Tag 6 และ Tag 9 ถูกต้อง:** ค่าของ Tag 6 ต้องเป็นสตริง URL (`asset_bundle_url`) และ Tag 9 ต้องเป็นตัวเลขพอร์ต `23301` (ไม่ใช่สลับกัน)
|
||||
- [ ] **รองรับคำสั่งแรกของ KCP (CmdID 5):** เมนู Console ของ Gameserver ต้องไม่ขึ้นว่า `Unhandled cmd_id 5` ตอนที่หลุดจากหน้า 0% เข้าสู่การเชื่อมต่อฉากเกม
|
||||
- [ ] **ไม่มีระบบเช็ค Discord License หรือถาม Password:** เซิร์ฟเวอร์ต้องเริ่มต้นทำงานได้ทันทีแบบไม่ต้องรอ Console Input ใดๆ
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6. รวมบัคและข้อผิดพลาดทั้งหมดที่เกิดขึ้นในระบบ (Comprehensive Bug Registry & Failure Post-Mortem)
|
||||
|
||||
ส่วนนี้ได้รวบรวมทุกบัค, Error Log, สาเหตุที่แท้จริง (Root Cause), และโค้ดแก้ไข (Fix) ที่เกิดขึ้นตลอดการพัฒนาและการดีบัก GaCy และ GaC-hkrpg-go ไว้อย่างละเอียดที่สุด เพื่อให้ AI สามารถนำไปใช้ป้องกันและแก้ปัญหาได้อย่างครอบคลุม:
|
||||
|
||||
---
|
||||
|
||||
### Bug #1: ค้าง 0% และเกิด Alert Dialog สีขาวว่างเปล่า (White Blank Dialog Loop)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
[HTTP] GET /query_gateway?version=OSBETAAndroid4.6.54_D16927089... -> 200
|
||||
[Client]: หน้าจอขึ้นรูปรถไฟ Astral Express บิน แถบดาวน์โหลดค้างที่ 0 %
|
||||
[Client]: สักครู่มี Dialog สีขาวไม่มีตัวหนังสือเด้งขึ้นมา พอกดปุ่ม ตัวเกมสั่ง Restart
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
1. ค่า `send_hotfix` ถูกตั้งเป็น `false` หรือโค้ดส่ง `asset_bundle_url = ""` ว่างเปล่ากลับไป
|
||||
2. ใน Star Rail 4.6 โมดูล `PatchDownloader` ของ Unity il2cpp บังคับว่าต้องมี URL ดาวน์โหลด Asset Bundle ถ้าไม่มีหรือต่อไม่ติด จะเกิด Fatal Error ทันที
|
||||
3. เนื่องจากตัวเกมไม่ได้ผูกข้อความแปลภาษาสำหรับกรณี Asset URL ว่างไว้ ทำให้หน้าต่างแสดงผลเป็นสีขาวล้วน (Empty Text)
|
||||
* **วิธีแก้:**
|
||||
- ต้องเปิด `send_hotfix: true` เสมอ
|
||||
- ฟิลด์ `asset_bundle_url`, `ex_resource_url`, `lua_url` ต้องมี URL ของ CDN แท้ (เช่น `https://autopatchos.starrails.com/asb/BetaLive/...`) บรรจุอยู่เสมอ ห้ามเป็นค่าว่าง
|
||||
|
||||
---
|
||||
|
||||
### Bug #2: เซิร์ฟเวอร์ส่ง IP `0.0.0.0` กลับไปใน Gateserver
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
[Dispatch] Gateserver response serialized: ip="0.0.0.0", port=23301
|
||||
[Client]: Connection refused / Timeout ทันทีหลังจบ HTTP Gateway
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- โค้ดของ Dispatch ไปหยิบค่า `_ds_mod.HOST` ซึ่งตั้งค่าไว้เป็น `"0.0.0.0"` (เพื่อให้ Server Socket บายด์รับทุก Interface) ส่งกลับไปใน Protobuf
|
||||
- Client ในระบบ Android หรืออุปกรณ์อื่นภายนอก ไม่สามารถเชื่อมต่อไปยัง IP `0.0.0.0` ได้
|
||||
* **วิธีแก้:**
|
||||
```python
|
||||
def resolve_client_gate_ip():
|
||||
from flask import request
|
||||
client_ip = request.remote_addr if request else "127.0.0.1"
|
||||
if not client_ip or client_ip in ("127.0.0.1", "::1", "localhost"):
|
||||
return "127.0.0.1"
|
||||
# หากเป็นเครื่องอื่น ให้ตอบด้วย LAN IP ของเซิร์ฟเวอร์
|
||||
import socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
s.connect(('8.8.8.8', 80))
|
||||
return s.getsockname()[0]
|
||||
except Exception:
|
||||
return "127.0.0.1"
|
||||
finally:
|
||||
s.close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #3: Protobuf Tag Inversion (4.3 vs 4.6 Tag Mismatch)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
[Client Debug]: AssetBundle download failed: invalid url format "23301"
|
||||
[Client]: Game crash / Restart loop at 0%
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- Schema เก่า (4.3) มี `port` อยู่ที่ **Tag 6** และ `asset_bundle_url` อยู่ที่ **Tag 15**
|
||||
- ใน 4.6 สลับเป็น `asset_bundle_url` อยู่ที่ **Tag 6** และ `port` อยู่ที่ **Tag 9**
|
||||
- เมื่อ Client 4.6 อ่านไบต์ที่สร้างจาก 4.3 ตัวเกมจะอ่านค่าพอร์ต (`23301`) ไปเป็น String URL ของ Asset Bundle
|
||||
* **วิธีแก้:**
|
||||
- แก้ไขตัวสร้าง Protobuf ให้ Tag 6 เป็น String URL และ Tag 9 เป็น uint32 พอร์ตเกม
|
||||
|
||||
---
|
||||
|
||||
### Bug #4: Protobuf Type Crash บน `lua_version` และ `ifix_version` (`TypeError: int expected str`)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
TypeError: 0 has type int, but expected one of: bytes, str
|
||||
File "dispatchserver.py", line 113, in query_gateway
|
||||
response.lua_version = hf.get('lua_version', 0)
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- ในนิยาม Protobuf 4.6 ฟิลด์ `lua_version` (Tag 1516) และ `ifix_version` (Tag 1286) ถูกกำหนดชนิดข้อมูลเป็น `string` (ไม่ใช่ uint32)
|
||||
- เมื่อโค้ดส่ง default value เป็นเลข `0` (int) ตัวไลบรารี Protobuf ของ Python จะ Raise `TypeError` ทันที ทำให้ HTTP 500
|
||||
* **วิธีแก้:**
|
||||
```python
|
||||
response.lua_version = str(hf.get('lua_version', '16927297'))
|
||||
response.ifix_version = str(hf.get('ifix_version', '0'))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #5: Protobuf AttributeError ฟิลด์ `unk9` และ `unk10` ไม่มีอยู่จริง
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
AttributeError: Protocol message Gateserver has no "unk9" field.
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- โค้ด Patch พยายามส่งตัวแปรเดา: `unk1=True, ..., unk9=True, unk10=True`
|
||||
- ในสเปก Protobuf ของ Star Rail 4.6 ฟิลด์ boolean unk มีถึงแค่ **`unk8`** (Tag 1975) เท่านั้น การส่ง `unk9` ทำให้เกิด Crash
|
||||
* **วิธีแก้:**
|
||||
- ส่งเฉพาะ boolean flags `unk1` ถึง `unk8` (Tags: 10, 15, 77, 454, 802, 918, 1559, 1975)
|
||||
|
||||
---
|
||||
|
||||
### Bug #6: บัคฟังก์ชัน Auto-Heal ทับค่า `send_hotfix = False` อัตโนมัติทุกครั้งที่เปิด
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
[Patch] persistent.json validated and auto-healed with complete avatar/scene defaults (send_hotfix=False)
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- ในไฟล์ `run_ppsr.py` และ `PersistentRepository.kt` มีโค้ดตรวจสอบสุขภาพไฟล์ `persistent.json` แต่คนเขียนเดิมใส่เงื่อนไขไว้ว่า:
|
||||
```python
|
||||
if data.get("send_hotfix") is not False:
|
||||
data["send_hotfix"] = False
|
||||
modified = True
|
||||
```
|
||||
- ส่งผลให้ไม่ว่าผู้ใช้จะแก้ไฟล์เป็น `true` อย่างไร พอเปิดแอพ เซิร์ฟเวอร์จะเซฟทับกลับเป็น `false` ทันที
|
||||
* **วิธีแก้:**
|
||||
```python
|
||||
if "send_hotfix" not in data:
|
||||
data["send_hotfix"] = True
|
||||
modified = True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #7: การหาคีย์ Hotfix ไม่เจอเพราะ Client ส่ง Hash ต่อท้ายเวอร์ชัน
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
KeyError: 'OSBETAAndroid4.6.54_D16927089_A16927089_L16927077'
|
||||
Couldn't find OSBETAAndroid4.6.54_D16927089_A16927089_L16927077 in hotfix.json
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- ตัวเกม Client ยิง Query String มาเป็น `version=OSBETAAndroid4.6.54_D16927089_A16927089_L16927077`
|
||||
- แต่ใน `hotfix.json` บันทึกไว้เฉพาะ `OSBETAAndroid4.6.54`
|
||||
- โค้ดดึง `hotfix[version]` ตรงๆ ทำให้เกิด `KeyError` หรือได้ค่าว่าง
|
||||
* **วิธีแก้:**
|
||||
```python
|
||||
clean_ver = version.split('_')[0] if version else version
|
||||
hf = hotfix.get(version) or hotfix.get(clean_ver)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #8: เซิร์ฟเวอร์ Beta ของทางการปิดตัว (`retcode: 3 / 测试资格校验失败`)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
Tag 12 (varint): 3
|
||||
Tag 13 (string): client version not match
|
||||
Tag 252 (string): 测试资格校验失败,请重新登录
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- ฟังก์ชัน `check_hotfix` ของ ppSR ดั้งเดิม ออกแบบมาให้ยิง Proxy ต่อไปยัง `https://beta-release01-asia.starrails.com/query_gateway` เพื่อดึงข้อมูล Hotfix สดๆ
|
||||
- แต่เมื่อระยะเวลาทดสอบ Beta จบลง เซิร์ฟเวอร์ทางการจะปฏิเสธคำขอทั้งหมดและส่ง retcode 3
|
||||
- ส่งผลให้ `gateserver.asset_bundle_url` ที่ตอบกลับมาเป็นค่าว่าง `""` ถ้าเซิร์ฟเวอร์ไม่มีการ Hardcode URL สำรองไว้ใน `hotfix.json` ระบบจะพังทันที
|
||||
* **วิธีแก้:**
|
||||
- ต้องมี URL CDN ของ 4.6 ที่บันทึกไว้ในเครื่อง (`hotfix.json`) เสมอ และหากดึงสดล้มเหลว ให้ใช้ข้อมูลในเครื่องตอบกลับ Client ทันที
|
||||
|
||||
---
|
||||
|
||||
### Bug #9: HTTP 405 Method Not Allowed บน `/hkrpg_global/mdk/shield/api/loadConfig`
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
[HTTP] GET /hkrpg_global/mdk/shield/api/loadConfig -> 405 Method Not Allowed
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- เส้นทาง API Shield ของ Flask ถูกลงทะเบียนไว้เฉพาะเมธอด `POST`:
|
||||
`@app.route('/hkrpg_<region>/mdk/shield/api/<action>', methods=['POST'])`
|
||||
- แต่ตัวเกม Star Rail Global ส่งเมธอด **`GET`** มาที่ `loadConfig`
|
||||
* **วิธีแก้:**
|
||||
```python
|
||||
@app.route('/<path:dummy>/mdk/shield/api/loadConfig', methods=['GET', 'POST'])
|
||||
@app.route('/hkrpg_<region>/mdk/shield/api/loadConfig', methods=['GET', 'POST'])
|
||||
def shield_load_config(*args, **kwargs):
|
||||
return jsonify({
|
||||
"retcode": 0,
|
||||
"message": "OK",
|
||||
"data": {
|
||||
"id": 6,
|
||||
"game_key": "hkrpg_global",
|
||||
"client": "ANDROID",
|
||||
"identity": "I_IDENTITY",
|
||||
"guest": False,
|
||||
"ignore_clean": 0,
|
||||
"scene": "S_NORMAL"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #10: HTTP 404 บน Telemetry & SDK Configuration Endpoints
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
[HTTP] GET /combo/box/api/config/sdk/combo -> 404
|
||||
[HTTP] GET /combo/box/api/config/sdk/drmSwitch -> 404
|
||||
[HTTP] POST /data_abtest_api/config/experiment/list -> 404
|
||||
[HTTP] GET /admin/mi18n/plat_oversea/... -> 404
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- ตัวเกม Star Rail Global มีการเรียก Endpoint ย่อยต่างๆ สำหรับตรวจสอบ DRM, A/B Testing และไฟล์ภาษา i18n
|
||||
- หากเซิร์ฟเวอร์ตอบกลับเป็น 404 HTML ตัวเกมบางเวอร์ชันจะหยุดทำงาน
|
||||
* **วิธีแก้:**
|
||||
- เพิ่ม Universal Fallback Handler ดักจับเส้นทางเหล่านี้แล้วตอบ JSON 200 OK:
|
||||
```python
|
||||
@app.errorhandler(404)
|
||||
def handle_404(e):
|
||||
from flask import request, jsonify
|
||||
path = request.path
|
||||
if any(k in path for k in ['combo', 'config', 'drm', 'experiment', 'abtest', 'mi18n', 'font']):
|
||||
return jsonify({"retcode": 0, "message": "OK", "data": {}}), 200
|
||||
if any(k in path for k in ['login', 'passport', 'token', 'auth', 'shield']):
|
||||
return jsonify({
|
||||
"retcode": 0,
|
||||
"message": "OK",
|
||||
"data": {
|
||||
"account_type": 1,
|
||||
"uid": "1",
|
||||
"token": "gacy_token_ok",
|
||||
"email": "player@gacy.local"
|
||||
}
|
||||
}), 200
|
||||
return jsonify({"retcode": 0, "message": "OK"}), 200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #11: UDP KCP `Unhandled cmd_id 5` (`PlayerGetTokenCsReq`)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
Dispatch server listening on 0.0.0.0:21000
|
||||
Forwarding to 127.0.0.1:23301
|
||||
[GameServer]: Unhandled cmd_id 5
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- ใน Star Rail 4.6 เมื่อตัวเกมผ่านหน้า 0% ไปได้ จะส่งคำสั่งแรกคือ `PlayerGetTokenCsReq`
|
||||
- หมายเลขคำสั่งใน 4.6 คือ **`5`** (ใน 4.3 คือ 88)
|
||||
- เซิร์ฟเวอร์ไม่มี Handler สำหรับหมายเลข 5 จึงทิ้งแพ็กเก็ต ทำให้ Client ตัดการเชื่อมต่อ
|
||||
* **วิธีแก้:**
|
||||
- อัปเดตตาราง `cmd_dict` ให้ `PlayerGetTokenCsReq` ชี้ไปที่ `5` และ `PlayerGetTokenScRsp` ชี้ไปที่ `71`
|
||||
- แมปฟังก์ชัน `PlayerGetTokenCsReq.handle` เข้ากับ CmdID `5` ใน `router.py`
|
||||
|
||||
---
|
||||
|
||||
### Bug #12: UDP KCP `Unhandled cmd_id 74` (`PlayerLoginCsReq`)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
PlayerGetTokenCsReq (5) -> PlayerGetTokenScRsp (71)
|
||||
[GameServer]: Unhandled cmd_id 74
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- หลังจาก Token ผ่าน Client จะส่งคำสั่งล็อกอินเข้าเกม `PlayerLoginCsReq`
|
||||
- ใน 4.6 หมายเลขคำสั่งคือ **`74`** (ใน 4.3 คือ 94) และผลตอบรับ `PlayerLoginScRsp` คือ **`19`** (ใน 4.3 คือ 9)
|
||||
* **วิธีแก้:**
|
||||
- แมป `PlayerLoginCsReq: 74` และ `PlayerLoginScRsp: 19` ใน `router.py`
|
||||
|
||||
---
|
||||
|
||||
### Bug #13: Socket Crash `OSError: [Errno 98] Address already in use`
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
OSError: [Errno 98] Address already in use: ('0.0.0.0', 21000)
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- เมื่อผู้ใช้กด Stop Server แล้วกด Start Server ใหม่ทันที Socket ของ Werkzeug/Flask ยังอยู่ในสถานะ `TIME_WAIT` ของระบบปฏิบัติการ
|
||||
* **วิธีแก้:**
|
||||
- ทำ Patch ครอบฟังก์ชัน `werkzeug.serving.make_server` เพื่อเซ็ตแฟล็ก Socket:
|
||||
```python
|
||||
import socket, struct
|
||||
def _patched_make_server(*args, **kwargs):
|
||||
kwargs.pop('options', None)
|
||||
server = _orig_make_server(*args, **kwargs)
|
||||
if hasattr(server, 'socket') and server.socket:
|
||||
server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
# ปิด Linger เพื่อให้ปล่อยพอร์ตทันทีเมื่อสั่งปิด
|
||||
server.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
|
||||
return server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #14: การหยุดทำงานของเซิร์ฟเวอร์ทำให้ทั้งแอพ Android ดับ (`System.exit(0)`)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
[Shutdown]: Force-stopping server execution...
|
||||
Process 12345 exited with code 0 (App crashed/closed to home screen)
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- โค้ด Python ดั้งเดิมมักเรียก `sys.exit(0)` เมื่อต้องการปิดเซิร์ฟเวอร์
|
||||
- ในสภาพแวดล้อม Chaquopy บน Android กระบวนการของ Python ทำงานอยู่บน JVM Process เดียวกันกับ UI ของ Android Application การเรียก `sys.exit()` จึงทำให้ Android Process ดับทั้งแอพ
|
||||
* **วิธีแก้:**
|
||||
- ดักจับ `sys.exit` ใน Python:
|
||||
```python
|
||||
def _safe_exit(code=0):
|
||||
print(f"[Sys] Intercepted sys.exit({code}), prevented Android process exit.")
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
sys.exit = _safe_exit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #15: ค้าง Thread เพราะคำสั่ง `input()` (Headless Blocking)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
[CLI]: Enter the correct password:
|
||||
(Server hangs indefinitely, no logs appear, ports not listening)
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- สคริปต์เซิร์ฟเวอร์มีการเรียก `builtins.input()` เพื่อรอการป้อนข้อมูลจาก Console
|
||||
- บน Android ไม่มี stdin ให้พิมพ์ ทำให้ Thread นั้นติดบล็อกตลอดกาล
|
||||
* **วิธีแก้:**
|
||||
- Mock คำสั่ง `builtins.input`:
|
||||
```python
|
||||
import builtins, time
|
||||
def _mock_input(prompt=""):
|
||||
if prompt:
|
||||
print(f"[CLI]: {prompt}")
|
||||
time.sleep(1)
|
||||
return ""
|
||||
builtins.input = _mock_input
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #16: `KeyError` ใน `Manager.load_persistent` เมื่อ Persistent ขาดคีย์บางตัว
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
KeyError: 'march_path'
|
||||
File "game/services/manager.py", line 45, in load_persistent
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- ในไฟล์ `persistent.json` ที่ผู้ใช้แก้ไขเอง อาจไม่มีฟิลด์ย่อย เช่น `tb_gender`, `tb_path`, `march_path`, `skin`
|
||||
- โค้ดใน `manager.pyc` เข้าถึงคีย์เหล่านี้ตรงๆ ด้วย `persistent['avatar']['march_path']`
|
||||
* **วิธีแก้:**
|
||||
- สร้างคลาส `SafeDict` ครอบ Dictionary ของ `persistent`:
|
||||
```python
|
||||
class SafeDict(dict):
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
defaults = {
|
||||
'tb_gender': 'Female',
|
||||
'tb_path': 'Elation',
|
||||
'march_path': 'Preservation',
|
||||
'leader': 0,
|
||||
'send_hotfix': True,
|
||||
'key': '',
|
||||
'skin': {},
|
||||
'lineup': [1511, 1502, 1506, 1308],
|
||||
'buff': [],
|
||||
'global_passive': [],
|
||||
'ex_rank': []
|
||||
}
|
||||
if key in defaults:
|
||||
return defaults[key]
|
||||
val = super().__getitem__(key)
|
||||
if isinstance(val, dict) and not isinstance(val, SafeDict):
|
||||
val = SafeDict(val)
|
||||
self[key] = val
|
||||
return val
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Bug #17: การเกิดบัคจาก ppSR License Check (`qyqwiourtyqot`)
|
||||
* **Log อาการ:**
|
||||
```text
|
||||
Incorrect or expired password.
|
||||
You can get the password for free from https://discord.gg/ppSR
|
||||
Enter the correct password:
|
||||
```
|
||||
* **สาเหตุ:**
|
||||
- การนำเอาไฟล์ `build.zip` หรือ `ppsr.pyc` จากโปรเจกต์ ppSR มาใช้ตรงๆ จะติดระบบตรวจสอบสิทธิ์ที่ผู้พัฒนา ppSR เขียนไว้
|
||||
- ระบบนี้ใช้ฟังก์ชัน `qyqwiourtyqot()` ใน `util/binary.pyc` ทำ HMAC-SHA256 ตรวจสอบกับรหัสที่แจกใน Discord รายสัปดาห์
|
||||
* **วิธีแก้:**
|
||||
- **ห้ามนำ binary ที่คอมไพล์แล้วของ ppSR มาใส่ในระบบ GaCy**
|
||||
- ใช้ตัวสร้าง Protobuf และตัวจัดการคำสั่งที่เป็นอิสระตามโค้ดในหัวข้อที่ 4
|
||||
@@ -0,0 +1,53 @@
|
||||
# แก้บัคจาก review งาน Gemini — 2026-11-01
|
||||
|
||||
ขอบเขตคือ 7 findings จาก review ของ CW ที่ commit แล้วและ Warp ที่ยังไม่ commit
|
||||
ไม่ใช่การยืนยันว่าพอร์ต CW ครบทุกระบบหรือแก้บัคทั้ง repository แล้ว
|
||||
|
||||
| Finding | การแก้ |
|
||||
| --- | --- |
|
||||
| Batch formation เซฟช่องซ้ำ/ตำแหน่งผิด | ทั้ง GridFightUpdatePos และ GEBBMDCOLCH เรียก transaction เดียวกัน ตรวจ Battle, preparation, session revision, UID, ขอบเขต, ช่องซ้ำ, avatar ซ้ำ และ capacity ก่อนเปลี่ยน/บันทึก ส่ง failure เมื่อ save ไม่สำเร็จ |
|
||||
| Weekly reward ตอบ success ว่าง | คำสั่งรับรางวัลทั้งสองแบบตอบ RET_GRID_FIGHT_CONF_MISS และไม่เปลี่ยนเซฟ จนกว่าจะมี entitlement/grant/claim persistence ที่ยืนยันได้ |
|
||||
| Talent ปลด ID ใดก็ได้ | Enable/Reset ทั้ง permanent/season ปฏิเสธการเปลี่ยนเซฟ ไม่คืนความสำเร็จปลอม ต้นทาง grid_fight_talent.zig ระบุชัดว่าเป็น stub และยังไม่มีค่าใช้จ่าย/เงื่อนไข/ผลของ talent ที่ยืนยัน |
|
||||
| Tests เขียนเซฟจริง | gameserver TestMain ใช้สำเนา runtime ใน temp directory; helper กลับ root หมายถึง fixture นี้ กาชา common/API ใช้ temp fixture ต่อ test ไม่มีการ restore backup ทับไฟล์ผู้ใช้ |
|
||||
| Pos=0 ถูกนับพันธะ | ตัวนับ board/battle และการเลือกเจ้าของ Trailblaze ใช้ช่วงตำแหน่งจาก constants ของ Client และตัด Pos=0 ออก |
|
||||
| Archive หายหลัง restart | เก็บ serialized archive/favourite ใน snapshot แยกตาม UID โดย archive ถูก append ใน CAS เดียวกับการจบรอบ ไม่มี global in-memory store และไม่สร้างประวัติเมื่อ save ขัดแย้ง |
|
||||
| Warp mode ไม่ถูกใช้ | API กับ GetGachaInfo ใช้ visibility loader เดียวกัน อ่าน mode ล่าสุดตอนเปิด Warp กรองกับ verified Client banner assets ไม่ส่ง ID จาก Client อื่นเพียงเพราะอยู่ในไฟล์นำเข้า |
|
||||
|
||||
คำสั่ง Warp/reload ไม่เขียน roster ทั้งชุดอีกแล้ว เปลี่ยนเฉพาะ warp_unlock_mode
|
||||
ผ่าน persistent patch ที่ล็อกไฟล์ และรายงานจำนวน banner ที่ผ่านการตรวจจริง
|
||||
ข้อความ `/unlockall all` ไม่อ้างว่าแจก player resources; `/unlockall skin`
|
||||
ไม่ตอบสำเร็จเมื่อยังไม่มี implementation
|
||||
|
||||
## Archive และ compatibility
|
||||
|
||||
- Snapshot เดิมอ่านได้โดยไม่มีการ migrate/write ระหว่างอ่าน
|
||||
- เพิ่ม `archives` เป็น serialized protobuf ของ FAIHLAKPABN; unknown fields ใน
|
||||
snapshot/record ยังคงอยู่ การ save CW ปกติไม่ลบ history
|
||||
- เลข archive ใช้ revision ของ transaction ที่บันทึก จึงไม่เริ่มนับใหม่หลัง restart
|
||||
- FavourArchive ตรวจ revision และ commit ภายใต้ persistent lock เช่นเดียวกับ run
|
||||
- ยกเลิก hardcoded archive Client version `4006`; encoding ของ field นี้ยังไม่
|
||||
ยืนยัน จึงเว้นไว้ ไม่คำนวณจากข้อความ patch version โดยเดา
|
||||
- Archive ที่อยู่ในหน่วยความจำของ binary รุ่นก่อนจะไม่มีในไฟล์เก่า งานนี้ไม่
|
||||
สร้างประวัติย้อนหลังจากการคาดเดา
|
||||
|
||||
## การตรวจ
|
||||
|
||||
เพิ่ม regression tests ที่อ่าน response ผ่าน typed dispatcher/KCP จริง:
|
||||
|
||||
- คำสั่ง Talent และรับรางวัลที่ยังไม่รองรับต้องคืน retcode ผิดพลาดและไม่แก้ save
|
||||
- ปฏิเสธ formation ช่องซ้ำ/ขอบเขตผิด/UID ไม่มี/Battle/session เก่า ทั้งสอง handler;
|
||||
valid swap ยังสำเร็จ
|
||||
- Pos=0 และ bench ไม่ให้ board/battle traits; ตัวที่ลงสนามยังมี traits
|
||||
- Archive และ favourite อ่านกลับจาก snapshot ใหม่ได้; stale save ไม่เพิ่ม history;
|
||||
normal save ไม่ลบ history
|
||||
- Warp modes กรองรายการจริงและไม่ปล่อย unverified ID; ค่า off=0 อ่านกลับได้
|
||||
- คำสั่ง banner ไม่เปลี่ยน freesr-data.json
|
||||
|
||||
ผลตรวจ: `go test ./... -count=1`, `go build ./...` และ `git diff --check` ผ่าน
|
||||
ตรวจ SHA-256 ของ `persistent` และ `freesr-data.json` ก่อน/หลังชุดทดสอบแล้วไม่เปลี่ยน
|
||||
เป็นการตรวจอัตโนมัติเท่านั้น ต้องตรวจ Client จริงต่อ โดยเฉพาะ batch formation,
|
||||
archive UI ที่เว้น Client version และการเปิด Warp ตามรายการ asset ที่มีอยู่
|
||||
|
||||
งานนี้ไม่เพิ่มการแจก Talent/weekly reward จนกว่าจะมีข้อมูลรองรับ และไม่เปิด
|
||||
การสุ่มกาชาจาก imported banner schema ที่ยังไม่ยืนยัน การปฏิเสธคำสั่งเหล่านี้
|
||||
เป็นการแก้ success ปลอม ไม่ใช่การอ้างว่าระบบ gameplay นั้นเสร็จแล้ว
|
||||
+22
-2
@@ -5,7 +5,7 @@
|
||||
**Modern, High-Performance Honkai: Star Rail Game Server Emulator in Go**
|
||||
Ported from [`GaC-hkrpg-ps`](https://github.com/CAtlas0784/GaC-hkrpg-rust) with protocol references from `PearlSR` & `Currency-War-SR`.
|
||||
|
||||
[English](README_EN.md) | [ภาษาไทย](../README.md)
|
||||
[English](README_EN.md) | [ภาษาไทย](../README.md) | [简体中文](README_ZH.md)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -60,7 +60,7 @@ The selected buff applies at the start of the next battle containing an actual C
|
||||
|
||||
`resources/ActivityPreviewPolicy.json` selects event-panel prefabs from `PreviewGameplayCatalog.json`. Additional previews are currently withheld: Client logs showed missing reward assets and invalid item lookups in ActiveActivity. Re-enable individual panels only after validating their installed assets, module identity and reward dependencies. Event-specific battles and reward claims remain incomplete.
|
||||
|
||||
Featured gacha candidates come from captured character-banner prefab rows. The current capture has two candidates, but the installed Client could not load the Seele prefab and left the UI transition incomplete. Runtime `AssetMgr.Exists` checks confirmed Standard and Beginner assets, but none of the four captured limited assets. `GachaSandboxPolicy.json` currently exposes only the verified Standard panel for browsing; draws return `RET_GACHA_NOT_SUPPORT` until usable featured assets are verified. An empty verified list rejects entry as well. A table row alone is insufficient evidence of an installed asset; verify the Client bundle before adding a path to that list. The grant implementation returns the pictured character and configured duplicate Eidolon material, preserving rank/EX/Traces; it is not yet verified in the Client. An avatar absent from the imported roster is rejected. The sandbox policy uses free draws and does not reproduce official rates or conversion rules.
|
||||
Warp advertises only pools captured from this Client whose prefabs were verified in `resources/GachaSandboxPolicy.json`. The Client log reports that handoff ID 2120 is absent from its `GachaBasicInfo` table; the limited pool prefabs checked are also missing. Sending those IDs prevents the page from opening. Imported historical banners are therefore not unlocked, and draws are disabled until pool data, item grants and protocol fields are verified against this Client. The `/unlockall warp` and `/reload banner` commands do not make unverified banners visible.
|
||||
|
||||
Runtime table captures and current verification limits are recorded in [docs/offline-gameplay-continuation.md](offline-gameplay-continuation.md). Compiling these handlers does not confirm the corresponding Client screens or gameplay.
|
||||
|
||||
@@ -173,3 +173,23 @@ Priority Order: **Environment Variables > `server.local.json` > `server.json`**
|
||||
- High-performance protocol analysis suite, IL2CPP metadata dumpers, network hook tooling, and reverse-engineering utilities utilized for architectural analysis.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 📜 License & Attribution Notice
|
||||
|
||||
This project is made available in accordance with the **Apache License 2.0** and **GNU General Public License v3 (GPL-3.0)** frameworks, strictly enforcing **Attribution** and **State Changes Notices**.
|
||||
|
||||
If you use, fork, modify, redistribute, or create derivative works from any part of this repository, you **MUST** strictly comply with the following conditions:
|
||||
|
||||
1. **Mandatory Attribution (Credit the Original Source):**
|
||||
- You must provide prominent credit and include a direct link back to this original repository.
|
||||
- You must retain all existing copyright notices, developer credits, and original license headers in all copies or substantial portions of the software.
|
||||
|
||||
2. **Explicit State Changes Requirement (Document All Modifications):**
|
||||
- In accordance with Section 4(b) of Apache 2.0 and Section 5(a) of GPL v3, any files that you modify **must carry prominent notices stating clearly that you changed the files, what modifications were made, and the date of modification**.
|
||||
- You must not claim modified versions as wholly original works, nor remove attribution to obscure the software origin.
|
||||
|
||||
3. **Non-Commercial Educational Purpose:**
|
||||
- This project is provided strictly for academic research, protocol analysis, and educational purposes. Commercial exploitation, sale, or paywalling of this project or its derivatives is strictly prohibited.
|
||||
@@ -0,0 +1,197 @@
|
||||
# 🚂 GaCy GO
|
||||
|
||||
<div align="center">
|
||||
|
||||
**基于 Go 语言开发的高性能《崩坏:星穹铁道》游戏服务端模拟器**
|
||||
移植自 [`GaC-hkrpg-ps`](https://github.com/CAtlas0784/GaC-hkrpg-rust),并参考 `PearlSR` 与 `Currency-War-SR` 协议规范。
|
||||
|
||||
[English](README_EN.md) | [ภาษาไทย](../README.md) | [简体中文](README_ZH.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 🇨🇳 简体中文
|
||||
|
||||
<div align="center">
|
||||
|
||||
[项目文档](./) • [Windows 快速上手](#-windows-快速上手) • [特色玩法与内置系统](#-特色玩法与内置系统) • [配置与防硬编码规范-agentsmd](#-配置与防硬编码规范-agentsmd) • [鸣谢与致敬](#-鸣谢与致敬-credits--acknowledgments)
|
||||
|
||||
</div>
|
||||
|
||||
本系统为使用 Go 语言实现的《崩坏:星穹铁道》游戏服务端模拟器,由 [`GaC-hkrpg-ps`](https://github.com/CAtlas0784/GaC-hkrpg-rust) 移植而来,底层协议参考 [PearlSR](https://git.xeondev.com/HonkaiSlopRail/pearl-sr)。包含游戏主服 (Game Server)、SDK 调度分发服 (SDK/Dispatch Server) 以及面向 GaCy-Srtool 的 REST API。
|
||||
|
||||
项目当前处于活跃开发状态,目标运行环境对应客户端 **4.6.53 OS**。命令映射表与最新处理器参考 PearlSR commit `005d2c12db4781071f4b5b68e8f364587886a710`(详见 [协议更新记录](protocol-update.md))。协议结构一致并不代表所有游戏系统与画面均已 100% 具备可玩性。
|
||||
|
||||
---
|
||||
|
||||
### 🎮 货币战争 (Currency Wars / CW) —— 开发中
|
||||
|
||||
- **自走棋模拟引擎 (Autochess Simulation Engine)**: 移植自 [Currency-War-SR by xaodev](https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR),实现了基于 Supply(补给节点)的遭遇战生成、难度/选择矩阵以及模式/章节/Boss 的经验成长规则,并维持经过客户端验证的战斗路径。完整审计记录见 [CW xaodev 交接文档](CW-XAODEV-CLAUDE-HANDOFF.md) 与 [CW 路径推进审计](cw-route-continuation-audit.md)。
|
||||
- **时间模拟系统 (FakeTime)**: 在 GaCy-Srtool 中,可使用 `/faketime status` 查看当前模拟的游戏时间,使用 `/faketime <Unix秒数>` 或 `/faketime <RFC3339时间格式>` 自由设定游戏世界时钟,使用 `/faketime off` 恢复宿主机真实时间。状态持久化记录于 `game_clock.state_file`(可通过环境变量 `GAC_GAME_CLOCK_STATE_FILE` 覆盖)。该机制仅作用于游戏登录与心跳逻辑,绝不修改 Windows 系统时钟或网络超时设定。
|
||||
- **昔涟 (Cyrene) 登录列车场景**: 通过 `LoginShow_UP` 清单与入场动画结束事件深度绑定,完整还原白色列车、粉紫色天空、花瓣飘落动效以及顺畅的退出重登过渡流程。详见 [登录场景研究笔记](login-cyrene-investigation.md)。
|
||||
- **GaCy-Srtool 紧急解卡 (Emergency Unstuck)**: GM 控制台中的 **Unstuck** 按钮会向服务端发送 `POST /api/client/unstuck`,在客户端下一次心跳周期触发 `UIManager.ClearWaitNetPacketFullScreenBlock()`。在不损坏 CW 战斗进度、不跨图传送、不伪造通关的前提下安全解除黑屏与转圈卡死。
|
||||
- **角色星级合成与备战席管理**: 自动实现同名同星级角色 3 合 1 升星、Preparation(备战席)装备绑定/追踪及主角焦点 (Spotlight) 选择机制。
|
||||
|
||||
#### CW 备战阶段常用指令 (CW Preparation Commands)
|
||||
- `/cw status`: 查看当前轮次阶段、金币、生命值 (HP) 与已保存等级。
|
||||
- `/cw gold <总额>`: 设定 CW 金币总数(例如 `/cw gold 1000`)。
|
||||
- `/cw hp <总额>`: 在当前关卡允许范围内设定角色当前生命值。
|
||||
- `/cw exp <增加量>`: 增加经验值并应用匹配的等级、人口容量与商店概率权重。
|
||||
- `/cw list role`: 列出服务端支持的 RoleID、AvatarID 与对应星级。
|
||||
- `/cw role <RoleID> <星级> [数量]`: 添加角色至备战席并触发自动升星;备战席已满将取消整笔操作。
|
||||
- `/cw list equip`: 列出当前捕获的所有装备 ID。
|
||||
- `/cw equip <ItemID> [数量]`: 将指定装备添加至 CW 行囊。
|
||||
- `/cw list orb|consumable|forge`: 列出当前可用的奖励球、消耗品与锻造 ID。
|
||||
- `/cw orb|consumable|forge <ItemID> [数量]`: 在备战阶段发放指定物品。
|
||||
- `/cw help`: 查看完整指令帮助与语法。
|
||||
|
||||
---
|
||||
|
||||
### ✨ 特色玩法与内置系统
|
||||
|
||||
#### 阿哈的帮助 (Aha's Help)
|
||||
|
||||
GaCy-Srtool 的持久化配置提供独立的 Aha's Help 辅助选择器。等级参数读取自 `resources/AhaHelpConfig.json`。接口 `GET /api/aha-help` 可查询可用层级;`POST /api/aha-help` 接受 `{"level":1}`,设为 0 表示禁用,设为 `null` 则使用原版 FreeSR 的侵蚀辅助开关。该配置通过原子写入保存于 `persistent.aha_help_level`,不影响 CW 及 EX 状态。
|
||||
|
||||
选定的 Buff 会在进入包含 `BattleRules.json` 真实侵蚀 (Corruption) 映射的下一场战斗时生效。这包含 srtool 导入侵蚀 Buff 的自定义关卡。官方挑战关卡仍通过 `StageInvasionConfig.json` 解析侵蚀,单纯复用关卡 ID 不会自动注入侵蚀。显式设为 0 会关闭该场侵蚀战的阿哈辅助。本设置独立于全局 Aha Global 开关,且不会中途篡改正在进行的战斗。`server_debug.log` 中的 `AHA_HELP` 字段将记录下发的侵蚀与辅助 ID。
|
||||
|
||||
#### 星辰跳跃 (Stella Jump)、活动预览与当期跃迁 (Featured Gacha)
|
||||
|
||||
`resources/StellaJumpConfig.json` 包含从当前客户端捕获的 16 个辰菱 GameBoy 挑战关卡。沙盒模式提供挑战包并于 `persistent.stella_jump` 记录最佳成绩与通关状态。开启菜单为只读操作。
|
||||
|
||||
`resources/ActivityPreviewPolicy.json` 负责从 `PreviewGameplayCatalog.json` 选择活动面板预制体。其余面板因客户端日志检测到奖励资产缺失或无效物品查询而被暂时封锁。待逐一验证已安装资产及依赖后方可恢复开启。
|
||||
|
||||
Warp 仅发送已从当前客户端捕获且确认预制体存在的卡池。客户端日志显示交接包的 2120 不在本机表中,已检查的限定卡池资源也缺失;抽卡暂时禁用,等待核实卡池、奖励与协议。
|
||||
|
||||
运行时捕获的数据表及当前验证边界均记录在 [docs/offline-gameplay-continuation.md](offline-gameplay-continuation.md)。
|
||||
|
||||
#### 自由镜头 (Freecam) 与面部跟随视角 (FaceCam)
|
||||
|
||||
- `Tab`: 开启/关闭 Freecam 及 FaceCam 模式;方向键: 移动镜头位置;按住鼠标右键并拖动: 旋转镜头朝向。
|
||||
- `Page Up` / `Page Down`: 镜头升降;`+` / `-`: 调整镜头移动速度。
|
||||
- FaceCam 会自动对齐当前操控角色的头部骨骼动画朝向,并持续跟随战斗中的变身角色(例如卡斯兰娜)。鼠标滚轮可临时调节视距。
|
||||
- 战斗中开启 Freecam 时,系统会自动通过 `InControlInputModule.allowMouseInput` 并挂起鼠标按键绑定,屏蔽 UI 点击干扰,同时保留键盘技能按键的正常使用。
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Windows 快速上手
|
||||
|
||||
#### 环境需求
|
||||
1. Windows 操作系统,配备 PowerShell 环境及用于首次构建的互联网连接。启动脚本会比对 `go.mod` 中声明的 Go 基础版本;若未安装或版本陈旧,将提示从官方镜像自动下载并校验 SHA-256,解压部署至 `launcher.go_install.directory`。
|
||||
2. 准备客户端与代理工具,将调度请求重定向至 `server.json` 配置的地址。
|
||||
3. 标准可执行程序依赖根目录的 `resources/` 资产;`for ship` 目录下的独立分发版已内置打包资源。
|
||||
4. 运行 `run.bat` 并选择对应菜单项:
|
||||
|
||||
| 选项 | 功能描述 |
|
||||
| :---: | :--- |
|
||||
| `1` 或 回车 | 编译 SDK 与 Game 服务器,启动双端服务并调起客户端 |
|
||||
| `2` | 直接运行已编译的现成 `.exe` 双端服务器 |
|
||||
| `3` | 仅启动 SDK 调度服务器 |
|
||||
| `4` | 仅启动 Game 游戏主服务器 |
|
||||
| `5` | 启动双端服务器并同时调起 Gay Proxy 代理工具 |
|
||||
| `6` | 仅执行双端 `.exe` 编译 |
|
||||
| `7` | 退出启动器 |
|
||||
| `8` 或 `stop` | 停止本项目关联的所有服务端进程 |
|
||||
| `9` 或 `requirements` | 检查本地 Go 环境并支持一键下载安装 |
|
||||
|
||||
SDK 服务与 Game 服务将分别在独立的控制台窗口中启动并打印实时运行日志。启动菜单在操作完成后 5 秒将自动退出,后台服务端继续运行。使用 `run.bat stop` 或直接关闭控制台窗口即可终止服务。
|
||||
|
||||
---
|
||||
|
||||
### ⚙️ 配置与防硬编码规范 (`AGENTS.md`)
|
||||
|
||||
依据项目核心准则 `AGENTS.md`,严禁在源码或启动脚本中硬编码任何特定机器路径、用户名、IP、端口或游戏部署版本。
|
||||
|
||||
配置加载优先级:**环境变量 > `server.local.json` > `server.json`**
|
||||
|
||||
- `server.json`: 启动器、Game 游戏服与 SDK 调度服共享的核心全局配置文件。
|
||||
- `server.local.json`: 本机特化配置文件(被 Git 忽略)。可通过复制 `server.local.example.json` 创建。
|
||||
- `GAC_CONFIG`: 可指定自定义主配置文件路径。
|
||||
|
||||
| 配置字段 | 环境变量覆盖 | 用途说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| `account_name` | `GAC_ACCOUNT_NAME` | SDK 登录响应返回的用户名及游戏内展示昵称 |
|
||||
| `player_uid` | `GAC_PLAYER_UID` | 玩家数字 UID (1–4294967295),双端共用 |
|
||||
| `client_version` | `GAC_CLIENT_VERSION` | 日志及 API 标识的客户端版本号 |
|
||||
| `game_host`, `game_port` | `GAC_GAME_HOST`, `GAC_GAME_PORT` | Game 服务端本地监听地址与端口 |
|
||||
| `sdk_host`, `sdk_port` | `GAC_SDK_HOST`, `GAC_SDK_PORT` | SDK 调度服本地监听地址与端口 |
|
||||
| `public_game_host` | `GAC_PUBLIC_GAME_HOST` | 告知客户端的 Game 网关外部连接地址 |
|
||||
| `public_sdk_url` | `GAC_PUBLIC_SDK_URL` | 告知客户端的 Dispatch 调度基础 URL |
|
||||
| `launcher.client_path` | `GAC_CLIENT_PATH` | 游戏客户端可执行程序或启动器路径 |
|
||||
| `launcher.proxy_path` | `GAC_PROXY_PATH` | 代理软件可执行文件路径 |
|
||||
| `launcher.go_command` | `GAC_GO_COMMAND` | 自定义 Go 解释器可执行路径 |
|
||||
|
||||
---
|
||||
|
||||
### 💖 鸣谢与致敬 (Credits & Acknowledgments)
|
||||
|
||||
`GaCy GO` 的成长离不开开源社区与星穹铁道逆向工程先驱者的探索沉淀。我们由衷向以下项目及团队致以最诚挚的敬意与谢意:
|
||||
|
||||
<details open>
|
||||
<summary><b>🏛️ 服务端架构与协议基础 (Server Architecture & Protocol)</b></summary>
|
||||
|
||||
- **PearlSR ([HonkaiSlopRail/pearl-sr](https://git.xeondev.com/HonkaiSlopRail/pearl-sr))**:
|
||||
- 参考 Go 语言服务端模拟器架构设计,提供 Dispatch 分发调度、网关连接管理与底层数据包处理基石。
|
||||
- 上游 Opcode 及 Command ID 定义索引 (`protocol/proto/upstream_cmdids.go`)。
|
||||
|
||||
- **LunarCore ([Melledy/LunarCore](https://github.com/Melledy/LunarCore))**:
|
||||
- 由 Melledy 及贡献者开创的 Java 版星穹铁道服务端模拟器 —— 战斗流程状态机、数据包字段结构及抽卡概率模型的行业奠基之作。
|
||||
|
||||
- **FreeSR Team**:
|
||||
- 奠定玩家核心数据与库存的 JSON 数据模型规范 (`freesr-data.json`),用于读取与持久化角色库存档案。
|
||||
|
||||
- **March7thHoney ([Mar7thLover/March7thHoney-OpenSource](https://github.com/Mar7thLover/March7thHoney-OpenSource))**:
|
||||
- 高性能 C#/.NET 星穹铁道服务端,在抽卡保底算法、商店机制以及 Muip 远程运维协议设计上提供了重要参考。
|
||||
|
||||
- **FFGO ([git.kain.io.vn](https://git.kain.io.vn/))**:
|
||||
- 开源服务端协议研究与参照标杆,在网络工作流阶段流转(Workflow Transitions)及包体兼容性校验上提供了强力参照。
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary><b>🎮 玩法模式与逻辑模拟 (Game Modes & Simulation)</b></summary>
|
||||
|
||||
- **Currency-War-SR ([xaodev/Currency-War-SR](https://gaytea.cod-algieba.ts.net/xaodev/Currency-War-SR))**:
|
||||
- 在 AGPL-3.0 协议下部分移植(保留于 `third_party/currency-war-sr`)。
|
||||
- 提供 4.6.53 货币战争(自走棋玩法)的核心模拟逻辑架构。
|
||||
- 角色/羁绊矩阵、补给后遭遇战生成算法、难度数值换算与经验成长规则 (`internal/common/currency_wars_xaodev.go`, `internal/gameserver/currency_wars_xaodev.go`, `resources/CurrencyWarsXaodev.json`)。
|
||||
|
||||
- **minatoSR ([haltinggoalies/minatoSR](https://github.com/haltinggoalies/minatoSR))**:
|
||||
- 提供了裁决之巅 / 幽影歧路 (Anomaly Arbitration) 全 40 个节点的 NPC 场景坐标与关卡覆盖数据 (`resources/ChallengePeakSceneOverrides.json`)。
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary><b>📦 游戏资源与逆向分析工具 (Game Data & Tooling)</b></summary>
|
||||
|
||||
- **Dimbreath ([DimbreathBot/TurnBasedGameData](https://github.com/DimbreathBot/TurnBasedGameData))**:
|
||||
- 核心 Excel 数据资产导出 (`ExcelBinOutput`)、哈希校验表与角色、物品、遗器、技能 ID 索引资源。
|
||||
|
||||
- **Prydwen Institute ([Prydwen.gg](https://www.prydwen.gg/star-rail/))**:
|
||||
- 提供角色配装养成指南、遗器主副词条推荐及梯队强度评估数据 (`resources/PrydwenRecommendations.json`)。
|
||||
|
||||
- **AstralOS ([Ma3-OS/AstralOS](https://gaytea.cod-algieba.ts.net/AstralOS/AstralOS))**:
|
||||
- 高性能协议分析工具箱、IL2CPP 元数据转储器、网络 Hook 框架以及前期架构逆向分析的重要支持工具。
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 📜 许可证与修改声明要求 (License & Attribution Notice)
|
||||
|
||||
本项目遵循 **Apache License 2.0** 与 **GNU General Public License v3 (GPL-3.0)** 许可证规范,严格执行 **原始出处署名 (Attribution)** 与 **修改内容声明 (State Changes Notice)** 要求。
|
||||
|
||||
任何个人或组织若引用、Fork、修改、二次开发或重新分发本项目的任何代码、脚本或工具组件,**必须严格遵守以下条款**:
|
||||
|
||||
1. **强制保留原作者署名与原始出处 (Attribution):**
|
||||
- 必须在项目显著位置明确注明本项目名称,并附带指向本原始仓库的有效链接。
|
||||
- 必须在源码及所有副本中完整保留原有的版权声明、作者名单及原始许可证声明文件。
|
||||
|
||||
2. **必须明确注明修改内容与变更记录 (State Changes Notice):**
|
||||
- 依照 Apache-2.0 第 4(b) 条及 GPL-3.0 第 5(a) 条之规定,**凡对原有文件或代码进行过任何修改、增删、重构或补充的部分,必须在相关文件中附带显著的修改通知(Prominent Notice),明确说明修改了哪些内容及修改时间**。
|
||||
- 严禁隐瞒原作者出处、抹除原作者信息或谎称修改后的衍生版本为独立原创作品。
|
||||
|
||||
3. **非商业用途与免责声明 (Non-Commercial Purpose):**
|
||||
- 本项目仅供网络协议逆向、学术研究与个人学习交流使用,严禁用于任何商业牟利、付费打包或转售行为。
|
||||
Reference in new issue
Block a user