From ec72b812de9492aef82987124f5338d78b2c893f Mon Sep 17 00:00:00 2001 From: AzenKain Date: Tue, 19 Aug 2025 20:01:31 +0700 Subject: [PATCH] UPDATE: native with new hdiff type --- .../internal/hdiffzservice.js | 14 ++- frontend/src/pages/hdiffz/index.tsx | 23 +++- internal/hdiffz-service.go | 116 +++++++++++++----- internal/ldifff-service.go | 1 + pkg/hpatchz/hpatchz.go | 34 +++++ pkg/models/hdiffFiles.go | 43 +++++++ pkg/models/hdiffmap.go | 14 +++ pkg/models/pkgVersion.go | 36 ++++++ pkg/sevenzip/sevenzip.go | 35 ++++++ 9 files changed, 280 insertions(+), 36 deletions(-) create mode 100644 internal/ldifff-service.go create mode 100644 pkg/hpatchz/hpatchz.go create mode 100644 pkg/models/hdiffFiles.go create mode 100644 pkg/models/pkgVersion.go diff --git a/frontend/bindings/firefly-launcher/internal/hdiffzservice.js b/frontend/bindings/firefly-launcher/internal/hdiffzservice.js index c57395d..25c96d7 100644 --- a/frontend/bindings/firefly-launcher/internal/hdiffzservice.js +++ b/frontend/bindings/firefly-launcher/internal/hdiffzservice.js @@ -6,6 +6,15 @@ // @ts-ignore: Unused imports import {Call as $Call, Create as $Create} from "@wailsio/runtime"; +/** + * @param {string} patchPath + * @returns {Promise<[boolean, string, string]> & { cancel(): void }} + */ +export function CheckTypeHDiff(patchPath) { + let $resultPromise = /** @type {any} */($Call.ByID(1068035136, patchPath)); + return $resultPromise; +} + /** * @param {string} gamePath * @returns {Promise<[boolean, string]> & { cancel(): void }} @@ -18,10 +27,11 @@ export function CutData(gamePath) { /** * @param {string} gamePath * @param {string} patchPath + * @param {boolean} isSkipVerify * @returns {Promise<[boolean, string]> & { cancel(): void }} */ -export function DataExtract(gamePath, patchPath) { - let $resultPromise = /** @type {any} */($Call.ByID(1843136452, gamePath, patchPath)); +export function DataExtract(gamePath, patchPath, isSkipVerify) { + let $resultPromise = /** @type {any} */($Call.ByID(1843136452, gamePath, patchPath, isSkipVerify)); return $resultPromise; } diff --git a/frontend/src/pages/hdiffz/index.tsx b/frontend/src/pages/hdiffz/index.tsx index 5ebca31..2dd68fb 100644 --- a/frontend/src/pages/hdiffz/index.tsx +++ b/frontend/src/pages/hdiffz/index.tsx @@ -116,19 +116,32 @@ export default function HdiffzPage() { setIsDiffLoading(false) return } - setStageType('Version Validate') + setStageType('Check Type HDiff') setProgressUpdate(0) setMaxProgressUpdate(1) - const [validVersion, errorVersion] = await HdiffzService.VersionValidate(gameDir, diffDir) - if (!validVersion) { - toast.error(errorVersion) + const [isOk, validType, errorType] = await HdiffzService.CheckTypeHDiff(diffDir) + if (!isOk) { + toast.error(errorType) setIsDiffLoading(false) return } setProgressUpdate(1) + if (validType === 'hdiffmap.json') { + setStageType('Version Validate') + setProgressUpdate(0) + setMaxProgressUpdate(1) + const [validVersion, errorVersion] = await HdiffzService.VersionValidate(gameDir, diffDir) + if (!validVersion) { + toast.error(errorVersion) + setIsDiffLoading(false) + return + } + setProgressUpdate(1) + } + setStageType('Data Extract') - const [validData, errorData] = await HdiffzService.DataExtract(gameDir, diffDir) + const [validData, errorData] = await HdiffzService.DataExtract(gameDir, diffDir, validType === 'hdifffiles.txt') if (!validData) { toast.error(errorData) setIsDiffLoading(false) diff --git a/internal/hdiffz-service.go b/internal/hdiffz-service.go index b49e54e..88cfdc6 100644 --- a/internal/hdiffz-service.go +++ b/internal/hdiffz-service.go @@ -7,19 +7,30 @@ import ( "firefly-launcher/pkg/models" "firefly-launcher/pkg/sevenzip" "firefly-launcher/pkg/verifier" + "firefly-launcher/pkg/hpatchz" "fmt" "io" "os" - "os/exec" "path/filepath" "strings" - "syscall" "github.com/wailsapp/wails/v3/pkg/application" ) type HdiffzService struct{} +func (h *HdiffzService) CheckTypeHDiff(patchPath string) (bool, string, string) { + isFileInTxt, _ := sevenzip.IsFileIn7z(patchPath, "hdifffiles.txt") + if isFileInTxt { + return true, "hdifffiles.txt", "" + } + isFileInJson, _ := sevenzip.IsFileIn7z(patchPath, "hdiffmap.json") + if isFileInJson { + return true, "hdiffmap.json", "" + } + return false, "", "not found hdifffiles.txt or hdiffmap.json" +} + func (h *HdiffzService) VersionValidate(gamePath, patchPath string) (bool, string) { oldVersionData, err := models.ParseBinaryVersion(filepath.Join(gamePath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes")) if err != nil { @@ -53,7 +64,8 @@ func (h *HdiffzService) VersionValidate(gamePath, patchPath string) (bool, strin return true, "validated" } -func (h *HdiffzService) DataExtract(gamePath, patchPath string) (bool, string) { + +func (h *HdiffzService) DataExtract(gamePath, patchPath string, isSkipVerify bool) (bool, string) { if _, err := os.Stat(gamePath); err != nil { return false, err.Error() } @@ -73,15 +85,17 @@ func (h *HdiffzService) DataExtract(gamePath, patchPath string) (bool, string) { return false, err.Error() } - validator, err := verifier.NewVerifier(gamePath, constant.TempUrl) - if 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() + if err := validator.VerifyAll(); err != nil { + os.RemoveAll(constant.TempUrl) + return false, err.Error() + } } return true, "validated" @@ -136,22 +150,42 @@ func (h *HdiffzService) CutData(gamePath string) (bool, string) { return false, err.Error() } - _ = os.RemoveAll(constant.TempUrl) - return true, "cut completed" } func (h *HdiffzService) PatchData(gamePath string) (bool, string) { - data, err := os.ReadFile(filepath.Join(gamePath, "hdiffmap.json")) - if err != nil { - return false, err.Error() - } + hdiffMapPath := filepath.Join(gamePath, "hdiffmap.json") + hdiffFilesPath := filepath.Join(gamePath, "hdifffiles.txt") var jsonData struct { - DiffMap []*models.DiffMapType `json:"diff_map"` + DiffMap []*models.HDiffData `json:"diff_map"` } - if err := json.Unmarshal(data, &jsonData); err != nil { - return false, err.Error() + + if _, err := os.Stat(hdiffMapPath); err == nil { + data, err := os.ReadFile(hdiffMapPath) + if err != nil { + return false, err.Error() + } + + var jsonDataDiffMap struct { + DiffMap []*models.DiffMapType `json:"diff_map"` + } + if err := json.Unmarshal(data, &jsonDataDiffMap); err != nil { + return false, err.Error() + } + for _, entry := range jsonDataDiffMap.DiffMap { + jsonData.DiffMap = append(jsonData.DiffMap, entry.ToHDiffData()) + } + } else if _, err := os.Stat(hdiffFilesPath); err == nil { + files, err := models.LoadHDiffFiles(hdiffFilesPath) + if err != nil { + return false, err.Error() + } + for _, entry := range files { + jsonData.DiffMap = append(jsonData.DiffMap, entry.ToHDiffData()) + } + } else { + return false, "no hdiff entries map exist" } for i, entry := range jsonData.DiffMap { @@ -160,25 +194,48 @@ func (h *HdiffzService) PatchData(gamePath string) (bool, string) { "progress": i, "maxProgress": len(jsonData.DiffMap), }) + sourceFile := filepath.Join(gamePath, entry.SourceFileName) patchFile := filepath.Join(gamePath, entry.PatchFileName) targetFile := filepath.Join(gamePath, entry.TargetFileName) - if _, err := os.Stat(sourceFile); os.IsNotExist(err) { - continue - } + // Check patch file tồn tại chưa if _, err := os.Stat(patchFile); os.IsNotExist(err) { continue } - cmd := exec.Command(constant.ToolHPatchzExe.String(), sourceFile, patchFile, targetFile) - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - _, err := cmd.CombinedOutput() - if err != nil { + // 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) continue } + + if _, err := os.Stat(sourceFile); os.IsNotExist(err) { + continue + } + + // Có source file → apply_patch + err := hpatchz.ApplyPatch(sourceFile, patchFile, targetFile) + if err != nil { + fmt.Printf("%s failed to patch! %v\n", entry.TargetFileName, err) + _ = os.Remove(patchFile) + return false, err.Error() + } + + if entry.SourceFileName != entry.TargetFileName { + _ = os.Remove(sourceFile) + } + _ = os.Remove(patchFile) } + os.Remove(filepath.Join(gamePath, "hdiffmap.json")) + os.Remove(filepath.Join(gamePath, "hdifffiles.txt")) return true, "patching completed" } @@ -200,13 +257,14 @@ func (h *HdiffzService) DeleteFiles(gamePath string) (bool, string) { } if err := scanner.Err(); err != nil { - return false, "" + return false, "no delete files exist" } for i, file := range deleteFiles { os.Remove(filepath.Join(gamePath, file)) application.Get().EmitEvent("hdiffz:progress", map[string]int{"progress": i, "maxProgress": len(deleteFiles)}) } - + _ = os.Remove(filepath.Join(gamePath, "deletefiles.txt")) return true, "" } + diff --git a/internal/ldifff-service.go b/internal/ldifff-service.go new file mode 100644 index 0000000..1c81965 --- /dev/null +++ b/internal/ldifff-service.go @@ -0,0 +1 @@ +package internal \ No newline at end of file diff --git a/pkg/hpatchz/hpatchz.go b/pkg/hpatchz/hpatchz.go new file mode 100644 index 0000000..08a1152 --- /dev/null +++ b/pkg/hpatchz/hpatchz.go @@ -0,0 +1,34 @@ +package hpatchz + +import ( + "firefly-launcher/pkg/constant" + "fmt" + "os/exec" + "syscall" +) + +func ApplyPatch(oldFile, diffFile, newFile string) error { + cmd := exec.Command(constant.ToolHPatchzExe.String(), "-f", oldFile, diffFile, newFile) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to execute hpatchz: %w", err) + } + if cmd.ProcessState.ExitCode() != 0 { + return fmt.Errorf("hpatchz failed: %s", string(output)) + } + return nil +} + +func ApplyPatchEmpty(diffFile, newFile string) error { + cmd := exec.Command(constant.ToolHPatchzExe.String(), "-f", "", diffFile, newFile) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to execute hpatchz: %w", err) + } + if cmd.ProcessState.ExitCode() != 0 { + return fmt.Errorf("hpatchz failed: %s", string(output)) + } + return nil +} diff --git a/pkg/models/hdiffFiles.go b/pkg/models/hdiffFiles.go new file mode 100644 index 0000000..0876895 --- /dev/null +++ b/pkg/models/hdiffFiles.go @@ -0,0 +1,43 @@ +package models + +import ( + "bufio" + "encoding/json" + "fmt" + "os" +) + +type HDiffFiles struct { + RemoteFile string `json:"remoteName"` +} +func (h *HDiffFiles) ToHDiffData() *HDiffData { + return &HDiffData{ + SourceFileName: h.RemoteFile, + TargetFileName: h.RemoteFile, + PatchFileName: fmt.Sprintf("%s.hdiff", h.RemoteFile), + } +} + +func LoadHDiffFiles(path string) ([]*HDiffFiles, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + var results []*HDiffFiles + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + var item HDiffFiles + if err := json.Unmarshal([]byte(line), &item); err == nil { + results = append(results, &item) + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return results, nil +} diff --git a/pkg/models/hdiffmap.go b/pkg/models/hdiffmap.go index 48443b0..1737485 100644 --- a/pkg/models/hdiffmap.go +++ b/pkg/models/hdiffmap.go @@ -12,4 +12,18 @@ type DiffMapType struct { PatchFileName string `json:"patch_file_name"` PatchFileMD5 string `json:"patch_file_md5"` PatchFileSize int64 `json:"patch_file_size"` +} + +type HDiffData struct { + SourceFileName string `json:"source_file_name"` + TargetFileName string `json:"target_file_name"` + PatchFileName string `json:"patch_file_name"` +} + +func (d *DiffMapType) ToHDiffData() *HDiffData { + return &HDiffData{ + SourceFileName: d.SourceFileName, + TargetFileName: d.TargetFileName, + PatchFileName: d.PatchFileName, + } } \ No newline at end of file diff --git a/pkg/models/pkgVersion.go b/pkg/models/pkgVersion.go new file mode 100644 index 0000000..dbae4a6 --- /dev/null +++ b/pkg/models/pkgVersion.go @@ -0,0 +1,36 @@ +package models + +import ( + "bufio" + "encoding/json" + "os" +) + +type PkgVersion struct { + RemoteFile string `json:"remoteName"` + MD5 string `json:"md5"` +} + +func LoadPkgVersion(path string) ([]*PkgVersion, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + var results []*PkgVersion + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + var item PkgVersion + if err := json.Unmarshal([]byte(line), &item); err == nil { + results = append(results, &item) + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return results, nil +} diff --git a/pkg/sevenzip/sevenzip.go b/pkg/sevenzip/sevenzip.go index 5e96fd4..b17c552 100644 --- a/pkg/sevenzip/sevenzip.go +++ b/pkg/sevenzip/sevenzip.go @@ -29,6 +29,41 @@ func IsFileIn7z(archivePath, fileInside string) (bool, error) { return false, fmt.Errorf("%s not found in %s", fileInside, archivePath) } +func ListFilesInZip(archivePath string) ([]string, error) { + cmd := exec.Command(constant.Tool7zaExe.String(), "l", archivePath) + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("7za list failed: %v\nOutput: %s", err, out.String()) + } + + lines := strings.Split(out.String(), "\n") + var files []string + foundTable := false + + for _, line := range lines { + if strings.HasPrefix(line, "----------") { + if foundTable { + break + } + foundTable = true + continue + } + + if foundTable { + fields := strings.Fields(line) + if len(fields) >= 6 { + fileName := strings.Join(fields[5:], " ") + files = append(files, fileName) + } + } + } + + return files, nil +} + func ExtractAFileFromZip(archivePath, fileInside, outDir string) error { cmd := exec.Command(constant.Tool7zaExe.String(), "e", archivePath, fileInside, "-o"+outDir, "-y") cmd.Stdout = os.Stdout