Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b2adcd7981 | |||
| ba58d24e06 | |||
| ec72b812de | |||
| 448ced1260 | |||
| d4c75b341f |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {Promise<[string, string]> & { cancel(): void }}
|
||||
* @returns {Promise<[boolean, string, string, string]> & { cancel(): void }}
|
||||
*/
|
||||
export function GetLanguage(path) {
|
||||
let $resultPromise = /** @type {any} */($Call.ByID(3450750492, path));
|
||||
@@ -19,7 +19,7 @@ export function GetLanguage(path) {
|
||||
* @param {string} path
|
||||
* @param {string} text
|
||||
* @param {string} voice
|
||||
* @returns {Promise<boolean> & { cancel(): void }}
|
||||
* @returns {Promise<[boolean, string]> & { cancel(): void }}
|
||||
*/
|
||||
export function SetLanguage(path, text, voice) {
|
||||
let $resultPromise = /** @type {any} */($Call.ByID(2793672496, path, text, voice));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -27,28 +27,42 @@ export default function LanguagePage() {
|
||||
|
||||
useEffect(() => {
|
||||
const getLanguage = async () => {
|
||||
if (gameDir) {
|
||||
const subPath = 'StarRail_Data/StreamingAssets/DesignData/Windows'
|
||||
const fullPath = `${gameDir}/${subPath}`
|
||||
if (!gameDir) return
|
||||
|
||||
const exists = await FSService.DirExists(fullPath)
|
||||
if (exists) {
|
||||
const [textLang, voiceLang] = await LanguageService.GetLanguage(fullPath)
|
||||
setTextLang(textLang)
|
||||
setVoiceLang(voiceLang)
|
||||
setFolderCheckResult('success')
|
||||
setSelectedTextLang(textLang)
|
||||
setSelectedVoiceLang(voiceLang)
|
||||
} else {
|
||||
setTextLang('')
|
||||
setVoiceLang('')
|
||||
setSelectedTextLang('')
|
||||
setSelectedVoiceLang('')
|
||||
setFolderCheckResult('error')
|
||||
setGameDir('')
|
||||
}
|
||||
const subPath = "StarRail_Data/StreamingAssets"
|
||||
const fullPath = `${gameDir}/${subPath}`
|
||||
|
||||
const exists = await FSService.DirExists(fullPath)
|
||||
if (!exists) {
|
||||
setTextLang("")
|
||||
setVoiceLang("")
|
||||
setSelectedTextLang("")
|
||||
setSelectedVoiceLang("")
|
||||
setFolderCheckResult("error")
|
||||
setGameDir("")
|
||||
return
|
||||
}
|
||||
|
||||
const [ok, textLang, voiceLang, err] = await LanguageService.GetLanguage(fullPath)
|
||||
if (!ok) {
|
||||
setTextLang("")
|
||||
setVoiceLang("")
|
||||
setSelectedTextLang("")
|
||||
setSelectedVoiceLang("")
|
||||
setFolderCheckResult("error")
|
||||
setGameDir("")
|
||||
toast.error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// success
|
||||
setTextLang(textLang)
|
||||
setVoiceLang(voiceLang)
|
||||
setFolderCheckResult("success")
|
||||
setSelectedTextLang(textLang)
|
||||
setSelectedVoiceLang(voiceLang)
|
||||
}
|
||||
|
||||
getLanguage()
|
||||
}, [gameDir])
|
||||
|
||||
@@ -86,19 +100,19 @@ export default function LanguagePage() {
|
||||
}
|
||||
try {
|
||||
setIsSettingLanguage(true)
|
||||
const result = await LanguageService.SetLanguage(
|
||||
const [ok, err] = await LanguageService.SetLanguage(
|
||||
`${gameDir}/StarRail_Data/StreamingAssets/DesignData/Windows`,
|
||||
selectedTextLang,
|
||||
selectedVoiceLang
|
||||
)
|
||||
if (result) {
|
||||
toast.success('Language set successfully')
|
||||
setTextLang(selectedTextLang)
|
||||
setVoiceLang(selectedVoiceLang)
|
||||
}
|
||||
else {
|
||||
toast.error('Language set failed')
|
||||
}
|
||||
if (ok) {
|
||||
toast.success('Language set successfully')
|
||||
setTextLang(selectedTextLang)
|
||||
setVoiceLang(selectedVoiceLang)
|
||||
}
|
||||
else {
|
||||
toast.error(err)
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
toast.error('SetLanguage error:', err)
|
||||
@@ -154,8 +168,8 @@ export default function LanguagePage() {
|
||||
</div>
|
||||
{folderCheckResult && (
|
||||
<div className={`flex items-center gap-2 p-3 rounded-lg ${folderCheckResult === 'success'
|
||||
? 'bg-success/5 text-success border border-success'
|
||||
: 'bg-error/5 text-error border border-error'
|
||||
? 'bg-success/5 text-success border border-success'
|
||||
: 'bg-error/5 text-error border border-error'
|
||||
}`}>
|
||||
{folderCheckResult === 'success' ? (
|
||||
<>
|
||||
|
||||
@@ -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, ""
|
||||
}
|
||||
|
||||
|
||||
@@ -2,145 +2,174 @@ package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
assetMeta "firefly-launcher/pkg/language-patch/asset-meta"
|
||||
excelLanguage "firefly-launcher/pkg/language-patch/excel-language"
|
||||
"firefly-launcher/pkg/models"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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
|
||||
return slices.Contains(valid, lang)
|
||||
}
|
||||
|
||||
func (l *LanguageService) GetLanguage(path string) (string, string, error) {
|
||||
files, err := os.ReadDir(path)
|
||||
func (l *LanguageService) GetLanguage(path string) (bool, string, string, string) {
|
||||
currentVersionGame, err := models.ParseBinaryVersion(filepath.Join(path, "BinaryVersion.bytes"))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return false, "", "", err.Error()
|
||||
}
|
||||
|
||||
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
|
||||
typeVersionGame := "os"
|
||||
if strings.Contains(currentVersionGame.Name, "CN") {
|
||||
typeVersionGame = "cn"
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("couldn't find file to read language from")
|
||||
}
|
||||
assetPath := filepath.Join(path, "DesignData\\Windows")
|
||||
|
||||
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)
|
||||
indexHash, err := assetMeta.GetIndexHash(assetPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, "", "", err.Error()
|
||||
}
|
||||
|
||||
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
|
||||
DesignIndex, err := assetMeta.DesignIndexFromBytes(assetPath, indexHash)
|
||||
if err != nil {
|
||||
return false, "", "", err.Error()
|
||||
}
|
||||
dataEntry, fileEntry, err := DesignIndex.FindDataAndFileByTarget(-515329346)
|
||||
if err != nil {
|
||||
return false, "", "", err.Error()
|
||||
}
|
||||
allowedLanguage := excelLanguage.NewExcelLanguage(assetPath, &dataEntry, &fileEntry)
|
||||
languageRows, err := allowedLanguage.Parse()
|
||||
if err != nil {
|
||||
return false, "", "", err.Error()
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("couldn't find file to patch. Make sure this file is placed in the correct folder")
|
||||
currentTextLang := ""
|
||||
currentVoiceLang := ""
|
||||
|
||||
pairs := []struct {
|
||||
area string
|
||||
typ *uint8
|
||||
}{
|
||||
{"os", nil},
|
||||
{"cn", func() *uint8 { v := uint8(1); return &v }()},
|
||||
{"os", func() *uint8 { v := uint8(1); return &v }()},
|
||||
{"cn", nil},
|
||||
}
|
||||
|
||||
for _, p := range pairs {
|
||||
var found *excelLanguage.LanguageRow
|
||||
for i := range languageRows {
|
||||
if languageRows[i].Area != nil && *languageRows[i].Area == p.area {
|
||||
if (languageRows[i].Type == nil && p.typ == nil) ||
|
||||
(languageRows[i].Type != nil && p.typ != nil && *languageRows[i].Type == *p.typ) {
|
||||
found = &languageRows[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
continue
|
||||
}
|
||||
if found.DefaultLanguage != nil && found.Area != nil && *found.Area == typeVersionGame && found.Type == nil {
|
||||
currentTextLang = *found.DefaultLanguage
|
||||
}
|
||||
if found.DefaultLanguage != nil && found.Area != nil && *found.Area == typeVersionGame && found.Type != nil {
|
||||
currentVoiceLang = *found.DefaultLanguage
|
||||
}
|
||||
}
|
||||
|
||||
if currentTextLang == "" || currentVoiceLang == "" || !isValidLang(currentTextLang) || !isValidLang(currentVoiceLang) {
|
||||
return false, "", "", "not found language"
|
||||
}
|
||||
|
||||
return true, currentTextLang, currentVoiceLang, ""
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (l *LanguageService) SetLanguage(path string, text, voice string) (bool, string) {
|
||||
indexHash, err := assetMeta.GetIndexHash(path)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
DesignIndex, err := assetMeta.DesignIndexFromBytes(path, indexHash)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
dataEntry, fileEntry, err := DesignIndex.FindDataAndFileByTarget(-515329346)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
allowedLanguage := excelLanguage.NewExcelLanguage(path, &dataEntry, &fileEntry)
|
||||
languageRows, err := allowedLanguage.Parse()
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
pairs := []struct {
|
||||
area string
|
||||
typ *uint8
|
||||
lang string
|
||||
}{
|
||||
{"os", nil, text},
|
||||
{"cn", func() *uint8 { v := uint8(1); return &v }(), voice},
|
||||
{"os", func() *uint8 { v := uint8(1); return &v }(), voice},
|
||||
{"cn", nil, text},
|
||||
}
|
||||
|
||||
for _, p := range pairs {
|
||||
var found *excelLanguage.LanguageRow
|
||||
for i := range languageRows {
|
||||
if languageRows[i].Area != nil && *languageRows[i].Area == p.area {
|
||||
if (languageRows[i].Type == nil && p.typ == nil) ||
|
||||
(languageRows[i].Type != nil && p.typ != nil && *languageRows[i].Type == *p.typ) {
|
||||
found = &languageRows[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
found.DefaultLanguage = &p.lang
|
||||
found.LanguageList = []string{p.lang}
|
||||
}
|
||||
|
||||
data, err := allowedLanguage.Unmarshal(languageRows)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
filePath := filepath.Join(path, fileEntry.FileByteName+".bytes")
|
||||
|
||||
f, err := os.OpenFile(filePath, os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := f.Seek(int64(dataEntry.Offset), 0); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
if _, err := f.Write(data); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
if len(data) < int(dataEntry.Size) {
|
||||
remaining := int(dataEntry.Size) - len(data)
|
||||
zeros := bytes.Repeat([]byte{0}, remaining)
|
||||
if _, err := f.Write(zeros); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
}
|
||||
return true, "success"
|
||||
}
|
||||
|
||||
1
internal/ldifff-service.go
Normal file
1
internal/ldifff-service.go
Normal file
@@ -0,0 +1 @@
|
||||
package internal
|
||||
@@ -11,7 +11,7 @@ const LauncherFile = "firefly-launcher.exe"
|
||||
const TempUrl = "./temp"
|
||||
|
||||
|
||||
const CurrentLauncherVersion = "1.2"
|
||||
const CurrentLauncherVersion = "1.4.0"
|
||||
|
||||
type ToolFile string
|
||||
|
||||
|
||||
34
pkg/hpatchz/hpatchz.go
Normal file
34
pkg/hpatchz/hpatchz.go
Normal 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
|
||||
}
|
||||
30
pkg/language-patch/asset-meta/bytehash16.go
Normal file
30
pkg/language-patch/asset-meta/bytehash16.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package assetMeta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ByteHash16 []byte
|
||||
|
||||
func ByteHash16FromBytes(r io.ReadSeeker) (ByteHash16, error) {
|
||||
fullHash := make([]byte, 16)
|
||||
buf := make([]byte, 4)
|
||||
for i := 0; i < 4; i++ {
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for j := 0; j < 4; j++ {
|
||||
fullHash[i*4+j] = buf[3-j]
|
||||
}
|
||||
}
|
||||
return ByteHash16(fullHash), nil
|
||||
}
|
||||
|
||||
func (b ByteHash16) String() string {
|
||||
s := ""
|
||||
for _, v := range b {
|
||||
s += fmt.Sprintf("%02x", v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
28
pkg/language-patch/asset-meta/dataEntry.go
Normal file
28
pkg/language-patch/asset-meta/dataEntry.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package assetMeta
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
type DataEntry struct {
|
||||
NameHash int32
|
||||
Size uint32
|
||||
Offset uint32
|
||||
}
|
||||
|
||||
func DataEntryFromBytes(r io.Reader) (*DataEntry, error) {
|
||||
var d DataEntry
|
||||
|
||||
if err := binary.Read(r, binary.BigEndian, &d.NameHash); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &d.Size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &d.Offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &d, nil
|
||||
}
|
||||
98
pkg/language-patch/asset-meta/designIndex.go
Normal file
98
pkg/language-patch/asset-meta/designIndex.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package assetMeta
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
||||
type DesignIndex struct {
|
||||
UnkI64 int64
|
||||
FileCount int32
|
||||
DesignDataCount int32
|
||||
FileList []FileEntry
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (d *DesignIndex) FindDataAndFileByTarget(target int32) (DataEntry, FileEntry, error) {
|
||||
for _, file := range d.FileList {
|
||||
for _, entry := range file.DataEntries {
|
||||
if entry.NameHash == target {
|
||||
return entry, file, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return DataEntry{}, FileEntry{}, errors.New("not found")
|
||||
}
|
||||
|
||||
|
||||
func DesignIndexFromBytes(assetFolder string, indexHash string) (*DesignIndex, error) {
|
||||
path := filepath.Join(assetFolder, fmt.Sprintf("DesignV_%s.bytes", indexHash))
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := bytes.NewReader(data)
|
||||
|
||||
var d DesignIndex
|
||||
|
||||
if err := binary.Read(r, binary.BigEndian, &d.UnkI64); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &d.FileCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &d.DesignDataCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.FileList = make([]FileEntry, 0, d.FileCount)
|
||||
for i := int32(0); i < d.FileCount; i++ {
|
||||
entry, err := FileEntryFromBytes(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.FileList = append(d.FileList, *entry)
|
||||
}
|
||||
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
|
||||
func GetIndexHash(assetFolder string) (string, error) {
|
||||
path := filepath.Join(assetFolder, "M_DesignV.bytes")
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Seek(0x1C, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hash := make([]byte, 0x10)
|
||||
index := 0
|
||||
for i := 0; i < 4; i++ {
|
||||
chunk := make([]byte, 4)
|
||||
_, err := f.Read(chunk)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for bytePos := 3; bytePos >= 0; bytePos-- {
|
||||
hash[index] = chunk[bytePos]
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash), nil
|
||||
}
|
||||
63
pkg/language-patch/asset-meta/fileEntry.go
Normal file
63
pkg/language-patch/asset-meta/fileEntry.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package assetMeta
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type FileEntry struct {
|
||||
NameHash int32
|
||||
FileByteName string
|
||||
Size int64
|
||||
DataCount int32
|
||||
DataEntries []DataEntry
|
||||
Unk uint8
|
||||
}
|
||||
|
||||
func FileEntryFromBytes(r io.Reader) (*FileEntry, error) {
|
||||
var f FileEntry
|
||||
|
||||
if err := binary.Read(r, binary.BigEndian, &f.NameHash); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, 16)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.FileByteName = toHex(buf)
|
||||
|
||||
if err := binary.Read(r, binary.BigEndian, &f.Size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &f.DataCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.DataEntries = make([]DataEntry, 0, f.DataCount)
|
||||
for i := int32(0); i < f.DataCount; i++ {
|
||||
entry, err := DataEntryFromBytes(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.DataEntries = append(f.DataEntries, *entry)
|
||||
}
|
||||
|
||||
// read 1 byte
|
||||
b := make([]byte, 1)
|
||||
if _, err := r.Read(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.Unk = b[0]
|
||||
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
func toHex(buf []byte) string {
|
||||
s := ""
|
||||
for _, b := range buf {
|
||||
s += fmt.Sprintf("%02x", b)
|
||||
}
|
||||
return s
|
||||
}
|
||||
32
pkg/language-patch/asset-meta/miniAsset.go
Normal file
32
pkg/language-patch/asset-meta/miniAsset.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package assetMeta
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
type MiniAsset struct {
|
||||
RevisionID uint32
|
||||
DesignIndexHash ByteHash16
|
||||
}
|
||||
|
||||
func MiniAssetFromBytes(r io.ReadSeeker) (*MiniAsset, error) {
|
||||
if _, err := r.Seek(6*4, io.SeekCurrent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var revID uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &revID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash, err := ByteHash16FromBytes(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MiniAsset{
|
||||
RevisionID: revID,
|
||||
DesignIndexHash: hash,
|
||||
}, nil
|
||||
}
|
||||
114
pkg/language-patch/excel-language/patch.go
Normal file
114
pkg/language-patch/excel-language/patch.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package excelLanguage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
assetMeta "firefly-launcher/pkg/language-patch/asset-meta"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type ExcelLanguage struct {
|
||||
AssetFolder string
|
||||
ExcelDataEntry *assetMeta.DataEntry
|
||||
ExcelFileEntry *assetMeta.FileEntry
|
||||
}
|
||||
|
||||
func NewExcelLanguage(assetFolder string, dataEntry *assetMeta.DataEntry, fileEntry *assetMeta.FileEntry) *ExcelLanguage {
|
||||
return &ExcelLanguage{
|
||||
AssetFolder: assetFolder,
|
||||
ExcelDataEntry: dataEntry,
|
||||
ExcelFileEntry: fileEntry,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ExcelLanguage) Unmarshal(rows []LanguageRow) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
buf.WriteByte(0)
|
||||
if err := writeI8Varint(buf, int8(len(rows))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
rowData, err := row.Unmarshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := buf.Write(rowData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *ExcelLanguage) Parse() ([]LanguageRow, error) {
|
||||
excelPath := filepath.Join(a.AssetFolder, a.ExcelFileEntry.FileByteName+".bytes")
|
||||
|
||||
f, err := os.Open(excelPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := f.Seek(int64(a.ExcelDataEntry.Offset), io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer := make([]byte, a.ExcelDataEntry.Size)
|
||||
if _, err := io.ReadFull(f, buffer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(buffer)
|
||||
|
||||
_, _ = reader.ReadByte() // skip first byte
|
||||
count, err := readI8Varint(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows := make([]LanguageRow, 0, count)
|
||||
for i := 0; i < count; i++ {
|
||||
bitmask, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
row := LanguageRow{}
|
||||
|
||||
if bitmask&(1<<0) != 0 {
|
||||
s, err := readString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row.Area = &s
|
||||
}
|
||||
if bitmask&(1<<1) != 0 {
|
||||
t, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row.Type = &t
|
||||
}
|
||||
if bitmask&(1<<2) != 0 {
|
||||
arr, err := readStringArray(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row.LanguageList = arr
|
||||
}
|
||||
if bitmask&(1<<3) != 0 {
|
||||
s, err := readString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row.DefaultLanguage = &s
|
||||
}
|
||||
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
81
pkg/language-patch/excel-language/row.go
Normal file
81
pkg/language-patch/excel-language/row.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package excelLanguage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type LanguageRow struct {
|
||||
Area *string
|
||||
Type *uint8
|
||||
LanguageList []string
|
||||
DefaultLanguage *string
|
||||
}
|
||||
|
||||
func (r *LanguageRow) Unmarshal() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
var bitmask uint8
|
||||
if r.Area != nil {
|
||||
bitmask |= 1 << 0
|
||||
}
|
||||
if r.Type != nil {
|
||||
bitmask |= 1 << 1
|
||||
}
|
||||
if len(r.LanguageList) > 0 {
|
||||
bitmask |= 1 << 2
|
||||
}
|
||||
if r.DefaultLanguage != nil {
|
||||
bitmask |= 1 << 3
|
||||
}
|
||||
|
||||
if err := buf.WriteByte(bitmask); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.Area != nil {
|
||||
if err := writeString(buf, *r.Area); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if r.Type != nil {
|
||||
if err := buf.WriteByte(*r.Type); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(r.LanguageList) > 0 {
|
||||
if err := writeStringArray(buf, r.LanguageList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if r.DefaultLanguage != nil {
|
||||
if err := writeString(buf, *r.DefaultLanguage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func writeString(buf *bytes.Buffer, s string) error {
|
||||
if len(s) > 255 {
|
||||
return errors.New("string too long")
|
||||
}
|
||||
if err := buf.WriteByte(uint8(len(s))); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := buf.Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
|
||||
func writeStringArray(buf *bytes.Buffer, arr []string) error {
|
||||
if err := writeI8Varint(buf, int8(len(arr))); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, s := range arr {
|
||||
if err := writeString(buf, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
53
pkg/language-patch/excel-language/utils.go
Normal file
53
pkg/language-patch/excel-language/utils.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package excelLanguage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
func writeI8Varint(buf *bytes.Buffer, v int8) error {
|
||||
uv := uint64((uint32(v) << 1) ^ uint32(v>>7)) // zigzag encode
|
||||
b := make([]byte, binary.MaxVarintLen64)
|
||||
n := binary.PutUvarint(b, uv)
|
||||
_, err := buf.Write(b[:n])
|
||||
return err
|
||||
}
|
||||
|
||||
func readI8Varint(r *bytes.Reader) (int, error) {
|
||||
uv, err := binary.ReadUvarint(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// zigzag decode
|
||||
v := int((uv >> 1) ^ uint64((int64(uv&1)<<63)>>63))
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func readString(r *bytes.Reader) (string, error) {
|
||||
l, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := make([]byte, l)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func readStringArray(r *bytes.Reader) ([]string, error) {
|
||||
length, err := readI8Varint(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr := make([]string, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
s, err := readString(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr = append(arr, s)
|
||||
}
|
||||
return arr, nil
|
||||
}
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BinaryVersion struct {
|
||||
Name string
|
||||
Major int
|
||||
Minor int
|
||||
Patch int
|
||||
@@ -22,42 +24,47 @@ func ParseBinaryVersion(path string) (*BinaryVersion, error) {
|
||||
|
||||
content := string(data)
|
||||
|
||||
dashPos := strings.LastIndex(content, "-")
|
||||
if dashPos == -1 {
|
||||
lastDash := strings.LastIndex(content, "-")
|
||||
if lastDash == -1 {
|
||||
return nil, errors.New("no dash found in version string")
|
||||
}
|
||||
|
||||
start := dashPos - 6
|
||||
if start < 0 {
|
||||
start = 0
|
||||
|
||||
secondLastDash := strings.LastIndex(content[:lastDash], "-")
|
||||
if secondLastDash == -1 {
|
||||
return nil, errors.New("only one dash found in version string")
|
||||
}
|
||||
|
||||
versionSlice := content[start:]
|
||||
end := strings.Index(versionSlice, "-")
|
||||
if end == -1 {
|
||||
end = len(versionSlice)
|
||||
}
|
||||
|
||||
versionStr := versionSlice[:end]
|
||||
parts := strings.SplitN(versionStr, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
versionSlice := content[secondLastDash+1 : lastDash]
|
||||
re := regexp.MustCompile(`^([A-Za-z]+)([\d\.]+)$`)
|
||||
matches := re.FindStringSubmatch(versionSlice)
|
||||
if len(matches) < 3 {
|
||||
return nil, errors.New("invalid version format")
|
||||
}
|
||||
binaryVersion := BinaryVersion{
|
||||
Name: matches[1],
|
||||
}
|
||||
numbers := strings.Split(matches[2], ".")
|
||||
|
||||
major, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(numbers) > 0 {
|
||||
binaryVersion.Major, err = strconv.Atoi(numbers[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
minor, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(numbers) > 1 {
|
||||
binaryVersion.Minor, err = strconv.Atoi(numbers[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
patch, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(numbers) > 2 {
|
||||
binaryVersion.Patch, err = strconv.Atoi(numbers[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &BinaryVersion{major, minor, patch}, nil
|
||||
return &binaryVersion, nil
|
||||
}
|
||||
|
||||
func (v *BinaryVersion) String() string {
|
||||
|
||||
43
pkg/models/hdiffFiles.go
Normal file
43
pkg/models/hdiffFiles.go
Normal 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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
36
pkg/models/pkgVersion.go
Normal file
36
pkg/models/pkgVersion.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user