init
This commit is contained in:
142
internal/fs-service.go
Normal file
142
internal/fs-service.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"firefly-launcher/pkg/sevenzip"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type FSService struct{}
|
||||
|
||||
func (f *FSService) PickFolder() (string, error) {
|
||||
dialog := application.OpenFileDialog().
|
||||
CanChooseDirectories(true).
|
||||
CanCreateDirectories(true).
|
||||
ResolvesAliases(true)
|
||||
if runtime.GOOS == "darwin" {
|
||||
dialog.SetMessage("Select a file/directory")
|
||||
} else {
|
||||
dialog.SetTitle("Select a file/directory")
|
||||
}
|
||||
if path, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *FSService) PickFile() (string, error) {
|
||||
dialog := application.OpenFileDialog().
|
||||
CanChooseFiles(true).
|
||||
ResolvesAliases(true)
|
||||
if runtime.GOOS == "darwin" {
|
||||
dialog.SetMessage("Select a file/directory")
|
||||
} else {
|
||||
dialog.SetTitle("Select a file/directory")
|
||||
}
|
||||
if path, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *FSService) DirExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
func (f *FSService) FileExists(path string) bool {
|
||||
if info, err := os.Stat(path); err == nil {
|
||||
return info.Mode().IsRegular()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FSService) StartApp(path string) (bool, error) {
|
||||
cmd := exec.Command(path)
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if strings.HasSuffix(path, "StarRail.exe") {
|
||||
go func() {
|
||||
_ = cmd.Wait()
|
||||
application.Get().EmitEvent("game:exit")
|
||||
}()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *FSService) StartWithConsole(path string) (bool, error) {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
return false, fmt.Errorf("file not found: %s", absPath)
|
||||
}
|
||||
cmd := exec.Command(absPath)
|
||||
cmd.Dir = filepath.Dir(absPath)
|
||||
cmd.Stdin = nil
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
CreationFlags: windows.CREATE_NEW_CONSOLE |
|
||||
windows.CREATE_BREAKAWAY_FROM_JOB,
|
||||
NoInheritHandles: true,
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
_ = cmd.Wait()
|
||||
if strings.HasSuffix(path, "launcher.exe") {
|
||||
application.Get().EmitEvent("game:exit")
|
||||
} else if strings.HasSuffix(path, "firefly-go_win.exe") {
|
||||
application.Get().EmitEvent("server:exit")
|
||||
} else if strings.HasSuffix(path, "FireflyProxy.exe") {
|
||||
application.Get().EmitEvent("proxy:exit")
|
||||
}
|
||||
}()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *FSService) OpenFolder(path string) (bool, string) {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return false, "failed to resolve absolute path: " + err.Error()
|
||||
}
|
||||
|
||||
if !f.DirExists(absPath) {
|
||||
return false, "directory not found: " + absPath
|
||||
}
|
||||
|
||||
url := "file:///" + filepath.ToSlash(absPath)
|
||||
application.Get().BrowserOpenURL(url)
|
||||
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (f *FSService) FileExistsInZip(archivePath, fileInside string) (bool, string) {
|
||||
exists, err := sevenzip.IsFileIn7z(archivePath, fileInside)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
return exists, ""
|
||||
}
|
||||
203
internal/git-service.go
Normal file
203
internal/git-service.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package internal
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
type GitService struct{}
|
||||
|
||||
func (g *GitService) GetLatestServerVersion(oldVersion string) (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().EmitEvent("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(oldVersion string) (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().EmitEvent("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))
|
||||
}
|
||||
212
internal/hdiffz-service.go
Normal file
212
internal/hdiffz-service.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"firefly-launcher/pkg/constant"
|
||||
"firefly-launcher/pkg/models"
|
||||
"firefly-launcher/pkg/sevenzip"
|
||||
"firefly-launcher/pkg/verifier"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type HdiffzService struct{}
|
||||
|
||||
func (h *HdiffzService) 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()
|
||||
}
|
||||
|
||||
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.ExtractAFileFromZip(patchPath, "StarRail_Data\\StreamingAssets\\BinaryVersion.bytes", constant.TempUrl); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
binPath := filepath.Join(constant.TempUrl, "BinaryVersion.bytes")
|
||||
newVersionData, err := models.ParseBinaryVersion(binPath)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
defer os.Remove(binPath)
|
||||
|
||||
v := newVersionData.Subtract(*oldVersionData)
|
||||
if v != 0 && v != 1 {
|
||||
return false, fmt.Sprintf("the diff version %s not valid with game version %s", newVersionData, oldVersionData)
|
||||
}
|
||||
|
||||
return true, "validated"
|
||||
}
|
||||
func (h *HdiffzService) DataExtract(gamePath, patchPath string) (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()
|
||||
}
|
||||
|
||||
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().EmitEvent("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()
|
||||
}
|
||||
|
||||
_ = 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()
|
||||
}
|
||||
|
||||
var jsonData struct {
|
||||
DiffMap []*models.DiffMapType `json:"diff_map"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &jsonData); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
for i, entry := range jsonData.DiffMap {
|
||||
application.Get().EmitEvent(
|
||||
"hdiffz:progress", map[string]int{
|
||||
"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
|
||||
}
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return true, "patching completed"
|
||||
}
|
||||
|
||||
func (h *HdiffzService) DeleteFiles(gamePath string) (bool, string) {
|
||||
var deleteFiles []string
|
||||
|
||||
file, err := os.Open(filepath.Join(gamePath, "deletefiles.txt"))
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line != "" {
|
||||
deleteFiles = append(deleteFiles, line)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
for i, file := range deleteFiles {
|
||||
os.Remove(filepath.Join(gamePath, file))
|
||||
application.Get().EmitEvent("hdiffz:progress", map[string]int{"progress": i, "maxProgress": len(deleteFiles)})
|
||||
}
|
||||
|
||||
return true, ""
|
||||
}
|
||||
146
internal/language-service.go
Normal file
146
internal/language-service.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type LanguageService struct{}
|
||||
|
||||
func isValidLang(lang string) bool {
|
||||
valid := []string{"en", "jp", "cn", "kr"}
|
||||
for _, v := range valid {
|
||||
if lang == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *LanguageService) GetLanguage(path string) (string, string, error) {
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
filePath := filepath.Join(path, file.Name())
|
||||
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
patternToFind := []byte("SpriteOutput/UI/Fonts/RPG_CN.ttf")
|
||||
idx := bytes.Index(content, patternToFind)
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
pattern := []byte("Korean")
|
||||
idx = bytes.Index(content, pattern)
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Move to os text language
|
||||
idx += 10
|
||||
idx += 4
|
||||
osText := string(content[idx : idx+2])
|
||||
idx += 3 * 4
|
||||
|
||||
// Move to cn voice language
|
||||
idx += 1
|
||||
idx += 5
|
||||
cnVoice := string(content[idx : idx+2])
|
||||
idx += 3 * 2 // skip 2 entries
|
||||
|
||||
// Move to os voice language
|
||||
idx += 1
|
||||
idx += 5
|
||||
osVoice := string(content[idx : idx+2])
|
||||
idx += 3 * 5 // skip 5 entries
|
||||
|
||||
// Move to cn text language
|
||||
idx += 1
|
||||
idx += 4
|
||||
cnText := string(content[idx : idx+2])
|
||||
|
||||
textLang := osText
|
||||
voiceLang := osVoice
|
||||
if !isValidLang(textLang) {
|
||||
textLang = cnText
|
||||
}
|
||||
if !isValidLang(voiceLang) {
|
||||
voiceLang = cnVoice
|
||||
}
|
||||
|
||||
return textLang, voiceLang, nil
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("couldn't find file to read language from")
|
||||
}
|
||||
|
||||
func replaceBytes(content []byte, idx int, choice string, param int) int {
|
||||
for i := 0; i < param; i++ {
|
||||
copy(content[idx:idx+2], []byte(choice))
|
||||
idx += 3
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
func (l *LanguageService) SetLanguage(path string, text, voice string) (bool, error) {
|
||||
files, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
filePath := filepath.Join(path, file.Name())
|
||||
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
patternToFind := []byte("SpriteOutput/UI/Fonts/RPG_CN.ttf")
|
||||
idx := bytes.Index(content, patternToFind)
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
pattern := []byte("Korean")
|
||||
idx = bytes.Index(content, pattern)
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
idx += 10
|
||||
idx += 4
|
||||
idx = replaceBytes(content, idx, text, 4)
|
||||
idx += 1
|
||||
|
||||
idx += 5
|
||||
idx = replaceBytes(content, idx, voice, 2)
|
||||
idx += 1
|
||||
|
||||
idx += 5
|
||||
idx = replaceBytes(content, idx, voice, 5)
|
||||
|
||||
idx += 1
|
||||
|
||||
idx += 4
|
||||
_ = replaceBytes(content, idx, text, 2)
|
||||
|
||||
err = os.WriteFile(filePath, content, 0644)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("couldn't find file to patch. Make sure this file is placed in the correct folder")
|
||||
}
|
||||
192
internal/util.go
Normal file
192
internal/util.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func humanFormat(bytes int64) string {
|
||||
n := float64(bytes)
|
||||
for _, unit := range []string{"", "Ki", "Mi", "Gi"} {
|
||||
if math.Abs(n) < 1024.0 {
|
||||
return fmt.Sprintf("%3.1f%sB", n, unit)
|
||||
}
|
||||
n /= 1024.0
|
||||
}
|
||||
return fmt.Sprintf("%.1fTiB", n)
|
||||
}
|
||||
|
||||
type WriteCounter struct {
|
||||
Total uint64
|
||||
StartTime time.Time
|
||||
OnEmit func(percent float64, speedMBps float64)
|
||||
TotalSize int64
|
||||
lastLoggedPercent int
|
||||
}
|
||||
|
||||
func NewWriteCounter(total int64, onEmit func(percent float64, speedMBps float64)) *WriteCounter {
|
||||
return &WriteCounter{
|
||||
StartTime: time.Now(),
|
||||
TotalSize: total,
|
||||
lastLoggedPercent: -1,
|
||||
OnEmit: onEmit,
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *WriteCounter) Write(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
wc.Total += uint64(n)
|
||||
wc.PrintProgress()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (wc *WriteCounter) PrintProgress() {
|
||||
elapsed := time.Since(wc.StartTime).Seconds()
|
||||
if elapsed < 0.001 {
|
||||
elapsed = 0.001
|
||||
}
|
||||
|
||||
speed := float64(wc.Total) / 1024 / 1024 / elapsed // MB/s
|
||||
percent := float64(wc.Total) / float64(wc.TotalSize) * 100
|
||||
if wc.OnEmit != nil {
|
||||
wc.OnEmit(percent, speed)
|
||||
}
|
||||
}
|
||||
|
||||
func DownloadFile(filepath string, url string, onEmit func(percent float64, speed float64)) error {
|
||||
tmpPath := filepath + ".tmp"
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get file: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("bad status: %s", resp.Status)
|
||||
}
|
||||
|
||||
out, err := os.Create(tmpPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create tmp file: %w", err)
|
||||
}
|
||||
|
||||
counter := NewWriteCounter(resp.ContentLength, onEmit)
|
||||
_, err = io.Copy(out, io.TeeReader(resp.Body, counter))
|
||||
if closeErr := out.Close(); closeErr != nil {
|
||||
return fmt.Errorf("failed to close tmp file: %w", closeErr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download file: %w", err)
|
||||
}
|
||||
|
||||
// Delete destination file if it exists
|
||||
if _, err := os.Stat(filepath); err == nil {
|
||||
if err := os.Remove(filepath); err != nil {
|
||||
return fmt.Errorf("failed to remove existing file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
err = os.Rename(tmpPath, filepath)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to rename after retries: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
func unzipParallel(src string, dest string) error {
|
||||
numCPU := runtime.NumCPU()
|
||||
|
||||
reserved := 1
|
||||
if numCPU > 4 {
|
||||
reserved = 2
|
||||
}
|
||||
maxWorkers := numCPU - reserved
|
||||
if maxWorkers < 1 {
|
||||
maxWorkers = 1
|
||||
}
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
err = os.MkdirAll(dest, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type job struct {
|
||||
f *zip.File
|
||||
}
|
||||
jobs := make(chan job)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Worker pool
|
||||
for i := 0; i < maxWorkers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := range jobs {
|
||||
err := extractFile(j.f, dest)
|
||||
if err != nil {
|
||||
fmt.Printf("Error extracting %s: %v\n", j.f.Name, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Feed jobs
|
||||
for _, f := range r.File {
|
||||
jobs <- job{f}
|
||||
}
|
||||
close(jobs)
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFile(f *zip.File, dest string) error {
|
||||
fp := filepath.Join(dest, f.Name)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
return os.MkdirAll(fp, f.Mode())
|
||||
}
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(fp), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
out, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, rc)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user