Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99b9df1ce5 | |||
| b2adcd7981 | |||
| ba58d24e06 | |||
| ec72b812de | |||
| 448ced1260 | |||
| d4c75b341f | |||
| fb1cd82434 | |||
| 461dfd93ba | |||
| 95f8ed357d |
|
Before Width: | Height: | Size: 944 KiB After Width: | Height: | Size: 1.1 MiB |
@@ -6,7 +6,7 @@
|
|||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>Firefly Launcher</string>
|
<string>Firefly Launcher</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>Firefly Launcher</string>
|
<string>firefly-launcher</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.fireflyshelter.fireflylauncher</string>
|
<string>com.fireflyshelter.fireflylauncher</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>Firefly Launcher</string>
|
<string>Firefly Launcher</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>Firefly Launcher</string>
|
<string>firefly-launcher</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.fireflyshelter.fireflylauncher</string>
|
<string>com.fireflyshelter.fireflylauncher</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# The lines below are called `modelines`. See `:help modeline`
|
# The lines below are called `modelines`. See `:help modeline`
|
||||||
|
|
||||||
name: "Firefly Launcher"
|
name: "firefly-launcher"
|
||||||
arch: ${GOARCH}
|
arch: ${GOARCH}
|
||||||
platform: "linux"
|
platform: "linux"
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
@@ -17,12 +17,12 @@ license: "MIT"
|
|||||||
release: "1"
|
release: "1"
|
||||||
|
|
||||||
contents:
|
contents:
|
||||||
- src: "./bin/Firefly Launcher"
|
- src: "./bin/firefly-launcher"
|
||||||
dst: "/usr/local/bin/Firefly Launcher"
|
dst: "/usr/local/bin/firefly-launcher"
|
||||||
- src: "./build/appicon.png"
|
- src: "./build/appicon.png"
|
||||||
dst: "/usr/share/icons/hicolor/128x128/apps/Firefly Launcher.png"
|
dst: "/usr/share/icons/hicolor/128x128/apps/firefly-launcher.png"
|
||||||
- src: "./build/linux/Firefly Launcher.desktop"
|
- src: "./build/linux/firefly-launcher.desktop"
|
||||||
dst: "/usr/share/applications/Firefly Launcher.desktop"
|
dst: "/usr/share/applications/firefly-launcher.desktop"
|
||||||
|
|
||||||
depends:
|
depends:
|
||||||
- gtk3
|
- gtk3
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 124 KiB |
@@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
<assemblyIdentity type="win32" name="com.fireflyshelter.fireflylauncher" version="0.1.0" processorArchitecture="*"/>
|
<assemblyIdentity type="win32" name="com.fireflyshelter.fireflylauncher" version="0.1.0" processorArchitecture="*"/>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<dependentAssembly>
|
<dependentAssembly>
|
||||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
</dependentAssembly>
|
</dependentAssembly>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Yêu cầu quyền admin -->
|
||||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
<security>
|
<security>
|
||||||
<requestedPrivileges>
|
<requestedPrivileges>
|
||||||
|
|||||||
24
frontend/bindings/firefly-launcher/internal/appservice.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} timeout
|
||||||
|
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
||||||
|
*/
|
||||||
|
export function CloseAppAfterTimeout(timeout) {
|
||||||
|
let $resultPromise = /** @type {any} */($Call.ByID(982751559, timeout));
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
||||||
|
*/
|
||||||
|
export function GetCurrentLauncherVersion() {
|
||||||
|
let $resultPromise = /** @type {any} */($Call.ByID(2542090372));
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
@@ -25,20 +25,26 @@ export function DownloadServerProgress(version) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} oldVersion
|
|
||||||
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
||||||
*/
|
*/
|
||||||
export function GetLatestProxyVersion(oldVersion) {
|
export function GetLatestLauncherVersion() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1462362449, oldVersion));
|
let $resultPromise = /** @type {any} */($Call.ByID(3191972855));
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} oldVersion
|
|
||||||
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
||||||
*/
|
*/
|
||||||
export function GetLatestServerVersion(oldVersion) {
|
export function GetLatestProxyVersion() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1447982978, oldVersion));
|
let $resultPromise = /** @type {any} */($Call.ByID(1462362449));
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
||||||
|
*/
|
||||||
|
export function GetLatestServerVersion() {
|
||||||
|
let $resultPromise = /** @type {any} */($Call.ByID(1447982978));
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,3 +63,12 @@ export function UnzipServer() {
|
|||||||
let $resultPromise = /** @type {any} */($Call.ByID(4110296071));
|
let $resultPromise = /** @type {any} */($Call.ByID(4110296071));
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} version
|
||||||
|
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
||||||
|
*/
|
||||||
|
export function UpdateLauncherProgress(version) {
|
||||||
|
let $resultPromise = /** @type {any} */($Call.ByID(2648938152, version));
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} patchPath
|
||||||
|
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
||||||
|
*/
|
||||||
|
export function CheckTypeHDiff(patchPath) {
|
||||||
|
let $resultPromise = /** @type {any} */($Call.ByID(1068035136, patchPath));
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} gamePath
|
* @param {string} gamePath
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
||||||
@@ -18,10 +27,11 @@ export function CutData(gamePath) {
|
|||||||
/**
|
/**
|
||||||
* @param {string} gamePath
|
* @param {string} gamePath
|
||||||
* @param {string} patchPath
|
* @param {string} patchPath
|
||||||
|
* @param {boolean} isSkipVerify
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
||||||
*/
|
*/
|
||||||
export function DataExtract(gamePath, patchPath) {
|
export function DataExtract(gamePath, patchPath, isSkipVerify) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1843136452, gamePath, patchPath));
|
let $resultPromise = /** @type {any} */($Call.ByID(1843136452, gamePath, patchPath, isSkipVerify));
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
import * as AppService from "./appservice.js";
|
||||||
import * as FSService from "./fsservice.js";
|
import * as FSService from "./fsservice.js";
|
||||||
import * as GitService from "./gitservice.js";
|
import * as GitService from "./gitservice.js";
|
||||||
import * as HdiffzService from "./hdiffzservice.js";
|
import * as HdiffzService from "./hdiffzservice.js";
|
||||||
import * as LanguageService from "./languageservice.js";
|
import * as LanguageService from "./languageservice.js";
|
||||||
export {
|
export {
|
||||||
|
AppService,
|
||||||
FSService,
|
FSService,
|
||||||
GitService,
|
GitService,
|
||||||
HdiffzService,
|
HdiffzService,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {Promise<[string, string]> & { cancel(): void }}
|
* @returns {Promise<[boolean, string, string, string]> & { cancel(): void }}
|
||||||
*/
|
*/
|
||||||
export function GetLanguage(path) {
|
export function GetLanguage(path) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3450750492, path));
|
let $resultPromise = /** @type {any} */($Call.ByID(3450750492, path));
|
||||||
@@ -19,7 +19,7 @@ export function GetLanguage(path) {
|
|||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {string} voice
|
* @param {string} voice
|
||||||
* @returns {Promise<boolean> & { cancel(): void }}
|
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
||||||
*/
|
*/
|
||||||
export function SetLanguage(path, text, voice) {
|
export function SetLanguage(path, text, voice) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(2793672496, path, text, voice));
|
let $resultPromise = /** @type {any} */($Call.ByID(2793672496, path, text, voice));
|
||||||
|
|||||||
2626
frontend/package-lock.json
generated
@@ -11,33 +11,33 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.12",
|
||||||
"@tanstack/react-router": "^1.124.0",
|
"@tanstack/react-router": "^1.131.27",
|
||||||
"@tanstack/react-router-devtools": "^1.124.0",
|
"@tanstack/react-router-devtools": "^1.131.27",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.541.0",
|
||||||
"motion": "^12.23.0",
|
"motion": "^12.23.12",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^19.1.1",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.12",
|
||||||
"zustand": "^5.0.6"
|
"zustand": "^5.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/router-plugin": "^1.124.0",
|
"@tanstack/router-plugin": "^1.131.27",
|
||||||
"@types/node": "^24.0.10",
|
"@types/node": "^24.3.0",
|
||||||
"@types/path-browserify": "^1.0.3",
|
"@types/path-browserify": "^1.0.3",
|
||||||
"@types/react": "^18.2.43",
|
"@types/react": "^19.1.11",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
||||||
"@typescript-eslint/parser": "^6.14.0",
|
"@typescript-eslint/parser": "^8.40.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^5.0.1",
|
||||||
"@wailsio/runtime": "^3.0.0-alpha.66",
|
"@wailsio/runtime": "^3.0.0-alpha.66",
|
||||||
"daisyui": "^5.0.43",
|
"daisyui": "^5.0.50",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^9.34.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^5.0.8"
|
"vite": "^7.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
frontend/public/ff-launcher.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
4
frontend/src/helper/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./server"
|
||||||
|
export * from "./proxy"
|
||||||
|
export * from "./launcher"
|
||||||
|
export * from "./sleep"
|
||||||
37
frontend/src/helper/launcher.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import useLauncherStore from "@/stores/launcherStore";
|
||||||
|
import { AppService, GitService } from "@bindings/firefly-launcher/internal";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { sleep } from "./sleep";
|
||||||
|
|
||||||
|
export async function CheckUpdateLauncher(): Promise<{ isUpdate: boolean; isExists: boolean; version: string }> {
|
||||||
|
const [currentOk, currentVersion] = await AppService.GetCurrentLauncherVersion()
|
||||||
|
if (!currentOk) {
|
||||||
|
toast.error("Launcher error: cannot get current version")
|
||||||
|
return { isUpdate: false, isExists: true, version: "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
const [latestOk, latestVersion, latestError] = await GitService.GetLatestLauncherVersion()
|
||||||
|
if (!latestOk) {
|
||||||
|
toast.error("Launcher error: " + latestError)
|
||||||
|
return { isUpdate: false, isExists: true, version: currentVersion }
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUpdate = latestVersion !== currentVersion
|
||||||
|
return { isUpdate, isExists: true, version: latestVersion }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function UpdateLauncher(launcherVersion: string) : Promise<void> {
|
||||||
|
const {setDownloadType } = useLauncherStore.getState()
|
||||||
|
setDownloadType("update:launcher:downloading")
|
||||||
|
const [ok, error] = await GitService.UpdateLauncherProgress(launcherVersion)
|
||||||
|
if (ok) {
|
||||||
|
setDownloadType("update:launcher:success")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toast.error(error)
|
||||||
|
setDownloadType("update:launcher:failed")
|
||||||
|
}
|
||||||
|
AppService.CloseAppAfterTimeout(5)
|
||||||
|
await sleep(5000)
|
||||||
|
}
|
||||||
34
frontend/src/helper/proxy.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import useLauncherStore from "@/stores/launcherStore";
|
||||||
|
import useSettingStore from "@/stores/settingStore";
|
||||||
|
import { FSService, GitService } from "@bindings/firefly-launcher/internal";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
export async function CheckUpdateProxy(proxyPath: string, proxyVersion: string) : Promise<{isUpdate: boolean, isExists: boolean, version: string}> {
|
||||||
|
const [ok, latestVersion, error] = await GitService.GetLatestProxyVersion()
|
||||||
|
const isExists = await FSService.FileExists(proxyPath)
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
toast.error("Proxy error: " + error)
|
||||||
|
return { isUpdate: false, isExists, version: "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUpdate = latestVersion !== proxyVersion
|
||||||
|
return { isUpdate, isExists, version: latestVersion }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function UpdateProxy(proxyVersion: string) : Promise<void> {
|
||||||
|
const {setDownloadType } = useLauncherStore.getState()
|
||||||
|
const {setProxyPath, setProxyVersion} = useSettingStore.getState()
|
||||||
|
setDownloadType("Downloading proxy...")
|
||||||
|
const [ok, error] = await GitService.DownloadProxyProgress(proxyVersion)
|
||||||
|
if (ok) {
|
||||||
|
setDownloadType("Unzipping proxy...")
|
||||||
|
GitService.UnzipProxy()
|
||||||
|
setDownloadType("Download proxy successfully")
|
||||||
|
setProxyVersion(proxyVersion)
|
||||||
|
setProxyPath("./proxy/FireflyProxy.exe")
|
||||||
|
} else {
|
||||||
|
toast.error(error)
|
||||||
|
setDownloadType("Download proxy failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
38
frontend/src/helper/server.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import useLauncherStore from '@/stores/launcherStore';
|
||||||
|
import useSettingStore from '@/stores/settingStore';
|
||||||
|
import { FSService, GitService } from '@bindings/firefly-launcher/internal';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
export async function CheckUpdateServer(
|
||||||
|
serverPath: string,
|
||||||
|
serverVersion: string
|
||||||
|
): Promise<{ isUpdate: boolean; isExists: boolean; version: string }> {
|
||||||
|
const [ok, latestVersion, error] = await GitService.GetLatestServerVersion()
|
||||||
|
const isExists = await FSService.FileExists(serverPath)
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
toast.error("Server error: " + error)
|
||||||
|
return { isUpdate: false, isExists, version: "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUpdate = latestVersion !== serverVersion
|
||||||
|
return { isUpdate, isExists, version: latestVersion }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function UpdateServer(serverVersion: string) : Promise<void> {
|
||||||
|
const {setDownloadType } = useLauncherStore.getState()
|
||||||
|
const {setServerPath, setServerVersion} = useSettingStore.getState()
|
||||||
|
setDownloadType("Downloading server...")
|
||||||
|
const [ok, error] = await GitService.DownloadServerProgress(serverVersion)
|
||||||
|
if (ok) {
|
||||||
|
setDownloadType("Unzipping server...")
|
||||||
|
GitService.UnzipServer()
|
||||||
|
setDownloadType("Download server successfully")
|
||||||
|
setServerVersion(serverVersion)
|
||||||
|
setServerPath("./server/firefly-go_win.exe")
|
||||||
|
} else {
|
||||||
|
toast.error(error)
|
||||||
|
setDownloadType("Download server failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
3
frontend/src/helper/sleep.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function sleep(ms: number) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// useGlobalEvents.ts
|
// useGlobalEvents.ts
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Events } from "@wailsio/runtime";
|
import { Events } from "@wailsio/runtime";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
export function useGlobalEvents({
|
export function useGlobalEvents({
|
||||||
setGameRunning,
|
setGameRunning,
|
||||||
@@ -51,6 +52,10 @@ export function useGlobalEvents({
|
|||||||
Events.On("proxy:exit", onProxyExit);
|
Events.On("proxy:exit", onProxyExit);
|
||||||
Events.On("hdiffz:progress", onUpdateProgress);
|
Events.On("hdiffz:progress", onUpdateProgress);
|
||||||
Events.On("hdiffz:message", onMessageUpdate);
|
Events.On("hdiffz:message", onMessageUpdate);
|
||||||
|
Events.On("hdiffz:error", (event: any) => {
|
||||||
|
const { message } = event.data[0];
|
||||||
|
toast.error(message);
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
Events.Off("download:server");
|
Events.Off("download:server");
|
||||||
@@ -60,6 +65,7 @@ export function useGlobalEvents({
|
|||||||
Events.Off("proxy:exit");
|
Events.Off("proxy:exit");
|
||||||
Events.Off("hdiffz:progress");
|
Events.Off("hdiffz:progress");
|
||||||
Events.Off("hdiffz:message");
|
Events.Off("hdiffz:message");
|
||||||
|
Events.Off("version:check");
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function AboutPage() {
|
|||||||
I created a lightweight and modern <span className="font-semibold text-success">Game Launcher</span> to help users easily launch and manage their games with better performance and simplicity.
|
I created a lightweight and modern <span className="font-semibold text-success">Game Launcher</span> to help users easily launch and manage their games with better performance and simplicity.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg leading-relaxed">
|
<p className="text-lg leading-relaxed">
|
||||||
The launcher is built using <span className="font-mono text-info">Go + Wails</span>, with a clean and responsive interface styled with <span className="text-warning">Tailwind CSS</span> and <span className="text-warning">DaisyUI</span>.
|
The launcher is built using <span className="font-mono text-info">Go + Wails3</span>, with a clean and responsive interface styled with <span className="text-warning">Tailwind CSS</span> and <span className="text-warning">DaisyUI</span>.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg leading-relaxed">
|
<p className="text-lg leading-relaxed">
|
||||||
My goal is to make tools that are fast, efficient, and enjoyable to use — and this launcher is just the beginning.
|
My goal is to make tools that are fast, efficient, and enjoyable to use — and this launcher is just the beginning.
|
||||||
|
|||||||
@@ -78,12 +78,12 @@ export default function AnalysisPage() {
|
|||||||
<span className="font-semibold text-blue-800">Backup Website</span>
|
<span className="font-semibold text-blue-800">Backup Website</span>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://sr-analysis.vercel.app/"
|
href="https://firefly-sranalysis.vercel.app/"
|
||||||
className="link link-warning font-mono text-sm break-all"
|
className="link link-warning font-mono text-sm break-all"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
https://sr-analysis.vercel.app/
|
https://firefly-sranalysis.vercel.app/
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -116,19 +116,32 @@ export default function HdiffzPage() {
|
|||||||
setIsDiffLoading(false)
|
setIsDiffLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStageType('Version Validate')
|
setStageType('Check Type HDiff')
|
||||||
setProgressUpdate(0)
|
setProgressUpdate(0)
|
||||||
setMaxProgressUpdate(1)
|
setMaxProgressUpdate(1)
|
||||||
const [validVersion, errorVersion] = await HdiffzService.VersionValidate(gameDir, diffDir)
|
const [isOk, validType, errorType] = await HdiffzService.CheckTypeHDiff(diffDir)
|
||||||
if (!validVersion) {
|
if (!isOk) {
|
||||||
toast.error(errorVersion)
|
toast.error(errorType)
|
||||||
setIsDiffLoading(false)
|
setIsDiffLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setProgressUpdate(1)
|
setProgressUpdate(1)
|
||||||
|
|
||||||
|
if (validType === 'hdiffmap.json') {
|
||||||
|
setStageType('Version Validate')
|
||||||
|
setProgressUpdate(0)
|
||||||
|
setMaxProgressUpdate(1)
|
||||||
|
const [validVersion, errorVersion] = await HdiffzService.VersionValidate(gameDir, diffDir)
|
||||||
|
if (!validVersion) {
|
||||||
|
toast.error(errorVersion)
|
||||||
|
setIsDiffLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setProgressUpdate(1)
|
||||||
|
}
|
||||||
|
|
||||||
setStageType('Data Extract')
|
setStageType('Data Extract')
|
||||||
const [validData, errorData] = await HdiffzService.DataExtract(gameDir, diffDir)
|
const [validData, errorData] = await HdiffzService.DataExtract(gameDir, diffDir, validType === 'hdifffiles.txt')
|
||||||
if (!validData) {
|
if (!validData) {
|
||||||
toast.error(errorData)
|
toast.error(errorData)
|
||||||
setIsDiffLoading(false)
|
setIsDiffLoading(false)
|
||||||
|
|||||||
@@ -27,28 +27,42 @@ export default function LanguagePage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getLanguage = async () => {
|
const getLanguage = async () => {
|
||||||
if (gameDir) {
|
if (!gameDir) return
|
||||||
const subPath = 'StarRail_Data/StreamingAssets/DesignData/Windows'
|
|
||||||
const fullPath = `${gameDir}/${subPath}`
|
|
||||||
|
|
||||||
const exists = await FSService.DirExists(fullPath)
|
const subPath = "StarRail_Data/StreamingAssets"
|
||||||
if (exists) {
|
const fullPath = `${gameDir}/${subPath}`
|
||||||
const [textLang, voiceLang] = await LanguageService.GetLanguage(fullPath)
|
|
||||||
setTextLang(textLang)
|
const exists = await FSService.DirExists(fullPath)
|
||||||
setVoiceLang(voiceLang)
|
if (!exists) {
|
||||||
setFolderCheckResult('success')
|
setTextLang("")
|
||||||
setSelectedTextLang(textLang)
|
setVoiceLang("")
|
||||||
setSelectedVoiceLang(voiceLang)
|
setSelectedTextLang("")
|
||||||
} else {
|
setSelectedVoiceLang("")
|
||||||
setTextLang('')
|
setFolderCheckResult("error")
|
||||||
setVoiceLang('')
|
setGameDir("")
|
||||||
setSelectedTextLang('')
|
return
|
||||||
setSelectedVoiceLang('')
|
|
||||||
setFolderCheckResult('error')
|
|
||||||
setGameDir('')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [ok, textLang, voiceLang, err] = await LanguageService.GetLanguage(fullPath)
|
||||||
|
if (!ok) {
|
||||||
|
setTextLang("")
|
||||||
|
setVoiceLang("")
|
||||||
|
setSelectedTextLang("")
|
||||||
|
setSelectedVoiceLang("")
|
||||||
|
setFolderCheckResult("error")
|
||||||
|
setGameDir("")
|
||||||
|
toast.error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// success
|
||||||
|
setTextLang(textLang)
|
||||||
|
setVoiceLang(voiceLang)
|
||||||
|
setFolderCheckResult("success")
|
||||||
|
setSelectedTextLang(textLang)
|
||||||
|
setSelectedVoiceLang(voiceLang)
|
||||||
}
|
}
|
||||||
|
|
||||||
getLanguage()
|
getLanguage()
|
||||||
}, [gameDir])
|
}, [gameDir])
|
||||||
|
|
||||||
@@ -86,19 +100,19 @@ export default function LanguagePage() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
setIsSettingLanguage(true)
|
setIsSettingLanguage(true)
|
||||||
const result = await LanguageService.SetLanguage(
|
const [ok, err] = await LanguageService.SetLanguage(
|
||||||
`${gameDir}/StarRail_Data/StreamingAssets/DesignData/Windows`,
|
`${gameDir}/StarRail_Data/StreamingAssets/DesignData/Windows`,
|
||||||
selectedTextLang,
|
selectedTextLang,
|
||||||
selectedVoiceLang
|
selectedVoiceLang
|
||||||
)
|
)
|
||||||
if (result) {
|
if (ok) {
|
||||||
toast.success('Language set successfully')
|
toast.success('Language set successfully')
|
||||||
setTextLang(selectedTextLang)
|
setTextLang(selectedTextLang)
|
||||||
setVoiceLang(selectedVoiceLang)
|
setVoiceLang(selectedVoiceLang)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
toast.error('Language set failed')
|
toast.error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast.error('SetLanguage error:', err)
|
toast.error('SetLanguage error:', err)
|
||||||
@@ -154,8 +168,8 @@ export default function LanguagePage() {
|
|||||||
</div>
|
</div>
|
||||||
{folderCheckResult && (
|
{folderCheckResult && (
|
||||||
<div className={`flex items-center gap-2 p-3 rounded-lg ${folderCheckResult === 'success'
|
<div className={`flex items-center gap-2 p-3 rounded-lg ${folderCheckResult === 'success'
|
||||||
? 'bg-success/5 text-success border border-success'
|
? 'bg-success/5 text-success border border-success'
|
||||||
: 'bg-error/5 text-error border border-error'
|
: 'bg-error/5 text-error border border-error'
|
||||||
}`}>
|
}`}>
|
||||||
{folderCheckResult === 'success' ? (
|
{folderCheckResult === 'success' ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Play, Menu, FolderOpen, MessageCircleQuestionMark } from 'lucide-react';
|
import { Play, Menu, FolderOpen, MessageCircleQuestionMark } from 'lucide-react';
|
||||||
import { FSService, GitService } from '@bindings/firefly-launcher/internal';
|
import { FSService, AppService } from '@bindings/firefly-launcher/internal';
|
||||||
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';
|
||||||
@@ -8,6 +8,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';
|
||||||
|
|
||||||
export default function LauncherPage() {
|
export default function LauncherPage() {
|
||||||
const { gamePath,
|
const { gamePath,
|
||||||
@@ -18,15 +19,17 @@ export default function LauncherPage() {
|
|||||||
gameDir,
|
gameDir,
|
||||||
serverVersion,
|
serverVersion,
|
||||||
proxyVersion,
|
proxyVersion,
|
||||||
setServerVersion,
|
|
||||||
setProxyVersion,
|
|
||||||
setServerPath,
|
|
||||||
setProxyPath
|
|
||||||
} = useSettingStore()
|
} = useSettingStore()
|
||||||
const { isOpenNotification, setIsOpenNotification } = useModalStore()
|
const {
|
||||||
|
isOpenDownloadDataModal,
|
||||||
|
isOpenUpdateDataModal,
|
||||||
|
isOpenSelfUpdateModal,
|
||||||
|
setIsOpenDownloadDataModal,
|
||||||
|
setIsOpenUpdateDataModal,
|
||||||
|
setIsOpenSelfUpdateModal
|
||||||
|
} = useModalStore()
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
modelName,
|
|
||||||
downloadType,
|
downloadType,
|
||||||
serverReady,
|
serverReady,
|
||||||
proxyReady,
|
proxyReady,
|
||||||
@@ -36,8 +39,10 @@ export default function LauncherPage() {
|
|||||||
gameRunning,
|
gameRunning,
|
||||||
progressDownload,
|
progressDownload,
|
||||||
downloadSpeed,
|
downloadSpeed,
|
||||||
|
updateData,
|
||||||
|
launcherVersion,
|
||||||
|
setLauncherVersion,
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
setModelName,
|
|
||||||
setDownloadType,
|
setDownloadType,
|
||||||
setServerReady,
|
setServerReady,
|
||||||
setProxyReady,
|
setProxyReady,
|
||||||
@@ -45,12 +50,12 @@ export default function LauncherPage() {
|
|||||||
setServerRunning,
|
setServerRunning,
|
||||||
setProxyRunning,
|
setProxyRunning,
|
||||||
setGameRunning,
|
setGameRunning,
|
||||||
|
setUpdateData,
|
||||||
} = useLauncherStore()
|
} = useLauncherStore()
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const check = async () => {
|
const check = async () => {
|
||||||
if (!serverPath || !proxyPath || !serverVersion || !proxyVersion) {
|
if (!serverVersion || !proxyVersion) {
|
||||||
setServerReady(false)
|
setServerReady(false)
|
||||||
setProxyReady(false)
|
setProxyReady(false)
|
||||||
return
|
return
|
||||||
@@ -67,30 +72,47 @@ export default function LauncherPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkStartUp = async (): Promise<void> => {
|
const checkStartUp = async (): Promise<void> => {
|
||||||
let isExists = false
|
const [_, version] = await AppService.GetCurrentLauncherVersion()
|
||||||
if (serverPath && proxyPath && serverVersion && proxyVersion) {
|
setLauncherVersion(version)
|
||||||
isExists = true
|
const launcherData = await CheckUpdateLauncher()
|
||||||
setServerReady(true)
|
if (launcherData.isUpdate) {
|
||||||
setProxyReady(true)
|
setUpdateData({
|
||||||
|
server: { isUpdate: false, isExists: false, version: "" },
|
||||||
|
proxy: { isUpdate: false, isExists: false, version: "" },
|
||||||
|
launcher: launcherData
|
||||||
|
})
|
||||||
|
setIsOpenSelfUpdateModal(true)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
const dataUpdate = await handlerCheckUpdate()
|
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
||||||
|
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
||||||
|
setUpdateData({
|
||||||
|
server: serverData,
|
||||||
|
proxy: proxyData,
|
||||||
|
launcher: launcherData
|
||||||
|
})
|
||||||
const exitGame = await FSService.FileExists(gamePath)
|
const exitGame = await FSService.FileExists(gamePath)
|
||||||
if (!exitGame) {
|
if (!exitGame) {
|
||||||
setGameRunning(false)
|
setGameRunning(false)
|
||||||
setGamePath("")
|
setGamePath("")
|
||||||
setGameDir("")
|
setGameDir("")
|
||||||
}
|
}
|
||||||
if (!dataUpdate.server.isUpdate && !dataUpdate.proxy.isUpdate) {
|
|
||||||
setServerReady(true)
|
if (!serverData.isExists || !proxyData.isExists) {
|
||||||
setProxyReady(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!isExists) {
|
|
||||||
setServerReady(false)
|
setServerReady(false)
|
||||||
setProxyReady(false)
|
setProxyReady(false)
|
||||||
|
setIsOpenDownloadDataModal(true)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
setModelName(!isExists ? "Download Data" : "Update Data")
|
|
||||||
setIsOpenNotification(true)
|
if (serverData.isUpdate || proxyData.isUpdate) {
|
||||||
|
setServerReady(true)
|
||||||
|
setProxyReady(true)
|
||||||
|
setIsOpenUpdateDataModal(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setServerReady(true)
|
||||||
|
setProxyReady(true)
|
||||||
}
|
}
|
||||||
checkStartUp()
|
checkStartUp()
|
||||||
}, []);
|
}, []);
|
||||||
@@ -121,9 +143,7 @@ export default function LauncherPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleStartGame = async () => {
|
const handleStartGame = async () => {
|
||||||
if (!gamePath) {
|
if (!gamePath) {
|
||||||
@@ -174,84 +194,42 @@ export default function LauncherPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlerCheckUpdate = async (): Promise<Record<string, { isUpdate: boolean, version: string }>> => {
|
|
||||||
let isUpdateServer = false
|
|
||||||
let isUpdateProxy = false
|
|
||||||
const [serverOk, serverNewVersion, serverError] = await GitService.GetLatestServerVersion(serverVersion)
|
|
||||||
const serverExists = await FSService.FileExists(serverPath)
|
|
||||||
if (serverOk) {
|
|
||||||
if (serverNewVersion !== serverVersion || !serverExists) {
|
|
||||||
isUpdateServer = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast.error("Server error: " + serverError)
|
|
||||||
}
|
|
||||||
const [proxyOk, proxyNewVersion, proxyError] = await GitService.GetLatestProxyVersion(proxyVersion)
|
|
||||||
const proxyExists = await FSService.FileExists(proxyPath)
|
|
||||||
if (proxyOk) {
|
|
||||||
if (proxyNewVersion !== proxyVersion || !proxyExists) {
|
|
||||||
isUpdateProxy = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast.error("Proxy error: " + proxyError)
|
|
||||||
}
|
|
||||||
return { server: { isUpdate: isUpdateServer, version: serverNewVersion }, proxy: { isUpdate: isUpdateProxy, version: proxyNewVersion } }
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlerUpdateServer = async (updateInfo: Record<string, { isUpdate: boolean, version: string }>) => {
|
const handlerUpdateData = async () => {
|
||||||
setIsDownloading(true)
|
setIsDownloading(true)
|
||||||
for (const [key, value] of Object.entries(updateInfo)) {
|
if (updateData.launcher.isUpdate) {
|
||||||
if (value.isUpdate) {
|
await UpdateLauncher(updateData.launcher.version)
|
||||||
if (key === "server") {
|
setUpdateData({...updateData, launcher: { isUpdate: false, isExists: true, version: updateData.launcher.version }})
|
||||||
setDownloadType("Downloading server...")
|
setIsOpenSelfUpdateModal(true)
|
||||||
const [ok, error] = await GitService.DownloadServerProgress(value.version)
|
|
||||||
if (ok) {
|
|
||||||
setDownloadType("Unzipping server...")
|
|
||||||
GitService.UnzipServer()
|
|
||||||
setDownloadType("Download server successfully")
|
|
||||||
setServerVersion(value.version)
|
|
||||||
|
|
||||||
setServerPath("./server/firefly-go_win.exe")
|
|
||||||
} else {
|
|
||||||
toast.error(error)
|
|
||||||
setDownloadType("Download server failed")
|
|
||||||
}
|
|
||||||
} else if (key === "proxy") {
|
|
||||||
setDownloadType("Downloading proxy...")
|
|
||||||
const [ok, error] = await GitService.DownloadProxyProgress(value.version)
|
|
||||||
if (ok) {
|
|
||||||
setDownloadType("Unzipping proxy...")
|
|
||||||
GitService.UnzipProxy()
|
|
||||||
setDownloadType("Download proxy successfully")
|
|
||||||
setProxyVersion(value.version)
|
|
||||||
setProxyPath("./proxy/FireflyProxy.exe")
|
|
||||||
|
|
||||||
} else {
|
|
||||||
toast.error(error)
|
|
||||||
setDownloadType("Download proxy failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (updateData.server.isUpdate || !updateData.server.isExists) {
|
||||||
|
await UpdateServer(updateData.server.version)
|
||||||
|
setServerReady(true)
|
||||||
|
setUpdateData({...updateData, server: { isUpdate: false, isExists: true, version: updateData.server.version }})
|
||||||
|
}
|
||||||
|
if (updateData.proxy.isUpdate || !updateData.proxy.isExists) {
|
||||||
|
await UpdateProxy(updateData.proxy.version)
|
||||||
|
setProxyReady(true)
|
||||||
|
setUpdateData({...updateData, proxy: { isUpdate: false, isExists: true, version: updateData.proxy.version }})
|
||||||
|
}
|
||||||
|
|
||||||
setDownloadType("")
|
setDownloadType("")
|
||||||
setIsDownloading(false)
|
setIsDownloading(false)
|
||||||
setServerReady(true)
|
|
||||||
setProxyReady(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Handle ESC key to close modal
|
// Handle ESC key to close modal
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscKey = (event: KeyboardEvent) => {
|
const handleEscKey = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
setIsOpenNotification(false);
|
setIsOpenDownloadDataModal(false);
|
||||||
|
setIsOpenUpdateDataModal(false);
|
||||||
|
setIsOpenSelfUpdateModal(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', handleEscKey);
|
window.addEventListener('keydown', handleEscKey);
|
||||||
return () => window.removeEventListener('keydown', handleEscKey);
|
return () => window.removeEventListener('keydown', handleEscKey);
|
||||||
}, [isOpenNotification]);
|
}, [isOpenDownloadDataModal, isOpenUpdateDataModal, isOpenSelfUpdateModal]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -280,11 +258,10 @@ export default function LauncherPage() {
|
|||||||
href="https://sranalysis.kain.id.vn/"
|
href="https://sranalysis.kain.id.vn/"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="https://icons.duckduckgo.com/ip3/sranalysis.kain.id.vn.ico"
|
src="https://sranalysis.kain.id.vn/ff-sranalysis.png"
|
||||||
alt="SRAnalysis Logo"
|
alt="SRAnalysis Logo"
|
||||||
className="w-8 h-8 rounded-full"
|
className="w-8 h-8 rounded-full"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -295,7 +272,7 @@ export default function LauncherPage() {
|
|||||||
href="https://srtools.kain.id.vn/"
|
href="https://srtools.kain.id.vn/"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="https://icons.duckduckgo.com/ip3/srtools.kain.id.vn.ico"
|
src="https://srtools.kain.id.vn/ff-srtool.png"
|
||||||
alt="SRTools Logo"
|
alt="SRTools Logo"
|
||||||
className="w-8 h-8 rounded-full"
|
className="w-8 h-8 rounded-full"
|
||||||
/>
|
/>
|
||||||
@@ -369,13 +346,27 @@ export default function LauncherPage() {
|
|||||||
<li><button onClick={handlePickFile}>Change Game Path</button></li>
|
<li><button onClick={handlePickFile}>Change Game Path</button></li>
|
||||||
<li><button
|
<li><button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const updateInfo = await handlerCheckUpdate()
|
const serverData = await CheckUpdateServer(serverPath, serverVersion)
|
||||||
if (updateInfo.server.isUpdate || updateInfo.proxy.isUpdate) {
|
const proxyData = await CheckUpdateProxy(proxyPath, proxyVersion)
|
||||||
setModelName("Update Data")
|
const launcherData = await CheckUpdateLauncher()
|
||||||
setIsOpenNotification(true)
|
setUpdateData({
|
||||||
} else {
|
server: serverData,
|
||||||
toast.success("No updates available")
|
proxy: proxyData,
|
||||||
|
launcher: launcherData
|
||||||
|
})
|
||||||
|
if (launcherData.isUpdate) {
|
||||||
|
setIsOpenSelfUpdateModal(true)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if (!serverData.isExists || !proxyData.isExists) {
|
||||||
|
setIsOpenDownloadDataModal(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (serverData.isUpdate || proxyData.isUpdate) {
|
||||||
|
setIsOpenUpdateDataModal(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toast.success("No updates available")
|
||||||
}}>
|
}}>
|
||||||
Check for Updates
|
Check for Updates
|
||||||
</button></li>
|
</button></li>
|
||||||
@@ -403,6 +394,11 @@ export default function LauncherPage() {
|
|||||||
|
|
||||||
{/* Downloading */}
|
{/* Downloading */}
|
||||||
{isDownloading && (
|
{isDownloading && (
|
||||||
|
updateData.proxy.isUpdate
|
||||||
|
|| updateData.server.isUpdate
|
||||||
|
|| !updateData.proxy.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/20 backdrop-blur-sm rounded-lg p-4 shadow-lg">
|
||||||
<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">
|
||||||
@@ -426,64 +422,185 @@ export default function LauncherPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{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="space-y-3 text-sm text-white/80 text-center">
|
||||||
|
{["update:launcher:downloading", "update:launcher:success", "update:launcher:failed"].includes(downloadType) && (
|
||||||
|
<div className="flex justify-center items-center gap-4 ml-4">
|
||||||
|
<span
|
||||||
|
className={`font-bold ${downloadType === "update:launcher:downloading"
|
||||||
|
? "text-yellow-200 text-2xl"
|
||||||
|
: downloadType === "update:launcher:success"
|
||||||
|
? "text-emerald-200 text-xl"
|
||||||
|
: "text-red-200 text-xl"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{downloadType === "update:launcher:downloading" && "Updating launcher"}
|
||||||
|
{downloadType === "update:launcher:success" && "Launcher updated successfully, auto closing after 5s"}
|
||||||
|
{downloadType === "update:launcher:failed" && "Launcher update failed, auto closing after 5s"}
|
||||||
|
<span className="dot-animation ml-1"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="text-xs text-white/60">
|
||||||
|
{progressDownload < 100 ? "Please wait..." : "Complete!"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
.dot-animation::after {
|
||||||
|
content: '';
|
||||||
|
animation: dots 1.2s steps(4, end) infinite;
|
||||||
|
}
|
||||||
|
@keyframes dots {
|
||||||
|
0% { content: ''; }
|
||||||
|
25% { content: '.'; }
|
||||||
|
50% { content: '..'; }
|
||||||
|
75% { content: '...'; }
|
||||||
|
100% { content: ''; }
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Version Info */}
|
{/* Version Info */}
|
||||||
{serverReady && proxyReady && !isDownloading && (
|
{serverReady && proxyReady && !isDownloading && (
|
||||||
<div className="hidden md:block fixed bottom-4 left-4 z-10 text-sm font-bold bg-black/20 backdrop-blur-sm rounded-lg p-2 shadow">
|
<div className="hidden md:block fixed bottom-4 left-4 z-10 text-sm font-bold bg-black/20 backdrop-blur-sm rounded-lg p-2 shadow">
|
||||||
<p className="text-primary">Version server: {serverVersion}</p>
|
<p className="text-primary">Version server: {serverVersion}</p>
|
||||||
<p className="mt-2 text-secondary">Version proxy: {proxyVersion}</p>
|
<p className="mt-2 text-secondary">Version proxy: {proxyVersion}</p>
|
||||||
|
<p className="mt-2 text-success">Version launcher: {launcherVersion}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{isOpenNotification && (
|
{isOpenUpdateDataModal && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||||
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||||
{modelName !== "Download Data" && (
|
<motion.button
|
||||||
<motion.button
|
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||||
whileHover={{ scale: 1.1, rotate: 90 }}
|
transition={{ duration: 0.2 }}
|
||||||
transition={{ duration: 0.2 }}
|
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||||
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
onClick={() => setIsOpenUpdateDataModal(false)}
|
||||||
onClick={() => setIsOpenNotification(false)}
|
>
|
||||||
>
|
✕
|
||||||
✕
|
</motion.button>
|
||||||
</motion.button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||||
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
||||||
{modelName}
|
Update Data
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-6 pb-6">
|
<div className="px-6 pb-6">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<p className="text-warning text-lg">
|
<p className="text-warning text-lg">
|
||||||
{modelName === "Download Data"
|
Do you want to update data server and proxy?
|
||||||
? "Download required data!"
|
|
||||||
: "Do you want to update data?"}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-3">
|
<div className="flex justify-end gap-3">
|
||||||
{modelName !== "Download Data" && (
|
<motion.button
|
||||||
<motion.button
|
whileHover={{ scale: 1.05 }}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
className="btn btn-outline btn-error"
|
||||||
className="btn btn-outline btn-error"
|
onClick={() => setIsOpenUpdateDataModal(false)}
|
||||||
onClick={() => setIsOpenNotification(false)}
|
>
|
||||||
>
|
No
|
||||||
Cancel
|
</motion.button>
|
||||||
</motion.button>
|
|
||||||
)}
|
|
||||||
<motion.button
|
<motion.button
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setIsOpenNotification(false)
|
setIsOpenUpdateDataModal(false)
|
||||||
const dataCheck = await handlerCheckUpdate()
|
await handlerUpdateData()
|
||||||
await handlerUpdateServer(dataCheck)
|
}}
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isOpenDownloadDataModal && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||||
|
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||||
|
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||||
|
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
||||||
|
Download Data
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-6 pb-6">
|
||||||
|
<div className="mb-6">
|
||||||
|
<p className="text-warning text-lg">
|
||||||
|
Data server and proxy download required
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||||
|
onClick={async () => {
|
||||||
|
setIsOpenDownloadDataModal(false)
|
||||||
|
await handlerUpdateData()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isOpenSelfUpdateModal && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||||
|
<div className="relative w-[90%] max-w-5xl bg-base-100 text-base-content rounded-xl border border-purple-500/50 shadow-lg shadow-purple-500/20">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.1, rotate: 90 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="btn btn-circle btn-md btn-error absolute right-3 top-3"
|
||||||
|
onClick={() => setIsOpenSelfUpdateModal(false)}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<div className="border-b border-purple-500/30 px-6 py-4 mb-4">
|
||||||
|
<h3 className="font-bold text-2xl text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-cyan-400">
|
||||||
|
Update Launcher
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="px-6 pb-6">
|
||||||
|
<div className="mb-6">
|
||||||
|
<p className="text-warning text-lg">
|
||||||
|
Do you want to update launcher?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-3">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
className="btn btn-outline btn-error"
|
||||||
|
onClick={() => setIsOpenSelfUpdateModal(false)}
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</motion.button>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
className="btn btn-primary bg-gradient-to-r from-orange-200 to-red-400 border-none"
|
||||||
|
onClick={async () => {
|
||||||
|
setIsOpenSelfUpdateModal(false)
|
||||||
|
await handlerUpdateData()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Yes
|
Yes
|
||||||
|
|||||||
@@ -15,17 +15,44 @@ export default function SrToolsPage() {
|
|||||||
<div className="space-y-3 text-blue-700">
|
<div className="space-y-3 text-blue-700">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-blue-600 text-lg">🏠</div>
|
<div className="text-blue-600 text-lg">🏠</div>
|
||||||
<p>This site uses a self-hosted version of <span className="font-semibold text-success">SR Tools</span> available at{" "}
|
<p>
|
||||||
<a
|
This site is a another version of {" "}
|
||||||
href="https://srtools.kain.id.vn/"
|
<span className="font-semibold text-success">SR Tools {" "}</span>
|
||||||
className="link link-info"
|
developed by {" "}
|
||||||
target="_blank"
|
<span className="font-semibold text-warning">Me {"(Kain)"}</span>
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
srtools.kain.id.vn
|
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<span className="text-blue-600 text-lg">🏆</span>
|
||||||
|
<span className="font-semibold text-blue-800">Master Website</span>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="https://srtools.kain.id.vn/"
|
||||||
|
className="link link-warning font-mono text-sm break-all"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
https://srtools.kain.id.vn/
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<span className="text-blue-600 text-lg">🔄</span>
|
||||||
|
<span className="font-semibold text-blue-800">Backup Website</span>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
href="https://firefly-srtools.vercel.app/"
|
||||||
|
className="link link-warning font-mono text-sm break-all"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
https://firefly-srtools.vercel.app/
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-blue-600 text-lg">👨💻</div>
|
<div className="text-blue-600 text-lg">👨💻</div>
|
||||||
<p>The original tool was created by a third-party developer named <span className="font-semibold text-warning">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
|
<p>The original tool was created by a third-party developer named <span className="font-semibold text-warning">Amazing</span>. This version is directly based on that work, without modification to core logic.</p>
|
||||||
|
|||||||
@@ -55,13 +55,18 @@ function RootLayout() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
|
<Link to="/" className="grid grid-cols-1 items-start text-left gap-0 hover:scale-105 px-2">
|
||||||
<h1 className="text-lg font-bold">
|
<div className="flex items-center justify-center">
|
||||||
<span className="text-emerald-500">Firefly Lauc</span>
|
<img src="/ff-launcher.png" alt="Logo" className='w-13 h-13' />
|
||||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
|
<div className="flex flex-col justify-center items-start">
|
||||||
her
|
<h1 className="text-xl font-bold">
|
||||||
</span>
|
<span className="text-emerald-500">Firefly </span>
|
||||||
</h1>
|
<span className="bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-orange-500 to-red-500">
|
||||||
<p className="text-sm text-gray-500">By Kain</p>
|
Launcher
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-gray-500">By Kain</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="navbar-center hidden md:flex">
|
<div className="navbar-center hidden md:flex">
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
|
|
||||||
interface LauncherState {
|
interface LauncherState {
|
||||||
modelName: string;
|
|
||||||
downloadType: string;
|
downloadType: string;
|
||||||
serverReady: boolean;
|
serverReady: boolean;
|
||||||
proxyReady: boolean;
|
proxyReady: boolean;
|
||||||
@@ -13,7 +12,8 @@ interface LauncherState {
|
|||||||
gameRunning: boolean;
|
gameRunning: boolean;
|
||||||
progressDownload: number;
|
progressDownload: number;
|
||||||
downloadSpeed: number;
|
downloadSpeed: number;
|
||||||
setModelName: (value: string) => void;
|
launcherVersion: string;
|
||||||
|
updateData: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>;
|
||||||
setDownloadType: (value: string) => void;
|
setDownloadType: (value: string) => void;
|
||||||
setServerReady: (value: boolean) => void;
|
setServerReady: (value: boolean) => void;
|
||||||
setProxyReady: (value: boolean) => void;
|
setProxyReady: (value: boolean) => void;
|
||||||
@@ -23,12 +23,13 @@ interface LauncherState {
|
|||||||
setIsLoading: (value: boolean) => void;
|
setIsLoading: (value: boolean) => void;
|
||||||
setGameRunning: (value: boolean) => void;
|
setGameRunning: (value: boolean) => void;
|
||||||
setProgressDownload: (value: number) => void;
|
setProgressDownload: (value: number) => void;
|
||||||
|
setLauncherVersion: (value: string) => void;
|
||||||
setDownloadSpeed: (value: number) => void;
|
setDownloadSpeed: (value: number) => void;
|
||||||
|
setUpdateData: (value: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useLauncherStore = create<LauncherState>((set, get) => ({
|
const useLauncherStore = create<LauncherState>((set, get) => ({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
modelName: "Download Data",
|
|
||||||
downloadType: "",
|
downloadType: "",
|
||||||
serverReady: false,
|
serverReady: false,
|
||||||
proxyReady: false,
|
proxyReady: false,
|
||||||
@@ -38,8 +39,13 @@ const useLauncherStore = create<LauncherState>((set, get) => ({
|
|||||||
gameRunning: false,
|
gameRunning: false,
|
||||||
progressDownload: 0,
|
progressDownload: 0,
|
||||||
downloadSpeed: 0,
|
downloadSpeed: 0,
|
||||||
|
launcherVersion: "",
|
||||||
|
updateData: {
|
||||||
|
server: { isUpdate: false, isExists: false, version: "" },
|
||||||
|
proxy: { isUpdate: false, isExists: false, version: "" },
|
||||||
|
launcher: { isUpdate: false, isExists: true, version: "" },
|
||||||
|
},
|
||||||
setIsLoading: (value: boolean) => set({ isLoading: value }),
|
setIsLoading: (value: boolean) => set({ isLoading: value }),
|
||||||
setModelName: (value: string) => set({ modelName: value }),
|
|
||||||
setDownloadType: (value: string) => set({ downloadType: value }),
|
setDownloadType: (value: string) => set({ downloadType: value }),
|
||||||
setServerReady: (value: boolean) => set({ serverReady: value }),
|
setServerReady: (value: boolean) => set({ serverReady: value }),
|
||||||
setProxyReady: (value: boolean) => set({ proxyReady: value }),
|
setProxyReady: (value: boolean) => set({ proxyReady: value }),
|
||||||
@@ -48,7 +54,9 @@ const useLauncherStore = create<LauncherState>((set, get) => ({
|
|||||||
setProxyRunning: (value: boolean) => set({ proxyRunning: value }),
|
setProxyRunning: (value: boolean) => set({ proxyRunning: value }),
|
||||||
setGameRunning: (value: boolean) => set({ gameRunning: value }),
|
setGameRunning: (value: boolean) => set({ gameRunning: value }),
|
||||||
setProgressDownload: (value: number) => set({ progressDownload: value }),
|
setProgressDownload: (value: number) => set({ progressDownload: value }),
|
||||||
|
setLauncherVersion: (value: string) => set({ launcherVersion: value }),
|
||||||
setDownloadSpeed: (value: number) => set({ downloadSpeed: value }),
|
setDownloadSpeed: (value: number) => set({ downloadSpeed: value }),
|
||||||
|
setUpdateData: (value: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>) => set({ updateData: value }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useLauncherStore;
|
export default useLauncherStore;
|
||||||
@@ -2,13 +2,21 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
|
|
||||||
interface ModalState {
|
interface ModalState {
|
||||||
isOpenNotification: boolean;
|
isOpenDownloadDataModal: boolean;
|
||||||
setIsOpenNotification: (modal: boolean) => void;
|
isOpenUpdateDataModal: boolean;
|
||||||
|
isOpenSelfUpdateModal: boolean;
|
||||||
|
setIsOpenDownloadDataModal: (modal: boolean) => void;
|
||||||
|
setIsOpenUpdateDataModal: (modal: boolean) => void;
|
||||||
|
setIsOpenSelfUpdateModal: (modal: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useModalStore = create<ModalState>((set, get) => ({
|
const useModalStore = create<ModalState>((set, get) => ({
|
||||||
isOpenNotification: false,
|
isOpenDownloadDataModal: false,
|
||||||
setIsOpenNotification: (modal: boolean) => set({ isOpenNotification: modal }),
|
isOpenUpdateDataModal: false,
|
||||||
|
isOpenSelfUpdateModal: false,
|
||||||
|
setIsOpenDownloadDataModal: (modal: boolean) => set({ isOpenDownloadDataModal: modal }),
|
||||||
|
setIsOpenUpdateDataModal: (modal: boolean) => set({ isOpenUpdateDataModal: modal }),
|
||||||
|
setIsOpenSelfUpdateModal: (modal: boolean) => set({ isOpenSelfUpdateModal: modal }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useModalStore;
|
export default useModalStore;
|
||||||
68
go.mod
@@ -1,54 +1,56 @@
|
|||||||
module firefly-launcher
|
module firefly-launcher
|
||||||
|
|
||||||
go 1.22.4
|
go 1.25
|
||||||
|
|
||||||
toolchain go1.24.4
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.26
|
||||||
golang.org/x/sys v0.28.0
|
golang.org/x/sys v0.35.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.1 // indirect
|
aead.dev/minisign v0.3.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
github.com/adrg/xdg v0.5.0 // indirect
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
|
github.com/adrg/xdg v0.5.3 // indirect
|
||||||
github.com/bep/debounce v1.2.1 // indirect
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
github.com/cloudflare/circl v1.3.8 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
github.com/ebitengine/purego v0.4.0-alpha.4 // indirect
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.0 // indirect
|
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.12.0 // indirect
|
github.com/go-git/go-git/v5 v5.16.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/google/uuid v1.4.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||||
github.com/leaanthony/u v1.1.0 // indirect
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
github.com/lmittmann/tint v1.0.4 // indirect
|
github.com/lmittmann/tint v1.1.2 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/minio/selfupdate v0.6.0
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
github.com/pjbgf/sha1cd v0.4.0 // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/samber/lo v1.38.1 // indirect
|
github.com/samber/lo v1.51.0 // indirect
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||||
github.com/wailsapp/go-webview2 v1.0.19 // indirect
|
github.com/wailsapp/go-webview2 v1.0.21 // indirect
|
||||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||||
golang.org/x/mod v0.19.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
|
||||||
golang.org/x/sync v0.9.0 // indirect
|
|
||||||
golang.org/x/tools v0.23.0 // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
193
go.sum
@@ -1,59 +1,60 @@
|
|||||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
|
||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
aead.dev/minisign v0.3.0 h1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=
|
||||||
|
aead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y=
|
||||||
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||||
github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
|
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||||
github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
|
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
|
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||||
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/ebitengine/purego v0.4.0-alpha.4 h1:Y7yIV06Yo5M2BAdD7EVPhfp6LZ0tEcQo5770OhYUVes=
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
github.com/ebitengine/purego v0.4.0-alpha.4/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
||||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
||||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
@@ -63,23 +64,25 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||||
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
|
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||||
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||||
github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
|
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
||||||
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||||
|
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@@ -87,99 +90,63 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
||||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
|
github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA=
|
||||||
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9 h1:b8CfRrhPno8Fra0xFp4Ifyj+ogmXBc35rsQWvcrHtsI=
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.26 h1:co3eifrIcG/J7sQ1oMPmLfvtAE4bezdhOtabI4Ua5fg=
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.9/go.mod h1:dSv6s722nSWaUyUiapAM1DHc5HKggNGY1a79shO85/g=
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.26/go.mod h1:UZpnhYuju4saspCJrIHAvC0H5XjtKnqd26FRxJLrQ0M=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
|
||||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
|
||||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
|
||||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
|
||||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
|
||||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
|
||||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|||||||
22
internal/app-serrvice.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"firefly-launcher/pkg/constant"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppService struct{}
|
||||||
|
|
||||||
|
func (a *AppService) GetCurrentLauncherVersion() (bool, string) {
|
||||||
|
return true, constant.CurrentLauncherVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppService) CloseAppAfterTimeout(timeout int) (bool, string) {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Duration(timeout) * time.Second)
|
||||||
|
application.Get().Quit()
|
||||||
|
}()
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
@@ -40,6 +39,7 @@ func (f *FSService) PickFile() (string, error) {
|
|||||||
} else {
|
} else {
|
||||||
dialog.SetTitle("Select a file/directory")
|
dialog.SetTitle("Select a file/directory")
|
||||||
}
|
}
|
||||||
|
dialog.AddFilter("Executable Files (*.exe)", "*.exe")
|
||||||
if path, err := dialog.PromptForSingleSelection(); err == nil {
|
if path, err := dialog.PromptForSingleSelection(); err == nil {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ func (f *FSService) StartApp(path string) (bool, error) {
|
|||||||
if strings.HasSuffix(path, "StarRail.exe") {
|
if strings.HasSuffix(path, "StarRail.exe") {
|
||||||
go func() {
|
go func() {
|
||||||
_ = cmd.Wait()
|
_ = cmd.Wait()
|
||||||
application.Get().EmitEvent("game:exit")
|
application.Get().Event.Emit("game:exit")
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,11 +107,11 @@ func (f *FSService) StartWithConsole(path string) (bool, error) {
|
|||||||
go func() {
|
go func() {
|
||||||
_ = cmd.Wait()
|
_ = cmd.Wait()
|
||||||
if strings.HasSuffix(path, "launcher.exe") {
|
if strings.HasSuffix(path, "launcher.exe") {
|
||||||
application.Get().EmitEvent("game:exit")
|
application.Get().Event.Emit("game:exit")
|
||||||
} else if strings.HasSuffix(path, "firefly-go_win.exe") {
|
} else if strings.HasSuffix(path, "firefly-go_win.exe") {
|
||||||
application.Get().EmitEvent("server:exit")
|
application.Get().Event.Emit("server:exit")
|
||||||
} else if strings.HasSuffix(path, "FireflyProxy.exe") {
|
} else if strings.HasSuffix(path, "FireflyProxy.exe") {
|
||||||
application.Get().EmitEvent("proxy:exit")
|
application.Get().Event.Emit("proxy:exit")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return true, nil
|
return true, nil
|
||||||
@@ -128,7 +128,7 @@ func (f *FSService) OpenFolder(path string) (bool, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
url := "file:///" + filepath.ToSlash(absPath)
|
url := "file:///" + filepath.ToSlash(absPath)
|
||||||
application.Get().BrowserOpenURL(url)
|
application.Get().Browser.OpenURL(url)
|
||||||
|
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
@@ -140,3 +140,4 @@ func (f *FSService) FileExistsInZip(archivePath, fileInside string) (bool, strin
|
|||||||
}
|
}
|
||||||
return exists, ""
|
return exists, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/minio/selfupdate"
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitService struct{}
|
type GitService struct{}
|
||||||
|
|
||||||
func (g *GitService) GetLatestServerVersion(oldVersion string) (bool, string, string) {
|
func (g *GitService) GetLatestServerVersion() (bool, string, string) {
|
||||||
resp, err := http.Get(constant.ServerGitUrl)
|
resp, err := http.Get(constant.ServerGitUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err.Error()
|
return false, "", err.Error()
|
||||||
@@ -38,7 +39,6 @@ func (g *GitService) GetLatestServerVersion(oldVersion string) (bool, string, st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitService) DownloadServerProgress(version string) (bool, string) {
|
func (g *GitService) DownloadServerProgress(version string) (bool, string) {
|
||||||
|
|
||||||
resp, err := http.Get(constant.ServerGitUrl)
|
resp, err := http.Get(constant.ServerGitUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -96,7 +96,7 @@ func (g *GitService) DownloadServerProgress(version string) (bool, string) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
DownloadFile(saveFile, assetWin.BrowserDownloadURL, func(percent float64, speed float64) {
|
DownloadFile(saveFile, assetWin.BrowserDownloadURL, func(percent float64, speed float64) {
|
||||||
application.Get().EmitEvent("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": fmt.Sprintf("%.2f", speed),
|
"speed": fmt.Sprintf("%.2f", speed),
|
||||||
})
|
})
|
||||||
@@ -109,7 +109,7 @@ func (g *GitService) UnzipServer() {
|
|||||||
os.Remove(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile))
|
os.Remove(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitService) GetLatestProxyVersion(oldVersion string) (bool, string, string) {
|
func (g *GitService) GetLatestProxyVersion() (bool, string, string) {
|
||||||
resp, err := http.Get(constant.ProxyGitUrl)
|
resp, err := http.Get(constant.ProxyGitUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err.Error()
|
return false, "", err.Error()
|
||||||
@@ -188,7 +188,7 @@ func (g *GitService) DownloadProxyProgress(version string) (bool, string) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
DownloadFile(saveFile, assetWin.BrowserDownloadURL, func(percent float64, speed float64) {
|
DownloadFile(saveFile, assetWin.BrowserDownloadURL, func(percent float64, speed float64) {
|
||||||
application.Get().EmitEvent("download:proxy", map[string]interface{}{
|
application.Get().Event.Emit("download:proxy", map[string]interface{}{
|
||||||
"percent": fmt.Sprintf("%.2f", percent),
|
"percent": fmt.Sprintf("%.2f", percent),
|
||||||
"speed": fmt.Sprintf("%.2f", speed),
|
"speed": fmt.Sprintf("%.2f", speed),
|
||||||
})
|
})
|
||||||
@@ -201,3 +201,82 @@ func (g *GitService) UnzipProxy() {
|
|||||||
unzipParallel(filepath.Join(constant.ProxyStorageUrl, constant.ProxyZipFile), constant.ProxyStorageUrl)
|
unzipParallel(filepath.Join(constant.ProxyStorageUrl, constant.ProxyZipFile), constant.ProxyStorageUrl)
|
||||||
os.Remove(filepath.Join(constant.ProxyStorageUrl, constant.ProxyZipFile))
|
os.Remove(filepath.Join(constant.ProxyStorageUrl, constant.ProxyZipFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GitService) GetLatestLauncherVersion() (bool, string, string) {
|
||||||
|
resp, err := http.Get(constant.LauncherGitUrl)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", err.Error()
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
var releases []models.ReleaseType
|
||||||
|
err = json.Unmarshal(body, &releases)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(releases) == 0 {
|
||||||
|
return false, "", "no releases found"
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, releases[0].TagName, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func (g *GitService) UpdateLauncherProgress(version string) (bool, string) {
|
||||||
|
resp, err := http.Get(constant.LauncherGitUrl)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
var releases []*models.ReleaseType
|
||||||
|
err = json.Unmarshal(body, &releases)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(releases) == 0 {
|
||||||
|
return false, "no releases found"
|
||||||
|
}
|
||||||
|
|
||||||
|
var releaseData *models.ReleaseType
|
||||||
|
for _, release := range releases {
|
||||||
|
if release.TagName == version {
|
||||||
|
releaseData = release
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if releaseData == nil || releaseData.TagName == "" {
|
||||||
|
return false, "no release found"
|
||||||
|
}
|
||||||
|
|
||||||
|
var assetWin models.AssetType
|
||||||
|
for _, asset := range releaseData.Assets {
|
||||||
|
if asset.Name == constant.LauncherFile {
|
||||||
|
assetWin = asset
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if assetWin.Name == "" {
|
||||||
|
return false, "no assets found"
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = http.Get(assetWin.BrowserDownloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
err = selfupdate.Apply(resp.Body, selfupdate.Options{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,19 +7,30 @@ import (
|
|||||||
"firefly-launcher/pkg/models"
|
"firefly-launcher/pkg/models"
|
||||||
"firefly-launcher/pkg/sevenzip"
|
"firefly-launcher/pkg/sevenzip"
|
||||||
"firefly-launcher/pkg/verifier"
|
"firefly-launcher/pkg/verifier"
|
||||||
|
"firefly-launcher/pkg/hpatchz"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HdiffzService struct{}
|
type HdiffzService struct{}
|
||||||
|
|
||||||
|
func (h *HdiffzService) CheckTypeHDiff(patchPath string) (bool, string, string) {
|
||||||
|
isFileInTxt, _ := sevenzip.IsFileIn7z(patchPath, "hdifffiles.txt")
|
||||||
|
if isFileInTxt {
|
||||||
|
return true, "hdifffiles.txt", ""
|
||||||
|
}
|
||||||
|
isFileInJson, _ := sevenzip.IsFileIn7z(patchPath, "hdiffmap.json")
|
||||||
|
if isFileInJson {
|
||||||
|
return true, "hdiffmap.json", ""
|
||||||
|
}
|
||||||
|
return false, "", "not found hdifffiles.txt or hdiffmap.json"
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HdiffzService) VersionValidate(gamePath, patchPath string) (bool, string) {
|
func (h *HdiffzService) VersionValidate(gamePath, patchPath string) (bool, string) {
|
||||||
oldVersionData, err := models.ParseBinaryVersion(filepath.Join(gamePath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes"))
|
oldVersionData, err := models.ParseBinaryVersion(filepath.Join(gamePath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -53,7 +64,8 @@ func (h *HdiffzService) VersionValidate(gamePath, patchPath string) (bool, strin
|
|||||||
|
|
||||||
return true, "validated"
|
return true, "validated"
|
||||||
}
|
}
|
||||||
func (h *HdiffzService) DataExtract(gamePath, patchPath string) (bool, string) {
|
|
||||||
|
func (h *HdiffzService) DataExtract(gamePath, patchPath string, isSkipVerify bool) (bool, string) {
|
||||||
if _, err := os.Stat(gamePath); err != nil {
|
if _, err := os.Stat(gamePath); err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
@@ -73,15 +85,17 @@ func (h *HdiffzService) DataExtract(gamePath, patchPath string) (bool, string) {
|
|||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
validator, err := verifier.NewVerifier(gamePath, constant.TempUrl)
|
if !isSkipVerify {
|
||||||
if err != nil {
|
validator, err := verifier.NewVerifier(gamePath, constant.TempUrl)
|
||||||
os.RemoveAll(constant.TempUrl)
|
if err != nil {
|
||||||
return false, err.Error()
|
os.RemoveAll(constant.TempUrl)
|
||||||
}
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
if err := validator.VerifyAll(); err != nil {
|
if err := validator.VerifyAll(); err != nil {
|
||||||
os.RemoveAll(constant.TempUrl)
|
os.RemoveAll(constant.TempUrl)
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, "validated"
|
return true, "validated"
|
||||||
@@ -102,7 +116,7 @@ func (h *HdiffzService) CutData(gamePath string) (bool, string) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
destPath := filepath.Join(gamePath, relPath)
|
destPath := filepath.Join(gamePath, relPath)
|
||||||
application.Get().EmitEvent("hdiffz:message", map[string]string{"message": destPath})
|
application.Get().Event.Emit("hdiffz:message", map[string]string{"message": destPath})
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return os.MkdirAll(destPath, os.ModePerm)
|
return os.MkdirAll(destPath, os.ModePerm)
|
||||||
}
|
}
|
||||||
@@ -136,49 +150,92 @@ func (h *HdiffzService) CutData(gamePath string) (bool, string) {
|
|||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = os.RemoveAll(constant.TempUrl)
|
|
||||||
|
|
||||||
return true, "cut completed"
|
return true, "cut completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HdiffzService) PatchData(gamePath string) (bool, string) {
|
func (h *HdiffzService) PatchData(gamePath string) (bool, string) {
|
||||||
data, err := os.ReadFile(filepath.Join(gamePath, "hdiffmap.json"))
|
hdiffMapPath := filepath.Join(gamePath, "hdiffmap.json")
|
||||||
if err != nil {
|
hdiffFilesPath := filepath.Join(gamePath, "hdifffiles.txt")
|
||||||
return false, err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
var jsonData struct {
|
var jsonData struct {
|
||||||
DiffMap []*models.DiffMapType `json:"diff_map"`
|
DiffMap []*models.HDiffData `json:"diff_map"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &jsonData); err != nil {
|
|
||||||
return false, err.Error()
|
if _, err := os.Stat(hdiffMapPath); err == nil {
|
||||||
|
data, err := os.ReadFile(hdiffMapPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonDataDiffMap struct {
|
||||||
|
DiffMap []*models.DiffMapType `json:"diff_map"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &jsonDataDiffMap); err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
for _, entry := range jsonDataDiffMap.DiffMap {
|
||||||
|
jsonData.DiffMap = append(jsonData.DiffMap, entry.ToHDiffData())
|
||||||
|
}
|
||||||
|
} else if _, err := os.Stat(hdiffFilesPath); err == nil {
|
||||||
|
files, err := models.LoadHDiffFiles(hdiffFilesPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
for _, entry := range files {
|
||||||
|
jsonData.DiffMap = append(jsonData.DiffMap, entry.ToHDiffData())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false, "no hdiff entries map exist"
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, entry := range jsonData.DiffMap {
|
for i, entry := range jsonData.DiffMap {
|
||||||
application.Get().EmitEvent(
|
application.Get().Event.Emit(
|
||||||
"hdiffz:progress", map[string]int{
|
"hdiffz:progress", map[string]int{
|
||||||
"progress": i,
|
"progress": i,
|
||||||
"maxProgress": len(jsonData.DiffMap),
|
"maxProgress": len(jsonData.DiffMap),
|
||||||
})
|
})
|
||||||
|
|
||||||
sourceFile := filepath.Join(gamePath, entry.SourceFileName)
|
sourceFile := filepath.Join(gamePath, entry.SourceFileName)
|
||||||
patchFile := filepath.Join(gamePath, entry.PatchFileName)
|
patchFile := filepath.Join(gamePath, entry.PatchFileName)
|
||||||
targetFile := filepath.Join(gamePath, entry.TargetFileName)
|
targetFile := filepath.Join(gamePath, entry.TargetFileName)
|
||||||
|
|
||||||
if _, err := os.Stat(sourceFile); os.IsNotExist(err) {
|
// Check patch file tồn tại chưa
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(patchFile); os.IsNotExist(err) {
|
if _, err := os.Stat(patchFile); os.IsNotExist(err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(constant.ToolHPatchzExe.String(), sourceFile, patchFile, targetFile)
|
// Nếu không có source file hoặc SourceFileName rỗng → apply_patch_empty
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
if entry.SourceFileName == "" {
|
||||||
_, err := cmd.CombinedOutput()
|
err := hpatchz.ApplyPatchEmpty(patchFile, targetFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Printf("%s failed to patch! %v\n", entry.TargetFileName, err)
|
||||||
|
_ = os.Remove(patchFile)
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
_ = os.Remove(patchFile)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(sourceFile); os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Có source file → apply_patch
|
||||||
|
err := hpatchz.ApplyPatch(sourceFile, patchFile, targetFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s failed to patch! %v\n", entry.TargetFileName, err)
|
||||||
|
_ = os.Remove(patchFile)
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.SourceFileName != entry.TargetFileName {
|
||||||
|
_ = os.Remove(sourceFile)
|
||||||
|
}
|
||||||
|
_ = os.Remove(patchFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os.Remove(filepath.Join(gamePath, "hdiffmap.json"))
|
||||||
|
os.Remove(filepath.Join(gamePath, "hdifffiles.txt"))
|
||||||
return true, "patching completed"
|
return true, "patching completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,13 +257,14 @@ func (h *HdiffzService) DeleteFiles(gamePath string) (bool, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return false, ""
|
return false, "no delete files exist"
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, file := range deleteFiles {
|
for i, file := range deleteFiles {
|
||||||
os.Remove(filepath.Join(gamePath, file))
|
os.Remove(filepath.Join(gamePath, file))
|
||||||
application.Get().EmitEvent("hdiffz:progress", map[string]int{"progress": i, "maxProgress": len(deleteFiles)})
|
application.Get().Event.Emit("hdiffz:progress", map[string]int{"progress": i, "maxProgress": len(deleteFiles)})
|
||||||
}
|
}
|
||||||
|
_ = os.Remove(filepath.Join(gamePath, "deletefiles.txt"))
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,145 +2,174 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
assetMeta "firefly-launcher/pkg/language-patch/asset-meta"
|
||||||
|
excelLanguage "firefly-launcher/pkg/language-patch/excel-language"
|
||||||
|
"firefly-launcher/pkg/models"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LanguageService struct{}
|
type LanguageService struct{}
|
||||||
|
|
||||||
func isValidLang(lang string) bool {
|
func isValidLang(lang string) bool {
|
||||||
valid := []string{"en", "jp", "cn", "kr"}
|
valid := []string{"en", "jp", "cn", "kr"}
|
||||||
for _, v := range valid {
|
return slices.Contains(valid, lang)
|
||||||
if lang == v {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LanguageService) GetLanguage(path string) (string, string, error) {
|
func (l *LanguageService) GetLanguage(path string) (bool, string, string, string) {
|
||||||
files, err := os.ReadDir(path)
|
currentVersionGame, err := models.ParseBinaryVersion(filepath.Join(path, "BinaryVersion.bytes"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return false, "", "", err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
typeVersionGame := "os"
|
||||||
filePath := filepath.Join(path, file.Name())
|
if strings.Contains(currentVersionGame.Name, "CN") {
|
||||||
|
typeVersionGame = "cn"
|
||||||
content, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
patternToFind := []byte("SpriteOutput/UI/Fonts/RPG_CN.ttf")
|
|
||||||
idx := bytes.Index(content, patternToFind)
|
|
||||||
if idx == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern := []byte("Korean")
|
|
||||||
idx = bytes.Index(content, pattern)
|
|
||||||
if idx == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to os text language
|
|
||||||
idx += 10
|
|
||||||
idx += 4
|
|
||||||
osText := string(content[idx : idx+2])
|
|
||||||
idx += 3 * 4
|
|
||||||
|
|
||||||
// Move to cn voice language
|
|
||||||
idx += 1
|
|
||||||
idx += 5
|
|
||||||
cnVoice := string(content[idx : idx+2])
|
|
||||||
idx += 3 * 2 // skip 2 entries
|
|
||||||
|
|
||||||
// Move to os voice language
|
|
||||||
idx += 1
|
|
||||||
idx += 5
|
|
||||||
osVoice := string(content[idx : idx+2])
|
|
||||||
idx += 3 * 5 // skip 5 entries
|
|
||||||
|
|
||||||
// Move to cn text language
|
|
||||||
idx += 1
|
|
||||||
idx += 4
|
|
||||||
cnText := string(content[idx : idx+2])
|
|
||||||
|
|
||||||
textLang := osText
|
|
||||||
voiceLang := osVoice
|
|
||||||
if !isValidLang(textLang) {
|
|
||||||
textLang = cnText
|
|
||||||
}
|
|
||||||
if !isValidLang(voiceLang) {
|
|
||||||
voiceLang = cnVoice
|
|
||||||
}
|
|
||||||
|
|
||||||
return textLang, voiceLang, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", "", fmt.Errorf("couldn't find file to read language from")
|
assetPath := filepath.Join(path, "DesignData\\Windows")
|
||||||
}
|
|
||||||
|
|
||||||
func replaceBytes(content []byte, idx int, choice string, param int) int {
|
indexHash, err := assetMeta.GetIndexHash(assetPath)
|
||||||
for i := 0; i < param; i++ {
|
|
||||||
copy(content[idx:idx+2], []byte(choice))
|
|
||||||
idx += 3
|
|
||||||
}
|
|
||||||
return idx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LanguageService) SetLanguage(path string, text, voice string) (bool, error) {
|
|
||||||
files, err := os.ReadDir(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, "", "", err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
DesignIndex, err := assetMeta.DesignIndexFromBytes(assetPath, indexHash)
|
||||||
filePath := filepath.Join(path, file.Name())
|
if err != nil {
|
||||||
|
return false, "", "", err.Error()
|
||||||
content, err := os.ReadFile(filePath)
|
}
|
||||||
if err != nil {
|
dataEntry, fileEntry, err := DesignIndex.FindDataAndFileByTarget(-515329346)
|
||||||
continue
|
if err != nil {
|
||||||
}
|
return false, "", "", err.Error()
|
||||||
|
}
|
||||||
patternToFind := []byte("SpriteOutput/UI/Fonts/RPG_CN.ttf")
|
allowedLanguage := excelLanguage.NewExcelLanguage(assetPath, &dataEntry, &fileEntry)
|
||||||
idx := bytes.Index(content, patternToFind)
|
languageRows, err := allowedLanguage.Parse()
|
||||||
if idx == -1 {
|
if err != nil {
|
||||||
continue
|
return false, "", "", err.Error()
|
||||||
}
|
|
||||||
|
|
||||||
pattern := []byte("Korean")
|
|
||||||
idx = bytes.Index(content, pattern)
|
|
||||||
if idx == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
idx += 10
|
|
||||||
idx += 4
|
|
||||||
idx = replaceBytes(content, idx, text, 4)
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
idx += 5
|
|
||||||
idx = replaceBytes(content, idx, voice, 2)
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
idx += 5
|
|
||||||
idx = replaceBytes(content, idx, voice, 5)
|
|
||||||
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
idx += 4
|
|
||||||
_ = replaceBytes(content, idx, text, 2)
|
|
||||||
|
|
||||||
err = os.WriteFile(filePath, content, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, fmt.Errorf("couldn't find file to patch. Make sure this file is placed in the correct folder")
|
currentTextLang := ""
|
||||||
|
currentVoiceLang := ""
|
||||||
|
|
||||||
|
pairs := []struct {
|
||||||
|
area string
|
||||||
|
typ *uint8
|
||||||
|
}{
|
||||||
|
{"os", nil},
|
||||||
|
{"cn", func() *uint8 { v := uint8(1); return &v }()},
|
||||||
|
{"os", func() *uint8 { v := uint8(1); return &v }()},
|
||||||
|
{"cn", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range pairs {
|
||||||
|
var found *excelLanguage.LanguageRow
|
||||||
|
for i := range languageRows {
|
||||||
|
if languageRows[i].Area != nil && *languageRows[i].Area == p.area {
|
||||||
|
if (languageRows[i].Type == nil && p.typ == nil) ||
|
||||||
|
(languageRows[i].Type != nil && p.typ != nil && *languageRows[i].Type == *p.typ) {
|
||||||
|
found = &languageRows[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if found.DefaultLanguage != nil && found.Area != nil && *found.Area == typeVersionGame && found.Type == nil {
|
||||||
|
currentTextLang = *found.DefaultLanguage
|
||||||
|
}
|
||||||
|
if found.DefaultLanguage != nil && found.Area != nil && *found.Area == typeVersionGame && found.Type != nil {
|
||||||
|
currentVoiceLang = *found.DefaultLanguage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentTextLang == "" || currentVoiceLang == "" || !isValidLang(currentTextLang) || !isValidLang(currentVoiceLang) {
|
||||||
|
return false, "", "", "not found language"
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, currentTextLang, currentVoiceLang, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func (l *LanguageService) SetLanguage(path string, text, voice string) (bool, string) {
|
||||||
|
indexHash, err := assetMeta.GetIndexHash(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
DesignIndex, err := assetMeta.DesignIndexFromBytes(path, indexHash)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
dataEntry, fileEntry, err := DesignIndex.FindDataAndFileByTarget(-515329346)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
allowedLanguage := excelLanguage.NewExcelLanguage(path, &dataEntry, &fileEntry)
|
||||||
|
languageRows, err := allowedLanguage.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := []struct {
|
||||||
|
area string
|
||||||
|
typ *uint8
|
||||||
|
lang string
|
||||||
|
}{
|
||||||
|
{"os", nil, text},
|
||||||
|
{"cn", func() *uint8 { v := uint8(1); return &v }(), voice},
|
||||||
|
{"os", func() *uint8 { v := uint8(1); return &v }(), voice},
|
||||||
|
{"cn", nil, text},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range pairs {
|
||||||
|
var found *excelLanguage.LanguageRow
|
||||||
|
for i := range languageRows {
|
||||||
|
if languageRows[i].Area != nil && *languageRows[i].Area == p.area {
|
||||||
|
if (languageRows[i].Type == nil && p.typ == nil) ||
|
||||||
|
(languageRows[i].Type != nil && p.typ != nil && *languageRows[i].Type == *p.typ) {
|
||||||
|
found = &languageRows[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
found.DefaultLanguage = &p.lang
|
||||||
|
found.LanguageList = []string{p.lang}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := allowedLanguage.Unmarshal(languageRows)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(path, fileEntry.FileByteName+".bytes")
|
||||||
|
|
||||||
|
f, err := os.OpenFile(filePath, os.O_RDWR, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := f.Seek(int64(dataEntry.Offset), 0); err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
if _, err := f.Write(data); err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) < int(dataEntry.Size) {
|
||||||
|
remaining := int(dataEntry.Size) - len(data)
|
||||||
|
zeros := bytes.Repeat([]byte{0}, remaining)
|
||||||
|
if _, err := f.Write(zeros); err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, "success"
|
||||||
}
|
}
|
||||||
|
|||||||
1
internal/ldifff-service.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package internal
|
||||||
@@ -109,8 +109,6 @@ func DownloadFile(filepath string, url string, onEmit func(percent float64, spee
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func unzipParallel(src string, dest string) error {
|
func unzipParallel(src string, dest string) error {
|
||||||
numCPU := runtime.NumCPU()
|
numCPU := runtime.NumCPU()
|
||||||
|
|
||||||
@@ -141,16 +139,14 @@ func unzipParallel(src string, dest string) error {
|
|||||||
|
|
||||||
// Worker pool
|
// Worker pool
|
||||||
for i := 0; i < maxWorkers; i++ {
|
for i := 0; i < maxWorkers; i++ {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for j := range jobs {
|
for j := range jobs {
|
||||||
err := extractFile(j.f, dest)
|
err := extractFile(j.f, dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error extracting %s: %v\n", j.f.Name, err)
|
fmt.Printf("Error extracting %s: %v\n", j.f.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feed jobs
|
// Feed jobs
|
||||||
|
|||||||
42
main.go
@@ -13,11 +13,6 @@ import (
|
|||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Wails uses Go's `embed` package to embed the frontend files into the binary.
|
|
||||||
// Any files in the frontend/dist folder will be embedded into the binary and
|
|
||||||
// made available to the frontend.
|
|
||||||
// See https://pkg.go.dev/embed for more information.
|
|
||||||
|
|
||||||
//go:embed all:frontend/dist
|
//go:embed all:frontend/dist
|
||||||
var assets embed.FS
|
var assets embed.FS
|
||||||
|
|
||||||
@@ -42,10 +37,8 @@ func extractFile(embedPath, outPath string) error {
|
|||||||
return os.WriteFile(outPath, data, 0755)
|
return os.WriteFile(outPath, data, 0755)
|
||||||
}
|
}
|
||||||
|
|
||||||
// main function serves as the application's entry point. It initializes the application, creates a window,
|
|
||||||
// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and
|
|
||||||
// logs any error that might occur.
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Extract required files
|
||||||
for outPath, embedPath := range constant.RequiredFiles {
|
for outPath, embedPath := range constant.RequiredFiles {
|
||||||
if !fileExists(outPath.String()) {
|
if !fileExists(outPath.String()) {
|
||||||
err := extractFile(embedPath, outPath.String())
|
err := extractFile(embedPath, outPath.String())
|
||||||
@@ -54,11 +47,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create a new Wails application by providing the necessary options.
|
|
||||||
// Variables 'Name' and 'Description' are for application metadata.
|
// Remove old executable
|
||||||
// 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files.
|
exePath, err := os.Executable()
|
||||||
// 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances.
|
if err == nil {
|
||||||
// 'Mac' options tailor the application when running an macOS.
|
dir := filepath.Dir(exePath)
|
||||||
|
base := filepath.Base(exePath)
|
||||||
|
oldPath := filepath.Join(dir, "."+base+".old")
|
||||||
|
|
||||||
|
fmt.Println("Old executable path:", oldPath)
|
||||||
|
os.Remove(oldPath)
|
||||||
|
}
|
||||||
|
// Create application
|
||||||
app := application.New(application.Options{
|
app := application.New(application.Options{
|
||||||
Name: "firefly-launcher",
|
Name: "firefly-launcher",
|
||||||
Description: "Firefly Launcher - Kain",
|
Description: "Firefly Launcher - Kain",
|
||||||
@@ -67,31 +67,24 @@ func main() {
|
|||||||
application.NewService(&internal.LanguageService{}),
|
application.NewService(&internal.LanguageService{}),
|
||||||
application.NewService(&internal.GitService{}),
|
application.NewService(&internal.GitService{}),
|
||||||
application.NewService(&internal.HdiffzService{}),
|
application.NewService(&internal.HdiffzService{}),
|
||||||
|
application.NewService(&internal.AppService{}),
|
||||||
},
|
},
|
||||||
Assets: application.AssetOptions{
|
Assets: application.AssetOptions{
|
||||||
Handler: application.AssetFileServerFS(assets),
|
Handler: application.AssetFileServerFS(assets),
|
||||||
|
|
||||||
},
|
},
|
||||||
Mac: application.MacOptions{
|
Mac: application.MacOptions{
|
||||||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create a new window with the necessary options.
|
// Create window
|
||||||
// 'Title' is the title of the window.
|
app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||||
// 'Mac' options tailor the window when running on macOS.
|
|
||||||
// 'BackgroundColour' is the background colour of the window.
|
|
||||||
// 'URL' is the URL that will be loaded into the webview.
|
|
||||||
|
|
||||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
|
||||||
|
|
||||||
Title: "Firefly Launcher - Kain",
|
Title: "Firefly Launcher - Kain",
|
||||||
Mac: application.MacWindow{
|
Mac: application.MacWindow{
|
||||||
InvisibleTitleBarHeight: 50,
|
InvisibleTitleBarHeight: 50,
|
||||||
Backdrop: application.MacBackdropTranslucent,
|
Backdrop: application.MacBackdropTranslucent,
|
||||||
TitleBar: application.MacTitleBarHiddenInset,
|
TitleBar: application.MacTitleBarHiddenInset,
|
||||||
},
|
},
|
||||||
|
|
||||||
BackgroundColour: application.NewRGB(27, 38, 54),
|
BackgroundColour: application.NewRGB(27, 38, 54),
|
||||||
Width: 1200,
|
Width: 1200,
|
||||||
Height: 600,
|
Height: 600,
|
||||||
@@ -99,7 +92,8 @@ func main() {
|
|||||||
DevToolsEnabled: true,
|
DevToolsEnabled: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
err := app.Run()
|
|
||||||
|
err = app.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ package constant
|
|||||||
|
|
||||||
const ProxyGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/Firefly_Proxy/releases"
|
const ProxyGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/Firefly_Proxy/releases"
|
||||||
const ServerGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Local_Archive/releases"
|
const ServerGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Local_Archive/releases"
|
||||||
|
const LauncherGitUrl = "https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/Firefly_Launcher/releases"
|
||||||
const ServerStorageUrl = "./server"
|
const ServerStorageUrl = "./server"
|
||||||
const ProxyStorageUrl = "./proxy"
|
const ProxyStorageUrl = "./proxy"
|
||||||
const ServerZipFile = "prebuild_win_x86.zip"
|
const ServerZipFile = "prebuild_win_x86.zip"
|
||||||
const ProxyZipFile = "64bit.zip"
|
const ProxyZipFile = "64bit.zip"
|
||||||
|
const LauncherFile = "firefly-launcher.exe"
|
||||||
const TempUrl = "./temp"
|
const TempUrl = "./temp"
|
||||||
|
|
||||||
|
|
||||||
|
const CurrentLauncherVersion = "1.4.1"
|
||||||
|
|
||||||
type ToolFile string
|
type ToolFile string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
34
pkg/hpatchz/hpatchz.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package hpatchz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"firefly-launcher/pkg/constant"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApplyPatch(oldFile, diffFile, newFile string) error {
|
||||||
|
cmd := exec.Command(constant.ToolHPatchzExe.String(), "-f", oldFile, diffFile, newFile)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute hpatchz: %w", err)
|
||||||
|
}
|
||||||
|
if cmd.ProcessState.ExitCode() != 0 {
|
||||||
|
return fmt.Errorf("hpatchz failed: %s", string(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplyPatchEmpty(diffFile, newFile string) error {
|
||||||
|
cmd := exec.Command(constant.ToolHPatchzExe.String(), "-f", "", diffFile, newFile)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute hpatchz: %w", err)
|
||||||
|
}
|
||||||
|
if cmd.ProcessState.ExitCode() != 0 {
|
||||||
|
return fmt.Errorf("hpatchz failed: %s", string(output))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
30
pkg/language-patch/asset-meta/bytehash16.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package assetMeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ByteHash16 []byte
|
||||||
|
|
||||||
|
func ByteHash16FromBytes(r io.ReadSeeker) (ByteHash16, error) {
|
||||||
|
fullHash := make([]byte, 16)
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for j := 0; j < 4; j++ {
|
||||||
|
fullHash[i*4+j] = buf[3-j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ByteHash16(fullHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b ByteHash16) String() string {
|
||||||
|
s := ""
|
||||||
|
for _, v := range b {
|
||||||
|
s += fmt.Sprintf("%02x", v)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
28
pkg/language-patch/asset-meta/dataEntry.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package assetMeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataEntry struct {
|
||||||
|
NameHash int32
|
||||||
|
Size uint32
|
||||||
|
Offset uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func DataEntryFromBytes(r io.Reader) (*DataEntry, error) {
|
||||||
|
var d DataEntry
|
||||||
|
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &d.NameHash); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &d.Size); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &d.Offset); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
98
pkg/language-patch/asset-meta/designIndex.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package assetMeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type DesignIndex struct {
|
||||||
|
UnkI64 int64
|
||||||
|
FileCount int32
|
||||||
|
DesignDataCount int32
|
||||||
|
FileList []FileEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func (d *DesignIndex) FindDataAndFileByTarget(target int32) (DataEntry, FileEntry, error) {
|
||||||
|
for _, file := range d.FileList {
|
||||||
|
for _, entry := range file.DataEntries {
|
||||||
|
if entry.NameHash == target {
|
||||||
|
return entry, file, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DataEntry{}, FileEntry{}, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func DesignIndexFromBytes(assetFolder string, indexHash string) (*DesignIndex, error) {
|
||||||
|
path := filepath.Join(assetFolder, fmt.Sprintf("DesignV_%s.bytes", indexHash))
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
|
||||||
|
var d DesignIndex
|
||||||
|
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &d.UnkI64); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &d.FileCount); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &d.DesignDataCount); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.FileList = make([]FileEntry, 0, d.FileCount)
|
||||||
|
for i := int32(0); i < d.FileCount; i++ {
|
||||||
|
entry, err := FileEntryFromBytes(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d.FileList = append(d.FileList, *entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func GetIndexHash(assetFolder string) (string, error) {
|
||||||
|
path := filepath.Join(assetFolder, "M_DesignV.bytes")
|
||||||
|
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.Seek(0x1C, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := make([]byte, 0x10)
|
||||||
|
index := 0
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
chunk := make([]byte, 4)
|
||||||
|
_, err := f.Read(chunk)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for bytePos := 3; bytePos >= 0; bytePos-- {
|
||||||
|
hash[index] = chunk[bytePos]
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(hash), nil
|
||||||
|
}
|
||||||
63
pkg/language-patch/asset-meta/fileEntry.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package assetMeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileEntry struct {
|
||||||
|
NameHash int32
|
||||||
|
FileByteName string
|
||||||
|
Size int64
|
||||||
|
DataCount int32
|
||||||
|
DataEntries []DataEntry
|
||||||
|
Unk uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func FileEntryFromBytes(r io.Reader) (*FileEntry, error) {
|
||||||
|
var f FileEntry
|
||||||
|
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &f.NameHash); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.FileByteName = toHex(buf)
|
||||||
|
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &f.Size); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &f.DataCount); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.DataEntries = make([]DataEntry, 0, f.DataCount)
|
||||||
|
for i := int32(0); i < f.DataCount; i++ {
|
||||||
|
entry, err := DataEntryFromBytes(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.DataEntries = append(f.DataEntries, *entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read 1 byte
|
||||||
|
b := make([]byte, 1)
|
||||||
|
if _, err := r.Read(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.Unk = b[0]
|
||||||
|
|
||||||
|
return &f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toHex(buf []byte) string {
|
||||||
|
s := ""
|
||||||
|
for _, b := range buf {
|
||||||
|
s += fmt.Sprintf("%02x", b)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
32
pkg/language-patch/asset-meta/miniAsset.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package assetMeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MiniAsset struct {
|
||||||
|
RevisionID uint32
|
||||||
|
DesignIndexHash ByteHash16
|
||||||
|
}
|
||||||
|
|
||||||
|
func MiniAssetFromBytes(r io.ReadSeeker) (*MiniAsset, error) {
|
||||||
|
if _, err := r.Seek(6*4, io.SeekCurrent); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var revID uint32
|
||||||
|
if err := binary.Read(r, binary.LittleEndian, &revID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := ByteHash16FromBytes(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MiniAsset{
|
||||||
|
RevisionID: revID,
|
||||||
|
DesignIndexHash: hash,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
114
pkg/language-patch/excel-language/patch.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package excelLanguage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
assetMeta "firefly-launcher/pkg/language-patch/asset-meta"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExcelLanguage struct {
|
||||||
|
AssetFolder string
|
||||||
|
ExcelDataEntry *assetMeta.DataEntry
|
||||||
|
ExcelFileEntry *assetMeta.FileEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExcelLanguage(assetFolder string, dataEntry *assetMeta.DataEntry, fileEntry *assetMeta.FileEntry) *ExcelLanguage {
|
||||||
|
return &ExcelLanguage{
|
||||||
|
AssetFolder: assetFolder,
|
||||||
|
ExcelDataEntry: dataEntry,
|
||||||
|
ExcelFileEntry: fileEntry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ExcelLanguage) Unmarshal(rows []LanguageRow) ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
buf.WriteByte(0)
|
||||||
|
if err := writeI8Varint(buf, int8(len(rows))); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
rowData, err := row.Unmarshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := buf.Write(rowData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ExcelLanguage) Parse() ([]LanguageRow, error) {
|
||||||
|
excelPath := filepath.Join(a.AssetFolder, a.ExcelFileEntry.FileByteName+".bytes")
|
||||||
|
|
||||||
|
f, err := os.Open(excelPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := f.Seek(int64(a.ExcelDataEntry.Offset), io.SeekStart); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, a.ExcelDataEntry.Size)
|
||||||
|
if _, err := io.ReadFull(f, buffer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bytes.NewReader(buffer)
|
||||||
|
|
||||||
|
_, _ = reader.ReadByte() // skip first byte
|
||||||
|
count, err := readI8Varint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := make([]LanguageRow, 0, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
bitmask, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := LanguageRow{}
|
||||||
|
|
||||||
|
if bitmask&(1<<0) != 0 {
|
||||||
|
s, err := readString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
row.Area = &s
|
||||||
|
}
|
||||||
|
if bitmask&(1<<1) != 0 {
|
||||||
|
t, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
row.Type = &t
|
||||||
|
}
|
||||||
|
if bitmask&(1<<2) != 0 {
|
||||||
|
arr, err := readStringArray(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
row.LanguageList = arr
|
||||||
|
}
|
||||||
|
if bitmask&(1<<3) != 0 {
|
||||||
|
s, err := readString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
row.DefaultLanguage = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = append(rows, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows, nil
|
||||||
|
}
|
||||||
81
pkg/language-patch/excel-language/row.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package excelLanguage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LanguageRow struct {
|
||||||
|
Area *string
|
||||||
|
Type *uint8
|
||||||
|
LanguageList []string
|
||||||
|
DefaultLanguage *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LanguageRow) Unmarshal() ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
var bitmask uint8
|
||||||
|
if r.Area != nil {
|
||||||
|
bitmask |= 1 << 0
|
||||||
|
}
|
||||||
|
if r.Type != nil {
|
||||||
|
bitmask |= 1 << 1
|
||||||
|
}
|
||||||
|
if len(r.LanguageList) > 0 {
|
||||||
|
bitmask |= 1 << 2
|
||||||
|
}
|
||||||
|
if r.DefaultLanguage != nil {
|
||||||
|
bitmask |= 1 << 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := buf.WriteByte(bitmask); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Area != nil {
|
||||||
|
if err := writeString(buf, *r.Area); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Type != nil {
|
||||||
|
if err := buf.WriteByte(*r.Type); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(r.LanguageList) > 0 {
|
||||||
|
if err := writeStringArray(buf, r.LanguageList); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.DefaultLanguage != nil {
|
||||||
|
if err := writeString(buf, *r.DefaultLanguage); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeString(buf *bytes.Buffer, s string) error {
|
||||||
|
if len(s) > 255 {
|
||||||
|
return errors.New("string too long")
|
||||||
|
}
|
||||||
|
if err := buf.WriteByte(uint8(len(s))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := buf.Write([]byte(s))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStringArray(buf *bytes.Buffer, arr []string) error {
|
||||||
|
if err := writeI8Varint(buf, int8(len(arr))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range arr {
|
||||||
|
if err := writeString(buf, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
53
pkg/language-patch/excel-language/utils.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package excelLanguage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeI8Varint(buf *bytes.Buffer, v int8) error {
|
||||||
|
uv := uint64((uint32(v) << 1) ^ uint32(v>>7)) // zigzag encode
|
||||||
|
b := make([]byte, binary.MaxVarintLen64)
|
||||||
|
n := binary.PutUvarint(b, uv)
|
||||||
|
_, err := buf.Write(b[:n])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func readI8Varint(r *bytes.Reader) (int, error) {
|
||||||
|
uv, err := binary.ReadUvarint(r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// zigzag decode
|
||||||
|
v := int((uv >> 1) ^ uint64((int64(uv&1)<<63)>>63))
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readString(r *bytes.Reader) (string, error) {
|
||||||
|
l, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf := make([]byte, l)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readStringArray(r *bytes.Reader) ([]string, error) {
|
||||||
|
length, err := readI8Varint(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
arr := make([]string, 0, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
s, err := readString(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
arr = append(arr, s)
|
||||||
|
}
|
||||||
|
return arr, nil
|
||||||
|
}
|
||||||
@@ -4,11 +4,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BinaryVersion struct {
|
type BinaryVersion struct {
|
||||||
|
Name string
|
||||||
Major int
|
Major int
|
||||||
Minor int
|
Minor int
|
||||||
Patch int
|
Patch int
|
||||||
@@ -22,42 +24,47 @@ func ParseBinaryVersion(path string) (*BinaryVersion, error) {
|
|||||||
|
|
||||||
content := string(data)
|
content := string(data)
|
||||||
|
|
||||||
dashPos := strings.LastIndex(content, "-")
|
lastDash := strings.LastIndex(content, "-")
|
||||||
if dashPos == -1 {
|
if lastDash == -1 {
|
||||||
return nil, errors.New("no dash found in version string")
|
return nil, errors.New("no dash found in version string")
|
||||||
}
|
}
|
||||||
|
|
||||||
start := dashPos - 6
|
secondLastDash := strings.LastIndex(content[:lastDash], "-")
|
||||||
if start < 0 {
|
if secondLastDash == -1 {
|
||||||
start = 0
|
return nil, errors.New("only one dash found in version string")
|
||||||
}
|
}
|
||||||
|
|
||||||
versionSlice := content[start:]
|
versionSlice := content[secondLastDash+1 : lastDash]
|
||||||
end := strings.Index(versionSlice, "-")
|
re := regexp.MustCompile(`^([A-Za-z]+)([\d\.]+)$`)
|
||||||
if end == -1 {
|
matches := re.FindStringSubmatch(versionSlice)
|
||||||
end = len(versionSlice)
|
if len(matches) < 3 {
|
||||||
}
|
|
||||||
|
|
||||||
versionStr := versionSlice[:end]
|
|
||||||
parts := strings.SplitN(versionStr, ".", 3)
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return nil, errors.New("invalid version format")
|
return nil, errors.New("invalid version format")
|
||||||
}
|
}
|
||||||
|
binaryVersion := BinaryVersion{
|
||||||
|
Name: matches[1],
|
||||||
|
}
|
||||||
|
numbers := strings.Split(matches[2], ".")
|
||||||
|
|
||||||
major, err := strconv.Atoi(parts[0])
|
if len(numbers) > 0 {
|
||||||
if err != nil {
|
binaryVersion.Major, err = strconv.Atoi(numbers[0])
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
minor, err := strconv.Atoi(parts[1])
|
if len(numbers) > 1 {
|
||||||
if err != nil {
|
binaryVersion.Minor, err = strconv.Atoi(numbers[1])
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
patch, err := strconv.Atoi(parts[2])
|
if len(numbers) > 2 {
|
||||||
if err != nil {
|
binaryVersion.Patch, err = strconv.Atoi(numbers[2])
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BinaryVersion{major, minor, patch}, nil
|
return &binaryVersion, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *BinaryVersion) String() string {
|
func (v *BinaryVersion) String() string {
|
||||||
|
|||||||
43
pkg/models/hdiffFiles.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HDiffFiles struct {
|
||||||
|
RemoteFile string `json:"remoteName"`
|
||||||
|
}
|
||||||
|
func (h *HDiffFiles) ToHDiffData() *HDiffData {
|
||||||
|
return &HDiffData{
|
||||||
|
SourceFileName: h.RemoteFile,
|
||||||
|
TargetFileName: h.RemoteFile,
|
||||||
|
PatchFileName: fmt.Sprintf("%s.hdiff", h.RemoteFile),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadHDiffFiles(path string) ([]*HDiffFiles, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var results []*HDiffFiles
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
var item HDiffFiles
|
||||||
|
if err := json.Unmarshal([]byte(line), &item); err == nil {
|
||||||
|
results = append(results, &item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
@@ -13,3 +13,17 @@ type DiffMapType struct {
|
|||||||
PatchFileMD5 string `json:"patch_file_md5"`
|
PatchFileMD5 string `json:"patch_file_md5"`
|
||||||
PatchFileSize int64 `json:"patch_file_size"`
|
PatchFileSize int64 `json:"patch_file_size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HDiffData struct {
|
||||||
|
SourceFileName string `json:"source_file_name"`
|
||||||
|
TargetFileName string `json:"target_file_name"`
|
||||||
|
PatchFileName string `json:"patch_file_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DiffMapType) ToHDiffData() *HDiffData {
|
||||||
|
return &HDiffData{
|
||||||
|
SourceFileName: d.SourceFileName,
|
||||||
|
TargetFileName: d.TargetFileName,
|
||||||
|
PatchFileName: d.PatchFileName,
|
||||||
|
}
|
||||||
|
}
|
||||||
36
pkg/models/pkgVersion.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PkgVersion struct {
|
||||||
|
RemoteFile string `json:"remoteName"`
|
||||||
|
MD5 string `json:"md5"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadPkgVersion(path string) ([]*PkgVersion, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var results []*PkgVersion
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
var item PkgVersion
|
||||||
|
if err := json.Unmarshal([]byte(line), &item); err == nil {
|
||||||
|
results = append(results, &item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
@@ -29,6 +29,41 @@ func IsFileIn7z(archivePath, fileInside string) (bool, error) {
|
|||||||
return false, fmt.Errorf("%s not found in %s", fileInside, archivePath)
|
return false, fmt.Errorf("%s not found in %s", fileInside, archivePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListFilesInZip(archivePath string) ([]string, error) {
|
||||||
|
cmd := exec.Command(constant.Tool7zaExe.String(), "l", archivePath)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &out
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("7za list failed: %v\nOutput: %s", err, out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
var files []string
|
||||||
|
foundTable := false
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "----------") {
|
||||||
|
if foundTable {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
foundTable = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundTable {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) >= 6 {
|
||||||
|
fileName := strings.Join(fields[5:], " ")
|
||||||
|
files = append(files, fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ExtractAFileFromZip(archivePath, fileInside, outDir string) error {
|
func ExtractAFileFromZip(archivePath, fileInside, outDir string) error {
|
||||||
cmd := exec.Command(constant.Tool7zaExe.String(), "e", archivePath, fileInside, "-o"+outDir, "-y")
|
cmd := exec.Command(constant.Tool7zaExe.String(), "e", archivePath, fileInside, "-o"+outDir, "-y")
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
|
|||||||
@@ -41,17 +41,15 @@ func NewVerifier(gamePath, hdiffPath string) (*Verifier, error) {
|
|||||||
|
|
||||||
func (v *Verifier) VerifyAll() error {
|
func (v *Verifier) VerifyAll() error {
|
||||||
for i, entry := range v.DiffMapEntries {
|
for i, entry := range v.DiffMapEntries {
|
||||||
application.Get().EmitEvent(
|
application.Get().Event.Emit(
|
||||||
"hdiffz:progress", map[string]int{
|
"hdiffz:progress", map[string]int{
|
||||||
"progress": i,
|
"progress": i,
|
||||||
"maxProgress": len(v.DiffMapEntries),
|
"maxProgress": len(v.DiffMapEntries),
|
||||||
})
|
})
|
||||||
if err := check(entry.SourceFileName, entry.SourceFileSize, entry.SourceFileMD5, v.GamePath); err != nil {
|
if err := check(entry.SourceFileName, entry.SourceFileSize, entry.SourceFileMD5, v.GamePath); err != nil {
|
||||||
return fmt.Errorf("source_file failed: %w", err)
|
application.Get().Event.Emit("hdiffz:error", err.Error())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
// if err := check(entry.PatchFileName, entry.PatchFileSize, entry.PatchFileMD5, v.HdiffPath); err != nil {
|
|
||||||
// return fmt.Errorf("patch_file failed: %w", err)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||