feat(ui): rebrand to G Launcher, add side server selector, update wallpaper and app icon, integrate GaCy-GO PS

This commit is contained in:
CAtlas0784 committed 2026-10-30 22:33:21 +07:00
1 parent 023ae32b58
commit 25843372ad
21 files changed
+397 -123

No files matched your search

+1
View File
@@ -34,6 +34,7 @@
| **Thai Mod Engine (ExcelData)** | ✅ | ✅ | ✅ | ⚠️ รอเกมจริง | `internal/language-service/thai-patch.go` มีระบบ `.backup` | | **Thai Mod Engine (ExcelData)** | ✅ | ✅ | ✅ | ⚠️ รอเกมจริง | `internal/language-service/thai-patch.go` มีระบบ `.backup` |
| **Multi-Language (i18n)** | ✅ | ✅ | ✅ | ✅ | 6 ภาษา (`th`, `en`, `ja`, `ko`, `vi`, `zh`) ใน `frontend/public/locales/` | | **Multi-Language (i18n)** | ✅ | ✅ | ✅ | ✅ | 6 ภาษา (`th`, `en`, `ja`, `ko`, `vi`, `zh`) ใน `frontend/public/locales/` |
| **Standalone Tool Extraction** | ✅ | ✅ | ✅ | ✅ | แตกไฟล์ `assets/` (`7za.exe`, `hpatchz.exe`, `zift.exe`) ลง `bin/` | | **Standalone Tool Extraction** | ✅ | ✅ | ✅ | ✅ | แตกไฟล์ `assets/` (`7za.exe`, `hpatchz.exe`, `zift.exe`) ลง `bin/` |
| **Server Selection & GaCy PS** | ✅ | ✅ | ✅ | ✅ | ปุ่มเลือกเซิร์ฟเวอร์ด้านข้าง + ระบบดาวน์โหลด GaCy-GO (ZIP หรือ standalone sdk/server) |
--- ---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 KiB

After

Width:  |  Height:  |  Size: 361 KiB

@@ -30,6 +30,13 @@ export function DownloadServerProgress(version) {
return $Call.ByID(1818966979, version); return $Call.ByID(1818966979, version);
} }
/**
* @returns {$CancellablePromise<[string, string]>}
*/
export function FindServerExecutables() {
return $Call.ByID(1413748964);
}
/** /**
* @returns {$CancellablePromise<[boolean, string, string]>} * @returns {$CancellablePromise<[boolean, string, string]>}
*/ */
Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

+12 -12
View File
@@ -1,4 +1,4 @@
{ {
"header": { "header": {
"home": "Home", "home": "Home",
"tools": "Tools", "tools": "Tools",
@@ -13,7 +13,7 @@
"settings": "Settings", "settings": "Settings",
"minimize": "Minimize", "minimize": "Minimize",
"close": "Close", "close": "Close",
"by": "Firefly Shelter" "by": "GaCy"
}, },
"background": { "background": {
"select_bg": "Select Background", "select_bg": "Select Background",
@@ -50,14 +50,14 @@
"launcher_label": "Launcher" "launcher_label": "Launcher"
}, },
"home": { "home": {
"header_title": "Firefly GO", "header_title": "G Launcher",
"tooltip_how_to": "How to use all tools & commands", "tooltip_how_to": "How to use all tools \u0026 commands",
"btn_select_game": "Select Game file", "btn_select_game": "Select Game file",
"btn_selecting": "Selecting...", "btn_selecting": "Selecting...",
"btn_start_game": "Start Game", "btn_start_game": "Start Game",
"btn_game_running": "Game is running", "btn_game_running": "Game is running",
"menu_change_path": "Change Game Path", "menu_change_path": "Change Game Path",
"menu_check_update": "Check for Updates Server & Proxy", "menu_check_update": "Check for Updates Server \u0026 Proxy",
"menu_open_server": "Open server folder", "menu_open_server": "Open server folder",
"menu_open_proxy": "Open proxy folder", "menu_open_proxy": "Open proxy folder",
"menu_open_voice": "Open voice folder", "menu_open_voice": "Open voice folder",
@@ -102,9 +102,9 @@
"modal_update_title": "Update Game", "modal_update_title": "Update Game",
"status_wait": "Please wait...", "status_wait": "Please wait...",
"inst_title": "📋 Instructions:", "inst_title": "📋 Instructions:",
"inst_step_1": "1. Click \"Select Game Folder\" and choose your game's root directory", "inst_step_1": "1. Click \"Select Game Folder\" and choose your game\u0027s root directory",
"inst_step_2": "2. Wait for the system to validate the game directory", "inst_step_2": "2. Wait for the system to validate the game directory",
"inst_step_3": "3. Click \"Select Diff file Folder\" and choose your diff file's root directory", "inst_step_3": "3. Click \"Select Diff file Folder\" and choose your diff file\u0027s root directory",
"inst_step_4": "4. Wait for the system to validate the diff file directory", "inst_step_4": "4. Wait for the system to validate the diff file directory",
"inst_step_5": "5. Click \"Update Game\" to save your changes", "inst_step_5": "5. Click \"Update Game\" to save your changes",
"toast_game_dir_not_found": "Game directory not found. Please select the correct folder.", "toast_game_dir_not_found": "Game directory not found. Please select the correct folder.",
@@ -139,7 +139,7 @@
"btn_apply": "Apply Language Settings", "btn_apply": "Apply Language Settings",
"btn_applying": "Applying...", "btn_applying": "Applying...",
"inst_title": "📋 Instructions:", "inst_title": "📋 Instructions:",
"inst_step_1": "1. Click \"Select Game Folder\" and choose your game's root directory", "inst_step_1": "1. Click \"Select Game Folder\" and choose your game\u0027s root directory",
"inst_step_2": "2. Wait for the system to validate the game directory", "inst_step_2": "2. Wait for the system to validate the game directory",
"inst_step_3": "3. Select your preferred text and voice languages", "inst_step_3": "3. Select your preferred text and voice languages",
"inst_step_4": "4. Click \"Apply Language Settings\" to save your changes", "inst_step_4": "4. Click \"Apply Language Settings\" to save your changes",
@@ -156,10 +156,10 @@
"sect1_launch_game": "Launch the game directly through the launcher with correct parameters and runtime environment.", "sect1_launch_game": "Launch the game directly through the launcher with correct parameters and runtime environment.",
"sect1_lang_pre": "Support switching in-game language (e.g., EN, JP, ZH, KR) via", "sect1_lang_pre": "Support switching in-game language (e.g., EN, JP, ZH, KR) via",
"sect1_lang_link": "Language Tools", "sect1_lang_link": "Language Tools",
"sect1_patch_title": "Patch & Update Game Files", "sect1_patch_title": "Patch \u0026 Update Game Files",
"sect1_patch_desc_1_pre": "Use the", "sect1_patch_desc_1_pre": "Use the",
"sect1_patch_desc_1_link": "Diff Tool", "sect1_patch_desc_1_link": "Diff Tool",
"sect1_patch_desc_1_post": "(DiffPatch) for fast & lightweight incremental updates.", "sect1_patch_desc_1_post": "(DiffPatch) for fast \u0026 lightweight incremental updates.",
"sect1_patch_desc_2_pre": "Supports", "sect1_patch_desc_2_pre": "Supports",
"sect1_patch_desc_2_post": ", and custom diff formats.", "sect1_patch_desc_2_post": ", and custom diff formats.",
"sect2_title": "FireflyGo Chat Commands", "sect2_title": "FireflyGo Chat Commands",
@@ -281,13 +281,13 @@
"btn_back": "Back to Home" "btn_back": "Back to Home"
}, },
"analysis": { "analysis": {
"title": "Firefly Analysis & Veritas Plugin", "title": "Firefly Analysis \u0026 Veritas Plugin",
"sect1_title": "About Veritas", "sect1_title": "About Veritas",
"sect1_p1_pre": "Veritas", "sect1_p1_pre": "Veritas",
"sect1_p1_mid": " is a powerful ", "sect1_p1_mid": " is a powerful ",
"sect1_p1_highlight": "Damage Logger", "sect1_p1_highlight": "Damage Logger",
"sect1_p1_post": " designed for analyzing damage in real-time during gameplay.", "sect1_p1_post": " designed for analyzing damage in real-time during gameplay.",
"sect1_p2": "It's lightweight, fast, and easy to use for comprehensive damage analysis.", "sect1_p2": "It\u0027s lightweight, fast, and easy to use for comprehensive damage analysis.",
"sect1_github": "GitHub Repository", "sect1_github": "GitHub Repository",
"sect2_title": "Web Analysis Tools", "sect2_title": "Web Analysis Tools",
"sect2_desc": "Use these web applications for real-time damage analysis with Veritas:", "sect2_desc": "Use these web applications for real-time damage analysis with Veritas:",
+4 -4
View File
@@ -1,4 +1,4 @@
{ {
"header": { "header": {
"home": "ホーム", "home": "ホーム",
"tools": "ツール", "tools": "ツール",
@@ -13,7 +13,7 @@
"settings": "設定", "settings": "設定",
"minimize": "最小化", "minimize": "最小化",
"close": "閉じる", "close": "閉じる",
"by": "Firefly Shelter" "by": "GaCy"
}, },
"background": { "background": {
"select_bg": "背景を選択", "select_bg": "背景を選択",
@@ -50,7 +50,7 @@
"launcher_label": "Launcher" "launcher_label": "Launcher"
}, },
"home": { "home": {
"header_title": "Firefly GO", "header_title": "G Launcher",
"tooltip_how_to": "ツールとコマンドの使い方", "tooltip_how_to": "ツールとコマンドの使い方",
"btn_select_game": "ゲームファイルを選択", "btn_select_game": "ゲームファイルを選択",
"btn_selecting": "選択中...", "btn_selecting": "選択中...",
@@ -281,7 +281,7 @@
"btn_back": "ホームに戻る" "btn_back": "ホームに戻る"
}, },
"analysis": { "analysis": {
"title": "Firefly 分析 & Veritas プラグイン", "title": "Firefly 分析 \u0026 Veritas プラグイン",
"sect1_title": "Veritas について", "sect1_title": "Veritas について",
"sect1_p1_pre": "Veritas", "sect1_p1_pre": "Veritas",
"sect1_p1_mid": " は、ゲームプレイ中のダメージをリアルタイムで分析するために設計された強力な ", "sect1_p1_mid": " は、ゲームプレイ中のダメージをリアルタイムで分析するために設計された強力な ",
+4 -4
View File
@@ -1,4 +1,4 @@
{ {
"header": { "header": {
"home": "홈", "home": "홈",
"tools": "도구", "tools": "도구",
@@ -13,7 +13,7 @@
"settings": "설정", "settings": "설정",
"minimize": "최소화", "minimize": "최소화",
"close": "닫기", "close": "닫기",
"by": "Firefly Shelter" "by": "GaCy"
}, },
"background": { "background": {
"select_bg": "배경 선택", "select_bg": "배경 선택",
@@ -50,7 +50,7 @@
"launcher_label": "Launcher" "launcher_label": "Launcher"
}, },
"home": { "home": {
"header_title": "Firefly GO", "header_title": "G Launcher",
"tooltip_how_to": "도구 및 명령 사용법", "tooltip_how_to": "도구 및 명령 사용법",
"btn_select_game": "게임 파일 선택", "btn_select_game": "게임 파일 선택",
"btn_selecting": "선택 중...", "btn_selecting": "선택 중...",
@@ -281,7 +281,7 @@
"btn_back": "홈으로 돌아가기" "btn_back": "홈으로 돌아가기"
}, },
"analysis": { "analysis": {
"title": "Firefly 분석 & Veritas 플러그인", "title": "Firefly 분석 \u0026 Veritas 플러그인",
"sect1_title": "Veritas 소개", "sect1_title": "Veritas 소개",
"sect1_p1_pre": "Veritas", "sect1_p1_pre": "Veritas",
"sect1_p1_mid": "는 게임 플레이 중 실시간으로 피해량을 분석하도록 설계된 강력한 ", "sect1_p1_mid": "는 게임 플레이 중 실시간으로 피해량을 분석하도록 설계된 강력한 ",
+2 -2
View File
@@ -1,4 +1,4 @@
{ {
"header": { "header": {
"home": "หน้าแรก", "home": "หน้าแรก",
"tools": "เครื่องมือ", "tools": "เครื่องมือ",
@@ -57,7 +57,7 @@
"btn_start_game": "เริ่มเกม", "btn_start_game": "เริ่มเกม",
"btn_game_running": "เกมกำลังทำงานอยู่", "btn_game_running": "เกมกำลังทำงานอยู่",
"menu_change_path": "เปลี่ยนโฟลเดอร์เกม", "menu_change_path": "เปลี่ยนโฟลเดอร์เกม",
"menu_check_update": "ตรวจสอบอัปเดตเซิร์ฟเวอร์ & พร็อกซี", "menu_check_update": "ตรวจสอบอัปเดตเซิร์ฟเวอร์ \u0026 พร็อกซี",
"menu_open_server": "เปิดโฟลเดอร์เซิร์ฟเวอร์", "menu_open_server": "เปิดโฟลเดอร์เซิร์ฟเวอร์",
"menu_open_proxy": "เปิดโฟลเดอร์พร็อกซี", "menu_open_proxy": "เปิดโฟลเดอร์พร็อกซี",
"menu_open_voice": "เปิดโฟลเดอร์เสียงพากย์", "menu_open_voice": "เปิดโฟลเดอร์เสียงพากย์",
+8 -8
View File
@@ -1,4 +1,4 @@
{ {
"header": { "header": {
"home": "Trang chủ", "home": "Trang chủ",
"tools": "Công cụ", "tools": "Công cụ",
@@ -13,7 +13,7 @@
"settings": "Cài đặt", "settings": "Cài đặt",
"minimize": "Thu nhỏ", "minimize": "Thu nhỏ",
"close": "Đóng", "close": "Đóng",
"by": "Firefly Shelter" "by": "GaCy"
}, },
"background": { "background": {
"select_bg": "Chọn nền", "select_bg": "Chọn nền",
@@ -50,14 +50,14 @@
"launcher_label": "Launcher" "launcher_label": "Launcher"
}, },
"home": { "home": {
"header_title": "Firefly GO", "header_title": "G Launcher",
"tooltip_how_to": "Cách sử dụng công cụ & lệnh", "tooltip_how_to": "Cách sử dụng công cụ \u0026 lệnh",
"btn_select_game": "Chọn tệp Game", "btn_select_game": "Chọn tệp Game",
"btn_selecting": "Đang chọn...", "btn_selecting": "Đang chọn...",
"btn_start_game": "Bắt đầu Game", "btn_start_game": "Bắt đầu Game",
"btn_game_running": "Game đang chạy", "btn_game_running": "Game đang chạy",
"menu_change_path": "Thay đổi đường dẫn Game", "menu_change_path": "Thay đổi đường dẫn Game",
"menu_check_update": "Kiểm tra cập nhật Server & Proxy", "menu_check_update": "Kiểm tra cập nhật Server \u0026 Proxy",
"menu_open_server": "Mở thư mục Server", "menu_open_server": "Mở thư mục Server",
"menu_open_proxy": "Mở thư mục Proxy", "menu_open_proxy": "Mở thư mục Proxy",
"menu_open_voice": "Mở thư mục Voice", "menu_open_voice": "Mở thư mục Voice",
@@ -156,10 +156,10 @@
"sect1_launch_game": "Khởi chạy game trực tiếp thông qua launcher với các thông số và môi trường chạy chính xác.", "sect1_launch_game": "Khởi chạy game trực tiếp thông qua launcher với các thông số và môi trường chạy chính xác.",
"sect1_lang_pre": "Hỗ trợ chuyển đổi ngôn ngữ trong game (ví dụ: EN, JP, ZH, KR) qua", "sect1_lang_pre": "Hỗ trợ chuyển đổi ngôn ngữ trong game (ví dụ: EN, JP, ZH, KR) qua",
"sect1_lang_link": "Công cụ Ngôn ngữ", "sect1_lang_link": "Công cụ Ngôn ngữ",
"sect1_patch_title": "Vá & Cập nhật Tệp Game", "sect1_patch_title": "Vá \u0026 Cập nhật Tệp Game",
"sect1_patch_desc_1_pre": "Sử dụng", "sect1_patch_desc_1_pre": "Sử dụng",
"sect1_patch_desc_1_link": "Công cụ Diff", "sect1_patch_desc_1_link": "Công cụ Diff",
"sect1_patch_desc_1_post": "(DiffPatch) để cập nhật gia tăng nhanh chóng & nhẹ nhàng.", "sect1_patch_desc_1_post": "(DiffPatch) để cập nhật gia tăng nhanh chóng \u0026 nhẹ nhàng.",
"sect1_patch_desc_2_pre": "Hỗ trợ", "sect1_patch_desc_2_pre": "Hỗ trợ",
"sect1_patch_desc_2_post": "và các định dạng diff tùy chỉnh.", "sect1_patch_desc_2_post": "và các định dạng diff tùy chỉnh.",
"sect2_title": "Lệnh Chat FireflyGo", "sect2_title": "Lệnh Chat FireflyGo",
@@ -281,7 +281,7 @@
"btn_back": "Trở về Trang chủ" "btn_back": "Trở về Trang chủ"
}, },
"analysis": { "analysis": {
"title": "Phân tích Firefly & Plugin Veritas", "title": "Phân tích Firefly \u0026 Plugin Veritas",
"sect1_title": "Về Veritas", "sect1_title": "Về Veritas",
"sect1_p1_pre": "", "sect1_p1_pre": "",
"sect1_p1_mid": " là một ", "sect1_p1_mid": " là một ",
+4 -4
View File
@@ -1,4 +1,4 @@
{ {
"header": { "header": {
"home": "首页", "home": "首页",
"tools": "工具", "tools": "工具",
@@ -13,7 +13,7 @@
"settings": "设置", "settings": "设置",
"minimize": "最小化", "minimize": "最小化",
"close": "关闭", "close": "关闭",
"by": "Firefly Shelter" "by": "GaCy"
}, },
"background": { "background": {
"select_bg": "选择背景", "select_bg": "选择背景",
@@ -50,7 +50,7 @@
"launcher_label": "Launcher" "launcher_label": "Launcher"
}, },
"home": { "home": {
"header_title": "Firefly GO", "header_title": "G Launcher",
"tooltip_how_to": "如何使用工具与命令", "tooltip_how_to": "如何使用工具与命令",
"btn_select_game": "选择游戏文件", "btn_select_game": "选择游戏文件",
"btn_selecting": "正在选择...", "btn_selecting": "正在选择...",
@@ -281,7 +281,7 @@
"btn_back": "返回主页" "btn_back": "返回主页"
}, },
"analysis": { "analysis": {
"title": "Firefly 分析 & Veritas 插件", "title": "Firefly 分析 \u0026 Veritas 插件",
"sect1_title": "关于 Veritas", "sect1_title": "关于 Veritas",
"sect1_p1_pre": "Veritas", "sect1_p1_pre": "Veritas",
"sect1_p1_mid": " 是一款强大的", "sect1_p1_mid": " 是一款强大的",
+1 -1
View File
@@ -1,4 +1,4 @@
import { Link } from "@tanstack/react-router"; import { Link } from "@tanstack/react-router";
import useModalStore from "@/stores/modalStore"; import useModalStore from "@/stores/modalStore";
import { Blend, BookOpen, Diff, Home, Info, Languages, Minus, Puzzle, Settings, TrendingUpDown, Wrench, X } from "lucide-react"; import { Blend, BookOpen, Diff, Home, Info, Languages, Minus, Puzzle, Settings, TrendingUpDown, Wrench, X } from "lucide-react";
import { AppService } from "@bindings/g-launcher/internal/app-service"; import { AppService } from "@bindings/g-launcher/internal/app-service";
+17 -6
View File
@@ -9,7 +9,15 @@ 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()
const isExists = await FSService.FileExists(serverPath) let isExists = await FSService.FileExists(serverPath)
if (!isExists) {
const [gameServer] = await GitService.FindServerExecutables()
if (gameServer) {
useSettingStore.getState().setServerPath(gameServer)
isExists = true
}
}
if (!ok) { if (!ok) {
toast.error("Server error: " + error) toast.error("Server error: " + error)
@@ -23,15 +31,18 @@ export async function CheckUpdateServer(
export async function UpdateServer(serverVersion: string) : Promise<void> { export async function UpdateServer(serverVersion: string) : Promise<void> {
const {setDownloadType } = useLauncherStore.getState() const {setDownloadType } = useLauncherStore.getState()
const {setServerPath, setServerVersion} = useSettingStore.getState() const {setServerPath, setSdkServerPath, setServerVersion} = useSettingStore.getState()
setDownloadType("Downloading server...") setDownloadType("Downloading GaCy PS server...")
const [ok, error] = await GitService.DownloadServerProgress(serverVersion) const [ok, error] = await GitService.DownloadServerProgress(serverVersion)
if (ok) { if (ok) {
setDownloadType("Unzipping server...") setDownloadType("Configuring server files...")
GitService.UnzipServer() const [gameServer, sdkServer] = await GitService.FindServerExecutables()
setDownloadType("Download server successfully") setDownloadType("Download server successfully")
setServerVersion(serverVersion) setServerVersion(serverVersion)
setServerPath("./server/firefly-go_win.exe") setServerPath(gameServer || "./server/gameserver-go.exe")
if (sdkServer) {
setSdkServerPath(sdkServer)
}
} else { } else {
toast.error(error) toast.error(error)
setDownloadType("Download server failed") setDownloadType("Download server failed")
+154 -61
View File
@@ -1,7 +1,8 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Play, Menu, FolderOpen, MessageCircleQuestionMark } from 'lucide-react'; import { Play, Menu, FolderOpen, MessageCircleQuestionMark, Server, Globe, CheckCircle2 } from 'lucide-react';
import { AppService } from '@bindings/g-launcher/internal/app-service'; import { AppService } from '@bindings/g-launcher/internal/app-service';
import { FSService } from '@bindings/g-launcher/internal/fs-service'; import { FSService } from '@bindings/g-launcher/internal/fs-service';
import { GitService } from '@bindings/g-launcher/internal/git-service';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import path from 'path-browserify' import path from 'path-browserify'
import useSettingStore from '@/stores/settingStore'; import useSettingStore from '@/stores/settingStore';
@@ -21,11 +22,16 @@ export default function LauncherPage() {
setGamePath, setGamePath,
setGameDir, setGameDir,
serverPath, serverPath,
setServerPath,
sdkServerPath,
setSdkServerPath,
proxyPath, proxyPath,
gameDir, gameDir,
serverVersion, serverVersion,
proxyVersion, proxyVersion,
background background,
selectedServer,
setSelectedServer
} = useSettingStore() } = useSettingStore()
const { t } = useTranslation() const { t } = useTranslation()
const { const {
@@ -40,8 +46,6 @@ export default function LauncherPage() {
const { const {
isLoading, isLoading,
downloadType, downloadType,
serverReady,
proxyReady,
isDownloading, isDownloading,
serverRunning, serverRunning,
proxyRunning, proxyRunning,
@@ -49,7 +53,6 @@ export default function LauncherPage() {
progressDownload, progressDownload,
downloadSpeed, downloadSpeed,
updateData, updateData,
// launcherVersion,
setLauncherVersion, setLauncherVersion,
setIsLoading, setIsLoading,
setDownloadType, setDownloadType,
@@ -64,13 +67,13 @@ export default function LauncherPage() {
const widgetLinks = [ const widgetLinks = [
{ {
tooltip: "Firefly SRAnalysis", tooltip: "SRAnalysis",
href: "https://sranalysis.punklorde.org", href: "https://sranalysis.punklorde.org",
img: "https://sranalysis.punklorde.org/ff-sranalysis.png", img: "https://sranalysis.punklorde.org/ff-sranalysis.png",
btnClass: "btn-primary" btnClass: "btn-primary"
}, },
{ {
tooltip: "Firefly SRTools", tooltip: "SRTools",
href: "https://srtools.punklorde.org", href: "https://srtools.punklorde.org",
img: "https://srtools.punklorde.org/ff-srtool.png", img: "https://srtools.punklorde.org/ff-srtool.png",
btnClass: "btn-secondary" btnClass: "btn-secondary"
@@ -91,7 +94,18 @@ export default function LauncherPage() {
return return
} }
const serverExists = await FSService.FileExists(serverPath) let serverExists = await FSService.FileExists(serverPath)
if (!serverExists) {
const [gameServer, sdkServer] = await GitService.FindServerExecutables()
if (gameServer) {
setServerPath(gameServer)
serverExists = true
}
if (sdkServer) {
setSdkServerPath(sdkServer)
}
}
const proxyExists = await FSService.FileExists(proxyPath) const proxyExists = await FSService.FileExists(proxyPath)
setServerReady(serverExists) setServerReady(serverExists)
setProxyReady(proxyExists) setProxyReady(proxyExists)
@@ -121,17 +135,10 @@ export default function LauncherPage() {
proxy: proxyData, proxy: proxyData,
launcher: launcherData launcher: launcherData
}) })
const exitGame = await FSService.FileExists(gamePath)
if (!exitGame) {
setGameRunning(false)
setGamePath("")
setGameDir("")
}
if (!serverData.isExists || !proxyData.isExists) { if (!serverData.isExists || !proxyData.isExists) {
setServerReady(false) setServerReady(false)
setProxyReady(false) setProxyReady(false)
setIsOpenDownloadDataModal(true)
return return
} }
@@ -175,6 +182,7 @@ export default function LauncherPage() {
const handleStartGame = async () => { const handleStartGame = async () => {
if (!gamePath) { if (!gamePath) {
toast.info("Please select the game executable first")
return return
} }
if (gameRunning) { if (gameRunning) {
@@ -182,36 +190,59 @@ export default function LauncherPage() {
} }
try { try {
setIsLoading(true) setIsLoading(true)
if (selectedServer === "gacy-ps") {
const [gameServer, sdkServer] = await GitService.FindServerExecutables()
const activeServer = gameServer || serverPath
if (!activeServer) {
toast.error("Please download GaCy PS server files first")
setIsOpenDownloadDataModal(true)
return
}
// 1. Start Proxy
const activeProxy = proxyPath || "./proxy/firefly-go-proxy.exe"
if (!proxyRunning && !gamePath.endsWith("launcher.exe")) { if (!proxyRunning && !gamePath.endsWith("launcher.exe")) {
const [resultProxy, error] = await FSService.StartWithConsole(proxyPath) const [resultProxy, error] = await FSService.StartWithConsole(activeProxy)
if (!resultProxy) { if (!resultProxy) {
toast.error(t("home.toast_start_proxy_failed") + error) toast.error(t("home.toast_start_proxy_failed") + ": " + error)
return return
} }
setProxyRunning(true) setProxyRunning(true)
} }
await sleep(500) await sleep(500)
// 2. Start SDK Server if exists
const activeSdk = sdkServer || sdkServerPath
if (activeSdk) {
await FSService.StartWithConsole(activeSdk)
await sleep(400)
}
// 3. Start Game Server
if (!serverRunning) { if (!serverRunning) {
const [resultServer, error] = await FSService.StartWithConsole(serverPath) const [resultServer, error] = await FSService.StartWithConsole(activeServer)
if (!resultServer) { if (!resultServer) {
toast.error(t("home.toast_start_server_failed") + error) toast.error(t("home.toast_start_server_failed") + ": " + error)
return return
} }
setServerRunning(true) setServerRunning(true)
} }
await sleep(1000) await sleep(1000)
}
// 4. Start Game
if (gamePath.endsWith("launcher.exe")) { if (gamePath.endsWith("launcher.exe")) {
const [resultGame, error] = await FSService.StartWithConsole(gamePath) const [resultGame, error] = await FSService.StartWithConsole(gamePath)
if (!resultGame) { if (!resultGame) {
toast.error(t("home.toast_start_game_failed") + error) toast.error(t("home.toast_start_game_failed") + ": " + error)
return return
} }
} else { } else {
const [resultGame, error] = await FSService.StartApp(gamePath) const [resultGame, error] = await FSService.StartApp(gamePath)
if (!resultGame) { if (!resultGame) {
toast.error(t("home.toast_start_game_failed") + error) toast.error(t("home.toast_start_game_failed") + ": " + error)
return return
} }
} }
@@ -266,58 +297,116 @@ export default function LauncherPage() {
return ( return (
<div className="relative min-h-fit overflow-hidden"> <div className="relative min-h-fit overflow-hidden">
<img <img
src={background} src={background || "bg-1.jpeg"}
alt="background" alt="background"
className="fixed inset-0 w-full h-full object-cover z-0" className="fixed inset-0 w-full h-full object-cover z-0"
onError={(e) => { onError={(e) => {
const target = e.currentTarget as HTMLImageElement const target = e.currentTarget as HTMLImageElement
target.src = "bg-12.jpg" target.src = "bg-1.jpeg"
}} }}
/> />
{/* Header */} {/* Header Title */}
<header className="hidden sm:flex fixed z-10 items-center justify-between py-6 px-4"> <header className="hidden sm:flex fixed z-10 items-center justify-between py-6 px-4">
<div className="text-2xl font-bold text-white bg-gray-500/5 rounded-full p-1">{t("home.header_title")}</div> <div className="text-2xl font-black tracking-wide text-white bg-black/40 backdrop-blur-md rounded-2xl px-4 py-1.5 border border-white/10 shadow-lg">
<span className="text-cyan-400">G</span> Launcher
</div>
</header> </header>
<div className="hidden sm:flex fixed top-1/4 right-4 z-10 flex-col space-y-3 bg-white/5 rounded-xl p-3 shadow-lg"> {/* Side Server Selector */}
<div className="hidden sm:flex fixed top-1/4 right-5 z-20 flex-col items-end gap-3">
<div className="text-[11px] uppercase tracking-wider font-bold text-white/80 bg-black/50 backdrop-blur-md px-3.5 py-1 rounded-full border border-white/15 shadow-sm">
เลือกเซิร์ฟเวอร์ (Select Server)
</div>
{/* Server Option 1: GaCy PS */}
<button
onClick={() => setSelectedServer("gacy-ps")}
className={`flex items-center gap-3 w-64 p-3 rounded-2xl backdrop-blur-md transition-all duration-200 border cursor-pointer ${
selectedServer === "gacy-ps"
? "bg-cyan-500/30 border-cyan-400 text-white shadow-xl shadow-cyan-500/20 ring-2 ring-cyan-400"
: "bg-black/55 border-white/15 text-white/80 hover:bg-black/75 hover:border-cyan-400/50"
}`}
>
<div className={`p-2.5 rounded-xl ${selectedServer === "gacy-ps" ? "bg-cyan-500 text-black font-bold" : "bg-cyan-500/20 text-cyan-400"}`}>
<Server className="w-5 h-5" />
</div>
<div className="flex flex-col text-left flex-1 min-w-0">
<div className="flex items-center justify-between">
<span className="font-bold text-sm tracking-tight truncate">GaCy PS</span>
<span className="badge badge-xs badge-info font-mono text-[10px]">4.6.54</span>
</div>
<span className="text-xs text-white/60 truncate">Private Server (Local)</span>
</div>
{selectedServer === "gacy-ps" && (
<CheckCircle2 className="w-5 h-5 text-cyan-400 shrink-0" />
)}
</button>
{/* Server Option 2: Official Server */}
<button
onClick={() => setSelectedServer("official")}
className={`flex items-center gap-3 w-64 p-3 rounded-2xl backdrop-blur-md transition-all duration-200 border cursor-pointer ${
selectedServer === "official"
? "bg-emerald-500/30 border-emerald-400 text-white shadow-xl shadow-emerald-500/20 ring-2 ring-emerald-400"
: "bg-black/55 border-white/15 text-white/80 hover:bg-black/75 hover:border-emerald-400/50"
}`}
>
<div className={`p-2.5 rounded-xl ${selectedServer === "official" ? "bg-emerald-500 text-black font-bold" : "bg-emerald-500/20 text-emerald-400"}`}>
<Globe className="w-5 h-5" />
</div>
<div className="flex flex-col text-left flex-1 min-w-0">
<div className="flex items-center justify-between">
<span className="font-bold text-sm tracking-tight truncate">Official Server</span>
<span className="badge badge-xs badge-success font-mono text-[10px]">Live</span>
</div>
<span className="text-xs text-white/60 truncate">Hoyoverse Official</span>
</div>
{selectedServer === "official" && (
<CheckCircle2 className="w-5 h-5 text-emerald-400 shrink-0" />
)}
</button>
{/* External Tools Mini Bar */}
<div className="flex items-center gap-2 pt-2">
{widgetLinks.map((link, idx) => ( {widgetLinks.map((link, idx) => (
<div key={idx} className="tooltip tooltip-left" data-tip={link.tooltip}> <div key={idx} className="tooltip tooltip-bottom" data-tip={link.tooltip}>
<a <a
className={`btn btn-circle ${link.btnClass}`} className="btn btn-circle btn-sm bg-black/50 border-white/20 hover:border-cyan-400"
target="_blank" target="_blank"
href={link.href} href={link.href}
> >
<img <img
src={link.img} src={link.img}
alt={link.tooltip} alt={link.tooltip}
className="w-8 h-8 rounded-full" className="w-5 h-5 rounded-full"
/> />
</a> </a>
</div> </div>
))} ))}
<div className="tooltip tooltip-bottom" data-tip={t("home.tooltip_how_to")}>
<div className="tooltip tooltip-left" data-tip={t("home.tooltip_how_to")}>
<Link <Link
to="/howto" to="/howto"
className="btn btn-warning btn-circle" className="btn btn-circle btn-sm btn-warning"
> >
<MessageCircleQuestionMark className="w-6 h-6 font-bold rounded-full" /> <MessageCircleQuestionMark className="w-4 h-4 font-bold rounded-full" />
</Link> </Link>
</div> </div>
</div> </div>
</div>
{/* Left Background Selector */}
<div className="hidden sm:flex fixed top-1/2 left-5 z-10"> <div className="hidden sm:flex fixed top-1/2 left-5 z-10">
<BackgroundSelector /> <BackgroundSelector />
</div> </div>
{/* Bottom Panel */} {/* Bottom Play Panel: Appears once a server is selected! */}
{serverReady && proxyReady && !isDownloading && ( {selectedServer !== "" && !isDownloading && (
<div className="fixed bottom-0 right-0 p-8 z-2"> <div className="fixed bottom-0 right-0 p-8 z-2 animate-in fade-in slide-in-from-bottom-4 duration-300">
<div className="flex flex-wrap items-center justify-center gap-2"> <div className="flex flex-wrap items-center justify-center gap-2">
{gamePath === "" ? ( {gamePath === "" ? (
<button <button
className="btn btn-accent btn-xl font-bold" className="btn btn-accent btn-xl font-bold shadow-xl"
onClick={handlePickFile} onClick={handlePickFile}
> >
<FolderOpen className="w-6 h-6" /> <FolderOpen className="w-6 h-6" />
@@ -325,19 +414,27 @@ export default function LauncherPage() {
</button> </button>
) : ( ) : (
<button <button
className="btn btn-warning btn-xl font-bold" className={`btn btn-xl font-bold shadow-2xl transition-all ${
selectedServer === "gacy-ps"
? "btn-warning text-black hover:brightness-110"
: "btn-success text-white hover:brightness-110"
}`}
onClick={handleStartGame} onClick={handleStartGame}
> >
<Play className="w-6 h-6" /> <Play className="w-6 h-6 fill-current" />
{isLoading ? t("home.btn_selecting") : gameRunning ? t("home.btn_game_running") : t("home.btn_start_game")} {isLoading
? t("home.btn_selecting")
: gameRunning
? t("home.btn_game_running")
: `${t("home.btn_start_game")} [${selectedServer === "gacy-ps" ? "GaCy PS" : "Official"}]`}
</button> </button>
)} )}
<div className="dropdown dropdown-top dropdown-end"> <div className="dropdown dropdown-top dropdown-end">
<div tabIndex={0} role="button" className="btn btn-black btn-circle btn-xl m-1"> <div tabIndex={0} role="button" className="btn btn-black btn-circle btn-xl m-1 border border-white/20 shadow-xl">
<Menu className="w-6 h-6" /> <Menu className="w-6 h-6" />
</div> </div>
<ul tabIndex={0} className="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm"> <ul tabIndex={0} className="dropdown-content menu bg-black/80 backdrop-blur-md rounded-box z-1 w-56 p-2 shadow-2xl border border-white/10 text-white">
<li><button onClick={handlePickFile}>{t("home.menu_change_path")}</button></li> <li><button onClick={handlePickFile}>{t("home.menu_change_path")}</button></li>
<li> <li>
<button <button
@@ -364,23 +461,16 @@ export default function LauncherPage() {
</button> </button>
</li> </li>
<li> <li>
<button onClick={() => {
</li>
<li>
<button disabled={!serverPath} onClick={() => {
if (serverPath) {
FSService.OpenFolder("./server") FSService.OpenFolder("./server")
}
}} }}
> >
{t("home.menu_open_server")} {t("home.menu_open_server")}
</button> </button>
</li> </li>
<li> <li>
<button disabled={!proxyPath} onClick={() => { <button onClick={() => {
if (proxyPath) {
FSService.OpenFolder("./proxy") FSService.OpenFolder("./proxy")
}
}} }}
> >
{t("home.menu_open_proxy")} {t("home.menu_open_proxy")}
@@ -397,20 +487,27 @@ export default function LauncherPage() {
</button> </button>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
)} )}
{/* Downloading */} {/* Notification when no server is selected yet */}
{selectedServer === "" && !isDownloading && (
<div className="fixed bottom-8 right-8 z-2 flex items-center gap-2 bg-black/60 backdrop-blur-md px-5 py-3 rounded-2xl border border-white/20 text-white shadow-xl animate-pulse">
<span className="text-cyan-400 text-lg">👉</span>
<span className="text-sm font-medium">กรุณาเลือกเซิร์ฟเวอร์ด้านบนเพื่อเริ่มเล่น (Select a server to play)</span>
</div>
)}
{/* Downloading Progress Bar */}
{isDownloading && ( {isDownloading && (
updateData.proxy.isUpdate updateData.proxy.isUpdate
|| updateData.server.isUpdate || updateData.server.isUpdate
|| !updateData.proxy.isExists || !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/20 backdrop-blur-sm rounded-lg p-4 shadow-lg"> <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="space-y-3"> <div className="space-y-3">
<div className="flex justify-center items-center text-sm text-white/80"> <div className="flex justify-center items-center text-sm text-white/80">
<span>{downloadType}</span> <span>{downloadType}</span>
@@ -434,8 +531,9 @@ export default function LauncherPage() {
</div> </div>
)} )}
{/* Launcher Self Update Bar */}
{isDownloading && updateData.launcher.isUpdate && ( {isDownloading && updateData.launcher.isUpdate && (
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-10 w-[60vw] bg-black/20 backdrop-blur-sm rounded-lg p-4 shadow-lg"> <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="space-y-3 text-sm text-white/80 text-center"> <div className="space-y-3 text-sm text-white/80 text-center">
{["update:launcher:downloading", "update:launcher:success", "update:launcher:failed"].includes(downloadType) && ( {["update:launcher:downloading", "update:launcher:success", "update:launcher:failed"].includes(downloadType) && (
<div className="flex justify-center items-center gap-4 ml-4"> <div className="flex justify-center items-center gap-4 ml-4">
@@ -477,12 +575,7 @@ export default function LauncherPage() {
</div> </div>
)} )}
<div className="hidden md:block fixed bottom-4 left-5 z-10"> {/* Modals */}
<img src="/heart-hsr.gif" alt="firefly animation" className="rounded-lg w-24 h-24" />
</div>
{/* Modal */}
<UpdateModal <UpdateModal
isOpen={isOpenUpdateDataModal} isOpen={isOpenUpdateDataModal}
onClose={() => setIsOpenUpdateDataModal(false)} onClose={() => setIsOpenUpdateDataModal(false)}
+9 -1
View File
@@ -7,9 +7,11 @@ interface SettingState {
gamePath: string; gamePath: string;
gameDir: string; gameDir: string;
serverPath: string; serverPath: string;
sdkServerPath: string;
proxyPath: string; proxyPath: string;
serverVersion: string; serverVersion: string;
proxyVersion: string; proxyVersion: string;
selectedServer: string;
closingOption: { closingOption: {
isMinimize: boolean; isMinimize: boolean;
isAsk: boolean; isAsk: boolean;
@@ -18,11 +20,13 @@ interface SettingState {
extraBackgrounds: string[]; extraBackgrounds: string[];
setExtraBackgrounds: (newExtraBackgrounds: string[]) => void; setExtraBackgrounds: (newExtraBackgrounds: string[]) => void;
setBackground: (newBackground: string) => void; setBackground: (newBackground: string) => void;
setSelectedServer: (newServer: string) => void;
setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => void; setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => void;
setLocale: (newLocale: string) => void; setLocale: (newLocale: string) => void;
setGamePath: (newGamePath: string) => void; setGamePath: (newGamePath: string) => void;
setGameDir: (newGameDir: string) => void; setGameDir: (newGameDir: string) => void;
setServerPath: (newServerPath: string) => void; setServerPath: (newServerPath: string) => void;
setSdkServerPath: (newSdkServerPath: string) => void;
setProxyPath: (newProxyPath: string) => void; setProxyPath: (newProxyPath: string) => void;
setServerVersion: (newServerVersion: string) => void; setServerVersion: (newServerVersion: string) => void;
setProxyVersion: (newProxyVersion: string) => void; setProxyVersion: (newProxyVersion: string) => void;
@@ -35,22 +39,26 @@ const useSettingStore = create<SettingState>()(
gamePath: "", gamePath: "",
gameDir: "", gameDir: "",
serverPath: "", serverPath: "",
sdkServerPath: "",
proxyPath: "", proxyPath: "",
serverVersion: "", serverVersion: "",
proxyVersion: "", proxyVersion: "",
selectedServer: "",
closingOption: { closingOption: {
isMinimize: false, isMinimize: false,
isAsk: true, isAsk: true,
}, },
background: "bg-12.jpg", background: "bg-1.jpeg",
extraBackgrounds: [], extraBackgrounds: [],
setExtraBackgrounds: (newExtraBackgrounds: string[]) => set({ extraBackgrounds: newExtraBackgrounds }), setExtraBackgrounds: (newExtraBackgrounds: string[]) => set({ extraBackgrounds: newExtraBackgrounds }),
setBackground: (newBackground: string) => set({ background: newBackground }), setBackground: (newBackground: string) => set({ background: newBackground }),
setSelectedServer: (newServer: string) => set({ selectedServer: newServer }),
setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => set({ closingOption: newClosingOption }), setClosingOption: (newClosingOption: { isMinimize: boolean; isAsk: boolean }) => set({ closingOption: newClosingOption }),
setLocale: (newLocale: string) => set({ locale: newLocale }), setLocale: (newLocale: string) => set({ locale: newLocale }),
setGamePath: (newGamePath: string) => set({ gamePath: newGamePath }), setGamePath: (newGamePath: string) => set({ gamePath: newGamePath }),
setGameDir: (newGameDir: string) => set({ gameDir: newGameDir }), setGameDir: (newGameDir: string) => set({ gameDir: newGameDir }),
setServerPath: (newServerPath: string) => set({ serverPath: newServerPath }), setServerPath: (newServerPath: string) => set({ serverPath: newServerPath }),
setSdkServerPath: (newSdkServerPath: string) => set({ sdkServerPath: newSdkServerPath }),
setProxyPath: (newProxyPath: string) => set({ proxyPath: newProxyPath }), setProxyPath: (newProxyPath: string) => set({ proxyPath: newProxyPath }),
setServerVersion: (newServerVersion: string) => set({ serverVersion: newServerVersion }), setServerVersion: (newServerVersion: string) => set({ serverVersion: newServerVersion }),
setProxyVersion: (newProxyVersion: string) => set({ proxyVersion: newProxyVersion }), setProxyVersion: (newProxyVersion: string) => set({ proxyVersion: newProxyVersion }),
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

+1 -1
View File
@@ -128,7 +128,7 @@ func (f *FSService) StartWithConsole(path string) (bool, string) {
base := strings.ToLower(filepath.Base(path)) base := strings.ToLower(filepath.Base(path))
if base == "launcher.exe" || base == "starrail.exe" { if base == "launcher.exe" || base == "starrail.exe" {
application.Get().Event.Emit("game:exit") application.Get().Event.Emit("game:exit")
} else if base == "firefly-go_win.exe" || base == "gameserver.exe" { } else if base == "firefly-go_win.exe" || base == "gameserver.exe" || base == "gameserver-go.exe" || base == "server.exe" || base == "sdkserver-go.exe" || base == "sdkserver.exe" {
application.Get().Event.Emit("server:exit") application.Get().Event.Emit("server:exit")
} else if base == "firefly-go-proxy.exe" || base == "proxy.exe" { } else if base == "firefly-go-proxy.exe" || base == "proxy.exe" {
application.Get().Event.Emit("proxy:exit") application.Get().Event.Emit("proxy:exit")
+21 -3
View File
@@ -40,9 +40,11 @@ func (g *GitService) GetLatestLauncherVersion() (bool, string, string) {
} }
} }
// Fallback to Gitea/Forgejo/GitHub Releases API client := &http.Client{Timeout: 10 * time.Second}
// Fallback to Gitea Releases API
if constant.LauncherGitUrl != "" { if constant.LauncherGitUrl != "" {
resp, err := http.Get(constant.LauncherGitUrl) resp, err := client.Get(constant.LauncherGitUrl)
if err == nil { if err == nil {
defer resp.Body.Close() defer resp.Body.Close()
body, readErr := io.ReadAll(resp.Body) body, readErr := io.ReadAll(resp.Body)
@@ -55,7 +57,23 @@ func (g *GitService) GetLatestLauncherVersion() (bool, string, string) {
} }
} }
return false, "", "no launcher updates found" // Fallback to Gitea Tags API
tagsURL := "https://gaytea.cod-algieba.ts.net/api/v1/repos/GaCy/G-Launcher/tags"
resp, err := client.Get(tagsURL)
if err == nil {
defer resp.Body.Close()
body, readErr := io.ReadAll(resp.Body)
if readErr == nil {
var tags []struct {
Name string `json:"name"`
}
if json.Unmarshal(body, &tags) == nil && len(tags) > 0 {
return true, tags[0].Name, ""
}
}
}
return true, constant.CurrentLauncherVersion, ""
} }
func (g *GitService) UpdateLauncherProgress(version string) (bool, string) { func (g *GitService) UpdateLauncherProgress(version string) (bool, string) {
+150 -14
View File
@@ -2,26 +2,31 @@ package gitService
import ( import (
"encoding/json" "encoding/json"
"fmt"
"g-launcher/pkg/constant" "g-launcher/pkg/constant"
"g-launcher/pkg/models" "g-launcher/pkg/models"
"fmt"
"io" "io"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
) )
func (g *GitService) GetLatestServerVersion() (bool, string, string) { func (g *GitService) GetLatestServerVersion() (bool, string, string) {
resp, err := http.Get(constant.ServerGitUrl) client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Get(constant.ServerGitUrl)
if err != nil { if err != nil {
return false, "", err.Error() return false, "", err.Error()
} }
defer resp.Body.Close() defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil {
return false, "", err.Error()
}
var releases []models.ReleaseType var releases []models.ReleaseType
err = json.Unmarshal(body, &releases) err = json.Unmarshal(body, &releases)
@@ -37,35 +42,166 @@ func (g *GitService) GetLatestServerVersion() (bool, string, string) {
} }
func (g *GitService) DownloadServerProgress(version string) (bool, string) { func (g *GitService) DownloadServerProgress(version string) (bool, string) {
asset, ok := g.getReleaseAsset(version, constant.ServerGitUrl, constant.ServerZipFile) client := &http.Client{Timeout: 15 * time.Second}
if !ok { resp, err := client.Get(constant.ServerGitUrl)
return false, "no release found" if err != nil {
return false, fmt.Sprintf("fetch server releases: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, fmt.Sprintf("read server releases: %v", err)
}
var releases []*models.ReleaseType
if err := json.Unmarshal(body, &releases); err != nil || len(releases) == 0 {
return false, "no releases found on server repository"
}
var targetRelease *models.ReleaseType
for _, rel := range releases {
if rel.TagName == version {
targetRelease = rel
break
}
}
if targetRelease == nil && len(releases) > 0 {
targetRelease = releases[0]
}
if targetRelease == nil {
return false, "could not find server release matching version " + version
} }
if err := os.MkdirAll(constant.ServerStorageUrl, 0755); err != nil { if err := os.MkdirAll(constant.ServerStorageUrl, 0755); err != nil {
return false, err.Error() return false, err.Error()
} }
saveFile := filepath.Join(constant.ServerStorageUrl, asset.Name) // 1. Search for .zip asset
tmpPath, err := g.downloadFileParallel(saveFile, asset.BrowserDownloadURL, 4, func(percent float64, speed string) { var zipAsset *models.AssetType
for _, asset := range targetRelease.Assets {
if strings.HasSuffix(strings.ToLower(asset.Name), ".zip") {
aCopy := asset
zipAsset = &aCopy
break
}
}
if zipAsset != nil {
// Case A: ZIP archive found
saveZipPath := filepath.Join(constant.ServerStorageUrl, zipAsset.Name)
tmpPath, err := g.downloadFileParallel(saveZipPath, zipAsset.BrowserDownloadURL, 4, func(percent float64, speed string) {
application.Get().Event.Emit("download:server", map[string]interface{}{ application.Get().Event.Emit("download:server", map[string]interface{}{
"percent": fmt.Sprintf("%.2f", percent), "percent": fmt.Sprintf("%.2f", percent),
"speed": speed, "speed": speed,
}) })
}) })
if err != nil { if err != nil {
return false, err.Error() return false, fmt.Sprintf("download server zip: %v", err)
} }
renamed := false
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
if err := os.Rename(tmpPath, saveFile); err == nil { if err := os.Rename(tmpPath, saveZipPath); err == nil {
return true, "" renamed = true
break
} }
time.Sleep(300 * time.Millisecond) time.Sleep(300 * time.Millisecond)
} }
return false, "failed to rename tmp file after retries" if !renamed {
os.Remove(tmpPath)
return false, "failed to rename downloaded server zip"
}
// Unzip immediately
if err := g.unzipParallel(saveZipPath, constant.ServerStorageUrl); err != nil {
return false, fmt.Sprintf("unzip server: %v", err)
}
os.Remove(saveZipPath)
return true, ""
}
// Case B: No ZIP archive, download sdkserver and server executables
var sdkAsset, serverAsset *models.AssetType
for _, asset := range targetRelease.Assets {
nameLower := strings.ToLower(asset.Name)
if strings.Contains(nameLower, "sdkserver") {
aCopy := asset
sdkAsset = &aCopy
} else if strings.Contains(nameLower, "server") {
aCopy := asset
serverAsset = &aCopy
}
}
if sdkAsset == nil && serverAsset == nil {
return false, "no zip or server executable assets found in release"
}
if sdkAsset != nil {
saveSdk := filepath.Join(constant.ServerStorageUrl, sdkAsset.Name)
tmpPath, err := g.downloadFileParallel(saveSdk, sdkAsset.BrowserDownloadURL, 4, func(percent float64, speed string) {
application.Get().Event.Emit("download:server", map[string]interface{}{
"percent": fmt.Sprintf("%.2f", percent*0.5),
"speed": speed,
})
})
if err == nil {
_ = os.Rename(tmpPath, saveSdk)
}
}
if serverAsset != nil {
saveServer := filepath.Join(constant.ServerStorageUrl, serverAsset.Name)
tmpPath, err := g.downloadFileParallel(saveServer, serverAsset.BrowserDownloadURL, 4, func(percent float64, speed string) {
application.Get().Event.Emit("download:server", map[string]interface{}{
"percent": fmt.Sprintf("%.2f", 50.0+percent*0.5),
"speed": speed,
})
})
if err == nil {
_ = os.Rename(tmpPath, saveServer)
}
}
return true, ""
} }
func (g *GitService) UnzipServer() { func (g *GitService) UnzipServer() {
g.unzipParallel(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile), constant.ServerStorageUrl) entries, err := os.ReadDir(constant.ServerStorageUrl)
os.Remove(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile)) if err != nil {
return
}
for _, entry := range entries {
if !entry.IsDir() && strings.HasSuffix(strings.ToLower(entry.Name()), ".zip") {
zipPath := filepath.Join(constant.ServerStorageUrl, entry.Name())
_ = g.unzipParallel(zipPath, constant.ServerStorageUrl)
_ = os.Remove(zipPath)
}
}
}
func (g *GitService) FindServerExecutables() (string, string) {
serverDir := constant.ServerStorageUrl
var gameServer, sdkServer string
// 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
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
}
}
return gameServer, sdkServer
} }
+2 -2
View File
@@ -1,7 +1,7 @@
package constant package constant
const ProxyGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Proxy/releases" const ProxyGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Proxy/releases"
const ServerGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Local_Archive/releases" const ServerGitUrl = "https://gaytea.cod-algieba.ts.net/api/v1/repos/GaCySR/GaCy-GO/releases"
const LauncherGitUrl = "https://gaytea.cod-algieba.ts.net/api/v1/repos/GaCy/G-Launcher/releases" const LauncherGitUrl = "https://gaytea.cod-algieba.ts.net/api/v1/repos/GaCy/G-Launcher/releases"
const LauncherManifestURL = "https://gaytea.cod-algieba.ts.net/GaCy/G-Launcher/releases/download/latest/latest.json" const LauncherManifestURL = "https://gaytea.cod-algieba.ts.net/GaCy/G-Launcher/releases/download/latest/latest.json"
const LauncherDownloadURL = "https://gaytea.cod-algieba.ts.net/GaCy/G-Launcher/releases/download/latest/G-Launcher.exe" const LauncherDownloadURL = "https://gaytea.cod-algieba.ts.net/GaCy/G-Launcher/releases/download/latest/G-Launcher.exe"
@@ -11,7 +11,7 @@ const LauncherBuildTempDir = "temp/updater_build"
const ServerStorageUrl = "./server" const ServerStorageUrl = "./server"
const ProxyStorageUrl = "./proxy" const ProxyStorageUrl = "./proxy"
const ServerZipFile = "prebuild_win_x86.zip" const ServerZipFile = "gacy 4.6.54.zip"
const ProxyFile = "firefly-go-proxy.exe" const ProxyFile = "firefly-go-proxy.exe"
const LauncherFile = "G-Launcher.exe" const LauncherFile = "G-Launcher.exe"
const TempUrl = "./temp" const TempUrl = "./temp"