2025-05-13 17:10:30 +08:00

255 lines
5.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}