fix: recursively resolve sdkserver and gameserver in subfolders, eliminate firefly proxy for GaCy PS

This commit is contained in:
CAtlas0784 committed 2026-10-30 23:23:17 +07:00
1 parent 5f45841936
commit 054ab83307
4 files changed
+141 -107

No files matched your search

+9
View File
@@ -68,6 +68,15 @@
* **สาเหตุ:** Git พยายามเปิด Interactive Prompt เพื่อถาม Username/Password ในเทอร์มินัลที่ไม่มี TTY
* **แนวทางแก้ไข:** ต้องตั้งค่า `$env:GIT_TERMINAL_PROMPT = "0"` ใน PowerShell หรือ `os.Setenv("GIT_TERMINAL_PROMPT", "0")` ใน Go ก่อนเรียกคำสั่ง Git เสมอ
### กับดักที่ 6: โครงสร้างไฟล์ GaCy-GO (for ship/sdkserver) และการตัด Firefly Proxy
* **อาการ:** ตัวเกม Star Rail ฟ้อง `Network connection error: Please check your network state, disable any network proxies...` และ Launcher หาเซิร์ฟเวอร์ไม่พบ
* **สาเหตุ:**
1. ไฟล์ Zip ของ `gacy 4.6.54.zip` แตกไฟล์ออกมามีโฟลเดอร์ย่อยชื่อ `for ship` บรรจุ `sdkserver-go.exe` หากค้นหาเฉพาะโฟลเดอร์ root จะมองไม่เห็น SDK Server ทำให้ Dispatch Server ไม่ถูกเปิด
2. โค้ดเดิมยังคงสั่งรัน `firefly-go-proxy.exe` เข้าไปดักจับและเปลี่ยนระบบ Proxy ของ Windows ทำให้ Client ของ Star Rail ตรวจจับว่ามี Proxy แปลกปลอมและปฏิเสธการเชื่อมต่อ
* **แนวทางแก้ไข:**
1. ปรับปรุง `FindServerExecutables()` ใน Go ให้ค้นหาไฟล์ไบนารีแบบ **Recursive Search** ทุกโฟลเดอร์ย่อย (`for ship`, `sdkserver`) และทำ Auto-Sync ไฟล์จาก `for ship` ขึ้นมาที่ root ของ `server/`
2. ตัดระบบ `firefly-go-proxy` ออกจากการเริ่มเล่น GaCy PS 100% โดยเรียงลำดับการเปิด: **SDK Server (Dispatch) -> รอ 800ms -> Game Server -> รอ 1000ms -> เปิดตัวเกม**
---
## 3. รายละเอียดสถาปัตยกรรมระบบอัปเดต (Isolated Updater Architecture)
+16 -8
View File
@@ -9,14 +9,20 @@ export async function CheckUpdateServer(
serverVersion: string
): Promise<{ isUpdate: boolean; isExists: boolean; version: string }> {
const [ok, latestVersion, error] = await GitService.GetLatestServerVersion()
let isExists = await FSService.FileExists(serverPath)
let isExists = false
if (!isExists) {
const [gameServer] = await GitService.FindServerExecutables()
if (gameServer) {
useSettingStore.getState().setServerPath(gameServer)
isExists = true
}
const [gameServer, sdkServer] = await GitService.FindServerExecutables()
if (gameServer) {
useSettingStore.getState().setServerPath(gameServer)
isExists = true
}
if (sdkServer) {
useSettingStore.getState().setSdkServerPath(sdkServer)
isExists = true
}
if (!isExists && serverPath) {
isExists = await FSService.FileExists(serverPath)
}
if (!ok) {
@@ -39,7 +45,9 @@ export async function UpdateServer(serverVersion: string) : Promise<void> {
const [gameServer, sdkServer] = await GitService.FindServerExecutables()
setDownloadType("Download server successfully")
setServerVersion(serverVersion)
setServerPath(gameServer || "./server/gameserver-go.exe")
if (gameServer) {
setServerPath(gameServer)
}
if (sdkServer) {
setSdkServerPath(sdkServer)
}
+45 -83
View File
@@ -10,7 +10,7 @@ import useModalStore from '@/stores/modalStore';
import useLauncherStore from '@/stores/launcherStore';
import { motion } from 'motion/react';
import { Link } from '@tanstack/react-router';
import { CheckUpdateLauncher, CheckUpdateProxy, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
import { CheckUpdateLauncher, CheckUpdateServer, sleep, UpdateLauncher, UpdateProxy, UpdateServer } from '@/helper';
import UpdateModal from '@/components/updateModal';
import { BackgroundSelector } from '@/components/backgroudModal';
import { useTranslation } from 'react-i18next';
@@ -25,11 +25,9 @@ export default function LauncherPage() {
setServerPath,
sdkServerPath,
setSdkServerPath,
proxyPath,
gameDir,
gameDir,
serverVersion,
proxyVersion,
background,
background,
selectedServer,
setSelectedServer
} = useSettingStore()
@@ -48,8 +46,7 @@ export default function LauncherPage() {
downloadType,
isDownloading,
serverRunning,
proxyRunning,
gameRunning,
gameRunning,
progressDownload,
downloadSpeed,
updateData,
@@ -60,8 +57,7 @@ export default function LauncherPage() {
setProxyReady,
setIsDownloading,
setServerRunning,
setProxyRunning,
setGameRunning,
setGameRunning,
setUpdateData,
} = useLauncherStore()
@@ -88,31 +84,25 @@ export default function LauncherPage() {
useEffect(() => {
const check = async () => {
if (!serverVersion || !proxyVersion) {
setServerReady(false)
setProxyReady(false)
return
const [gameServer, sdkServer] = await GitService.FindServerExecutables()
let serverExists = false
if (gameServer) {
setServerPath(gameServer)
serverExists = true
}
let serverExists = await FSService.FileExists(serverPath)
if (!serverExists) {
const [gameServer, sdkServer] = await GitService.FindServerExecutables()
if (gameServer) {
setServerPath(gameServer)
serverExists = true
}
if (sdkServer) {
setSdkServerPath(sdkServer)
}
if (sdkServer) {
setSdkServerPath(sdkServer)
serverExists = true
}
if (!serverExists && serverPath) {
serverExists = await FSService.FileExists(serverPath)
}
const proxyExists = await FSService.FileExists(proxyPath)
setServerReady(serverExists)
setProxyReady(proxyExists)
setProxyReady(true)
}
check()
}, [serverPath, proxyPath, serverVersion, proxyVersion])
}, [serverPath, serverVersion])
useEffect(() => {
const checkStartUp = async (): Promise<void> => {
@@ -122,34 +112,30 @@ export default function LauncherPage() {
if (launcherData.isUpdate) {
setUpdateData({
server: { isUpdate: false, isExists: false, version: "" },
proxy: { isUpdate: false, isExists: false, version: "" },
proxy: { isUpdate: false, isExists: true, version: "" },
launcher: launcherData
})
setIsOpenSelfUpdateModal(true)
return
}
const serverData = await CheckUpdateServer(serverPath, serverVersion)
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
setUpdateData({
setUpdateData({
server: serverData,
proxy: proxyData,
proxy: { isUpdate: false, isExists: true, version: "" },
launcher: launcherData
})
if (!serverData.isExists || !proxyData.isExists) {
if (!serverData.isExists) {
setServerReady(false)
setProxyReady(false)
return
}
if (serverData.isUpdate || proxyData.isUpdate) {
if (serverData.isUpdate) {
setServerReady(true)
setProxyReady(true)
setIsOpenUpdateDataModal(true)
return
}
setServerReady(true)
setProxyReady(true)
}
checkStartUp()
}, []);
@@ -188,7 +174,7 @@ export default function LauncherPage() {
}
const handleStartGame = async () => {
await FSService.LogMessage("INFO", "handleStartGame initiated: selectedServer=" + selectedServer + ", gamePath=" + gamePath + ", serverPath=" + serverPath + ", proxyPath=" + proxyPath)
await FSService.LogMessage("INFO", "handleStartGame initiated: selectedServer=" + selectedServer + ", gamePath=" + gamePath + ", serverPath=" + serverPath)
if (!gamePath) {
await FSService.LogMessage("WARN", "handleStartGame aborted: gamePath is empty")
toast.info("Please select the game executable first")
@@ -205,46 +191,33 @@ export default function LauncherPage() {
await FSService.LogMessage("INFO", "handleStartGame: querying GaCy PS server executables...")
const [gameServer, sdkServer] = await GitService.FindServerExecutables()
const activeServer = gameServer || serverPath
await FSService.LogMessage("INFO", "handleStartGame: found gameServer=" + gameServer + ", sdkServer=" + sdkServer + ", activeServer=" + activeServer)
const activeSdk = sdkServer || sdkServerPath
await FSService.LogMessage("INFO", "handleStartGame: found gameServer=" + gameServer + ", sdkServer=" + sdkServer + ", activeServer=" + activeServer + ", activeSdk=" + activeSdk)
if (!activeServer) {
await FSService.LogMessage("ERROR", "handleStartGame: activeServer missing. Opening download modal.")
if (!activeServer && !activeSdk) {
await FSService.LogMessage("ERROR", "handleStartGame: neither gameServer nor sdkServer found. Opening download modal.")
toast.error("Please download GaCy PS server files first")
setIsOpenDownloadDataModal(true)
return
}
// 1. Start Proxy
const activeProxy = proxyPath || "./proxy/firefly-go-proxy.exe"
await FSService.LogMessage("INFO", "handleStartGame: activeProxy=" + activeProxy + ", proxyRunning=" + proxyRunning)
if (!proxyRunning && !gamePath.endsWith("launcher.exe")) {
await FSService.LogMessage("INFO", "handleStartGame: starting proxy via StartWithConsole(" + activeProxy + ")")
const [resultProxy, error] = await FSService.StartWithConsole(activeProxy)
if (!resultProxy) {
await FSService.LogMessage("ERROR", "handleStartGame: proxy start failed: " + error)
toast.error(t("home.toast_start_proxy_failed") + ": " + error)
return
}
await FSService.LogMessage("INFO", "handleStartGame: proxy started successfully")
setProxyRunning(true)
}
await sleep(500)
// 2. Start SDK Server if exists
const activeSdk = sdkServer || sdkServerPath
// 1. Start SDK Server first (handles dispatch & login)
if (activeSdk) {
await FSService.LogMessage("INFO", "handleStartGame: starting SDK server via StartWithConsole(" + activeSdk + ")")
await FSService.LogMessage("INFO", "handleStartGame: starting SDK Server via StartWithConsole(" + activeSdk + ")")
const [resultSdk, sdkErr] = await FSService.StartWithConsole(activeSdk)
if (!resultSdk) {
await FSService.LogMessage("WARN", "handleStartGame: SDK server start warning: " + sdkErr)
} else {
await FSService.LogMessage("INFO", "handleStartGame: SDK server started successfully")
await FSService.LogMessage("ERROR", "handleStartGame: SDK server start failed: " + sdkErr)
toast.error("Failed to start SDK Server: " + sdkErr)
return
}
await sleep(400)
await FSService.LogMessage("INFO", "handleStartGame: SDK server started successfully")
await sleep(800)
} else {
await FSService.LogMessage("WARN", "handleStartGame: activeSdk not found! Proceeding with gameServer only.")
}
// 3. Start Game Server
if (!serverRunning) {
// 2. Start Game Server second
if (activeServer && !serverRunning) {
await FSService.LogMessage("INFO", "handleStartGame: starting Game server via StartWithConsole(" + activeServer + ")")
const [resultServer, error] = await FSService.StartWithConsole(activeServer)
if (!resultServer) {
@@ -254,10 +227,10 @@ export default function LauncherPage() {
}
await FSService.LogMessage("INFO", "handleStartGame: Game server started successfully")
setServerRunning(true)
await sleep(1000)
}
await sleep(1000)
} else {
await FSService.LogMessage("INFO", "handleStartGame: running in Official Server mode (no proxy/PS)")
await FSService.LogMessage("INFO", "handleStartGame: running in Official Server mode (no PS)")
}
// 4. Start Game
@@ -473,18 +446,17 @@ export default function LauncherPage() {
<button
onClick={async () => {
const serverData = await CheckUpdateServer(serverPath, serverVersion)
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
setUpdateData({
server: serverData,
proxy: proxyData,
proxy: { isUpdate: false, isExists: true, version: "" },
launcher: updateData.launcher
})
if (!serverData.isExists || !proxyData.isExists) {
if (!serverData.isExists) {
setIsOpenDownloadDataModal(true)
return
}
if (serverData.isUpdate || proxyData.isUpdate) {
if (serverData.isUpdate) {
setIsOpenUpdateDataModal(true)
return
}
@@ -501,14 +473,6 @@ export default function LauncherPage() {
{t("home.menu_open_server")}
</button>
</li>
<li>
<button onClick={() => {
FSService.OpenFolder("./proxy")
}}
>
{t("home.menu_open_proxy")}
</button>
</li>
<li>
<button disabled={!gameDir} onClick={() => {
if (gameDir) {
@@ -546,9 +510,7 @@ export default function LauncherPage() {
{/* Downloading Progress Bar */}
{isDownloading && (
updateData.proxy.isUpdate
|| updateData.server.isUpdate
|| !updateData.proxy.isExists
updateData.server.isUpdate
|| !updateData.server.isExists
) && (
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/60 backdrop-blur-md rounded-2xl p-4 shadow-2xl border border-white/10">
+71 -16
View File
@@ -119,6 +119,7 @@ func (g *GitService) DownloadServerProgress(version string) (bool, string) {
return false, fmt.Sprintf("unzip server: %v", err)
}
os.Remove(saveZipPath)
g.organizeExtractedServer(constant.ServerStorageUrl)
return true, ""
}
@@ -180,30 +181,84 @@ func (g *GitService) UnzipServer() {
_ = os.Remove(zipPath)
}
}
g.organizeExtractedServer(constant.ServerStorageUrl)
}
func (g *GitService) organizeExtractedServer(dest string) {
forShipPath := filepath.Join(dest, "for ship")
if info, err := os.Stat(forShipPath); err == nil && info.IsDir() {
logger.Info("organizeExtractedServer: found 'for ship' subdirectory in %s, synchronizing files", dest)
entries, err := os.ReadDir(forShipPath)
if err == nil {
for _, entry := range entries {
src := filepath.Join(forShipPath, entry.Name())
dst := filepath.Join(dest, entry.Name())
if !entry.IsDir() {
if _, err := os.Stat(dst); os.IsNotExist(err) {
data, readErr := os.ReadFile(src)
if readErr == nil {
_ = os.WriteFile(dst, data, 0755)
logger.Info("organizeExtractedServer: copied %s -> %s", entry.Name(), dst)
}
}
}
}
}
}
}
func (g *GitService) FindServerExecutables() (string, string) {
serverDir := constant.ServerStorageUrl
var candidateRoots []string
if exePath, err := os.Executable(); err == nil {
exeDir := filepath.Dir(exePath)
candidateRoots = append(candidateRoots, filepath.Join(exeDir, "server"))
candidateRoots = append(candidateRoots, filepath.Join(filepath.Dir(exeDir), "server"))
}
candidateRoots = append(candidateRoots, constant.ServerStorageUrl, "./bin/server", "server", "bin/server")
var gameServer, sdkServer string
seen := make(map[string]bool)
// Candidate filenames for game server
for _, name := range []string{"gameserver-go.exe", "server.exe", "gameserver.exe", "firefly-go_win.exe"} {
p := filepath.Join(serverDir, name)
if info, err := os.Stat(p); err == nil && !info.IsDir() {
gameServer = p
for _, root := range candidateRoots {
absRoot, err := filepath.Abs(root)
if err != nil || seen[absRoot] {
continue
}
seen[absRoot] = true
if info, err := os.Stat(absRoot); err != nil || !info.IsDir() {
continue
}
_ = filepath.Walk(absRoot, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
nameLower := strings.ToLower(info.Name())
// Search SDK server
if sdkServer == "" {
if nameLower == "sdkserver-go.exe" || nameLower == "sdkserver.exe" || (strings.Contains(nameLower, "sdkserver") && strings.HasSuffix(nameLower, ".exe")) {
sdkServer = path
}
}
// Search Game server
if gameServer == "" {
if nameLower == "gameserver-go.exe" || nameLower == "server.exe" || nameLower == "gameserver.exe" || nameLower == "firefly-go_win.exe" || (strings.Contains(nameLower, "gameserver") && strings.HasSuffix(nameLower, ".exe")) {
gameServer = path
}
}
return nil
})
if gameServer != "" && sdkServer != "" {
break
}
}
// Candidate filenames for sdk server
for _, name := range []string{"sdkserver-go.exe", "sdkserver.exe"} {
p := filepath.Join(serverDir, name)
if info, err := os.Stat(p); err == nil && !info.IsDir() {
sdkServer = p
break
}
}
logger.Info("FindServerExecutables: serverDir=%s, found gameServer=%s, sdkServer=%s", serverDir, gameServer, sdkServer)
logger.Info("FindServerExecutables: found gameServer=%s, sdkServer=%s", gameServer, sdkServer)
return gameServer, sdkServer
}