255 lines
5.9 KiB
Go
255 lines
5.9 KiB
Go
package main
|
||
|
||
import (
|
||
"archive/zip"
|
||
"bytes"
|
||
"encoding/binary"
|
||
"io"
|
||
"log"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"syscall"
|
||
)
|
||
|
||
//go:generate go run github.com/akavel/rsrc -ico app.ico -o app.syso
|
||
|
||
func main() {
|
||
hideConsoleWindow()
|
||
|
||
if isSelfExtracting() {
|
||
extractFiles()
|
||
return
|
||
}
|
||
|
||
createSelfExtractingExe()
|
||
}
|
||
|
||
// 检查自身是否是自解压文件
|
||
func isSelfExtracting() bool {
|
||
exePath, err := os.Executable()
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
exeFile, err := os.Open(exePath)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
defer exeFile.Close()
|
||
|
||
fileInfo, err := exeFile.Stat()
|
||
if err != nil {
|
||
return false
|
||
}
|
||
fileSize := fileInfo.Size()
|
||
|
||
// 检查文件末尾是否有zip大小标记
|
||
if _, err = exeFile.Seek(-8, io.SeekEnd); err != nil {
|
||
return false
|
||
}
|
||
|
||
var zipSize uint64
|
||
if err := binary.Read(exeFile, binary.LittleEndian, &zipSize); err != nil {
|
||
return false
|
||
}
|
||
|
||
// 验证zip大小是否合理
|
||
if zipSize == 0 || zipSize > uint64(fileSize)-8 {
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
func hideConsoleWindow() {
|
||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||
proc := kernel32.NewProc("GetConsoleWindow")
|
||
hwnd, _, _ := proc.Call()
|
||
if hwnd != 0 {
|
||
user32 := syscall.NewLazyDLL("user32.dll")
|
||
showWindow := user32.NewProc("ShowWindow")
|
||
showWindow.Call(hwnd, 0) // SW_HIDE
|
||
}
|
||
}
|
||
|
||
func createSelfExtractingExe() {
|
||
outputExe := "install.exe"
|
||
requiredFiles := []string{"install.bat", "mswsock.dll"}
|
||
|
||
// 检查图标文件是否存在
|
||
if _, err := os.Stat("app.ico"); os.IsNotExist(err) {
|
||
log.Println("警告: app.ico 图标文件不存在,将使用默认图标")
|
||
}
|
||
|
||
for _, file := range requiredFiles {
|
||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||
log.Fatalf("文件 %s 不存在", file)
|
||
}
|
||
}
|
||
|
||
zipBuffer := new(bytes.Buffer)
|
||
zipWriter := zip.NewWriter(zipBuffer)
|
||
|
||
for _, file := range requiredFiles {
|
||
if err := addFileToZip(zipWriter, file); err != nil {
|
||
log.Fatalf("添加文件到 zip 失败: %v", err)
|
||
}
|
||
}
|
||
|
||
if err := zipWriter.Close(); err != nil {
|
||
log.Fatalf("关闭 zip writer 失败: %v", err)
|
||
}
|
||
|
||
outFile, err := os.Create(outputExe)
|
||
if err != nil {
|
||
log.Fatalf("创建输出文件失败: %v", err)
|
||
}
|
||
defer outFile.Close()
|
||
|
||
stub, err := os.ReadFile(os.Args[0])
|
||
if err != nil {
|
||
log.Fatalf("读取 stub 程序失败: %v", err)
|
||
}
|
||
|
||
if _, err = outFile.Write(stub); err != nil {
|
||
log.Fatalf("写入 stub 程序失败: %v", err)
|
||
}
|
||
|
||
if _, err = outFile.Write(zipBuffer.Bytes()); err != nil {
|
||
log.Fatalf("写入 zip 数据失败: %v", err)
|
||
}
|
||
|
||
zipSize := uint64(zipBuffer.Len())
|
||
if err := binary.Write(outFile, binary.LittleEndian, zipSize); err != nil {
|
||
log.Fatalf("写入 zip 大小标记失败: %v", err)
|
||
}
|
||
|
||
log.Printf("成功创建自解压文件: %s", outputExe)
|
||
}
|
||
|
||
func extractFiles() {
|
||
exePath, err := os.Executable()
|
||
if err != nil {
|
||
log.Fatalf("获取可执行文件路径失败: %v", err)
|
||
}
|
||
|
||
exeFile, err := os.Open(exePath)
|
||
if err != nil {
|
||
log.Fatalf("打开可执行文件失败: %v", err)
|
||
}
|
||
defer exeFile.Close()
|
||
|
||
fileInfo, err := exeFile.Stat()
|
||
if err != nil {
|
||
log.Fatalf("获取文件信息失败: %v", err)
|
||
}
|
||
fileSize := fileInfo.Size()
|
||
|
||
if _, err = exeFile.Seek(-8, io.SeekEnd); err != nil {
|
||
log.Fatalf("定位 zip 大小标记失败: %v", err)
|
||
}
|
||
|
||
var zipSize uint64
|
||
if err := binary.Read(exeFile, binary.LittleEndian, &zipSize); err != nil {
|
||
log.Fatalf("读取 zip 大小失败: %v", err)
|
||
}
|
||
|
||
zipOffset := fileSize - 8 - int64(zipSize)
|
||
if _, err = exeFile.Seek(zipOffset, io.SeekStart); err != nil {
|
||
log.Fatalf("定位 zip 数据失败: %v", err)
|
||
}
|
||
|
||
zipData := make([]byte, zipSize)
|
||
if _, err = io.ReadFull(exeFile, zipData); err != nil {
|
||
log.Fatalf("读取 zip 数据失败: %v", err)
|
||
}
|
||
|
||
zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
|
||
if err != nil {
|
||
log.Fatalf("创建 zip reader 失败: %v", err)
|
||
}
|
||
|
||
// 将文件解压到临时目录
|
||
tempDir := os.TempDir()
|
||
extractDir := filepath.Join(tempDir, "myapp_install")
|
||
if err := os.MkdirAll(extractDir, 0755); err != nil {
|
||
log.Fatalf("创建解压目录失败: %v", err)
|
||
}
|
||
|
||
log.Printf("正在解压文件到临时目录: %s", extractDir)
|
||
|
||
for _, file := range zipReader.File {
|
||
if file.FileInfo().IsDir() {
|
||
continue
|
||
}
|
||
|
||
rc, err := file.Open()
|
||
if err != nil {
|
||
log.Fatalf("打开 zip 中的文件 %s 失败: %v", file.Name, err)
|
||
}
|
||
|
||
targetPath := filepath.Join(extractDir, filepath.Base(file.Name))
|
||
outFile, err := os.Create(targetPath)
|
||
if err != nil {
|
||
rc.Close()
|
||
log.Fatalf("创建目标文件 %s 失败: %v", targetPath, err)
|
||
}
|
||
|
||
_, err = io.Copy(outFile, rc)
|
||
outFile.Close()
|
||
rc.Close()
|
||
|
||
if err != nil {
|
||
log.Fatalf("写入文件 %s 失败: %v", targetPath, err)
|
||
}
|
||
|
||
log.Printf("已解压文件: %s", targetPath)
|
||
}
|
||
|
||
// 运行install.bat(无闪现窗口)
|
||
runInstallBat(extractDir)
|
||
}
|
||
|
||
func runInstallBat(extractDir string) {
|
||
installBat := filepath.Join(extractDir, "install.bat")
|
||
|
||
// 方法1:直接创建新窗口运行(不需要管理员权限时)
|
||
cmd := exec.Command("cmd", "/c", "start", "cmd", "/c", installBat)
|
||
cmd.Dir = extractDir
|
||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||
|
||
if err := cmd.Start(); err != nil {
|
||
log.Printf("启动install.bat失败: %v", err)
|
||
}
|
||
}
|
||
|
||
func addFileToZip(zipWriter *zip.Writer, filename string) error {
|
||
fileToZip, err := os.Open(filename)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer fileToZip.Close()
|
||
|
||
info, err := fileToZip.Stat()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
header, err := zip.FileInfoHeader(info)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
header.Name = filepath.Base(filename)
|
||
header.Method = zip.Deflate
|
||
|
||
writer, err := zipWriter.CreateHeader(header)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
_, err = io.Copy(writer, fileToZip)
|
||
return err
|
||
}
|