diff --git a/.gitignore b/.gitignore index 91af0b8..3b7ab23 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ bin tests/ server/ proxy/ +proto/ temp/ frontend/dist frontend/node_modules diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a0e8ac7 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +PROTO_SRC=./proto +GEN_DEST=./pkg + +build_proto: + @echo Compiling Protobuf files... + protoc --go_out=${GEN_DEST} --go-grpc_out=${GEN_DEST} ${PROTO_SRC}/*.proto + @echo Done! \ No newline at end of file diff --git a/frontend/bindings/firefly-launcher/internal/appservice.js b/frontend/bindings/firefly-launcher/internal/app-service/appservice.js similarity index 81% rename from frontend/bindings/firefly-launcher/internal/appservice.js rename to frontend/bindings/firefly-launcher/internal/app-service/appservice.js index 81c8ca3..a1a0e5f 100644 --- a/frontend/bindings/firefly-launcher/internal/appservice.js +++ b/frontend/bindings/firefly-launcher/internal/app-service/appservice.js @@ -11,7 +11,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime"; * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function CloseAppAfterTimeout(timeout) { - let $resultPromise = /** @type {any} */($Call.ByID(982751559, timeout)); + let $resultPromise = /** @type {any} */($Call.ByID(1705931481, timeout)); return $resultPromise; } @@ -19,6 +19,6 @@ export function CloseAppAfterTimeout(timeout) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function GetCurrentLauncherVersion() { - let $resultPromise = /** @type {any} */($Call.ByID(2542090372)); + let $resultPromise = /** @type {any} */($Call.ByID(3575133982)); return $resultPromise; } diff --git a/frontend/bindings/firefly-launcher/internal/app-service/index.js b/frontend/bindings/firefly-launcher/internal/app-service/index.js new file mode 100644 index 0000000..d662869 --- /dev/null +++ b/frontend/bindings/firefly-launcher/internal/app-service/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as AppService from "./appservice.js"; +export { + AppService +}; diff --git a/frontend/bindings/firefly-launcher/internal/hdiffzservice.js b/frontend/bindings/firefly-launcher/internal/diff-service/diffservice.js similarity index 64% rename from frontend/bindings/firefly-launcher/internal/hdiffzservice.js rename to frontend/bindings/firefly-launcher/internal/diff-service/diffservice.js index 25c96d7..0d6fa5b 100644 --- a/frontend/bindings/firefly-launcher/internal/hdiffzservice.js +++ b/frontend/bindings/firefly-launcher/internal/diff-service/diffservice.js @@ -11,7 +11,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime"; * @returns {Promise<[boolean, string, string]> & { cancel(): void }} */ export function CheckTypeHDiff(patchPath) { - let $resultPromise = /** @type {any} */($Call.ByID(1068035136, patchPath)); + let $resultPromise = /** @type {any} */($Call.ByID(3717449114, patchPath)); return $resultPromise; } @@ -20,7 +20,7 @@ export function CheckTypeHDiff(patchPath) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function CutData(gamePath) { - let $resultPromise = /** @type {any} */($Call.ByID(3671642725, gamePath)); + let $resultPromise = /** @type {any} */($Call.ByID(2019290107, gamePath)); return $resultPromise; } @@ -31,7 +31,7 @@ export function CutData(gamePath) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function DataExtract(gamePath, patchPath, isSkipVerify) { - let $resultPromise = /** @type {any} */($Call.ByID(1843136452, gamePath, patchPath, isSkipVerify)); + let $resultPromise = /** @type {any} */($Call.ByID(2161622254, gamePath, patchPath, isSkipVerify)); return $resultPromise; } @@ -40,7 +40,7 @@ export function DataExtract(gamePath, patchPath, isSkipVerify) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function DeleteFiles(gamePath) { - let $resultPromise = /** @type {any} */($Call.ByID(989019003, gamePath)); + let $resultPromise = /** @type {any} */($Call.ByID(1103091613, gamePath)); return $resultPromise; } @@ -48,8 +48,17 @@ export function DeleteFiles(gamePath) { * @param {string} gamePath * @returns {Promise<[boolean, string]> & { cancel(): void }} */ -export function PatchData(gamePath) { - let $resultPromise = /** @type {any} */($Call.ByID(3608591627, gamePath)); +export function HDiffPatchData(gamePath) { + let $resultPromise = /** @type {any} */($Call.ByID(3944051994, gamePath)); + return $resultPromise; +} + +/** + * @param {string} gamePath + * @returns {Promise<[boolean, string]> & { cancel(): void }} + */ +export function LDiffPatchData(gamePath) { + let $resultPromise = /** @type {any} */($Call.ByID(360123238, gamePath)); return $resultPromise; } @@ -59,6 +68,6 @@ export function PatchData(gamePath) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function VersionValidate(gamePath, patchPath) { - let $resultPromise = /** @type {any} */($Call.ByID(3916254383, gamePath, patchPath)); + let $resultPromise = /** @type {any} */($Call.ByID(2105077257, gamePath, patchPath)); return $resultPromise; } diff --git a/frontend/bindings/firefly-launcher/internal/diff-service/index.js b/frontend/bindings/firefly-launcher/internal/diff-service/index.js new file mode 100644 index 0000000..0d65671 --- /dev/null +++ b/frontend/bindings/firefly-launcher/internal/diff-service/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as DiffService from "./diffservice.js"; +export { + DiffService +}; diff --git a/frontend/bindings/firefly-launcher/internal/fsservice.js b/frontend/bindings/firefly-launcher/internal/fs-service/fsservice.js similarity index 69% rename from frontend/bindings/firefly-launcher/internal/fsservice.js rename to frontend/bindings/firefly-launcher/internal/fs-service/fsservice.js index ccac2e6..b881887 100644 --- a/frontend/bindings/firefly-launcher/internal/fsservice.js +++ b/frontend/bindings/firefly-launcher/internal/fs-service/fsservice.js @@ -11,7 +11,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime"; * @returns {Promise & { cancel(): void }} */ export function DirExists(path) { - let $resultPromise = /** @type {any} */($Call.ByID(1291974398, path)); + let $resultPromise = /** @type {any} */($Call.ByID(1772289644, path)); return $resultPromise; } @@ -20,7 +20,7 @@ export function DirExists(path) { * @returns {Promise & { cancel(): void }} */ export function FileExists(path) { - let $resultPromise = /** @type {any} */($Call.ByID(3373618865, path)); + let $resultPromise = /** @type {any} */($Call.ByID(1782610747, path)); return $resultPromise; } @@ -30,7 +30,7 @@ export function FileExists(path) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function FileExistsInZip(archivePath, fileInside) { - let $resultPromise = /** @type {any} */($Call.ByID(1737012553, archivePath, fileInside)); + let $resultPromise = /** @type {any} */($Call.ByID(2509699047, archivePath, fileInside)); return $resultPromise; } @@ -39,15 +39,16 @@ export function FileExistsInZip(archivePath, fileInside) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function OpenFolder(path) { - let $resultPromise = /** @type {any} */($Call.ByID(2733319263, path)); + let $resultPromise = /** @type {any} */($Call.ByID(1635714001, path)); return $resultPromise; } /** + * @param {string} filter * @returns {Promise & { cancel(): void }} */ -export function PickFile() { - let $resultPromise = /** @type {any} */($Call.ByID(2181012076)); +export function PickFile(filter) { + let $resultPromise = /** @type {any} */($Call.ByID(3756474934, filter)); return $resultPromise; } @@ -55,7 +56,7 @@ export function PickFile() { * @returns {Promise & { cancel(): void }} */ export function PickFolder() { - let $resultPromise = /** @type {any} */($Call.ByID(3906046322)); + let $resultPromise = /** @type {any} */($Call.ByID(3654471460)); return $resultPromise; } @@ -64,7 +65,7 @@ export function PickFolder() { * @returns {Promise & { cancel(): void }} */ export function StartApp(path) { - let $resultPromise = /** @type {any} */($Call.ByID(3825990132, path)); + let $resultPromise = /** @type {any} */($Call.ByID(1267568402, path)); return $resultPromise; } @@ -73,6 +74,6 @@ export function StartApp(path) { * @returns {Promise & { cancel(): void }} */ export function StartWithConsole(path) { - let $resultPromise = /** @type {any} */($Call.ByID(2364569062, path)); + let $resultPromise = /** @type {any} */($Call.ByID(3249271428, path)); return $resultPromise; } diff --git a/frontend/bindings/firefly-launcher/internal/fs-service/index.js b/frontend/bindings/firefly-launcher/internal/fs-service/index.js new file mode 100644 index 0000000..2f42239 --- /dev/null +++ b/frontend/bindings/firefly-launcher/internal/fs-service/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as FSService from "./fsservice.js"; +export { + FSService +}; diff --git a/frontend/bindings/firefly-launcher/internal/gitservice.js b/frontend/bindings/firefly-launcher/internal/git-service/gitservice.js similarity index 72% rename from frontend/bindings/firefly-launcher/internal/gitservice.js rename to frontend/bindings/firefly-launcher/internal/git-service/gitservice.js index 659c557..602b412 100644 --- a/frontend/bindings/firefly-launcher/internal/gitservice.js +++ b/frontend/bindings/firefly-launcher/internal/git-service/gitservice.js @@ -11,7 +11,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime"; * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function DownloadProxyProgress(version) { - let $resultPromise = /** @type {any} */($Call.ByID(1951249093, version)); + let $resultPromise = /** @type {any} */($Call.ByID(3559275334, version)); return $resultPromise; } @@ -20,7 +20,7 @@ export function DownloadProxyProgress(version) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function DownloadServerProgress(version) { - let $resultPromise = /** @type {any} */($Call.ByID(314135954, version)); + let $resultPromise = /** @type {any} */($Call.ByID(1954767259, version)); return $resultPromise; } @@ -28,7 +28,7 @@ export function DownloadServerProgress(version) { * @returns {Promise<[boolean, string, string]> & { cancel(): void }} */ export function GetLatestLauncherVersion() { - let $resultPromise = /** @type {any} */($Call.ByID(3191972855)); + let $resultPromise = /** @type {any} */($Call.ByID(262637602)); return $resultPromise; } @@ -36,7 +36,7 @@ export function GetLatestLauncherVersion() { * @returns {Promise<[boolean, string, string]> & { cancel(): void }} */ export function GetLatestProxyVersion() { - let $resultPromise = /** @type {any} */($Call.ByID(1462362449)); + let $resultPromise = /** @type {any} */($Call.ByID(289488362)); return $resultPromise; } @@ -44,7 +44,7 @@ export function GetLatestProxyVersion() { * @returns {Promise<[boolean, string, string]> & { cancel(): void }} */ export function GetLatestServerVersion() { - let $resultPromise = /** @type {any} */($Call.ByID(1447982978)); + let $resultPromise = /** @type {any} */($Call.ByID(2918980975)); return $resultPromise; } @@ -52,7 +52,7 @@ export function GetLatestServerVersion() { * @returns {Promise & { cancel(): void }} */ export function UnzipProxy() { - let $resultPromise = /** @type {any} */($Call.ByID(4071181044)); + let $resultPromise = /** @type {any} */($Call.ByID(2563246729)); return $resultPromise; } @@ -60,7 +60,7 @@ export function UnzipProxy() { * @returns {Promise & { cancel(): void }} */ export function UnzipServer() { - let $resultPromise = /** @type {any} */($Call.ByID(4110296071)); + let $resultPromise = /** @type {any} */($Call.ByID(1126363284)); return $resultPromise; } @@ -69,6 +69,6 @@ export function UnzipServer() { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function UpdateLauncherProgress(version) { - let $resultPromise = /** @type {any} */($Call.ByID(2648938152, version)); + let $resultPromise = /** @type {any} */($Call.ByID(97272861, version)); return $resultPromise; } diff --git a/frontend/bindings/firefly-launcher/internal/git-service/index.js b/frontend/bindings/firefly-launcher/internal/git-service/index.js new file mode 100644 index 0000000..14b221b --- /dev/null +++ b/frontend/bindings/firefly-launcher/internal/git-service/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as GitService from "./gitservice.js"; +export { + GitService +}; diff --git a/frontend/bindings/firefly-launcher/internal/index.js b/frontend/bindings/firefly-launcher/internal/index.js deleted file mode 100644 index 5d00e90..0000000 --- a/frontend/bindings/firefly-launcher/internal/index.js +++ /dev/null @@ -1,16 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -import * as AppService from "./appservice.js"; -import * as FSService from "./fsservice.js"; -import * as GitService from "./gitservice.js"; -import * as HdiffzService from "./hdiffzservice.js"; -import * as LanguageService from "./languageservice.js"; -export { - AppService, - FSService, - GitService, - HdiffzService, - LanguageService -}; diff --git a/frontend/bindings/firefly-launcher/internal/language-service/index.js b/frontend/bindings/firefly-launcher/internal/language-service/index.js new file mode 100644 index 0000000..b7689be --- /dev/null +++ b/frontend/bindings/firefly-launcher/internal/language-service/index.js @@ -0,0 +1,8 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as LanguageService from "./languageservice.js"; +export { + LanguageService +}; diff --git a/frontend/bindings/firefly-launcher/internal/languageservice.js b/frontend/bindings/firefly-launcher/internal/language-service/languageservice.js similarity index 80% rename from frontend/bindings/firefly-launcher/internal/languageservice.js rename to frontend/bindings/firefly-launcher/internal/language-service/languageservice.js index 1824044..fa7b4b7 100644 --- a/frontend/bindings/firefly-launcher/internal/languageservice.js +++ b/frontend/bindings/firefly-launcher/internal/language-service/languageservice.js @@ -11,7 +11,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime"; * @returns {Promise<[boolean, string, string, string]> & { cancel(): void }} */ export function GetLanguage(path) { - let $resultPromise = /** @type {any} */($Call.ByID(3450750492, path)); + let $resultPromise = /** @type {any} */($Call.ByID(3574191687, path)); return $resultPromise; } @@ -22,6 +22,6 @@ export function GetLanguage(path) { * @returns {Promise<[boolean, string]> & { cancel(): void }} */ export function SetLanguage(path, text, voice) { - let $resultPromise = /** @type {any} */($Call.ByID(2793672496, path, text, voice)); + let $resultPromise = /** @type {any} */($Call.ByID(2768939795, path, text, voice)); return $resultPromise; } diff --git a/frontend/src/helper/launcher.ts b/frontend/src/helper/launcher.ts index 75bfcbc..190504c 100644 --- a/frontend/src/helper/launcher.ts +++ b/frontend/src/helper/launcher.ts @@ -1,7 +1,8 @@ import useLauncherStore from "@/stores/launcherStore"; -import { AppService, GitService } from "@bindings/firefly-launcher/internal"; +import { AppService, } from "@bindings/firefly-launcher/internal/app-service"; import { toast } from "react-toastify"; import { sleep } from "./sleep"; +import { GitService } from "@bindings/firefly-launcher/internal/git-service"; export async function CheckUpdateLauncher(): Promise<{ isUpdate: boolean; isExists: boolean; version: string }> { const [currentOk, currentVersion] = await AppService.GetCurrentLauncherVersion() diff --git a/frontend/src/helper/proxy.ts b/frontend/src/helper/proxy.ts index b22099f..702f71c 100644 --- a/frontend/src/helper/proxy.ts +++ b/frontend/src/helper/proxy.ts @@ -1,6 +1,7 @@ import useLauncherStore from "@/stores/launcherStore"; import useSettingStore from "@/stores/settingStore"; -import { FSService, GitService } from "@bindings/firefly-launcher/internal"; +import { FSService } from "@bindings/firefly-launcher/internal/fs-service"; +import { GitService } from "@bindings/firefly-launcher/internal/git-service"; import { toast } from "react-toastify"; export async function CheckUpdateProxy(proxyPath: string, proxyVersion: string) : Promise<{isUpdate: boolean, isExists: boolean, version: string}> { diff --git a/frontend/src/helper/server.ts b/frontend/src/helper/server.ts index f5d592f..3a0116c 100644 --- a/frontend/src/helper/server.ts +++ b/frontend/src/helper/server.ts @@ -1,6 +1,7 @@ import useLauncherStore from '@/stores/launcherStore'; import useSettingStore from '@/stores/settingStore'; -import { FSService, GitService } from '@bindings/firefly-launcher/internal'; +import { FSService } from '@bindings/firefly-launcher/internal/fs-service'; +import { GitService } from '@bindings/firefly-launcher/internal/git-service'; import { toast } from 'react-toastify'; export async function CheckUpdateServer( diff --git a/frontend/src/hooks/useGlobalEvents.tsx b/frontend/src/hooks/useGlobalEvents.tsx index dba5f5f..6f312f1 100644 --- a/frontend/src/hooks/useGlobalEvents.tsx +++ b/frontend/src/hooks/useGlobalEvents.tsx @@ -12,6 +12,7 @@ export function useGlobalEvents({ setProgressDownload, setDownloadSpeed, setMessageUpdate, + setStageType, }: { setGameRunning: (v: boolean) => void; @@ -22,6 +23,7 @@ export function useGlobalEvents({ setProgressDownload: (v: number) => void; setDownloadSpeed: (v: number) => void; setMessageUpdate: (v: string) => void; + setStageType: (v: string) => void, }) { useEffect(() => { const onGameExit = () => setGameRunning(false); @@ -45,14 +47,20 @@ export function useGlobalEvents({ setMessageUpdate(message); }; + const onStageUpdate = (event: any) => { + const { stage } = event.data[0]; + setStageType(stage); + }; + Events.On("download:server", onDownload); Events.On("download:proxy", onDownload); Events.On("game:exit", onGameExit); Events.On("server:exit", onServerExit); Events.On("proxy:exit", onProxyExit); - Events.On("hdiffz:progress", onUpdateProgress); - Events.On("hdiffz:message", onMessageUpdate); - Events.On("hdiffz:error", (event: any) => { + Events.On("diff:progress", onUpdateProgress); + Events.On("diff:message", onMessageUpdate); + Events.On("diff:stage", onStageUpdate); + Events.On("diff:error", (event: any) => { const { message } = event.data[0]; toast.error(message); }); @@ -63,8 +71,9 @@ export function useGlobalEvents({ Events.Off("game:exit"); Events.Off("server:exit"); Events.Off("proxy:exit"); - Events.Off("hdiffz:progress"); - Events.Off("hdiffz:message"); + Events.Off("diff:progress") + Events.Off("diff:message"); + Events.Off("diff:stage"); Events.Off("version:check"); }; }, []); diff --git a/frontend/src/pages/hdiffz/index.tsx b/frontend/src/pages/diff/index.tsx similarity index 87% rename from frontend/src/pages/hdiffz/index.tsx rename to frontend/src/pages/diff/index.tsx index 2dd68fb..b24afa0 100644 --- a/frontend/src/pages/hdiffz/index.tsx +++ b/frontend/src/pages/diff/index.tsx @@ -2,11 +2,12 @@ import useSettingStore from "@/stores/settingStore" import { Check, Folder, File, X, Settings } from "lucide-react" import { useEffect } from "react" import { toast } from "react-toastify" -import { FSService, HdiffzService} from "@bindings/firefly-launcher/internal" +import { DiffService} from "@bindings/firefly-launcher/internal/diff-service" +import { FSService } from "@bindings/firefly-launcher/internal/fs-service" import { motion } from "motion/react" -import useHdiffzStore from "@/stores/hdiffzStore" +import useDiffStore from "@/stores/diffStore" -export default function HdiffzPage() { +export default function DiffPage() { const { gameDir, setGameDir } = useSettingStore() const { isLoading, @@ -27,7 +28,7 @@ export default function HdiffzPage() { setStageType, messageUpdate, setMessageUpdate - } = useHdiffzStore() + } = useDiffStore() useEffect(() => { const getLanguage = async () => { @@ -78,7 +79,7 @@ export default function HdiffzPage() { const handlePickDiffFile = async () => { try { setIsLoading({game: false, diff: true}) - const basePath = await FSService.PickFile() + const basePath = await FSService.PickFile("") if (basePath) { if (!basePath.endsWith(".7z") && !basePath.endsWith(".zip") && !basePath.endsWith(".rar")) { toast.error('Not valid file type') @@ -86,9 +87,15 @@ export default function HdiffzPage() { setDiffDir('') return } - const [exists, error] = await FSService.FileExistsInZip(basePath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes") - if (!exists) { - toast.error(error) + const [isOk, validType, errorType] = await DiffService.CheckTypeHDiff(basePath) + if (!isOk) { + toast.error(errorType) + setDiffCheckResult('error') + setDiffDir('') + return + } + if (validType == "") { + toast.error('Not valid file type') setDiffCheckResult('error') setDiffDir('') return @@ -119,7 +126,7 @@ export default function HdiffzPage() { setStageType('Check Type HDiff') setProgressUpdate(0) setMaxProgressUpdate(1) - const [isOk, validType, errorType] = await HdiffzService.CheckTypeHDiff(diffDir) + const [isOk, validType, errorType] = await DiffService.CheckTypeHDiff(diffDir) if (!isOk) { toast.error(errorType) setIsDiffLoading(false) @@ -127,11 +134,13 @@ export default function HdiffzPage() { } setProgressUpdate(1) + + if (validType === 'hdiffmap.json') { setStageType('Version Validate') setProgressUpdate(0) setMaxProgressUpdate(1) - const [validVersion, errorVersion] = await HdiffzService.VersionValidate(gameDir, diffDir) + const [validVersion, errorVersion] = await DiffService.VersionValidate(gameDir, diffDir) if (!validVersion) { toast.error(errorVersion) setIsDiffLoading(false) @@ -140,8 +149,9 @@ export default function HdiffzPage() { setProgressUpdate(1) } + const isSkipVerify = validType === 'manifest' || validType === 'hdifffiles.txt' setStageType('Data Extract') - const [validData, errorData] = await HdiffzService.DataExtract(gameDir, diffDir, validType === 'hdifffiles.txt') + const [validData, errorData] = await DiffService.DataExtract(gameDir, diffDir, isSkipVerify) if (!validData) { toast.error(errorData) setIsDiffLoading(false) @@ -150,28 +160,38 @@ export default function HdiffzPage() { setStageType('Cut Data') setMessageUpdate('') - const [validCut, errorCut] = await HdiffzService.CutData(gameDir) + const [validCut, errorCut] = await DiffService.CutData(gameDir) if (!validCut) { toast.error(errorCut) setIsDiffLoading(false) return } - setStageType('Patch Data') - const [validPatch, errorPatch] = await HdiffzService.PatchData(gameDir) - if (!validPatch) { - toast.error(errorPatch) - setIsDiffLoading(false) - return + if ( validType === 'hdifffiles.txt' || validType === 'hdiffmap.json') { + setStageType('Patch Data') + const [validPatch, errorPatch] = await DiffService.HDiffPatchData(gameDir) + if (!validPatch) { + toast.error(errorPatch) + setIsDiffLoading(false) + return + } + setStageType('Delete old files') + const [validDelete, errorDelete] = await DiffService.DeleteFiles(gameDir) + if (!validDelete) { + toast.error(errorDelete) + setIsDiffLoading(false) + return + } + } else if (validType === 'manifest') { + setStageType('Patch Data') + const [validPatch, errorPatch] = await DiffService.LDiffPatchData(gameDir) + if (!validPatch) { + toast.error(errorPatch) + setIsDiffLoading(false) + return + } } - setStageType('Delete old files') - const [validDelete, errorDelete] = await HdiffzService.DeleteFiles(gameDir) - if (!validDelete) { - toast.error(errorDelete) - setIsDiffLoading(false) - return - } toast.success('Update game completed') } catch (err: any) { console.error(err) diff --git a/frontend/src/pages/language/index.tsx b/frontend/src/pages/language/index.tsx index 9f5c1fd..4b14238 100644 --- a/frontend/src/pages/language/index.tsx +++ b/frontend/src/pages/language/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { Folder, Settings, Check, X, Globe, Mic } from 'lucide-react' -import { FSService } from '@bindings/firefly-launcher/internal' -import { LanguageService } from '@bindings/firefly-launcher/internal' +import { FSService } from '@bindings/firefly-launcher/internal/fs-service' +import { LanguageService } from '@bindings/firefly-launcher/internal/language-service' import { toast } from 'react-toastify' import useSettingStore from '@/stores/settingStore' diff --git a/frontend/src/pages/launcher/index.tsx b/frontend/src/pages/launcher/index.tsx index a0dec1c..c31a622 100644 --- a/frontend/src/pages/launcher/index.tsx +++ b/frontend/src/pages/launcher/index.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react'; import { Play, Menu, FolderOpen, MessageCircleQuestionMark } from 'lucide-react'; -import { FSService, AppService } from '@bindings/firefly-launcher/internal'; +import { AppService } from '@bindings/firefly-launcher/internal/app-service'; +import { FSService } from '@bindings/firefly-launcher/internal/fs-service'; import { toast } from 'react-toastify'; import path from 'path-browserify' import useSettingStore from '@/stores/settingStore'; @@ -120,7 +121,7 @@ export default function LauncherPage() { const handlePickFile = async () => { try { setIsLoading(true) - const basePath = await FSService.PickFile() + const basePath = await FSService.PickFile("exe") if (basePath.endsWith("StarRail.exe") || basePath.endsWith("launcher.exe")) { const normalized = basePath.replace(/\\/g, '/') const folderPath = path.dirname(normalized) diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 8d0151d..f4f107a 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -12,7 +12,7 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as SrtoolsRouteImport } from './routes/srtools' import { Route as LanguageRouteImport } from './routes/language' import { Route as HowtoRouteImport } from './routes/howto' -import { Route as HdiffzRouteImport } from './routes/hdiffz' +import { Route as DiffRouteImport } from './routes/diff' import { Route as AnalysisRouteImport } from './routes/analysis' import { Route as AboutRouteImport } from './routes/about' import { Route as IndexRouteImport } from './routes/index' @@ -32,9 +32,9 @@ const HowtoRoute = HowtoRouteImport.update({ path: '/howto', getParentRoute: () => rootRouteImport, } as any) -const HdiffzRoute = HdiffzRouteImport.update({ - id: '/hdiffz', - path: '/hdiffz', +const DiffRoute = DiffRouteImport.update({ + id: '/diff', + path: '/diff', getParentRoute: () => rootRouteImport, } as any) const AnalysisRoute = AnalysisRouteImport.update({ @@ -57,7 +57,7 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/about': typeof AboutRoute '/analysis': typeof AnalysisRoute - '/hdiffz': typeof HdiffzRoute + '/diff': typeof DiffRoute '/howto': typeof HowtoRoute '/language': typeof LanguageRoute '/srtools': typeof SrtoolsRoute @@ -66,7 +66,7 @@ export interface FileRoutesByTo { '/': typeof IndexRoute '/about': typeof AboutRoute '/analysis': typeof AnalysisRoute - '/hdiffz': typeof HdiffzRoute + '/diff': typeof DiffRoute '/howto': typeof HowtoRoute '/language': typeof LanguageRoute '/srtools': typeof SrtoolsRoute @@ -76,7 +76,7 @@ export interface FileRoutesById { '/': typeof IndexRoute '/about': typeof AboutRoute '/analysis': typeof AnalysisRoute - '/hdiffz': typeof HdiffzRoute + '/diff': typeof DiffRoute '/howto': typeof HowtoRoute '/language': typeof LanguageRoute '/srtools': typeof SrtoolsRoute @@ -87,7 +87,7 @@ export interface FileRouteTypes { | '/' | '/about' | '/analysis' - | '/hdiffz' + | '/diff' | '/howto' | '/language' | '/srtools' @@ -96,7 +96,7 @@ export interface FileRouteTypes { | '/' | '/about' | '/analysis' - | '/hdiffz' + | '/diff' | '/howto' | '/language' | '/srtools' @@ -105,7 +105,7 @@ export interface FileRouteTypes { | '/' | '/about' | '/analysis' - | '/hdiffz' + | '/diff' | '/howto' | '/language' | '/srtools' @@ -115,7 +115,7 @@ export interface RootRouteChildren { IndexRoute: typeof IndexRoute AboutRoute: typeof AboutRoute AnalysisRoute: typeof AnalysisRoute - HdiffzRoute: typeof HdiffzRoute + DiffRoute: typeof DiffRoute HowtoRoute: typeof HowtoRoute LanguageRoute: typeof LanguageRoute SrtoolsRoute: typeof SrtoolsRoute @@ -144,11 +144,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HowtoRouteImport parentRoute: typeof rootRouteImport } - '/hdiffz': { - id: '/hdiffz' - path: '/hdiffz' - fullPath: '/hdiffz' - preLoaderRoute: typeof HdiffzRouteImport + '/diff': { + id: '/diff' + path: '/diff' + fullPath: '/diff' + preLoaderRoute: typeof DiffRouteImport parentRoute: typeof rootRouteImport } '/analysis': { @@ -179,7 +179,7 @@ const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AboutRoute: AboutRoute, AnalysisRoute: AnalysisRoute, - HdiffzRoute: HdiffzRoute, + DiffRoute: DiffRoute, HowtoRoute: HowtoRoute, LanguageRoute: LanguageRoute, SrtoolsRoute: SrtoolsRoute, diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index 742eece..6607242 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -3,7 +3,7 @@ import ThemeController from '../components/themeController' import { ToastContainer } from 'react-toastify' import { useGlobalEvents } from '@/hooks'; import useLauncherStore from '@/stores/launcherStore'; -import useHdiffzStore from '@/stores/hdiffzStore'; +import useDiffStore from '@/stores/diffStore'; export const Route = createRootRoute({ component: RootLayout @@ -11,7 +11,7 @@ export const Route = createRootRoute({ function RootLayout() { const { setGameRunning, setServerRunning, setProxyRunning, setProgressDownload, setDownloadSpeed } = useLauncherStore() - const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate } = useHdiffzStore() + const { setProgressUpdate, setMaxProgressUpdate, setMessageUpdate, setStageType } = useDiffStore() useGlobalEvents({ setGameRunning, setServerRunning, @@ -21,6 +21,7 @@ function RootLayout() { setProgressDownload, setDownloadSpeed, setMessageUpdate, + setStageType }); return ( @@ -40,7 +41,7 @@ function RootLayout() { Tools
  • Language
  • -
  • Hdiffz
  • +
  • Diff
  • @@ -77,7 +78,7 @@ function RootLayout() { Tools
    • Language
    • -
    • Hdiffz
    • +
    • Diff
  • diff --git a/frontend/src/routes/diff.tsx b/frontend/src/routes/diff.tsx new file mode 100644 index 0000000..f7b40ce --- /dev/null +++ b/frontend/src/routes/diff.tsx @@ -0,0 +1,7 @@ +import DiffPage from '@/pages/diff' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/diff')({ + component: DiffPage, +}) + diff --git a/frontend/src/routes/hdiffz.tsx b/frontend/src/routes/hdiffz.tsx deleted file mode 100644 index 442ff5b..0000000 --- a/frontend/src/routes/hdiffz.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import HdiffzPage from '@/pages/hdiffz' -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/hdiffz')({ - component: HdiffzPage, -}) - diff --git a/frontend/src/stores/hdiffzStore.ts b/frontend/src/stores/diffStore.ts similarity index 93% rename from frontend/src/stores/hdiffzStore.ts rename to frontend/src/stores/diffStore.ts index 9efee7f..dccb11a 100644 --- a/frontend/src/stores/hdiffzStore.ts +++ b/frontend/src/stores/diffStore.ts @@ -1,7 +1,7 @@ import { create } from 'zustand' -interface LauncherState { +interface DiffState { folderCheckResult: 'success' | 'error' | null, isLoading: {game: boolean, diff: boolean}, diffDir: string, @@ -22,7 +22,7 @@ interface LauncherState { setStageType: (value: string) => void, } -const useLauncherStore = create((set, get) => ({ +const useDiffStore = create((set, get) => ({ isLoading: {game: false, diff: false}, folderCheckResult: null, diffDir: "", @@ -43,4 +43,4 @@ const useLauncherStore = create((set, get) => ({ setStageType: (value: string) => set({ stageType: value }), })); -export default useLauncherStore; \ No newline at end of file +export default useDiffStore; \ No newline at end of file diff --git a/go.mod b/go.mod index f8aea04..c01fbf7 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/klauspost/compress v1.18.0 github.com/leaanthony/go-ansi-parser v1.6.1 // indirect github.com/leaanthony/u v1.1.1 // indirect github.com/lmittmann/tint v1.1.2 // indirect @@ -49,8 +50,9 @@ require ( github.com/wailsapp/mimetype v1.4.1 // 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 // indirect + golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b golang.org/x/net v0.43.0 // indirect + google.golang.org/protobuf v1.36.8 gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 8e01c00..b70e343 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7 github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 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/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -147,6 +149,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/app-serrvice.go b/internal/app-service/app.go similarity index 95% rename from internal/app-serrvice.go rename to internal/app-service/app.go index 4ec6184..ebf4350 100644 --- a/internal/app-serrvice.go +++ b/internal/app-service/app.go @@ -1,4 +1,4 @@ -package internal +package appService import ( "firefly-launcher/pkg/constant" diff --git a/internal/hdiffz-service.go b/internal/diff-service/hdiffz.go similarity index 51% rename from internal/hdiffz-service.go rename to internal/diff-service/hdiffz.go index 9cd846d..4a6f289 100644 --- a/internal/hdiffz-service.go +++ b/internal/diff-service/hdiffz.go @@ -1,15 +1,13 @@ -package internal +package diffService import ( "bufio" "encoding/json" "firefly-launcher/pkg/constant" + "firefly-launcher/pkg/hpatchz" "firefly-launcher/pkg/models" "firefly-launcher/pkg/sevenzip" - "firefly-launcher/pkg/verifier" - "firefly-launcher/pkg/hpatchz" "fmt" - "io" "os" "path/filepath" "strings" @@ -17,21 +15,23 @@ import ( "github.com/wailsapp/wails/v3/pkg/application" ) -type HdiffzService struct{} +type DiffService struct{} -func (h *HdiffzService) CheckTypeHDiff(patchPath string) (bool, string, string) { - isFileInTxt, _ := sevenzip.IsFileIn7z(patchPath, "hdifffiles.txt") - if isFileInTxt { +func (h *DiffService) CheckTypeHDiff(patchPath string) (bool, string, string) { + if ok, err := sevenzip.IsFileIn7z(patchPath, "manifest"); err == nil && ok { + return true, "manifest", "" + } + + if ok, err := sevenzip.IsFileIn7z(patchPath, "hdifffiles.txt"); err == nil && ok { return true, "hdifffiles.txt", "" } - isFileInJson, _ := sevenzip.IsFileIn7z(patchPath, "hdiffmap.json") - if isFileInJson { + if ok, err := sevenzip.IsFileIn7z(patchPath, "hdiffmap.json"); err == nil && ok { return true, "hdiffmap.json", "" } return false, "", "not found hdifffiles.txt or hdiffmap.json" } -func (h *HdiffzService) 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")) if err != nil { return false, err.Error() @@ -65,95 +65,7 @@ func (h *HdiffzService) VersionValidate(gamePath, patchPath string) (bool, strin return true, "validated" } -func (h *HdiffzService) DataExtract(gamePath, patchPath string, isSkipVerify bool) (bool, string) { - if _, err := os.Stat(gamePath); err != nil { - return false, err.Error() - } - - if _, err := os.Stat(patchPath); err != nil { - return false, err.Error() - } - - if _, err := os.Stat(constant.TempUrl); os.IsNotExist(err) { - if err := os.MkdirAll(constant.TempUrl, os.ModePerm); err != nil { - return false, err.Error() - } - } - - if err := sevenzip.ExtractAllFilesFromZip(patchPath, constant.TempUrl); err != nil { - os.RemoveAll(constant.TempUrl) - return false, err.Error() - } - - 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 *HdiffzService) CutData(gamePath string) (bool, string) { - if _, err := os.Stat(constant.TempUrl); os.IsNotExist(err) { - return false, err.Error() - } - - err := filepath.Walk(constant.TempUrl, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - relPath, err := filepath.Rel(constant.TempUrl, path) - if err != nil { - return err - } - destPath := filepath.Join(gamePath, relPath) - application.Get().Event.Emit("hdiffz:message", map[string]string{"message": destPath}) - if info.IsDir() { - return os.MkdirAll(destPath, os.ModePerm) - } - - if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil { - return err - } - - if err := os.Rename(path, destPath); err != nil { - srcFile, err := os.Open(path) - if err != nil { - return err - } - defer srcFile.Close() - - dstFile, err := os.Create(destPath) - if err != nil { - return err - } - defer dstFile.Close() - - if _, err := io.Copy(dstFile, srcFile); err != nil { - return err - } - _ = os.Remove(path) - } - return nil - }) - - if err != nil { - return false, err.Error() - } - - return true, "cut completed" -} - -func (h *HdiffzService) PatchData(gamePath string) (bool, string) { +func (h *DiffService) HDiffPatchData(gamePath string) (bool, string) { hdiffMapPath := filepath.Join(gamePath, "hdiffmap.json") hdiffFilesPath := filepath.Join(gamePath, "hdifffiles.txt") @@ -187,10 +99,10 @@ func (h *HdiffzService) PatchData(gamePath string) (bool, string) { } else { return false, "no hdiff entries map exist" } - + application.Get().Event.Emit("diff:stage", map[string]string{"stage": "Patching HDiff"}) for i, entry := range jsonData.DiffMap { application.Get().Event.Emit( - "hdiffz:progress", map[string]int{ + "diff:progress", map[string]int{ "progress": i, "maxProgress": len(jsonData.DiffMap), }) @@ -199,20 +111,13 @@ func (h *HdiffzService) PatchData(gamePath string) (bool, string) { patchFile := filepath.Join(gamePath, entry.PatchFileName) targetFile := filepath.Join(gamePath, entry.TargetFileName) - // Check patch file tồn tại chưa if _, err := os.Stat(patchFile); os.IsNotExist(err) { continue } - // Nếu không có source file hoặc SourceFileName rỗng → apply_patch_empty if entry.SourceFileName == "" { - err := hpatchz.ApplyPatchEmpty(patchFile, targetFile) - if err != nil { - fmt.Printf("%s failed to patch! %v\n", entry.TargetFileName, err) - _ = os.Remove(patchFile) - return false, err.Error() - } - _ = os.Remove(patchFile) + hpatchz.ApplyPatchEmpty(patchFile, targetFile) + os.Remove(patchFile) continue } @@ -220,18 +125,11 @@ func (h *HdiffzService) PatchData(gamePath string) (bool, string) { 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() - } - + hpatchz.ApplyPatch(sourceFile, patchFile, targetFile) if entry.SourceFileName != entry.TargetFileName { - _ = os.Remove(sourceFile) + os.Remove(sourceFile) } - _ = os.Remove(patchFile) + os.Remove(patchFile) } os.Remove(filepath.Join(gamePath, "hdiffmap.json")) @@ -239,7 +137,7 @@ func (h *HdiffzService) PatchData(gamePath string) (bool, string) { return true, "patching completed" } -func (h *HdiffzService) DeleteFiles(gamePath string) (bool, string) { +func (h *DiffService) DeleteFiles(gamePath string) (bool, string) { var deleteFiles []string file, err := os.Open(filepath.Join(gamePath, "deletefiles.txt")) @@ -262,9 +160,8 @@ func (h *HdiffzService) DeleteFiles(gamePath string) (bool, string) { for i, file := range deleteFiles { os.Remove(filepath.Join(gamePath, file)) - application.Get().Event.Emit("hdiffz:progress", map[string]int{"progress": i, "maxProgress": len(deleteFiles)}) + application.Get().Event.Emit("diff:progress", map[string]int{"progress": i, "maxProgress": len(deleteFiles)}) } - _ = os.Remove(filepath.Join(gamePath, "deletefiles.txt")) + os.Remove(filepath.Join(gamePath, "deletefiles.txt")) return true, "" } - diff --git a/internal/diff-service/ldifff.go b/internal/diff-service/ldifff.go new file mode 100644 index 0000000..da46015 --- /dev/null +++ b/internal/diff-service/ldifff.go @@ -0,0 +1,172 @@ +package diffService + +import ( + "firefly-launcher/pkg/firefly" + "firefly-launcher/pkg/firefly/pb" + "firefly-launcher/pkg/hpatchz" + "firefly-launcher/pkg/models" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func (h *DiffService) LDiffPatchData(gamePath string) (bool, string) { + entries, err := os.ReadDir(gamePath) + if err != nil { + return false, err.Error() + } + ldiffPath := filepath.Join(gamePath, "ldiff") + + for _, entry := range entries { + if entry.IsDir() { + continue + } + if !entry.Type().IsRegular() { + continue + } + + name := entry.Name() + if strings.HasPrefix(name, "manifest") { + manifestName := entry.Name() + manifestPath := filepath.Join(gamePath, manifestName) + + manifest, err := firefly.LoadManifestProto(manifestPath) + if err != nil { + continue + } + + ldiffEntries, err := os.ReadDir(ldiffPath) + if err != nil { + return false, err.Error() + } + application.Get().Event.Emit("diff:stage", map[string]string{"stage": "Processing LDiff"}) + for i, ldiffEntry := range ldiffEntries { + assetName := ldiffEntry.Name() + var matchingAssets []struct { + AssetName string + AssetSize int64 + Asset *pb.AssetManifest + } + + application.Get().Event.Emit( + "diff:progress", map[string]int{ + "progress": i, + "maxProgress": len(ldiffEntries), + }) + + var wg sync.WaitGroup + var mu sync.Mutex + + for _, assetGroup := range manifest.Assets { + assetGroup := assetGroup + wg.Go(func() { + if data := assetGroup.AssetData; data != nil { + for _, asset := range data.Assets { + if asset.ChunkFileName == assetName { + mu.Lock() + matchingAssets = append(matchingAssets, struct { + AssetName string + AssetSize int64 + Asset *pb.AssetManifest + }{assetGroup.AssetName, assetGroup.AssetSize, asset}) + mu.Unlock() + } + } + } + }) + } + + wg.Wait() + + for _, ma := range matchingAssets { + err := firefly.LDiffFile(ma.Asset, ma.AssetName, ma.AssetSize, ldiffPath, gamePath) + if err != nil { + continue + } + } + } + + diffMapNames := make([]string, len(ldiffEntries)) + for i, e := range ldiffEntries { + diffMapNames[i] = e.Name() + } + + diffMapList, err := MakeDiffMap(manifest, diffMapNames) + if err != nil { + return false, err.Error() + } + application.Get().Event.Emit("diff:stage", map[string]string{"stage": "Patching HDiff"}) + for i, entry := range diffMapList { + application.Get().Event.Emit( + "diff:progress", map[string]int{ + "progress": i, + "maxProgress": len(diffMapList), + }) + sourceFile := filepath.Join(gamePath, entry.SourceFileName) + patchFile := filepath.Join(gamePath, entry.PatchFileName) + targetFile := filepath.Join(gamePath, entry.TargetFileName) + + if _, err := os.Stat(patchFile); os.IsNotExist(err) { + continue + } + + if entry.SourceFileName == "" { + hpatchz.ApplyPatchEmpty(patchFile, targetFile) + os.Remove(patchFile) + continue + } + + if _, err := os.Stat(sourceFile); os.IsNotExist(err) { + continue + } + + hpatchz.ApplyPatch(sourceFile, patchFile, targetFile) + if entry.SourceFileName != entry.TargetFileName { + os.Remove(sourceFile) + } + os.Remove(patchFile) + } + + } + } + os.RemoveAll(ldiffPath) + return true, "patching completed" +} + +func MakeDiffMap(manifest *pb.ManifestProto, chunkNames []string) ([]*models.HDiffData, error) { + var hdiffFiles []*models.HDiffData + + for _, asset := range manifest.Assets { + assetName := asset.AssetName + assetSize := asset.AssetSize + + if asset.AssetData != nil { + for _, chunk := range asset.AssetData.Assets { + matched := false + for _, name := range chunkNames { + if name == chunk.ChunkFileName { + matched = true + break + } + } + if !matched { + continue + } + + if chunk.OriginalFileSize != 0 || chunk.HdiffFileSize != assetSize { + hdiffFiles = append(hdiffFiles, &models.HDiffData{ + SourceFileName: chunk.OriginalFilePath, + TargetFileName: assetName, + PatchFileName: fmt.Sprintf("%s.hdiff", assetName), + }) + } + } + } + } + + return hdiffFiles, nil +} diff --git a/internal/diff-service/utils.go b/internal/diff-service/utils.go new file mode 100644 index 0000000..ece007e --- /dev/null +++ b/internal/diff-service/utils.go @@ -0,0 +1,100 @@ +package diffService + +import ( + "firefly-launcher/pkg/constant" + "firefly-launcher/pkg/verifier" + "firefly-launcher/pkg/sevenzip" + "github.com/wailsapp/wails/v3/pkg/application" + "os" + "path/filepath" + "io" +) + +func (h *DiffService) DataExtract(gamePath, patchPath string, isSkipVerify bool) (bool, string) { + os.RemoveAll(constant.TempUrl) + if _, err := os.Stat(gamePath); err != nil { + return false, err.Error() + } + + if _, err := os.Stat(patchPath); err != nil { + return false, err.Error() + } + + if _, err := os.Stat(constant.TempUrl); os.IsNotExist(err) { + if err := os.MkdirAll(constant.TempUrl, os.ModePerm); err != nil { + return false, err.Error() + } + } + + if err := sevenzip.ExtractAllFilesFromZip(patchPath, constant.TempUrl); err != nil { + os.RemoveAll(constant.TempUrl) + return false, err.Error() + } + + 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) { + if _, err := os.Stat(constant.TempUrl); os.IsNotExist(err) { + return false, err.Error() + } + + err := filepath.Walk(constant.TempUrl, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + relPath, err := filepath.Rel(constant.TempUrl, path) + if err != nil { + return err + } + destPath := filepath.Join(gamePath, relPath) + application.Get().Event.Emit("diff:message", map[string]string{"message": destPath}) + if info.IsDir() { + return os.MkdirAll(destPath, os.ModePerm) + } + + if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil { + return err + } + + if err := os.Rename(path, destPath); err != nil { + srcFile, err := os.Open(path) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(destPath) + if err != nil { + return err + } + defer dstFile.Close() + + if _, err := io.Copy(dstFile, srcFile); err != nil { + return err + } + os.Remove(path) + } + return nil + }) + + if err != nil { + os.RemoveAll(constant.TempUrl) + return false, err.Error() + } + os.RemoveAll(constant.TempUrl) + return true, "cut completed" +} \ No newline at end of file diff --git a/internal/fs-service.go b/internal/fs-service/fs.go similarity index 95% rename from internal/fs-service.go rename to internal/fs-service/fs.go index d250e2c..c5bb431 100644 --- a/internal/fs-service.go +++ b/internal/fs-service/fs.go @@ -1,4 +1,4 @@ -package internal +package fsService import ( "firefly-launcher/pkg/sevenzip" @@ -30,7 +30,7 @@ func (f *FSService) PickFolder() (string, error) { return "", nil } -func (f *FSService) PickFile() (string, error) { +func (f *FSService) PickFile(filter string) (string, error) { dialog := application.OpenFileDialog(). CanChooseFiles(true). ResolvesAliases(true) @@ -39,7 +39,9 @@ func (f *FSService) PickFile() (string, error) { } else { dialog.SetTitle("Select a file/directory") } - dialog.AddFilter("Executable Files (*.exe)", "*.exe") + if filter == "exe" { + dialog.AddFilter("Executable Files (*.exe)", "*.exe") + } if path, err := dialog.PromptForSingleSelection(); err == nil { return path, nil } diff --git a/internal/git-service.go b/internal/git-service.go deleted file mode 100644 index e3046d7..0000000 --- a/internal/git-service.go +++ /dev/null @@ -1,282 +0,0 @@ -package internal - -import ( - "encoding/json" - "firefly-launcher/pkg/constant" - "firefly-launcher/pkg/models" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - - "github.com/minio/selfupdate" - "github.com/wailsapp/wails/v3/pkg/application" -) - -type GitService struct{} - -func (g *GitService) GetLatestServerVersion() (bool, string, string) { - resp, err := http.Get(constant.ServerGitUrl) - 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) DownloadServerProgress(version string) (bool, string) { - resp, err := http.Get(constant.ServerGitUrl) - 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.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() - } - } - - saveFile := filepath.Join(constant.ServerStorageUrl, assetWin.Name) - - resp, err = http.Get(assetWin.BrowserDownloadURL) - if err != nil { - return false, err.Error() - } - defer resp.Body.Close() - - 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, "" -} - -func (g *GitService) UnzipServer() { - unzipParallel(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile), constant.ServerStorageUrl) - os.Remove(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile)) -} - -func (g *GitService) GetLatestProxyVersion() (bool, string, string) { - resp, err := http.Get(constant.ProxyGitUrl) - 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) DownloadProxyProgress(version string) (bool, string) { - resp, err := http.Get(constant.ProxyGitUrl) - 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.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() - } - } - saveFile := filepath.Join(constant.ProxyStorageUrl, assetWin.Name) - - resp, err = http.Get(assetWin.BrowserDownloadURL) - if err != nil { - return false, err.Error() - } - defer resp.Body.Close() - - 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, "" -} - -func (g *GitService) UnzipProxy() { - unzipParallel(filepath.Join(constant.ProxyStorageUrl, constant.ProxyZipFile), constant.ProxyStorageUrl) - 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, "" -} diff --git a/internal/git-service/launcher.go b/internal/git-service/launcher.go new file mode 100644 index 0000000..73730d1 --- /dev/null +++ b/internal/git-service/launcher.go @@ -0,0 +1,93 @@ +package gitService + +import ( + "encoding/json" + "firefly-launcher/pkg/constant" + "firefly-launcher/pkg/models" + "io" + "net/http" + "github.com/minio/selfupdate" + +) + +type GitService struct{} + + +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, "" +} diff --git a/internal/git-service/proxy.go b/internal/git-service/proxy.go new file mode 100644 index 0000000..9cde54e --- /dev/null +++ b/internal/git-service/proxy.go @@ -0,0 +1,106 @@ +package gitService + +import ( + "firefly-launcher/pkg/constant" + "firefly-launcher/pkg/models" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "encoding/json" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func (g *GitService) GetLatestProxyVersion() (bool, string, string) { + resp, err := http.Get(constant.ProxyGitUrl) + 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) DownloadProxyProgress(version string) (bool, string) { + resp, err := http.Get(constant.ProxyGitUrl) + 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.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() + } + } + saveFile := filepath.Join(constant.ProxyStorageUrl, assetWin.Name) + + resp, err = http.Get(assetWin.BrowserDownloadURL) + if err != nil { + return false, err.Error() + } + defer resp.Body.Close() + + 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, "" +} + +func (g *GitService) UnzipProxy() { + unzipParallel(filepath.Join(constant.ProxyStorageUrl, constant.ProxyZipFile), constant.ProxyStorageUrl) + os.Remove(filepath.Join(constant.ProxyStorageUrl, constant.ProxyZipFile)) +} \ No newline at end of file diff --git a/internal/git-service/server.go b/internal/git-service/server.go new file mode 100644 index 0000000..4f88523 --- /dev/null +++ b/internal/git-service/server.go @@ -0,0 +1,107 @@ +package gitService + +import ( + "encoding/json" + "firefly-launcher/pkg/constant" + "firefly-launcher/pkg/models" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func (g *GitService) GetLatestServerVersion() (bool, string, string) { + resp, err := http.Get(constant.ServerGitUrl) + 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) DownloadServerProgress(version string) (bool, string) { + resp, err := http.Get(constant.ServerGitUrl) + 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.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() + } + } + + saveFile := filepath.Join(constant.ServerStorageUrl, assetWin.Name) + + resp, err = http.Get(assetWin.BrowserDownloadURL) + if err != nil { + return false, err.Error() + } + defer resp.Body.Close() + + 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, "" +} + +func (g *GitService) UnzipServer() { + unzipParallel(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile), constant.ServerStorageUrl) + os.Remove(filepath.Join(constant.ServerStorageUrl, constant.ServerZipFile)) +} \ No newline at end of file diff --git a/internal/util.go b/internal/git-service/util.go similarity index 99% rename from internal/util.go rename to internal/git-service/util.go index 9d4230b..a942ea3 100644 --- a/internal/util.go +++ b/internal/git-service/util.go @@ -1,4 +1,4 @@ -package internal +package gitService import ( "archive/zip" diff --git a/internal/language-service.go b/internal/language-service/language.go similarity index 99% rename from internal/language-service.go rename to internal/language-service/language.go index 4702c12..f64e170 100644 --- a/internal/language-service.go +++ b/internal/language-service/language.go @@ -1,4 +1,4 @@ -package internal +package languageService import ( "bytes" diff --git a/internal/ldifff-service.go b/internal/ldifff-service.go deleted file mode 100644 index 1c81965..0000000 --- a/internal/ldifff-service.go +++ /dev/null @@ -1 +0,0 @@ -package internal \ No newline at end of file diff --git a/main.go b/main.go index 58dd2a5..1885c83 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,13 @@ package main import ( "embed" _ "embed" - "firefly-launcher/internal" + + appService "firefly-launcher/internal/app-service" + diffService "firefly-launcher/internal/diff-service" + fsService "firefly-launcher/internal/fs-service" + gitService "firefly-launcher/internal/git-service" + languageService "firefly-launcher/internal/language-service" + "firefly-launcher/pkg/constant" "fmt" "log" @@ -63,11 +69,11 @@ func main() { Name: "firefly-launcher", Description: "Firefly Launcher - Kain", Services: []application.Service{ - application.NewService(&internal.FSService{}), - application.NewService(&internal.LanguageService{}), - application.NewService(&internal.GitService{}), - application.NewService(&internal.HdiffzService{}), - application.NewService(&internal.AppService{}), + application.NewService(&fsService.FSService{}), + application.NewService(&languageService.LanguageService{}), + application.NewService(&gitService.GitService{}), + application.NewService(&diffService.DiffService{}), + application.NewService(&appService.AppService{}), }, Assets: application.AssetOptions{ Handler: application.AssetFileServerFS(assets), diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index c4dd032..53322a8 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -10,8 +10,7 @@ const ProxyZipFile = "64bit.zip" const LauncherFile = "firefly-launcher.exe" const TempUrl = "./temp" - -const CurrentLauncherVersion = "1.4.1" +const CurrentLauncherVersion = "1.5.0" type ToolFile string diff --git a/pkg/firefly/chunk.go b/pkg/firefly/chunk.go new file mode 100644 index 0000000..ef5f7a2 --- /dev/null +++ b/pkg/firefly/chunk.go @@ -0,0 +1,73 @@ +package firefly + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "golang.org/x/exp/mmap" +) + +type ChunkInfo struct { + Name string + Offset int64 + Size int64 +} + +func ProcessWithBufReader(path string, chunks []ChunkInfo) { + file, err := os.Open(path) + if err != nil { + fmt.Printf("Error opening file %s: %v\n", path, err) + return + } + defer file.Close() + + for _, chunk := range chunks { + buffer, err := ReadChunkData(file, chunk.Offset, chunk.Size) + if err != nil { + fmt.Printf("Error reading chunk %s: %v\n", chunk.Name, err) + continue + } + + assetPath := filepath.Join("chunk_tmp", chunk.Name) + parentDir := filepath.Dir(assetPath) + if _, err := os.Stat(parentDir); os.IsNotExist(err) { + if err := os.MkdirAll(parentDir, 0o755); err != nil { + fmt.Printf("Error creating directory %s: %v\n", parentDir, err) + continue + } + } + + if err := os.WriteFile(assetPath, buffer, 0o644); err != nil { + fmt.Printf("Error writing chunk file %s: %v\n", assetPath, err) + } + } +} + +func ReadChunkData(file *os.File, offset, size int64) ([]byte, error) { + info, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("error getting file info: %w", err) + } + + if info.Size() > 1024*1024 && size > 1024*1024 { + mmapReader, err := mmap.Open(file.Name()) + if err == nil { + defer mmapReader.Close() + buffer := make([]byte, size) + _, err := mmapReader.ReadAt(buffer, offset) + if err != nil && err != io.EOF { + return nil, fmt.Errorf("error reading mmap: %w", err) + } + return buffer, nil + } + } + + buffer := make([]byte, size) + _, err = file.ReadAt(buffer, offset) + if err != nil && err != io.EOF { + return nil, fmt.Errorf("error reading buffered: %w", err) + } + return buffer, nil +} \ No newline at end of file diff --git a/pkg/firefly/ldiff.go b/pkg/firefly/ldiff.go new file mode 100644 index 0000000..6c11be1 --- /dev/null +++ b/pkg/firefly/ldiff.go @@ -0,0 +1,94 @@ +package firefly + +import ( + "firefly-launcher/pkg/firefly/pb" + "fmt" + "os" + "path/filepath" + + "golang.org/x/exp/mmap" +) + + +func LDiffFile(data *pb.AssetManifest, assetName string, assetSize int64, ldiffsDir, outputDir string) error { + path := filepath.Join(ldiffsDir, data.ChunkFileName) + + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("%s does not exist: %w", path, err) + } + + fileSize := info.Size() + var buffer []byte + + if fileSize > 10*1024*1024 && data.HdiffFileSize > 1*1024*1024 { + // mmap for large files using x/exp/mmap + reader, err := mmap.Open(path) + if err != nil { + // fallback to buffered read + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("error opening file %s: %w", path, err) + } + defer file.Close() + buffer, err = ReadBuffer(file, data.HdiffFileInChunkOffset, data.HdiffFileSize) + if err != nil { + return err + } + } else { + defer reader.Close() + buffer = make([]byte, data.HdiffFileSize) + _, err := reader.ReadAt(buffer, data.HdiffFileInChunkOffset) + if err != nil { + return fmt.Errorf("error reading mmap data: %w", err) + } + } + } else { + // small files, buffered read + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("error opening file %s: %w", path, err) + } + defer file.Close() + + buffer, err = ReadBuffer(file, data.HdiffFileInChunkOffset, data.HdiffFileSize) + if err != nil { + return err + } + } + + extension := "" + if data.OriginalFileSize != 0 || assetSize != data.HdiffFileSize { + extension = ".hdiff" + } + assetPath := filepath.Join(outputDir, assetName+extension) + parentDir := filepath.Dir(assetPath) + if _, err := os.Stat(parentDir); os.IsNotExist(err) { + if err := os.MkdirAll(parentDir, 0o755); err != nil { + return fmt.Errorf("error creating directory %s: %w", parentDir, err) + } + } + + if err := os.WriteFile(assetPath, buffer, 0o644); err != nil { + return fmt.Errorf("error writing file %s: %w", assetPath, err) + } + + return nil +} + + + +func ReadBuffer(file *os.File, offset int64, size int64) ([]byte, error) { + buffer := make([]byte, size) + + n, err := file.ReadAt(buffer, offset) + if err != nil { + return nil, fmt.Errorf("error reading data: %w", err) + } + if int64(n) != size { + return nil, fmt.Errorf("expected %d bytes, but read %d bytes", size, n) + } + + return buffer, nil +} + diff --git a/pkg/firefly/loader.go b/pkg/firefly/loader.go new file mode 100644 index 0000000..bc9b46e --- /dev/null +++ b/pkg/firefly/loader.go @@ -0,0 +1,66 @@ +package firefly + +import ( + "bufio" + "firefly-launcher/pkg/firefly/pb" + "io" + "os" + + "github.com/klauspost/compress/zstd" + "google.golang.org/protobuf/proto" +) + +func LoadChunkProto(path string) (*pb.ChunkProto, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + reader := bufio.NewReader(file) + decoder, err := zstd.NewReader(reader) + if err != nil { + return nil, err + } + defer decoder.Close() + + data, err := io.ReadAll(decoder) + if err != nil { + return nil, err + } + + var chunk pb.ChunkProto + if err := proto.Unmarshal(data, &chunk); err != nil { + return nil, err + } + + return &chunk, nil +} + +// Load ManifestProto từ file Zstd + Protobuf +func LoadManifestProto(path string) (*pb.ManifestProto, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + reader := bufio.NewReader(file) + decoder, err := zstd.NewReader(reader) + if err != nil { + return nil, err + } + defer decoder.Close() + + data, err := io.ReadAll(decoder) + if err != nil { + return nil, err + } + + var manifest pb.ManifestProto + if err := proto.Unmarshal(data, &manifest); err != nil { + return nil, err + } + + return &manifest, nil +} \ No newline at end of file diff --git a/pkg/firefly/pb/firefly.pb.go b/pkg/firefly/pb/firefly.pb.go new file mode 100644 index 0000000..9543a86 --- /dev/null +++ b/pkg/firefly/pb/firefly.pb.go @@ -0,0 +1,651 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v4.25.6 +// source: proto/firefly.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ChunkProto struct { + state protoimpl.MessageState `protogen:"open.v1"` + Assets []*AssetChunkProperty `protobuf:"bytes,1,rep,name=assets,proto3" json:"assets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChunkProto) Reset() { + *x = ChunkProto{} + mi := &file_proto_firefly_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChunkProto) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChunkProto) ProtoMessage() {} + +func (x *ChunkProto) ProtoReflect() protoreflect.Message { + mi := &file_proto_firefly_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChunkProto.ProtoReflect.Descriptor instead. +func (*ChunkProto) Descriptor() ([]byte, []int) { + return file_proto_firefly_proto_rawDescGZIP(), []int{0} +} + +func (x *ChunkProto) GetAssets() []*AssetChunkProperty { + if x != nil { + return x.Assets + } + return nil +} + +type AssetChunkProperty struct { + state protoimpl.MessageState `protogen:"open.v1"` + AssetName string `protobuf:"bytes,1,opt,name=asset_name,json=assetName,proto3" json:"asset_name,omitempty"` + AssetChunks []*AssetChunk `protobuf:"bytes,2,rep,name=asset_chunks,json=assetChunks,proto3" json:"asset_chunks,omitempty"` + AssetType int32 `protobuf:"varint,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + AssetSize int64 `protobuf:"varint,4,opt,name=asset_size,json=assetSize,proto3" json:"asset_size,omitempty"` + AssetHashMd5 string `protobuf:"bytes,5,opt,name=asset_hash_md5,json=assetHashMd5,proto3" json:"asset_hash_md5,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssetChunkProperty) Reset() { + *x = AssetChunkProperty{} + mi := &file_proto_firefly_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssetChunkProperty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetChunkProperty) ProtoMessage() {} + +func (x *AssetChunkProperty) ProtoReflect() protoreflect.Message { + mi := &file_proto_firefly_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetChunkProperty.ProtoReflect.Descriptor instead. +func (*AssetChunkProperty) Descriptor() ([]byte, []int) { + return file_proto_firefly_proto_rawDescGZIP(), []int{1} +} + +func (x *AssetChunkProperty) GetAssetName() string { + if x != nil { + return x.AssetName + } + return "" +} + +func (x *AssetChunkProperty) GetAssetChunks() []*AssetChunk { + if x != nil { + return x.AssetChunks + } + return nil +} + +func (x *AssetChunkProperty) GetAssetType() int32 { + if x != nil { + return x.AssetType + } + return 0 +} + +func (x *AssetChunkProperty) GetAssetSize() int64 { + if x != nil { + return x.AssetSize + } + return 0 +} + +func (x *AssetChunkProperty) GetAssetHashMd5() string { + if x != nil { + return x.AssetHashMd5 + } + return "" +} + +type AssetChunk struct { + state protoimpl.MessageState `protogen:"open.v1"` + ChunkName string `protobuf:"bytes,1,opt,name=chunk_name,json=chunkName,proto3" json:"chunk_name,omitempty"` + ChunkDecompressedHashMd5 string `protobuf:"bytes,2,opt,name=chunk_decompressed_hash_md5,json=chunkDecompressedHashMd5,proto3" json:"chunk_decompressed_hash_md5,omitempty"` + ChunkOnFileOffset int64 `protobuf:"varint,3,opt,name=chunk_on_file_offset,json=chunkOnFileOffset,proto3" json:"chunk_on_file_offset,omitempty"` + ChunkSize int64 `protobuf:"varint,4,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"` + ChunkSizeDecompressed int64 `protobuf:"varint,5,opt,name=chunk_size_decompressed,json=chunkSizeDecompressed,proto3" json:"chunk_size_decompressed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssetChunk) Reset() { + *x = AssetChunk{} + mi := &file_proto_firefly_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssetChunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetChunk) ProtoMessage() {} + +func (x *AssetChunk) ProtoReflect() protoreflect.Message { + mi := &file_proto_firefly_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetChunk.ProtoReflect.Descriptor instead. +func (*AssetChunk) Descriptor() ([]byte, []int) { + return file_proto_firefly_proto_rawDescGZIP(), []int{2} +} + +func (x *AssetChunk) GetChunkName() string { + if x != nil { + return x.ChunkName + } + return "" +} + +func (x *AssetChunk) GetChunkDecompressedHashMd5() string { + if x != nil { + return x.ChunkDecompressedHashMd5 + } + return "" +} + +func (x *AssetChunk) GetChunkOnFileOffset() int64 { + if x != nil { + return x.ChunkOnFileOffset + } + return 0 +} + +func (x *AssetChunk) GetChunkSize() int64 { + if x != nil { + return x.ChunkSize + } + return 0 +} + +func (x *AssetChunk) GetChunkSizeDecompressed() int64 { + if x != nil { + return x.ChunkSizeDecompressed + } + return 0 +} + +type ManifestProto struct { + state protoimpl.MessageState `protogen:"open.v1"` + Assets []*AssetManifestProperty `protobuf:"bytes,1,rep,name=assets,proto3" json:"assets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ManifestProto) Reset() { + *x = ManifestProto{} + mi := &file_proto_firefly_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ManifestProto) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ManifestProto) ProtoMessage() {} + +func (x *ManifestProto) ProtoReflect() protoreflect.Message { + mi := &file_proto_firefly_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ManifestProto.ProtoReflect.Descriptor instead. +func (*ManifestProto) Descriptor() ([]byte, []int) { + return file_proto_firefly_proto_rawDescGZIP(), []int{3} +} + +func (x *ManifestProto) GetAssets() []*AssetManifestProperty { + if x != nil { + return x.Assets + } + return nil +} + +type AssetManifestProperty struct { + state protoimpl.MessageState `protogen:"open.v1"` + AssetName string `protobuf:"bytes,1,opt,name=asset_name,json=assetName,proto3" json:"asset_name,omitempty"` + AssetSize int64 `protobuf:"varint,2,opt,name=asset_size,json=assetSize,proto3" json:"asset_size,omitempty"` + AssetHashMd5 string `protobuf:"bytes,3,opt,name=asset_hash_md5,json=assetHashMd5,proto3" json:"asset_hash_md5,omitempty"` + AssetData *AssetManifestChunk `protobuf:"bytes,4,opt,name=asset_data,json=assetData,proto3" json:"asset_data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssetManifestProperty) Reset() { + *x = AssetManifestProperty{} + mi := &file_proto_firefly_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssetManifestProperty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetManifestProperty) ProtoMessage() {} + +func (x *AssetManifestProperty) ProtoReflect() protoreflect.Message { + mi := &file_proto_firefly_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetManifestProperty.ProtoReflect.Descriptor instead. +func (*AssetManifestProperty) Descriptor() ([]byte, []int) { + return file_proto_firefly_proto_rawDescGZIP(), []int{4} +} + +func (x *AssetManifestProperty) GetAssetName() string { + if x != nil { + return x.AssetName + } + return "" +} + +func (x *AssetManifestProperty) GetAssetSize() int64 { + if x != nil { + return x.AssetSize + } + return 0 +} + +func (x *AssetManifestProperty) GetAssetHashMd5() string { + if x != nil { + return x.AssetHashMd5 + } + return "" +} + +func (x *AssetManifestProperty) GetAssetData() *AssetManifestChunk { + if x != nil { + return x.AssetData + } + return nil +} + +type AssetManifestChunk struct { + state protoimpl.MessageState `protogen:"open.v1"` + LatestAssetVersion string `protobuf:"bytes,1,opt,name=latest_asset_version,json=latestAssetVersion,proto3" json:"latest_asset_version,omitempty"` + Assets []*AssetManifest `protobuf:"bytes,2,rep,name=assets,proto3" json:"assets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssetManifestChunk) Reset() { + *x = AssetManifestChunk{} + mi := &file_proto_firefly_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssetManifestChunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetManifestChunk) ProtoMessage() {} + +func (x *AssetManifestChunk) ProtoReflect() protoreflect.Message { + mi := &file_proto_firefly_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetManifestChunk.ProtoReflect.Descriptor instead. +func (*AssetManifestChunk) Descriptor() ([]byte, []int) { + return file_proto_firefly_proto_rawDescGZIP(), []int{5} +} + +func (x *AssetManifestChunk) GetLatestAssetVersion() string { + if x != nil { + return x.LatestAssetVersion + } + return "" +} + +func (x *AssetManifestChunk) GetAssets() []*AssetManifest { + if x != nil { + return x.Assets + } + return nil +} + +type AssetManifest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ChunkFileName string `protobuf:"bytes,1,opt,name=chunk_file_name,json=chunkFileName,proto3" json:"chunk_file_name,omitempty"` + ChunkFileVersion string `protobuf:"bytes,2,opt,name=chunk_file_version,json=chunkFileVersion,proto3" json:"chunk_file_version,omitempty"` + ChunkFileNode string `protobuf:"bytes,3,opt,name=chunk_file_node,json=chunkFileNode,proto3" json:"chunk_file_node,omitempty"` + ChunkFileSize int64 `protobuf:"varint,4,opt,name=chunk_file_size,json=chunkFileSize,proto3" json:"chunk_file_size,omitempty"` + ChunkFileMd5 string `protobuf:"bytes,5,opt,name=chunk_file_md5,json=chunkFileMd5,proto3" json:"chunk_file_md5,omitempty"` + HdiffFileInChunkOffset int64 `protobuf:"varint,6,opt,name=hdiff_file_in_chunk_offset,json=hdiffFileInChunkOffset,proto3" json:"hdiff_file_in_chunk_offset,omitempty"` + HdiffFileSize int64 `protobuf:"varint,7,opt,name=hdiff_file_size,json=hdiffFileSize,proto3" json:"hdiff_file_size,omitempty"` + OriginalFilePath string `protobuf:"bytes,8,opt,name=original_file_path,json=originalFilePath,proto3" json:"original_file_path,omitempty"` + OriginalFileSize int64 `protobuf:"varint,9,opt,name=original_file_size,json=originalFileSize,proto3" json:"original_file_size,omitempty"` + OriginalFileMd5 string `protobuf:"bytes,10,opt,name=original_file_md5,json=originalFileMd5,proto3" json:"original_file_md5,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssetManifest) Reset() { + *x = AssetManifest{} + mi := &file_proto_firefly_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssetManifest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetManifest) ProtoMessage() {} + +func (x *AssetManifest) ProtoReflect() protoreflect.Message { + mi := &file_proto_firefly_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetManifest.ProtoReflect.Descriptor instead. +func (*AssetManifest) Descriptor() ([]byte, []int) { + return file_proto_firefly_proto_rawDescGZIP(), []int{6} +} + +func (x *AssetManifest) GetChunkFileName() string { + if x != nil { + return x.ChunkFileName + } + return "" +} + +func (x *AssetManifest) GetChunkFileVersion() string { + if x != nil { + return x.ChunkFileVersion + } + return "" +} + +func (x *AssetManifest) GetChunkFileNode() string { + if x != nil { + return x.ChunkFileNode + } + return "" +} + +func (x *AssetManifest) GetChunkFileSize() int64 { + if x != nil { + return x.ChunkFileSize + } + return 0 +} + +func (x *AssetManifest) GetChunkFileMd5() string { + if x != nil { + return x.ChunkFileMd5 + } + return "" +} + +func (x *AssetManifest) GetHdiffFileInChunkOffset() int64 { + if x != nil { + return x.HdiffFileInChunkOffset + } + return 0 +} + +func (x *AssetManifest) GetHdiffFileSize() int64 { + if x != nil { + return x.HdiffFileSize + } + return 0 +} + +func (x *AssetManifest) GetOriginalFilePath() string { + if x != nil { + return x.OriginalFilePath + } + return "" +} + +func (x *AssetManifest) GetOriginalFileSize() int64 { + if x != nil { + return x.OriginalFileSize + } + return 0 +} + +func (x *AssetManifest) GetOriginalFileMd5() string { + if x != nil { + return x.OriginalFileMd5 + } + return "" +} + +var File_proto_firefly_proto protoreflect.FileDescriptor + +var file_proto_firefly_proto_rawDesc = string([]byte{ + 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x66, 0x69, 0x72, 0x65, 0x66, 0x6c, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x66, 0x69, 0x72, 0x65, 0x66, 0x6c, 0x79, 0x22, 0x41, + 0x0a, 0x0a, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x33, 0x0a, 0x06, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x66, + 0x69, 0x72, 0x65, 0x66, 0x6c, 0x79, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x52, 0x06, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x73, 0x22, 0xcf, 0x01, 0x0a, 0x12, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, + 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, + 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x66, 0x69, 0x72, 0x65, 0x66, 0x6c, 0x79, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, + 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x24, 0x0a, + 0x0e, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6d, 0x64, 0x35, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x48, 0x61, 0x73, 0x68, + 0x4d, 0x64, 0x35, 0x22, 0xf2, 0x01, 0x0a, 0x0a, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x64, 0x65, 0x63, 0x6f, 0x6d, + 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6d, 0x64, 0x35, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x44, 0x65, 0x63, + 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x48, 0x61, 0x73, 0x68, 0x4d, 0x64, 0x35, + 0x12, 0x2f, 0x0a, 0x14, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, + 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x4f, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x36, 0x0a, 0x17, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x64, + 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x15, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x44, 0x65, 0x63, 0x6f, + 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x22, 0x47, 0x0a, 0x0d, 0x4d, 0x61, 0x6e, 0x69, + 0x66, 0x65, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x36, 0x0a, 0x06, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x66, 0x69, 0x72, 0x65, + 0x66, 0x6c, 0x79, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, + 0x74, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x52, 0x06, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x73, 0x22, 0xb7, 0x01, 0x0a, 0x15, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, + 0x65, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x6d, 0x64, 0x35, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x48, 0x61, 0x73, 0x68, 0x4d, 0x64, 0x35, 0x12, + 0x3a, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x66, 0x69, 0x72, 0x65, 0x66, 0x6c, 0x79, 0x2e, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, + 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x76, 0x0a, 0x12, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x12, 0x30, 0x0a, 0x14, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x73, 0x73, 0x65, + 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x06, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x66, 0x69, 0x72, 0x65, 0x66, 0x6c, 0x79, 0x2e, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x06, 0x61, 0x73, 0x73, + 0x65, 0x74, 0x73, 0x22, 0xc7, 0x03, 0x0a, 0x0d, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4d, 0x61, 0x6e, + 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x66, + 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x2c, 0x0a, + 0x12, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, + 0x46, 0x69, 0x6c, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x63, + 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x4e, + 0x6f, 0x64, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x66, 0x69, 0x6c, + 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x68, + 0x75, 0x6e, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x63, + 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6d, 0x64, 0x35, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x64, + 0x35, 0x12, 0x3a, 0x0a, 0x1a, 0x68, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, + 0x69, 0x6e, 0x5f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x68, 0x64, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, 0x65, + 0x49, 0x6e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x26, 0x0a, + 0x0f, 0x68, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x68, 0x64, 0x69, 0x66, 0x66, 0x46, 0x69, 0x6c, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, + 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x10, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, 0x50, + 0x61, 0x74, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, + 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x10, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x69, 0x7a, + 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, + 0x6c, 0x65, 0x5f, 0x6d, 0x64, 0x35, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, 0x4d, 0x64, 0x35, 0x42, 0x0e, 0x5a, + 0x0c, 0x2e, 0x2f, 0x66, 0x69, 0x72, 0x65, 0x66, 0x6c, 0x79, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_proto_firefly_proto_rawDescOnce sync.Once + file_proto_firefly_proto_rawDescData []byte +) + +func file_proto_firefly_proto_rawDescGZIP() []byte { + file_proto_firefly_proto_rawDescOnce.Do(func() { + file_proto_firefly_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_firefly_proto_rawDesc), len(file_proto_firefly_proto_rawDesc))) + }) + return file_proto_firefly_proto_rawDescData +} + +var file_proto_firefly_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_proto_firefly_proto_goTypes = []any{ + (*ChunkProto)(nil), // 0: firefly.ChunkProto + (*AssetChunkProperty)(nil), // 1: firefly.AssetChunkProperty + (*AssetChunk)(nil), // 2: firefly.AssetChunk + (*ManifestProto)(nil), // 3: firefly.ManifestProto + (*AssetManifestProperty)(nil), // 4: firefly.AssetManifestProperty + (*AssetManifestChunk)(nil), // 5: firefly.AssetManifestChunk + (*AssetManifest)(nil), // 6: firefly.AssetManifest +} +var file_proto_firefly_proto_depIdxs = []int32{ + 1, // 0: firefly.ChunkProto.assets:type_name -> firefly.AssetChunkProperty + 2, // 1: firefly.AssetChunkProperty.asset_chunks:type_name -> firefly.AssetChunk + 4, // 2: firefly.ManifestProto.assets:type_name -> firefly.AssetManifestProperty + 5, // 3: firefly.AssetManifestProperty.asset_data:type_name -> firefly.AssetManifestChunk + 6, // 4: firefly.AssetManifestChunk.assets:type_name -> firefly.AssetManifest + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_proto_firefly_proto_init() } +func file_proto_firefly_proto_init() { + if File_proto_firefly_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_firefly_proto_rawDesc), len(file_proto_firefly_proto_rawDesc)), + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_firefly_proto_goTypes, + DependencyIndexes: file_proto_firefly_proto_depIdxs, + MessageInfos: file_proto_firefly_proto_msgTypes, + }.Build() + File_proto_firefly_proto = out.File + file_proto_firefly_proto_goTypes = nil + file_proto_firefly_proto_depIdxs = nil +}