3 Commits
1.2 ... 1.3.0

Author SHA1 Message Date
ec72b812de UPDATE: native with new hdiff type 2025-08-19 20:01:31 +07:00
448ced1260 FIX: fix admin require 2025-08-08 12:26:45 +07:00
d4c75b341f UPDATE: update icon 2025-07-26 22:19:47 +07:00
14 changed files with 290 additions and 46 deletions

View File

@@ -6,7 +6,7 @@
<key>CFBundleName</key>
<string>Firefly Launcher</string>
<key>CFBundleExecutable</key>
<string>Firefly Launcher</string>
<string>firefly-launcher</string>
<key>CFBundleIdentifier</key>
<string>com.fireflyshelter.fireflylauncher</string>
<key>CFBundleVersion</key>

View File

@@ -6,7 +6,7 @@
<key>CFBundleName</key>
<string>Firefly Launcher</string>
<key>CFBundleExecutable</key>
<string>Firefly Launcher</string>
<string>firefly-launcher</string>
<key>CFBundleIdentifier</key>
<string>com.fireflyshelter.fireflylauncher</string>
<key>CFBundleVersion</key>

View File

@@ -3,7 +3,7 @@
#
# The lines below are called `modelines`. See `:help modeline`
name: "Firefly Launcher"
name: "firefly-launcher"
arch: ${GOARCH}
platform: "linux"
version: "0.1.0"
@@ -17,12 +17,12 @@ license: "MIT"
release: "1"
contents:
- src: "./bin/Firefly Launcher"
dst: "/usr/local/bin/Firefly Launcher"
- src: "./bin/firefly-launcher"
dst: "/usr/local/bin/firefly-launcher"
- src: "./build/appicon.png"
dst: "/usr/share/icons/hicolor/128x128/apps/Firefly Launcher.png"
- src: "./build/linux/Firefly Launcher.desktop"
dst: "/usr/share/applications/Firefly Launcher.desktop"
dst: "/usr/share/icons/hicolor/128x128/apps/firefly-launcher.png"
- src: "./build/linux/firefly-launcher.desktop"
dst: "/usr/share/applications/firefly-launcher.desktop"
depends:
- gtk3

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity type="win32" name="com.fireflyshelter.fireflylauncher" version="0.1.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<!-- Yêu cầu quyền admin -->
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>

View File

@@ -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;
}

View File

@@ -116,6 +116,18 @@ export default function HdiffzPage() {
setIsDiffLoading(false)
return
}
setStageType('Check Type HDiff')
setProgressUpdate(0)
setMaxProgressUpdate(1)
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)
@@ -126,9 +138,10 @@ export default function HdiffzPage() {
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)

View File

@@ -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,6 +85,7 @@ func (h *HdiffzService) DataExtract(gamePath, patchPath string) (bool, string) {
return false, err.Error()
}
if !isSkipVerify {
validator, err := verifier.NewVerifier(gamePath, constant.TempUrl)
if err != nil {
os.RemoveAll(constant.TempUrl)
@@ -83,6 +96,7 @@ func (h *HdiffzService) DataExtract(gamePath, patchPath string) (bool, string) {
os.RemoveAll(constant.TempUrl)
return false, err.Error()
}
}
return true, "validated"
}
@@ -136,23 +150,43 @@ 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"))
hdiffMapPath := filepath.Join(gamePath, "hdiffmap.json")
hdiffFilesPath := filepath.Join(gamePath, "hdifffiles.txt")
var jsonData struct {
DiffMap []*models.HDiffData `json:"diff_map"`
}
if _, err := os.Stat(hdiffMapPath); err == nil {
data, err := os.ReadFile(hdiffMapPath)
if err != nil {
return false, err.Error()
}
var jsonData struct {
var jsonDataDiffMap struct {
DiffMap []*models.DiffMapType `json:"diff_map"`
}
if err := json.Unmarshal(data, &jsonData); err != nil {
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 {
application.Get().EmitEvent(
@@ -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()
// 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, ""
}

View File

@@ -0,0 +1 @@
package internal

View File

@@ -11,7 +11,7 @@ const LauncherFile = "firefly-launcher.exe"
const TempUrl = "./temp"
const CurrentLauncherVersion = "1.2"
const CurrentLauncherVersion = "1.2.1"
type ToolFile string

34
pkg/hpatchz/hpatchz.go Normal file
View File

@@ -0,0 +1,34 @@
package hpatchz
import (
"firefly-launcher/pkg/constant"
"fmt"
"os/exec"
"syscall"
)
func ApplyPatch(oldFile, diffFile, newFile string) error {
cmd := exec.Command(constant.ToolHPatchzExe.String(), "-f", oldFile, diffFile, newFile)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to execute hpatchz: %w", err)
}
if cmd.ProcessState.ExitCode() != 0 {
return fmt.Errorf("hpatchz failed: %s", string(output))
}
return nil
}
func ApplyPatchEmpty(diffFile, newFile string) error {
cmd := exec.Command(constant.ToolHPatchzExe.String(), "-f", "", diffFile, newFile)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to execute hpatchz: %w", err)
}
if cmd.ProcessState.ExitCode() != 0 {
return fmt.Errorf("hpatchz failed: %s", string(output))
}
return nil
}

43
pkg/models/hdiffFiles.go Normal file
View File

@@ -0,0 +1,43 @@
package models
import (
"bufio"
"encoding/json"
"fmt"
"os"
)
type HDiffFiles struct {
RemoteFile string `json:"remoteName"`
}
func (h *HDiffFiles) ToHDiffData() *HDiffData {
return &HDiffData{
SourceFileName: h.RemoteFile,
TargetFileName: h.RemoteFile,
PatchFileName: fmt.Sprintf("%s.hdiff", h.RemoteFile),
}
}
func LoadHDiffFiles(path string) ([]*HDiffFiles, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var results []*HDiffFiles
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
var item HDiffFiles
if err := json.Unmarshal([]byte(line), &item); err == nil {
results = append(results, &item)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return results, nil
}

View File

@@ -13,3 +13,17 @@ type DiffMapType struct {
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,
}
}

36
pkg/models/pkgVersion.go Normal file
View File

@@ -0,0 +1,36 @@
package models
import (
"bufio"
"encoding/json"
"os"
)
type PkgVersion struct {
RemoteFile string `json:"remoteName"`
MD5 string `json:"md5"`
}
func LoadPkgVersion(path string) ([]*PkgVersion, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var results []*PkgVersion
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
var item PkgVersion
if err := json.Unmarshal([]byte(line), &item); err == nil {
results = append(results, &item)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return results, nil
}

View File

@@ -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