Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 100c93e8da | |||
| 1e17f76340 | |||
| 1f9c95d6ac | |||
| d58e76d821 | |||
| a4de02bc18 |
Binary file not shown.
Binary file not shown.
BIN
assets/hpatchz.exe
Normal file
BIN
assets/hpatchz.exe
Normal file
Binary file not shown.
@@ -4,21 +4,19 @@
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} timeout
|
* @param {number} timeout
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function CloseAppAfterTimeout(timeout) {
|
export function CloseAppAfterTimeout(timeout) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1705931481, timeout));
|
return $Call.ByID(1705931481, timeout);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function GetCurrentLauncherVersion() {
|
export function GetCurrentLauncherVersion() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3575133982));
|
return $Call.ByID(3575133982);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,70 +4,62 @@
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} patchPath
|
* @param {string} patchPath
|
||||||
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string, string]>}
|
||||||
*/
|
*/
|
||||||
export function CheckTypeHDiff(patchPath) {
|
export function CheckTypeHDiff(patchPath) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3717449114, patchPath));
|
return $Call.ByID(3717449114, patchPath);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} gamePath
|
* @param {string} gamePath
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function CutData(gamePath) {
|
export function CutData(gamePath) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(2019290107, gamePath));
|
return $Call.ByID(2019290107, gamePath);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} gamePath
|
* @param {string} gamePath
|
||||||
* @param {string} patchPath
|
* @param {string} patchPath
|
||||||
* @param {boolean} isSkipVerify
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
|
||||||
*/
|
*/
|
||||||
export function DataExtract(gamePath, patchPath, isSkipVerify) {
|
export function DataExtract(gamePath, patchPath) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(2161622254, gamePath, patchPath, isSkipVerify));
|
return $Call.ByID(2161622254, gamePath, patchPath);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} gamePath
|
* @param {string} gamePath
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function DeleteFiles(gamePath) {
|
export function DeleteFiles(gamePath) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1103091613, gamePath));
|
return $Call.ByID(1103091613, gamePath);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} gamePath
|
* @param {string} gamePath
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function HDiffPatchData(gamePath) {
|
export function HDiffPatchData(gamePath) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3944051994, gamePath));
|
return $Call.ByID(3944051994, gamePath);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} gamePath
|
* @param {string} gamePath
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function LDiffPatchData(gamePath) {
|
export function LDiffPatchData(gamePath) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(360123238, gamePath));
|
return $Call.ByID(360123238, gamePath);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} gamePath
|
* @param {string} gamePath
|
||||||
* @param {string} patchPath
|
* @param {string} patchPath
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function VersionValidate(gamePath, patchPath) {
|
export function VersionValidate(gamePath, patchPath) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(2105077257, gamePath, patchPath));
|
return $Call.ByID(2105077257, gamePath, patchPath);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,76 +4,68 @@
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {Promise<boolean> & { cancel(): void }}
|
* @returns {$CancellablePromise<boolean>}
|
||||||
*/
|
*/
|
||||||
export function DirExists(path) {
|
export function DirExists(path) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1772289644, path));
|
return $Call.ByID(1772289644, path);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {Promise<boolean> & { cancel(): void }}
|
* @returns {$CancellablePromise<boolean>}
|
||||||
*/
|
*/
|
||||||
export function FileExists(path) {
|
export function FileExists(path) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1782610747, path));
|
return $Call.ByID(1782610747, path);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} archivePath
|
* @param {string} archivePath
|
||||||
* @param {string} fileInside
|
* @param {string} fileInside
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function FileExistsInZip(archivePath, fileInside) {
|
export function FileExistsInZip(archivePath, fileInside) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(2509699047, archivePath, fileInside));
|
return $Call.ByID(2509699047, archivePath, fileInside);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function OpenFolder(path) {
|
export function OpenFolder(path) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1635714001, path));
|
return $Call.ByID(1635714001, path);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} filter
|
* @param {string} filter
|
||||||
* @returns {Promise<string> & { cancel(): void }}
|
* @returns {$CancellablePromise<string>}
|
||||||
*/
|
*/
|
||||||
export function PickFile(filter) {
|
export function PickFile(filter) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3756474934, filter));
|
return $Call.ByID(3756474934, filter);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<string> & { cancel(): void }}
|
* @returns {$CancellablePromise<string>}
|
||||||
*/
|
*/
|
||||||
export function PickFolder() {
|
export function PickFolder() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3654471460));
|
return $Call.ByID(3654471460);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {Promise<boolean> & { cancel(): void }}
|
* @returns {$CancellablePromise<boolean>}
|
||||||
*/
|
*/
|
||||||
export function StartApp(path) {
|
export function StartApp(path) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1267568402, path));
|
return $Call.ByID(1267568402, path);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {Promise<boolean> & { cancel(): void }}
|
* @returns {$CancellablePromise<boolean>}
|
||||||
*/
|
*/
|
||||||
export function StartWithConsole(path) {
|
export function StartWithConsole(path) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3249271428, path));
|
return $Call.ByID(3249271428, path);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,71 +4,63 @@
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} version
|
* @param {string} version
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function DownloadProxyProgress(version) {
|
export function DownloadProxyProgress(version) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3559275334, version));
|
return $Call.ByID(3559275334, version);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} version
|
* @param {string} version
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function DownloadServerProgress(version) {
|
export function DownloadServerProgress(version) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1954767259, version));
|
return $Call.ByID(1954767259, version);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string, string]>}
|
||||||
*/
|
*/
|
||||||
export function GetLatestLauncherVersion() {
|
export function GetLatestLauncherVersion() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(262637602));
|
return $Call.ByID(262637602);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string, string]>}
|
||||||
*/
|
*/
|
||||||
export function GetLatestProxyVersion() {
|
export function GetLatestProxyVersion() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(289488362));
|
return $Call.ByID(289488362);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<[boolean, string, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string, string]>}
|
||||||
*/
|
*/
|
||||||
export function GetLatestServerVersion() {
|
export function GetLatestServerVersion() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(2918980975));
|
return $Call.ByID(2918980975);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<void> & { cancel(): void }}
|
* @returns {$CancellablePromise<void>}
|
||||||
*/
|
*/
|
||||||
export function UnzipProxy() {
|
export function UnzipProxy() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(2563246729));
|
return $Call.ByID(2563246729);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<void> & { cancel(): void }}
|
* @returns {$CancellablePromise<void>}
|
||||||
*/
|
*/
|
||||||
export function UnzipServer() {
|
export function UnzipServer() {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(1126363284));
|
return $Call.ByID(1126363284);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} version
|
* @param {string} version
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function UpdateLauncherProgress(version) {
|
export function UpdateLauncherProgress(version) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(97272861, version));
|
return $Call.ByID(97272861, version);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,22 @@
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {Promise<[boolean, string, string, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string, string, string]>}
|
||||||
*/
|
*/
|
||||||
export function GetLanguage(path) {
|
export function GetLanguage(path) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(3574191687, path));
|
return $Call.ByID(3574191687, path);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {string} voice
|
* @param {string} voice
|
||||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
* @returns {$CancellablePromise<[boolean, string]>}
|
||||||
*/
|
*/
|
||||||
export function SetLanguage(path, text, voice) {
|
export function SetLanguage(path, text, voice) {
|
||||||
let $resultPromise = /** @type {any} */($Call.ByID(2768939795, path, text, voice));
|
return $Call.ByID(2768939795, path, text, voice);
|
||||||
return $resultPromise;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function useGlobalEvents({
|
|||||||
setProgressUpdate: (v: number) => void;
|
setProgressUpdate: (v: number) => void;
|
||||||
setMaxProgressUpdate: (v: number) => void;
|
setMaxProgressUpdate: (v: number) => void;
|
||||||
setProgressDownload: (v: number) => void;
|
setProgressDownload: (v: number) => void;
|
||||||
setDownloadSpeed: (v: number) => void;
|
setDownloadSpeed: (v: string) => void;
|
||||||
setMessageUpdate: (v: string) => void;
|
setMessageUpdate: (v: string) => void;
|
||||||
setStageType: (v: string) => void,
|
setStageType: (v: string) => void,
|
||||||
}) {
|
}) {
|
||||||
@@ -33,7 +33,7 @@ export function useGlobalEvents({
|
|||||||
const onDownload = (event: any) => {
|
const onDownload = (event: any) => {
|
||||||
const { percent, speed } = event.data[0];
|
const { percent, speed } = event.data[0];
|
||||||
setProgressDownload(Number(percent));
|
setProgressDownload(Number(percent));
|
||||||
setDownloadSpeed(Number(speed));
|
setDownloadSpeed(speed);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateProgress = (event: any) => {
|
const onUpdateProgress = (event: any) => {
|
||||||
|
|||||||
@@ -4,20 +4,15 @@ import { RouterProvider, createRouter } from '@tanstack/react-router'
|
|||||||
import './styles/index.css'
|
import './styles/index.css'
|
||||||
import "../runtime.js"
|
import "../runtime.js"
|
||||||
import "@wailsio/runtime";
|
import "@wailsio/runtime";
|
||||||
// Import the generated route tree
|
|
||||||
import { routeTree } from './routeTree.gen.js'
|
import { routeTree } from './routeTree.gen.js'
|
||||||
|
|
||||||
// Create a new router instance
|
|
||||||
const router = createRouter({ routeTree })
|
const router = createRouter({ routeTree })
|
||||||
|
|
||||||
// Register the router instance for type safety
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface Register {
|
interface Register {
|
||||||
router: typeof router
|
router: typeof router
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the app
|
|
||||||
const rootElement = document.getElementById('root')!
|
const rootElement = document.getElementById('root')!
|
||||||
if (!rootElement.innerHTML) {
|
if (!rootElement.innerHTML) {
|
||||||
const root = ReactDOM.createRoot(rootElement)
|
const root = ReactDOM.createRoot(rootElement)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Link } from "@tanstack/react-router";
|
|||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
||||||
<div className="max-w-3xl w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-6">
|
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-6">
|
||||||
<h1 className="text-4xl font-bold text-primary text-center">About</h1>
|
<h1 className="text-4xl font-bold text-primary text-center">About</h1>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Link } from '@tanstack/react-router';
|
|||||||
export default function AnalysisPage() {
|
export default function AnalysisPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
||||||
<div className="max-w-4xl w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
|
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
|
||||||
<h1 className="text-4xl font-bold text-primary text-center">
|
<h1 className="text-4xl font-bold text-primary text-center">
|
||||||
Firefly Analysis & Veritas Plugin
|
Firefly Analysis & Veritas Plugin
|
||||||
</h1>
|
</h1>
|
||||||
@@ -144,7 +144,7 @@ export default function AnalysisPage() {
|
|||||||
<div className="space-y-2 text-purple-700">
|
<div className="space-y-2 text-purple-700">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span className="font-medium min-w-[20px] text-purple-600">1.</span>
|
<span className="font-medium min-w-[20px] text-purple-600">1.</span>
|
||||||
<p>Launch the <span className="font-semibold">game</span> and your <span className="font-semibold">Firefly Private Server (PS)</span>.</p>
|
<p>Launch the <span className="font-semibold">game</span> and your <span className="font-semibold">Firefly GO Server (PS)</span>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<span className="font-medium min-w-[20px] text-purple-600">2.</span>
|
<span className="font-medium min-w-[20px] text-purple-600">2.</span>
|
||||||
|
|||||||
@@ -103,90 +103,79 @@ export default function DiffPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdateGame = async () => {
|
const handleUpdateGame = async () => {
|
||||||
|
const handleResult = (ok: boolean, error: string) => {
|
||||||
|
if (!ok) {
|
||||||
|
toast.error(error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsDiffLoading(true)
|
setIsDiffLoading(true)
|
||||||
|
|
||||||
if (!gameDir || !diffDir) {
|
if (!gameDir || !diffDir) {
|
||||||
toast.error('Please select game directory and diff file')
|
toast.error('Please select game directory and diff file')
|
||||||
setIsDiffLoading(false)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setStageType('Check Type HDiff')
|
setStageType('Check Type HDiff')
|
||||||
setProgressUpdate(0)
|
setProgressUpdate(0)
|
||||||
setMaxProgressUpdate(1)
|
setMaxProgressUpdate(1)
|
||||||
|
|
||||||
const [isOk, validType, errorType] = await DiffService.CheckTypeHDiff(diffDir)
|
const [isOk, validType, errorType] = await DiffService.CheckTypeHDiff(diffDir)
|
||||||
if (!isOk) {
|
if (!handleResult(isOk, errorType)) return
|
||||||
toast.error(errorType)
|
|
||||||
setIsDiffLoading(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setProgressUpdate(1)
|
setProgressUpdate(1)
|
||||||
|
|
||||||
if (validType === 'hdiffmap.json') {
|
if (['hdiffmap.json', 'hdifffiles.txt', 'hdifffiles.json'].includes(validType)) {
|
||||||
setStageType('Version Validate')
|
setStageType('Version Validate')
|
||||||
setProgressUpdate(0)
|
setProgressUpdate(0)
|
||||||
setMaxProgressUpdate(1)
|
setMaxProgressUpdate(1)
|
||||||
const [validVersion, errorVersion] = await DiffService.VersionValidate(gameDir, diffDir)
|
const [validVersion, errorVersion] = await DiffService.VersionValidate(gameDir, diffDir)
|
||||||
if (!validVersion) {
|
if (!handleResult(validVersion, errorVersion)) return
|
||||||
toast.error(errorVersion)
|
|
||||||
setIsDiffLoading(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setProgressUpdate(1)
|
setProgressUpdate(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSkipVerify = validType === 'manifest' || validType === 'hdifffiles.txt'
|
|
||||||
setStageType('Data Extract')
|
setStageType('Data Extract')
|
||||||
const [validData, errorData] = await DiffService.DataExtract(gameDir, diffDir, isSkipVerify)
|
const [validData, errorData] = await DiffService.DataExtract(gameDir, diffDir)
|
||||||
if (!validData) {
|
if (!handleResult(validData, errorData)) return
|
||||||
toast.error(errorData)
|
|
||||||
setIsDiffLoading(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setStageType('Cut Data')
|
setStageType('Cut Data')
|
||||||
setMessageUpdate('')
|
setMessageUpdate('')
|
||||||
const [validCut, errorCut] = await DiffService.CutData(gameDir)
|
const [validCut, errorCut] = await DiffService.CutData(gameDir)
|
||||||
if (!validCut) {
|
if (!handleResult(validCut, errorCut)) return
|
||||||
toast.error(errorCut)
|
|
||||||
setIsDiffLoading(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( validType === 'hdifffiles.txt' || validType === 'hdiffmap.json') {
|
switch (validType) {
|
||||||
|
case 'hdifffiles.txt':
|
||||||
|
case 'hdiffmap.json':
|
||||||
|
case 'hdifffiles.json': {
|
||||||
setStageType('Patch Data')
|
setStageType('Patch Data')
|
||||||
const [validPatch, errorPatch] = await DiffService.HDiffPatchData(gameDir)
|
const [validPatch, errorPatch] = await DiffService.HDiffPatchData(gameDir)
|
||||||
if (!validPatch) {
|
if (!handleResult(validPatch, errorPatch)) return
|
||||||
toast.error(errorPatch)
|
|
||||||
setIsDiffLoading(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setStageType('Delete old files')
|
setStageType('Delete old files')
|
||||||
const [validDelete, errorDelete] = await DiffService.DeleteFiles(gameDir)
|
const [validDelete, errorDelete] = await DiffService.DeleteFiles(gameDir)
|
||||||
if (!validDelete) {
|
if (!handleResult(validDelete, errorDelete)) return
|
||||||
toast.error(errorDelete)
|
break
|
||||||
setIsDiffLoading(false)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else if (validType === 'manifest') {
|
case 'manifest': {
|
||||||
setStageType('Patch Data')
|
setStageType('Patch Data')
|
||||||
const [validPatch, errorPatch] = await DiffService.LDiffPatchData(gameDir)
|
const [validPatch, errorPatch] = await DiffService.LDiffPatchData(gameDir)
|
||||||
if (!validPatch) {
|
if (!handleResult(validPatch, errorPatch)) return
|
||||||
toast.error(errorPatch)
|
break
|
||||||
setIsDiffLoading(false)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('Update game completed')
|
toast.success('Update game completed')
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
toast.error('PickFile error:', err)
|
toast.error(`PickFile error: ${err}`)
|
||||||
setIsDiffLoading(false)
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsDiffLoading(false)
|
setIsDiffLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-2 mx-4">
|
<div className="p-2 mx-4">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
|
|
||||||
export default function SrToolsPage() {
|
export default function FireflyToolsPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
||||||
<div className="max-w-4xl w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
|
<div className="w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
|
||||||
<h1 className="text-4xl font-bold text-primary text-center">SR Tools</h1>
|
<h1 className="text-4xl font-bold text-primary text-center">Firefly Tools</h1>
|
||||||
|
|
||||||
{/* Section 1: About SR Tools */}
|
{/* Section 1: About SR Tools */}
|
||||||
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
|
<div className="bg-blue-50 border-l-4 border-blue-400 p-6 rounded-r-lg">
|
||||||
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
|
<h2 className="text-2xl font-bold text-blue-800 flex items-center gap-2 mb-4">
|
||||||
<span>ℹ️</span>
|
<span>ℹ️</span>
|
||||||
<span>About SR Tools</span>
|
<span>About Firefly Tools</span>
|
||||||
</h2>
|
</h2>
|
||||||
<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>
|
<p>
|
||||||
This site is a another version of {" "}
|
This site is a another version of {" "}
|
||||||
<span className="font-semibold text-success">SR Tools {" "}</span>
|
<span className="font-semibold text-success">Firefly Tools {" "}</span>
|
||||||
developed by {" "}
|
developed by {" "}
|
||||||
<span className="font-semibold text-warning">Me {"(Kain)"}</span>
|
<span className="font-semibold text-warning">Me {"(Kain)"}</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -95,9 +95,24 @@ export default function SrToolsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-green-600 text-lg">🔌</div>
|
<div className="text-green-600 text-lg">🔌</div>
|
||||||
<p>Instantly apply setups to <span className="font-semibold text-warning">Firefly Private Server</span> using <span className="font-semibold">Connect PS</span> — no manual file uploads required.</p>
|
<p>Instantly apply setups to <span className="font-semibold text-warning">Firefly GO Server</span> using <span className="font-semibold">Connect PS</span> — no manual file uploads required.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="text-green-600 text-2xl">✨</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-green-800 text-lg">Extra Settings</h4>
|
||||||
|
<p className="text-green-700 mt-1">
|
||||||
|
Enhance your <span className="font-semibold text-warning">Firefly GO Server</span> experience with extra features:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside mt-2 space-y-1 text-green-700">
|
||||||
|
<li>🎭 <span className="font-medium">Hidden Game UI</span> — remove the entire game interface.</li>
|
||||||
|
<li>🚫 <span className="font-medium">Disable Censorship</span> — get rid of Lens Flare censor 💀.</li>
|
||||||
|
<li>🧪 <span className="font-medium">Theorycraft Mode</span> — configure HP, cycles, and more via the web.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-green-600 text-lg">📂</div>
|
<div className="text-green-600 text-lg">📂</div>
|
||||||
<p>Export and import full builds using <code className="bg-gray-200 px-2 py-1 rounded text-sm">freesr-data.json</code>.</p>
|
<p>Export and import full builds using <code className="bg-gray-200 px-2 py-1 rounded text-sm">freesr-data.json</code>.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,7 +3,7 @@ import { Link } from '@tanstack/react-router';
|
|||||||
export default function HowToPage() {
|
export default function HowToPage() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
<div className="min-h-screen bg-base-200 flex items-center justify-center p-6">
|
||||||
<div className="max-w-4xl w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
|
<div className=" w-full bg-base-100 shadow-xl rounded-2xl p-8 space-y-8">
|
||||||
<h1 className="text-4xl font-bold text-primary text-center">How to Use</h1>
|
<h1 className="text-4xl font-bold text-primary text-center">How to Use</h1>
|
||||||
|
|
||||||
{/* Section 1: Launcher Features */}
|
{/* Section 1: Launcher Features */}
|
||||||
@@ -28,13 +28,23 @@ export default function HowToPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="text-green-600 text-lg">📦</div>
|
<div className="text-green-600 text-2xl">📦</div>
|
||||||
<p>Patch and update game files using{" "}
|
<div>
|
||||||
<a href="/hdiff" className="link link-info font-mono">Hdiffz Tool</a>
|
<p className="text-green-800 font-semibold">
|
||||||
{" "}(HDiffPatch) — fast & lightweight incremental updates.
|
Patch & Update Game Files
|
||||||
|
</p>
|
||||||
|
<p className="text-green-700">
|
||||||
|
Use the{" "}
|
||||||
|
<a href="/diff" className="link link-info font-mono">Diff Tool</a>{" "}
|
||||||
|
(<span className="font-medium">DiffPatch</span>) for fast & lightweight incremental updates.
|
||||||
|
</p>
|
||||||
|
<p className="text-green-700 mt-1">
|
||||||
|
Supports <span className="font-semibold">Hdiff</span>, <span className="font-semibold">Ldiff</span>, and custom diff formats.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section 2: FireflyGo Commands */}
|
{/* Section 2: FireflyGo Commands */}
|
||||||
@@ -65,8 +75,60 @@ export default function HowToPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="text-blue-600 text-lg">✨</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h4 className="font-semibold text-blue-800 mb-1">Extra Settings</h4>
|
||||||
|
|
||||||
|
<div className="space-y-4 text-blue-700 text-sm">
|
||||||
|
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||||
|
<h5 className="font-semibold text-blue-800 flex items-center gap-2">
|
||||||
|
🎭 Hidden UI
|
||||||
|
</h5>
|
||||||
|
<p className="mt-1">
|
||||||
|
Instantly hides the entire game UI — often used in DIM showcase videos.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||||
|
<h5 className="font-semibold text-blue-800 flex items-center gap-2">
|
||||||
|
🚫 Disable Censorship
|
||||||
|
</h5>
|
||||||
|
<p className="mt-1">
|
||||||
|
Remove the Lens Flare censor effect 💀 for a cleaner experience.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||||
|
<h5 className="font-semibold text-blue-800 flex items-center gap-2">
|
||||||
|
🧪 Theorycraft Mode
|
||||||
|
</h5>
|
||||||
|
<p className="mt-1">
|
||||||
|
No need to type chat commands anymore — configure everything through the
|
||||||
|
web: adjust monster HP, set cycles, view logs, and more.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="mt-4 aspect-w-16 aspect-h-9">
|
||||||
|
<iframe
|
||||||
|
src="https://www.youtube.com/embed/uiKdFrvn9NQ"
|
||||||
|
title="Extra Settings Tutorial"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowFullScreen
|
||||||
|
className="rounded-lg w-full h-[300px]"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Commands List */}
|
{/* Commands List */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 mt-4">
|
||||||
<h3 className="text-lg font-semibold text-blue-800">Available Commands:</h3>
|
<h3 className="text-lg font-semibold text-blue-800">Available Commands:</h3>
|
||||||
|
|
||||||
{/* Theorycraft Toggle */}
|
{/* Theorycraft Toggle */}
|
||||||
@@ -103,16 +165,28 @@ export default function HowToPage() {
|
|||||||
<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>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h4 className="font-semibold text-blue-800 mb-1">HP Override <span className="text-red-600 text-sm">(Theorycraft only)</span></h4>
|
<h4 className="font-semibold text-blue-800 mb-1">
|
||||||
<div className="space-y-1 text-blue-700">
|
HP Override <span className="text-red-600 text-sm">(Theorycraft only)</span>
|
||||||
<p><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp WAVE HP</code> — Set monster HP for a specific wave</p>
|
</h4>
|
||||||
<p className="text-sm">Example: <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp 1 2000000</code> sets Wave 1 monster HP to 2,000,000</p>
|
<div className="space-y-2 text-blue-700 text-sm">
|
||||||
<p className="text-sm"><code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp 0</code> disables HP override</p>
|
<p>
|
||||||
|
<code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp N</code> — Set monster HP (only available in Theorycraft mode)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp 0</code> — Disable the set HP feature
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp Wave V1 V2 ...</code> — Set HP for each monster in a specific wave
|
||||||
|
</p>
|
||||||
|
<p className="ml-4">
|
||||||
|
Example: <code className="bg-blue-100 px-1 py-0.5 rounded text-sm">/hp 1 2000000 3000000</code> sets wave 1 monster1 HP=2,000,000 and monster2 HP=3,000,000
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Log Command */}
|
{/* Log Command */}
|
||||||
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
<div className="bg-white border border-blue-200 rounded-lg p-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ export default function LauncherPage() {
|
|||||||
<div className="flex justify-center items-center text-sm text-white/80">
|
<div className="flex justify-center items-center text-sm text-white/80">
|
||||||
<span>{downloadType}</span>
|
<span>{downloadType}</span>
|
||||||
<div className="flex items-center gap-4 ml-4">
|
<div className="flex items-center gap-4 ml-4">
|
||||||
<span className="text-cyan-400 font-semibold">{downloadSpeed.toFixed(1)} MB/s</span>
|
<span className="text-cyan-400 font-semibold">{downloadSpeed}</span>
|
||||||
<span className="text-white font-bold">{progressDownload.toFixed(1)}%</span>
|
<span className="text-white font-bold">{progressDownload.toFixed(1)}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,7 +414,7 @@ export default function LauncherPage() {
|
|||||||
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full"
|
className="h-full bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full"
|
||||||
initial={{ width: 0 }}
|
initial={{ width: 0 }}
|
||||||
animate={{ width: `${progressDownload}%` }}
|
animate={{ width: `${progressDownload}%` }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ type: "tween", ease: "linear", duration: 0.03 }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center text-xs text-white/60">
|
<div className="text-center text-xs text-white/60">
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function RootLayout() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="navbar-center hidden md:flex">
|
<div className="navbar-center hidden md:flex">
|
||||||
<ul className="menu menu-horizontal px-1">
|
<ul className="menu menu-horizontal px-1 gap-4">
|
||||||
<li><Link to="/">Home</Link></li>
|
<li><Link to="/">Home</Link></li>
|
||||||
<li>
|
<li>
|
||||||
<details>
|
<details>
|
||||||
@@ -87,7 +87,7 @@ function RootLayout() {
|
|||||||
<summary>Plugins</summary>
|
<summary>Plugins</summary>
|
||||||
<ul className="p-2">
|
<ul className="p-2">
|
||||||
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
|
<li><Link to="/analysis">Analysis (Veritas)</Link></li>
|
||||||
<li><Link to="/srtools">SrTools</Link></li>
|
<li><Link to="/srtools">Firefly Tools</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import SrToolsPage from '@/pages/srtools'
|
import FireflyToolsPage from '@/pages/fireflytools'
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
export const Route = createFileRoute('/srtools')({
|
export const Route = createFileRoute('/srtools')({
|
||||||
component: SrToolsPage,
|
component: FireflyToolsPage,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface LauncherState {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
gameRunning: boolean;
|
gameRunning: boolean;
|
||||||
progressDownload: number;
|
progressDownload: number;
|
||||||
downloadSpeed: number;
|
downloadSpeed: string;
|
||||||
launcherVersion: string;
|
launcherVersion: string;
|
||||||
updateData: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>;
|
updateData: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>;
|
||||||
setDownloadType: (value: string) => void;
|
setDownloadType: (value: string) => void;
|
||||||
@@ -24,7 +24,7 @@ interface LauncherState {
|
|||||||
setGameRunning: (value: boolean) => void;
|
setGameRunning: (value: boolean) => void;
|
||||||
setProgressDownload: (value: number) => void;
|
setProgressDownload: (value: number) => void;
|
||||||
setLauncherVersion: (value: string) => void;
|
setLauncherVersion: (value: string) => void;
|
||||||
setDownloadSpeed: (value: number) => void;
|
setDownloadSpeed: (value: string) => void;
|
||||||
setUpdateData: (value: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>) => void;
|
setUpdateData: (value: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ const useLauncherStore = create<LauncherState>((set, get) => ({
|
|||||||
proxyRunning: false,
|
proxyRunning: false,
|
||||||
gameRunning: false,
|
gameRunning: false,
|
||||||
progressDownload: 0,
|
progressDownload: 0,
|
||||||
downloadSpeed: 0,
|
downloadSpeed: "",
|
||||||
launcherVersion: "",
|
launcherVersion: "",
|
||||||
updateData: {
|
updateData: {
|
||||||
server: { isUpdate: false, isExists: false, version: "" },
|
server: { isUpdate: false, isExists: false, version: "" },
|
||||||
@@ -55,7 +55,7 @@ const useLauncherStore = create<LauncherState>((set, get) => ({
|
|||||||
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 }),
|
setLauncherVersion: (value: string) => set({ launcherVersion: value }),
|
||||||
setDownloadSpeed: (value: number) => set({ downloadSpeed: value }),
|
setDownloadSpeed: (value: string) => set({ downloadSpeed: value }),
|
||||||
setUpdateData: (value: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>) => set({ updateData: value }),
|
setUpdateData: (value: Record<'server' | 'proxy' | 'launcher', { isUpdate: boolean, isExists: boolean, version: string }>) => set({ updateData: value }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -10,6 +10,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
aead.dev/minisign v0.3.0 // indirect
|
aead.dev/minisign v0.3.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,7 +50,6 @@ require (
|
|||||||
github.com/wailsapp/go-webview2 v1.0.21 // 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.41.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
||||||
golang.org/x/net v0.43.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.8
|
google.golang.org/protobuf v1.36.8
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"firefly-launcher/pkg/hpatchz"
|
"firefly-launcher/pkg/hpatchz"
|
||||||
"firefly-launcher/pkg/models"
|
"firefly-launcher/pkg/models"
|
||||||
"firefly-launcher/pkg/sevenzip"
|
"firefly-launcher/pkg/sevenzip"
|
||||||
|
"firefly-launcher/pkg/verifier"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -21,6 +22,9 @@ func (h *DiffService) CheckTypeHDiff(patchPath string) (bool, string, string) {
|
|||||||
if ok, err := sevenzip.IsFileIn7z(patchPath, "hdifffiles.txt"); err == nil && ok {
|
if ok, err := sevenzip.IsFileIn7z(patchPath, "hdifffiles.txt"); err == nil && ok {
|
||||||
return true, "hdifffiles.txt", ""
|
return true, "hdifffiles.txt", ""
|
||||||
}
|
}
|
||||||
|
if ok, err := sevenzip.IsFileIn7z(patchPath, "hdifffiles.txt"); err == nil && ok {
|
||||||
|
return true, "hdifffiles.json", ""
|
||||||
|
}
|
||||||
if ok, err := sevenzip.IsFileIn7z(patchPath, "hdiffmap.json"); err == nil && ok {
|
if ok, err := sevenzip.IsFileIn7z(patchPath, "hdiffmap.json"); err == nil && ok {
|
||||||
return true, "hdiffmap.json", ""
|
return true, "hdiffmap.json", ""
|
||||||
}
|
}
|
||||||
@@ -32,11 +36,10 @@ func (h *DiffService) CheckTypeHDiff(patchPath string) (bool, string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *DiffService) VersionValidate(gamePath, patchPath string) (bool, string) {
|
func (h *DiffService) VersionValidate(gamePath, patchPath string) (bool, string) {
|
||||||
oldVersionData, err := models.ParseBinaryVersion(filepath.Join(gamePath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes"))
|
oldBinPath := filepath.Join(gamePath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes")
|
||||||
if err != nil {
|
if _, err := os.Stat(oldBinPath); err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(patchPath); err != nil {
|
if _, err := os.Stat(patchPath); err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
@@ -46,28 +49,104 @@ func (h *DiffService) VersionValidate(gamePath, patchPath string) (bool, string)
|
|||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
okFull, errFull := sevenzip.IsFileIn7z(patchPath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes")
|
||||||
|
okDiff, errDiff := sevenzip.IsFileIn7z(patchPath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes.hdiff")
|
||||||
|
|
||||||
|
if (errFull != nil && errDiff != nil) || (!okFull && !okDiff) {
|
||||||
|
return false, "BinaryVersion file not found in patch"
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempBinFile string
|
||||||
|
|
||||||
|
if okFull {
|
||||||
if err := sevenzip.ExtractAFileFromZip(patchPath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes", constant.TempUrl); err != nil {
|
if err := sevenzip.ExtractAFileFromZip(patchPath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes", constant.TempUrl); err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
|
tempBinFile = filepath.Join(constant.TempUrl, "BinaryVersion.bytes")
|
||||||
binPath := filepath.Join(constant.TempUrl, "BinaryVersion.bytes")
|
} else {
|
||||||
newVersionData, err := models.ParseBinaryVersion(binPath)
|
if err := sevenzip.ExtractAFileFromZip(patchPath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes.hdiff", constant.TempUrl); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
defer os.Remove(binPath)
|
patchBinFile := filepath.Join(constant.TempUrl, "BinaryVersion.bytes.hdiff")
|
||||||
|
sourceBinFile := oldBinPath
|
||||||
v := newVersionData.Subtract(*oldVersionData)
|
tempBinFile = filepath.Join(constant.TempUrl, "BinaryVersion.bytes")
|
||||||
if v != 0 && v != 1 {
|
if err := hpatchz.ApplyPatch(sourceBinFile, patchBinFile, tempBinFile); err != nil {
|
||||||
return false, fmt.Sprintf("the diff version %s not valid with game version %s", newVersionData, oldVersionData)
|
os.Remove(patchBinFile)
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
os.Remove(patchBinFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
okFullPkg, err1 := sevenzip.IsFileIn7z(patchPath, "pkg_version")
|
||||||
|
okDiffPkg, err2 := sevenzip.IsFileIn7z(patchPath, "pkg_version.hdiff")
|
||||||
|
if err1 != nil && err2 != nil {
|
||||||
|
return false, err1.Error()
|
||||||
|
}
|
||||||
|
if okFullPkg {
|
||||||
|
if err := sevenzip.ExtractAFileFromZip(patchPath, "pkg_version", constant.TempUrl); err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if okDiffPkg {
|
||||||
|
if err := sevenzip.ExtractAFileFromZip(patchPath, "pkg_version.hdiff", constant.TempUrl); err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
patchPkgFile := filepath.Join(constant.TempUrl, "pkg_version.hdiff")
|
||||||
|
sourcePkgFile := filepath.Join(gamePath, "pkg_version")
|
||||||
|
tempPkgFile := filepath.Join(constant.TempUrl, "pkg_version")
|
||||||
|
if err := hpatchz.ApplyPatch(sourcePkgFile, patchPkgFile, tempPkgFile); err != nil {
|
||||||
|
os.Remove(patchPkgFile)
|
||||||
|
os.Remove(tempPkgFile)
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
os.Remove(patchPkgFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempPkgFile := filepath.Join(constant.TempUrl, "pkg_version")
|
||||||
|
pkgDataList, err := models.LoadPkgVersion(tempPkgFile)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(tempPkgFile)
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
os.Remove(tempPkgFile)
|
||||||
|
|
||||||
|
// MD5 check BinaryVersion
|
||||||
|
flags := false
|
||||||
|
for _, pkgData := range pkgDataList {
|
||||||
|
if filepath.ToSlash(pkgData.RemoteFile) == "StarRail_Data/StreamingAssets/BinaryVersion.bytes" {
|
||||||
|
flags = true
|
||||||
|
md5, err := verifier.FileMD5(tempBinFile)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(tempBinFile)
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
if md5 != pkgData.MD5 {
|
||||||
|
os.Remove(tempBinFile)
|
||||||
|
return false, fmt.Sprintf("md5 mismatch for %s: expected %s, got %s",
|
||||||
|
tempBinFile, pkgData.MD5, md5)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !flags {
|
||||||
|
os.Remove(tempBinFile)
|
||||||
|
return false, "BinaryVersion file not found in patch"
|
||||||
|
}
|
||||||
|
_, err = models.ParseBinaryVersion(tempBinFile)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(tempBinFile)
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Remove(tempBinFile)
|
||||||
return true, "validated"
|
return true, "validated"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *DiffService) HDiffPatchData(gamePath string) (bool, string) {
|
func (h *DiffService) HDiffPatchData(gamePath string) (bool, string) {
|
||||||
hdiffMapPath := filepath.Join(gamePath, "hdiffmap.json")
|
hdiffMapPath := filepath.Join(gamePath, "hdiffmap.json")
|
||||||
hdiffFilesPath := filepath.Join(gamePath, "hdifffiles.txt")
|
hdiffFilesPath := filepath.Join(gamePath, "hdifffiles.txt")
|
||||||
|
hdifffilesJsonPath := filepath.Join(gamePath, "hdifffiles.json")
|
||||||
|
|
||||||
var jsonData struct {
|
var jsonData struct {
|
||||||
DiffMap []*models.HDiffData `json:"diff_map"`
|
DiffMap []*models.HDiffData `json:"diff_map"`
|
||||||
@@ -88,6 +167,14 @@ func (h *DiffService) HDiffPatchData(gamePath string) (bool, string) {
|
|||||||
for _, entry := range jsonDataDiffMap.DiffMap {
|
for _, entry := range jsonDataDiffMap.DiffMap {
|
||||||
jsonData.DiffMap = append(jsonData.DiffMap, entry.ToHDiffData())
|
jsonData.DiffMap = append(jsonData.DiffMap, entry.ToHDiffData())
|
||||||
}
|
}
|
||||||
|
} else if _, err := os.Stat(hdifffilesJsonPath); err == nil {
|
||||||
|
data, err := os.ReadFile(hdifffilesJsonPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &jsonData); err != nil {
|
||||||
|
return false, err.Error()
|
||||||
|
}
|
||||||
} else if _, err := os.Stat(hdiffFilesPath); err == nil {
|
} else if _, err := os.Stat(hdiffFilesPath); err == nil {
|
||||||
files, err := models.LoadHDiffFiles(hdiffFilesPath)
|
files, err := models.LoadHDiffFiles(hdiffFilesPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -99,6 +186,7 @@ func (h *DiffService) HDiffPatchData(gamePath string) (bool, string) {
|
|||||||
} else {
|
} else {
|
||||||
return false, "no hdiff entries map exist"
|
return false, "no hdiff entries map exist"
|
||||||
}
|
}
|
||||||
|
|
||||||
application.Get().Event.Emit("diff:stage", map[string]string{"stage": "Patching HDiff"})
|
application.Get().Event.Emit("diff:stage", map[string]string{"stage": "Patching HDiff"})
|
||||||
for i, entry := range jsonData.DiffMap {
|
for i, entry := range jsonData.DiffMap {
|
||||||
application.Get().Event.Emit(
|
application.Get().Event.Emit(
|
||||||
@@ -134,6 +222,7 @@ func (h *DiffService) HDiffPatchData(gamePath string) (bool, string) {
|
|||||||
|
|
||||||
os.Remove(filepath.Join(gamePath, "hdiffmap.json"))
|
os.Remove(filepath.Join(gamePath, "hdiffmap.json"))
|
||||||
os.Remove(filepath.Join(gamePath, "hdifffiles.txt"))
|
os.Remove(filepath.Join(gamePath, "hdifffiles.txt"))
|
||||||
|
os.Remove(filepath.Join(gamePath, "hdifffiles.json"))
|
||||||
return true, "patching completed"
|
return true, "patching completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ package diffService
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"firefly-launcher/pkg/constant"
|
"firefly-launcher/pkg/constant"
|
||||||
"firefly-launcher/pkg/verifier"
|
|
||||||
"firefly-launcher/pkg/sevenzip"
|
"firefly-launcher/pkg/sevenzip"
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"io"
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *DiffService) DataExtract(gamePath, patchPath string, isSkipVerify bool) (bool, string) {
|
func (h *DiffService) DataExtract(gamePath, patchPath string) (bool, string) {
|
||||||
os.RemoveAll(constant.TempUrl)
|
os.RemoveAll(constant.TempUrl)
|
||||||
if _, err := os.Stat(gamePath); err != nil {
|
if _, err := os.Stat(gamePath); err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
@@ -30,20 +30,7 @@ func (h *DiffService) DataExtract(gamePath, patchPath string, isSkipVerify bool)
|
|||||||
os.RemoveAll(constant.TempUrl)
|
os.RemoveAll(constant.TempUrl)
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
|
return true, "extract completed"
|
||||||
if !isSkipVerify {
|
|
||||||
validator, err := verifier.NewVerifier(gamePath, constant.TempUrl)
|
|
||||||
if err != nil {
|
|
||||||
os.RemoveAll(constant.TempUrl)
|
|
||||||
return false, err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validator.VerifyAll(); err != nil {
|
|
||||||
os.RemoveAll(constant.TempUrl)
|
|
||||||
return false, err.Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, "validated"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *DiffService) CutData(gamePath string) (bool, string) {
|
func (h *DiffService) CutData(gamePath string) (bool, string) {
|
||||||
|
|||||||
@@ -38,53 +38,17 @@ func (g *GitService) GetLatestLauncherVersion() (bool, string, string) {
|
|||||||
|
|
||||||
|
|
||||||
func (g *GitService) UpdateLauncherProgress(version string) (bool, string) {
|
func (g *GitService) UpdateLauncherProgress(version string) (bool, string) {
|
||||||
resp, err := http.Get(constant.LauncherGitUrl)
|
asset, ok := g.getReleaseAsset(version, constant.LauncherGitUrl, constant.LauncherFile)
|
||||||
if err != nil {
|
if !ok {
|
||||||
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"
|
return false, "no release found"
|
||||||
}
|
}
|
||||||
|
|
||||||
var assetWin models.AssetType
|
resp, err := http.Get(asset.BrowserDownloadURL)
|
||||||
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 {
|
if err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
err = selfupdate.Apply(resp.Body, selfupdate.Options{})
|
err = selfupdate.Apply(resp.Body, selfupdate.Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package gitService
|
package gitService
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"firefly-launcher/pkg/constant"
|
"firefly-launcher/pkg/constant"
|
||||||
"firefly-launcher/pkg/models"
|
"firefly-launcher/pkg/models"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -8,7 +9,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"encoding/json"
|
"time"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,72 +37,35 @@ func (g *GitService) GetLatestProxyVersion() (bool, string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitService) DownloadProxyProgress(version string) (bool, string) {
|
func (g *GitService) DownloadProxyProgress(version string) (bool, string) {
|
||||||
resp, err := http.Get(constant.ProxyGitUrl)
|
asset, ok := g.getReleaseAsset(version, constant.ProxyGitUrl, constant.ProxyZipFile)
|
||||||
if err != nil {
|
if !ok {
|
||||||
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"
|
return false, "no release found"
|
||||||
}
|
}
|
||||||
|
|
||||||
var assetWin models.AssetType
|
if err := os.MkdirAll(constant.ProxyStorageUrl, 0755); err != nil {
|
||||||
for _, asset := range releaseData.Assets {
|
|
||||||
if asset.Name == constant.ProxyZipFile {
|
|
||||||
assetWin = asset
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if assetWin.Name == "" {
|
|
||||||
return false, "no assets found"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Mkdir(constant.ProxyStorageUrl, 0755); err != nil {
|
|
||||||
if !os.IsExist(err) {
|
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
saveFile := filepath.Join(constant.ProxyStorageUrl, assetWin.Name)
|
|
||||||
|
|
||||||
resp, err = http.Get(assetWin.BrowserDownloadURL)
|
saveFile := filepath.Join(constant.ProxyStorageUrl, asset.Name)
|
||||||
|
tmpPath, err := g.downloadFileParallel(saveFile, asset.BrowserDownloadURL, 4, func(percent float64, speed string) {
|
||||||
|
application.Get().Event.Emit("download:proxy", map[string]interface{}{
|
||||||
|
"percent": fmt.Sprintf("%.2f", percent),
|
||||||
|
"speed": speed,
|
||||||
|
})
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
for i := 0; i < 3; i++ {
|
||||||
|
if err := os.Rename(tmpPath, saveFile); err == nil {
|
||||||
DownloadFile(saveFile, assetWin.BrowserDownloadURL, func(percent float64, speed float64) {
|
|
||||||
application.Get().Event.Emit("download:proxy", map[string]interface{}{
|
|
||||||
"percent": fmt.Sprintf("%.2f", percent),
|
|
||||||
"speed": fmt.Sprintf("%.2f", speed),
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return false, "failed to rename tmp file after retries"
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GitService) UnzipProxy() {
|
func (g *GitService) UnzipProxy() {
|
||||||
unzipParallel(filepath.Join(constant.ProxyStorageUrl, constant.ProxyZipFile), constant.ProxyStorageUrl)
|
g.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))
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
@@ -36,72 +37,35 @@ func (g *GitService) GetLatestServerVersion() (bool, string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GitService) DownloadServerProgress(version string) (bool, string) {
|
func (g *GitService) DownloadServerProgress(version string) (bool, string) {
|
||||||
resp, err := http.Get(constant.ServerGitUrl)
|
asset, ok := g.getReleaseAsset(version, constant.ServerGitUrl, constant.ServerZipFile)
|
||||||
if err != nil {
|
if !ok {
|
||||||
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"
|
return false, "no release found"
|
||||||
}
|
}
|
||||||
|
|
||||||
var assetWin models.AssetType
|
if err := os.MkdirAll(constant.ServerStorageUrl, 0755); err != nil {
|
||||||
for _, asset := range releaseData.Assets {
|
|
||||||
if asset.Name == constant.ServerZipFile {
|
|
||||||
assetWin = asset
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if assetWin.Name == "" {
|
|
||||||
return false, "no assets found"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Mkdir(constant.ServerStorageUrl, 0755); err != nil {
|
|
||||||
if !os.IsExist(err) {
|
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
saveFile := filepath.Join(constant.ServerStorageUrl, assetWin.Name)
|
saveFile := filepath.Join(constant.ServerStorageUrl, asset.Name)
|
||||||
|
tmpPath, err := g.downloadFileParallel(saveFile, asset.BrowserDownloadURL, 4, func(percent float64, speed string) {
|
||||||
resp, err = http.Get(assetWin.BrowserDownloadURL)
|
application.Get().Event.Emit("download:server", map[string]interface{}{
|
||||||
|
"percent": fmt.Sprintf("%.2f", percent),
|
||||||
|
"speed": speed,
|
||||||
|
})
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err.Error()
|
return false, err.Error()
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
for i := 0; i < 3; i++ {
|
||||||
|
if err := os.Rename(tmpPath, saveFile); err == nil {
|
||||||
DownloadFile(saveFile, assetWin.BrowserDownloadURL, func(percent float64, speed float64) {
|
|
||||||
application.Get().Event.Emit("download:server", map[string]interface{}{
|
|
||||||
"percent": fmt.Sprintf("%.2f", percent),
|
|
||||||
"speed": fmt.Sprintf("%.2f", speed),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return false, "failed to rename tmp file after retries"
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GitService) UnzipServer() {
|
func (g *GitService) UnzipServer() {
|
||||||
unzipParallel(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile), constant.ServerStorageUrl)
|
g.unzipParallel(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile), constant.ServerStorageUrl)
|
||||||
os.Remove(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile))
|
os.Remove(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile))
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,8 @@ package gitService
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"encoding/json"
|
||||||
|
"firefly-launcher/pkg/models"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
@@ -11,41 +13,57 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func humanFormat(bytes int64) string {
|
func HumanFormat(bytes float64) string {
|
||||||
n := float64(bytes)
|
if math.IsNaN(bytes) || math.IsInf(bytes, 0) {
|
||||||
for _, unit := range []string{"", "Ki", "Mi", "Gi"} {
|
return fmt.Sprintf("%v", bytes)
|
||||||
if math.Abs(n) < 1024.0 {
|
|
||||||
return fmt.Sprintf("%3.1f%sB", n, unit)
|
|
||||||
}
|
}
|
||||||
n /= 1024.0
|
if bytes == 0 {
|
||||||
|
return "0B"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%.1fTiB", n)
|
|
||||||
|
neg := bytes < 0
|
||||||
|
if neg {
|
||||||
|
bytes = -bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
units := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||||
|
i := 0
|
||||||
|
for bytes >= 1024 && i < len(units)-1 {
|
||||||
|
bytes /= 1024
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
res := fmt.Sprintf("%.1f%s", bytes, units[i])
|
||||||
|
if neg {
|
||||||
|
res = "-" + res
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
type WriteCounter struct {
|
type WriteCounter struct {
|
||||||
Total uint64
|
Total uint64
|
||||||
StartTime time.Time
|
StartTime time.Time
|
||||||
OnEmit func(percent float64, speedMBps float64)
|
OnEmit func(percent float64, speed string)
|
||||||
TotalSize int64
|
TotalSize int64
|
||||||
lastLoggedPercent int
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWriteCounter(total int64, onEmit func(percent float64, speedMBps float64)) *WriteCounter {
|
func NewWriteCounter(total int64, onEmit func(percent float64, speed string)) *WriteCounter {
|
||||||
return &WriteCounter{
|
return &WriteCounter{
|
||||||
StartTime: time.Now(),
|
StartTime: time.Now(),
|
||||||
TotalSize: total,
|
TotalSize: total,
|
||||||
lastLoggedPercent: -1,
|
|
||||||
OnEmit: onEmit,
|
OnEmit: onEmit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WriteCounter) Write(p []byte) (int, error) {
|
func (wc *WriteCounter) Add(n int) {
|
||||||
n := len(p)
|
wc.mu.Lock()
|
||||||
|
defer wc.mu.Unlock()
|
||||||
wc.Total += uint64(n)
|
wc.Total += uint64(n)
|
||||||
wc.PrintProgress()
|
wc.PrintProgress()
|
||||||
return n, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WriteCounter) PrintProgress() {
|
func (wc *WriteCounter) PrintProgress() {
|
||||||
@@ -53,63 +71,108 @@ func (wc *WriteCounter) PrintProgress() {
|
|||||||
if elapsed < 0.001 {
|
if elapsed < 0.001 {
|
||||||
elapsed = 0.001
|
elapsed = 0.001
|
||||||
}
|
}
|
||||||
|
speed := float64(wc.Total) / elapsed
|
||||||
speed := float64(wc.Total) / 1024 / 1024 / elapsed // MB/s
|
|
||||||
percent := float64(wc.Total) / float64(wc.TotalSize) * 100
|
percent := float64(wc.Total) / float64(wc.TotalSize) * 100
|
||||||
if wc.OnEmit != nil {
|
if wc.OnEmit != nil {
|
||||||
wc.OnEmit(percent, speed)
|
wc.OnEmit(percent, fmt.Sprintf("%s/s", HumanFormat(speed)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DownloadFile(filepath string, url string, onEmit func(percent float64, speed float64)) error {
|
func (g *GitService) downloadFileParallel(filePath, url string, numParts int, onEmit func(percent float64, speed string)) (tmpPath string, err error) {
|
||||||
tmpPath := filepath + ".tmp"
|
resp, err := http.Head(url)
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get file: %w", err)
|
return "", fmt.Errorf("failed to get head: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("bad status: %s", resp.Status)
|
return "", fmt.Errorf("bad status: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size := resp.ContentLength
|
||||||
|
tmpPath = filePath + ".tmp"
|
||||||
|
|
||||||
out, err := os.Create(tmpPath)
|
out, err := os.Create(tmpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create tmp file: %w", err)
|
return "", fmt.Errorf("failed to create tmp file: %w", err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
counter := NewWriteCounter(size, onEmit)
|
||||||
|
partSize := size / int64(numParts)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
for i := 0; i < numParts; i++ {
|
||||||
|
start := int64(i) * partSize
|
||||||
|
end := start + partSize - 1
|
||||||
|
if i == numParts-1 {
|
||||||
|
end = size - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
counter := NewWriteCounter(resp.ContentLength, onEmit)
|
_ = start
|
||||||
_, err = io.Copy(out, io.TeeReader(resp.Body, counter))
|
_ = end
|
||||||
if closeErr := out.Close(); closeErr != nil {
|
|
||||||
return fmt.Errorf("failed to close tmp file: %w", closeErr)
|
wg.Go(func() {
|
||||||
}
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, end))
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download file: %w", err)
|
return
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Delete destination file if it exists
|
buf := make([]byte, 32*1024)
|
||||||
if _, err := os.Stat(filepath); err == nil {
|
var written int64
|
||||||
if err := os.Remove(filepath); err != nil {
|
for {
|
||||||
return fmt.Errorf("failed to remove existing file: %w", err)
|
n, err := resp.Body.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
mu.Lock()
|
||||||
|
out.Seek(start+written, 0)
|
||||||
|
out.Write(buf[:n])
|
||||||
|
mu.Unlock()
|
||||||
|
written += int64(n)
|
||||||
|
counter.Add(n)
|
||||||
}
|
}
|
||||||
}
|
if err == io.EOF {
|
||||||
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
err = os.Rename(tmpPath, filepath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(300 * time.Millisecond)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to rename after retries: %w", err)
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
wg.Wait()
|
||||||
|
return tmpPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func unzipParallel(src string, dest string) error {
|
// --- Helper getReleaseAsset ---
|
||||||
|
func (g *GitService) getReleaseAsset(version, url, fileName string) (models.AssetType, bool) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return models.AssetType{}, false
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
var releases []*models.ReleaseType
|
||||||
|
if err := json.Unmarshal(body, &releases); err != nil || len(releases) == 0 {
|
||||||
|
return models.AssetType{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, release := range releases {
|
||||||
|
if release.TagName == version {
|
||||||
|
for _, asset := range release.Assets {
|
||||||
|
if asset.Name == fileName {
|
||||||
|
return asset, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return models.AssetType{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitService) unzipParallel(src string, dest string) error {
|
||||||
numCPU := runtime.NumCPU()
|
numCPU := runtime.NumCPU()
|
||||||
|
|
||||||
reserved := 1
|
reserved := 1
|
||||||
@@ -137,11 +200,11 @@ func unzipParallel(src string, dest string) error {
|
|||||||
jobs := make(chan job)
|
jobs := make(chan job)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
// Worker pool
|
|
||||||
for i := 0; i < maxWorkers; i++ {
|
for i := 0; i < maxWorkers; i++ {
|
||||||
|
|
||||||
wg.Go(func() {
|
wg.Go(func() {
|
||||||
for j := range jobs {
|
for j := range jobs {
|
||||||
err := extractFile(j.f, dest)
|
err := g.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)
|
||||||
}
|
}
|
||||||
@@ -149,17 +212,17 @@ func unzipParallel(src string, dest string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feed jobs
|
|
||||||
for _, f := range r.File {
|
for _, f := range r.File {
|
||||||
jobs <- job{f}
|
jobs <- job{f}
|
||||||
}
|
}
|
||||||
close(jobs)
|
close(jobs)
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractFile(f *zip.File, dest string) error {
|
func (g *GitService) extractFile(f *zip.File, dest string) error {
|
||||||
fp := filepath.Join(dest, f.Name)
|
fp := filepath.Join(dest, f.Name)
|
||||||
|
|
||||||
if f.FileInfo().IsDir() {
|
if f.FileInfo().IsDir() {
|
||||||
|
|||||||
1
main.go
1
main.go
@@ -98,7 +98,6 @@ func main() {
|
|||||||
DevToolsEnabled: true,
|
DevToolsEnabled: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
err = app.Run()
|
err = app.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const ProxyZipFile = "64bit.zip"
|
|||||||
const LauncherFile = "firefly-launcher.exe"
|
const LauncherFile = "firefly-launcher.exe"
|
||||||
const TempUrl = "./temp"
|
const TempUrl = "./temp"
|
||||||
|
|
||||||
const CurrentLauncherVersion = "1.5.1"
|
const CurrentLauncherVersion = "1.9.0"
|
||||||
|
|
||||||
type ToolFile string
|
type ToolFile string
|
||||||
|
|
||||||
@@ -19,15 +19,13 @@ const (
|
|||||||
Tool7zaDLL ToolFile = "bin/7za.dll"
|
Tool7zaDLL ToolFile = "bin/7za.dll"
|
||||||
Tool7zxaDLL ToolFile = "bin/7zxa.dll"
|
Tool7zxaDLL ToolFile = "bin/7zxa.dll"
|
||||||
ToolHPatchzExe ToolFile = "bin/hpatchz.exe"
|
ToolHPatchzExe ToolFile = "bin/hpatchz.exe"
|
||||||
ToolHDiffzExe ToolFile = "bin/hdiffz.exe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var RequiredFiles = map[ToolFile]string{
|
var RequiredFiles = map[ToolFile]string{
|
||||||
Tool7zaExe: "assets/7zip/7za.exe",
|
Tool7zaExe: "assets/7za.exe",
|
||||||
Tool7zaDLL: "assets/7zip/7za.dll",
|
Tool7zaDLL: "assets/7za.dll",
|
||||||
Tool7zxaDLL: "assets/7zip/7zxa.dll",
|
Tool7zxaDLL: "assets/7zxa.dll",
|
||||||
ToolHPatchzExe: "assets/HDiffPatch/hpatchz.exe",
|
ToolHPatchzExe: "assets/hpatchz.exe",
|
||||||
ToolHDiffzExe: "assets/HDiffPatch/hdiffz.exe",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t ToolFile) GetEmbedPath() string {
|
func (t ToolFile) GetEmbedPath() string {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BinaryVersion struct {
|
type BinaryVersion struct {
|
||||||
@@ -14,6 +13,7 @@ type BinaryVersion struct {
|
|||||||
Major int
|
Major int
|
||||||
Minor int
|
Minor int
|
||||||
Patch int
|
Patch int
|
||||||
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseBinaryVersion(path string) (*BinaryVersion, error) {
|
func ParseBinaryVersion(path string) (*BinaryVersion, error) {
|
||||||
@@ -23,58 +23,39 @@ func ParseBinaryVersion(path string) (*BinaryVersion, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
content := string(data)
|
content := string(data)
|
||||||
|
fmt.Println(content)
|
||||||
|
|
||||||
lastDash := strings.LastIndex(content, "-")
|
re := regexp.MustCompile(`([A-Za-z]+)(\d+)(?:\.(\d+))?(?:\.(\d+))?`)
|
||||||
if lastDash == -1 {
|
matches := re.FindStringSubmatch(content)
|
||||||
return nil, errors.New("no dash found in version string")
|
if len(matches) < 2 {
|
||||||
}
|
|
||||||
|
|
||||||
secondLastDash := strings.LastIndex(content[:lastDash], "-")
|
|
||||||
if secondLastDash == -1 {
|
|
||||||
return nil, errors.New("only one dash found in version string")
|
|
||||||
}
|
|
||||||
|
|
||||||
versionSlice := content[secondLastDash+1 : lastDash]
|
|
||||||
re := regexp.MustCompile(`^([A-Za-z]+)([\d\.]+)$`)
|
|
||||||
matches := re.FindStringSubmatch(versionSlice)
|
|
||||||
if len(matches) < 3 {
|
|
||||||
return nil, errors.New("invalid version format")
|
return nil, errors.New("invalid version format")
|
||||||
}
|
}
|
||||||
|
|
||||||
binaryVersion := BinaryVersion{
|
binaryVersion := BinaryVersion{
|
||||||
Name: matches[1],
|
Name: matches[1],
|
||||||
}
|
}
|
||||||
numbers := strings.Split(matches[2], ".")
|
if matches[2] != "" {
|
||||||
|
binaryVersion.Major, _ = strconv.Atoi(matches[2])
|
||||||
if len(numbers) > 0 {
|
|
||||||
binaryVersion.Major, err = strconv.Atoi(numbers[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
if len(matches) > 3 && matches[3] != "" {
|
||||||
|
binaryVersion.Minor, _ = strconv.Atoi(matches[3])
|
||||||
}
|
}
|
||||||
if len(numbers) > 1 {
|
if len(matches) > 4 && matches[4] != "" {
|
||||||
binaryVersion.Minor, err = strconv.Atoi(numbers[1])
|
binaryVersion.Patch, _ = strconv.Atoi(matches[4])
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(numbers) > 2 {
|
|
||||||
binaryVersion.Patch, err = strconv.Atoi(numbers[2])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binaryVersion.Data = data
|
||||||
return &binaryVersion, nil
|
return &binaryVersion, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *BinaryVersion) String() string {
|
func (v *BinaryVersion) String() string {
|
||||||
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
return fmt.Sprintf("%s-%d.%d.%d", v.Name, v.Major, v.Minor, v.Patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v BinaryVersion) ToInt() int {
|
func (v BinaryVersion) ToInt() int {
|
||||||
return v.Major*100 + v.Minor*10 + v.Patch
|
return v.Major*100 + v.Minor*10 + v.Patch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v BinaryVersion) Subtract(other BinaryVersion) int {
|
func (v BinaryVersion) Subtract(other *BinaryVersion) int {
|
||||||
return v.ToInt() - other.ToInt()
|
return v.ToInt() - other.ToInt()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func check(relPath string, expectedSize int64, expectedMD5, base string) error {
|
|||||||
fullPath, expectedSize, info.Size())
|
fullPath, expectedSize, info.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
md5Hash, err := fileMD5(fullPath)
|
md5Hash, err := FileMD5(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading %s: %w", fullPath, err)
|
return fmt.Errorf("error reading %s: %w", fullPath, err)
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func check(relPath string, expectedSize int64, expectedMD5, base string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileMD5(path string) (string, error) {
|
func FileMD5(path string) (string, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
Reference in New Issue
Block a user