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 * **สาเหตุ:** Git พยายามเปิด Interactive Prompt เพื่อถาม Username/Password ในเทอร์มินัลที่ไม่มี TTY
* **แนวทางแก้ไข:** ต้องตั้งค่า `$env:GIT_TERMINAL_PROMPT = "0"` ใน PowerShell หรือ `os.Setenv("GIT_TERMINAL_PROMPT", "0")` ใน Go ก่อนเรียกคำสั่ง Git เสมอ * **แนวทางแก้ไข:** ต้องตั้งค่า `$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) ## 3. รายละเอียดสถาปัตยกรรมระบบอัปเดต (Isolated Updater Architecture)
+16 -8
View File
@@ -9,14 +9,20 @@ export async function CheckUpdateServer(
serverVersion: string serverVersion: string
): Promise<{ isUpdate: boolean; isExists: boolean; version: string }> { ): Promise<{ isUpdate: boolean; isExists: boolean; version: string }> {
const [ok, latestVersion, error] = await GitService.GetLatestServerVersion() const [ok, latestVersion, error] = await GitService.GetLatestServerVersion()
let isExists = await FSService.FileExists(serverPath) let isExists = false
if (!isExists) { const [gameServer, sdkServer] = await GitService.FindServerExecutables()
const [gameServer] = await GitService.FindServerExecutables() if (gameServer) {
if (gameServer) { useSettingStore.getState().setServerPath(gameServer)
useSettingStore.getState().setServerPath(gameServer) isExists = true
isExists = true }
} if (sdkServer) {
useSettingStore.getState().setSdkServerPath(sdkServer)
isExists = true
}
if (!isExists && serverPath) {
isExists = await FSService.FileExists(serverPath)
} }
if (!ok) { if (!ok) {
@@ -39,7 +45,9 @@ export async function UpdateServer(serverVersion: string) : Promise<void> {
const [gameServer, sdkServer] = await GitService.FindServerExecutables() const [gameServer, sdkServer] = await GitService.FindServerExecutables()
setDownloadType("Download server successfully") setDownloadType("Download server successfully")
setServerVersion(serverVersion) setServerVersion(serverVersion)
setServerPath(gameServer || "./server/gameserver-go.exe") if (gameServer) {
setServerPath(gameServer)
}
if (sdkServer) { if (sdkServer) {
setSdkServerPath(sdkServer) setSdkServerPath(sdkServer)
} }
+45 -83
View File
@@ -10,7 +10,7 @@ import useModalStore from '@/stores/modalStore';
import useLauncherStore from '@/stores/launcherStore'; import useLauncherStore from '@/stores/launcherStore';
import { motion } from 'motion/react'; import { motion } from 'motion/react';
import { Link } from '@tanstack/react-router'; 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 UpdateModal from '@/components/updateModal';
import { BackgroundSelector } from '@/components/backgroudModal'; import { BackgroundSelector } from '@/components/backgroudModal';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -25,11 +25,9 @@ export default function LauncherPage() {
setServerPath, setServerPath,
sdkServerPath, sdkServerPath,
setSdkServerPath, setSdkServerPath,
proxyPath, gameDir,
gameDir,
serverVersion, serverVersion,
proxyVersion, background,
background,
selectedServer, selectedServer,
setSelectedServer setSelectedServer
} = useSettingStore() } = useSettingStore()
@@ -48,8 +46,7 @@ export default function LauncherPage() {
downloadType, downloadType,
isDownloading, isDownloading,
serverRunning, serverRunning,
proxyRunning, gameRunning,
gameRunning,
progressDownload, progressDownload,
downloadSpeed, downloadSpeed,
updateData, updateData,
@@ -60,8 +57,7 @@ export default function LauncherPage() {
setProxyReady, setProxyReady,
setIsDownloading, setIsDownloading,
setServerRunning, setServerRunning,
setProxyRunning, setGameRunning,
setGameRunning,
setUpdateData, setUpdateData,
} = useLauncherStore() } = useLauncherStore()
@@ -88,31 +84,25 @@ export default function LauncherPage() {
useEffect(() => { useEffect(() => {
const check = async () => { const check = async () => {
if (!serverVersion || !proxyVersion) { const [gameServer, sdkServer] = await GitService.FindServerExecutables()
setServerReady(false) let serverExists = false
setProxyReady(false) if (gameServer) {
return setServerPath(gameServer)
serverExists = true
} }
if (sdkServer) {
let serverExists = await FSService.FileExists(serverPath) setSdkServerPath(sdkServer)
if (!serverExists) { serverExists = true
const [gameServer, sdkServer] = await GitService.FindServerExecutables() }
if (gameServer) { if (!serverExists && serverPath) {
setServerPath(gameServer) serverExists = await FSService.FileExists(serverPath)
serverExists = true
}
if (sdkServer) {
setSdkServerPath(sdkServer)
}
} }
const proxyExists = await FSService.FileExists(proxyPath)
setServerReady(serverExists) setServerReady(serverExists)
setProxyReady(proxyExists) setProxyReady(true)
} }
check() check()
}, [serverPath, proxyPath, serverVersion, proxyVersion]) }, [serverPath, serverVersion])
useEffect(() => { useEffect(() => {
const checkStartUp = async (): Promise<void> => { const checkStartUp = async (): Promise<void> => {
@@ -122,34 +112,30 @@ export default function LauncherPage() {
if (launcherData.isUpdate) { if (launcherData.isUpdate) {
setUpdateData({ setUpdateData({
server: { isUpdate: false, isExists: false, version: "" }, server: { isUpdate: false, isExists: false, version: "" },
proxy: { isUpdate: false, isExists: false, version: "" }, proxy: { isUpdate: false, isExists: true, version: "" },
launcher: launcherData launcher: launcherData
}) })
setIsOpenSelfUpdateModal(true) setIsOpenSelfUpdateModal(true)
return return
} }
const serverData = await CheckUpdateServer(serverPath, serverVersion) const serverData = await CheckUpdateServer(serverPath, serverVersion)
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion) setUpdateData({
setUpdateData({
server: serverData, server: serverData,
proxy: proxyData, proxy: { isUpdate: false, isExists: true, version: "" },
launcher: launcherData launcher: launcherData
}) })
if (!serverData.isExists || !proxyData.isExists) { if (!serverData.isExists) {
setServerReady(false) setServerReady(false)
setProxyReady(false)
return return
} }
if (serverData.isUpdate || proxyData.isUpdate) { if (serverData.isUpdate) {
setServerReady(true) setServerReady(true)
setProxyReady(true)
setIsOpenUpdateDataModal(true) setIsOpenUpdateDataModal(true)
return return
} }
setServerReady(true) setServerReady(true)
setProxyReady(true)
} }
checkStartUp() checkStartUp()
}, []); }, []);
@@ -188,7 +174,7 @@ export default function LauncherPage() {
} }
const handleStartGame = async () => { 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) { if (!gamePath) {
await FSService.LogMessage("WARN", "handleStartGame aborted: gamePath is empty") await FSService.LogMessage("WARN", "handleStartGame aborted: gamePath is empty")
toast.info("Please select the game executable first") 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...") await FSService.LogMessage("INFO", "handleStartGame: querying GaCy PS server executables...")
const [gameServer, sdkServer] = await GitService.FindServerExecutables() const [gameServer, sdkServer] = await GitService.FindServerExecutables()
const activeServer = gameServer || serverPath 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) { if (!activeServer && !activeSdk) {
await FSService.LogMessage("ERROR", "handleStartGame: activeServer missing. Opening download modal.") await FSService.LogMessage("ERROR", "handleStartGame: neither gameServer nor sdkServer found. Opening download modal.")
toast.error("Please download GaCy PS server files first") toast.error("Please download GaCy PS server files first")
setIsOpenDownloadDataModal(true) setIsOpenDownloadDataModal(true)
return return
} }
// 1. Start Proxy // 1. Start SDK Server first (handles dispatch & login)
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
if (activeSdk) { 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) const [resultSdk, sdkErr] = await FSService.StartWithConsole(activeSdk)
if (!resultSdk) { if (!resultSdk) {
await FSService.LogMessage("WARN", "handleStartGame: SDK server start warning: " + sdkErr) await FSService.LogMessage("ERROR", "handleStartGame: SDK server start failed: " + sdkErr)
} else { toast.error("Failed to start SDK Server: " + sdkErr)
await FSService.LogMessage("INFO", "handleStartGame: SDK server started successfully") 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 // 2. Start Game Server second
if (!serverRunning) { if (activeServer && !serverRunning) {
await FSService.LogMessage("INFO", "handleStartGame: starting Game server via StartWithConsole(" + activeServer + ")") await FSService.LogMessage("INFO", "handleStartGame: starting Game server via StartWithConsole(" + activeServer + ")")
const [resultServer, error] = await FSService.StartWithConsole(activeServer) const [resultServer, error] = await FSService.StartWithConsole(activeServer)
if (!resultServer) { if (!resultServer) {
@@ -254,10 +227,10 @@ export default function LauncherPage() {
} }
await FSService.LogMessage("INFO", "handleStartGame: Game server started successfully") await FSService.LogMessage("INFO", "handleStartGame: Game server started successfully")
setServerRunning(true) setServerRunning(true)
await sleep(1000)
} }
await sleep(1000)
} else { } 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 // 4. Start Game
@@ -473,18 +446,17 @@ export default function LauncherPage() {
<button <button
onClick={async () => { onClick={async () => {
const serverData = await CheckUpdateServer(serverPath, serverVersion) const serverData = await CheckUpdateServer(serverPath, serverVersion)
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
setUpdateData({ setUpdateData({
server: serverData, server: serverData,
proxy: proxyData, proxy: { isUpdate: false, isExists: true, version: "" },
launcher: updateData.launcher launcher: updateData.launcher
}) })
if (!serverData.isExists || !proxyData.isExists) { if (!serverData.isExists) {
setIsOpenDownloadDataModal(true) setIsOpenDownloadDataModal(true)
return return
} }
if (serverData.isUpdate || proxyData.isUpdate) { if (serverData.isUpdate) {
setIsOpenUpdateDataModal(true) setIsOpenUpdateDataModal(true)
return return
} }
@@ -501,14 +473,6 @@ export default function LauncherPage() {
{t("home.menu_open_server")} {t("home.menu_open_server")}
</button> </button>
</li> </li>
<li>
<button onClick={() => {
FSService.OpenFolder("./proxy")
}}
>
{t("home.menu_open_proxy")}
</button>
</li>
<li> <li>
<button disabled={!gameDir} onClick={() => { <button disabled={!gameDir} onClick={() => {
if (gameDir) { if (gameDir) {
@@ -546,9 +510,7 @@ export default function LauncherPage() {
{/* Downloading Progress Bar */} {/* Downloading Progress Bar */}
{isDownloading && ( {isDownloading && (
updateData.proxy.isUpdate updateData.server.isUpdate
|| updateData.server.isUpdate
|| !updateData.proxy.isExists
|| !updateData.server.isExists || !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"> <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) return false, fmt.Sprintf("unzip server: %v", err)
} }
os.Remove(saveZipPath) os.Remove(saveZipPath)
g.organizeExtractedServer(constant.ServerStorageUrl)
return true, "" return true, ""
} }
@@ -180,30 +181,84 @@ func (g *GitService) UnzipServer() {
_ = os.Remove(zipPath) _ = 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) { 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 var gameServer, sdkServer string
seen := make(map[string]bool)
// Candidate filenames for game server for _, root := range candidateRoots {
for _, name := range []string{"gameserver-go.exe", "server.exe", "gameserver.exe", "firefly-go_win.exe"} { absRoot, err := filepath.Abs(root)
p := filepath.Join(serverDir, name) if err != nil || seen[absRoot] {
if info, err := os.Stat(p); err == nil && !info.IsDir() { continue
gameServer = p }
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 break
} }
} }
// Candidate filenames for sdk server logger.Info("FindServerExecutables: found gameServer=%s, sdkServer=%s", gameServer, sdkServer)
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)
return gameServer, sdkServer return gameServer, sdkServer
} }