From e57ee27b95596db96f0ecccdd02568315ad27fb0 Mon Sep 17 00:00:00 2001 From: kingecg Date: Sat, 13 Sep 2025 18:23:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90packer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 28 ++++++ Makefile | 21 +++++ cmd/packer/counter.go | 16 ++++ cmd/packer/main.go | 198 ++++++++++++++++++++++++++++++++++++++++++ data/data.go | 167 +++++++++++++++++++++++++++++++++++ exmaple/compress.text | 94 ++++++++++++++++++++ exmaple/main.go | 31 +++++++ exmaple/res.text | 1 + go.mod | 3 + 9 files changed, 559 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 Makefile create mode 100644 cmd/packer/counter.go create mode 100644 cmd/packer/main.go create mode 100644 data/data.go create mode 100644 exmaple/compress.text create mode 100644 exmaple/main.go create mode 100644 exmaple/res.text create mode 100644 go.mod diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..65db6b3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}", + "args":[ + "-z", + "-b", + "exmaple/example", + "-f", + "exmaple/res.text", + "-f", + "exmaple/compress.text", + "-o", + "exmaple/packedexample" + ], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..02beba8 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +# Makefile for building the packer command-line tool + +# Variables +BINARY_NAME := packer +BUILD_DIR := ./bin +SRC_DIR := ./cmd/packer + +# Build the packer tool +build: + @echo "Building $(BINARY_NAME)..." + @mkdir -p $(BUILD_DIR) + @go build -o $(BUILD_DIR)/$(BINARY_NAME) $(SRC_DIR) + @echo "Build completed. Binary saved to $(BUILD_DIR)/$(BINARY_NAME)" + +# Clean build artifacts +clean: + @echo "Cleaning build artifacts..." + @rm -rf $(BUILD_DIR) + @echo "Clean completed." + +.PHONY: build clean \ No newline at end of file diff --git a/cmd/packer/counter.go b/cmd/packer/counter.go new file mode 100644 index 0000000..a2cf293 --- /dev/null +++ b/cmd/packer/counter.go @@ -0,0 +1,16 @@ +package main + +import ( + "io" +) + +type CountWriter struct { + Writer io.Writer + Count int64 +} + +func (cw *CountWriter) Write(p []byte) (n int, err error) { + n, err = cw.Writer.Write(p) + cw.Count += int64(n) + return n, err +} diff --git a/cmd/packer/main.go b/cmd/packer/main.go new file mode 100644 index 0000000..ece59b9 --- /dev/null +++ b/cmd/packer/main.go @@ -0,0 +1,198 @@ +package main + +import ( + "encoding/binary" + + "compress/flate" + "flag" + "fmt" + "git.kingecg.top/kingecg/gopacker/data" + "io" + "os" + "path/filepath" + "strings" +) + +type stringSlice []string + +// 实现 flag.Value 接口 +func (s *stringSlice) String() string { + return strings.Join(*s, ",") +} + +func (s *stringSlice) Set(value string) error { + *s = append(*s, value) + return nil +} +func addFile(bootPathDir string, filePath string, outputFile *os.File, fs *data.FileSystem) error { + relPath, err := filepath.Rel(bootPathDir, filePath) + if err != nil { + return err + } + relPath = strings.ReplaceAll(relPath, "\\\\", "/") + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + fileInfo, err := file.Stat() + if err != nil { + return err + } + // Get current offset in output file + offset, err := outputFile.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + // Copy file content to output + zippedSize := int64(0) + if needZip { + c := &CountWriter{Writer: outputFile} + zipWriter, err := flate.NewWriter(c, flate.BestCompression) + if err != nil { + return err + } + _, err = io.Copy(zipWriter, file) + // + if err != nil { + return err + } + err = zipWriter.Close() + if err != nil { + return err + } + zippedSize = c.Count + // fmt.Println("zippedSize:", zippedSize) + } else { + _, err = io.Copy(outputFile, file) + if err != nil { + return err + } + } + + // Record file info in FileSystem + fs.Files[relPath] = data.File{ + Size: fileInfo.Size(), + ZSize: zippedSize, + Offset: offset, + } + return nil +} + +var needZip bool + +func main() { + var filePaths stringSlice + bootPath := flag.String("b", "", "Path to the boot program") + flag.BoolVar(&needZip, "z", false, "Whether to zip the output file") + flag.Var(&filePaths, "f", "Paths to resource files or directories") + outputPath := flag.String("o", "", "Path to the output file") + flag.Parse() + + if *bootPath == "" || *outputPath == "" { + fmt.Println("Error: boot path (-b) and output path (-o) are required") + os.Exit(1) + } + + // Open boot file + bootFile, err := os.Open(*bootPath) + if err != nil { + fmt.Printf("Error opening boot file: %v\n", err) + os.Exit(1) + } + defer bootFile.Close() + + bootPathDir := filepath.Dir(*bootPath) + // Create output file + outputFile, err := os.Create(*outputPath) + if err != nil { + fmt.Printf("Error creating output file: %v\n", err) + os.Exit(1) + } + defer outputFile.Close() + + // Copy boot file to output + _, err = io.Copy(outputFile, bootFile) + if err != nil { + fmt.Printf("Error copying boot file: %v\n", err) + os.Exit(1) + } + + // Initialize FileSystem + fs := data.FileSystem{ + Files: make(map[string]data.File), + } + + // Process resource files + fps := []string(filePaths) + for _, filePath := range fps { + fileInfo, err := os.Stat(filePath) + if err != nil { + fmt.Printf("Error accessing resource file: %v\n", err) + os.Exit(1) + } + + if fileInfo.IsDir() { + // Handle directory + err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + err := addFile(bootPathDir, path, outputFile, &fs) + if err != nil { + fmt.Printf("Error processing file: %v\n", err) + return err + } + } + return nil + }) + if err != nil { + fmt.Printf("Error processing directory: %v\n", err) + os.Exit(1) + } + } else { + // Handle single file + err := addFile(bootPathDir, filePath, outputFile, &fs) + if err != nil { + fmt.Printf("Error processing file: %v\n", err) + os.Exit(1) + } + } + } + + // Serialize FileSystem to JSON + jsonData, err := fs.ToJSON() + if err != nil { + fmt.Printf("Error serializing FileSystem: %v\n", err) + os.Exit(1) + } + + // Write JSON data to output file + jsonBytes := []byte(jsonData) + _, err = outputFile.Write(jsonBytes) + if err != nil { + fmt.Printf("Error writing JSON data: %v\n", err) + os.Exit(1) + } + + // Write JSON data length (8 bytes) + jsonLen := uint64(len(jsonBytes)) + err = binary.Write(outputFile, binary.LittleEndian, jsonLen) + if err != nil { + fmt.Printf("Error writing JSON length: %v\n", err) + os.Exit(1) + } + + fmt.Println("Packaging completed successfully!") + + // change file mod to be executable + err = os.Chmod(*outputPath, 0755) + if err != nil { + fmt.Printf("Error setting executable permission: %v\n", err) + os.Exit(1) + } + + fmt.Println("Set executable permission successfully!") + +} diff --git a/data/data.go b/data/data.go new file mode 100644 index 0000000..9156baa --- /dev/null +++ b/data/data.go @@ -0,0 +1,167 @@ +package data + +import ( + "bytes" + "compress/flate" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "os" +) + +type File struct { + Size int64 `json:"size"` + ZSize int64 `json:"zsize"` + Offset int64 `json:"offset"` +} + +func (f *File) Read(pf *os.File, b []byte) (int, error) { + if f.ZSize > 0 { + compressed := make([]byte, f.ZSize) + _, err := pf.ReadAt(compressed, f.Offset) + if err != nil { + return 0, err + } + // Decompress + reader := flate.NewReader(bytes.NewReader(compressed)) + defer reader.Close() + n, err := reader.Read(b) + if err != nil && err != io.EOF { + fmt.Println("Error reading file:", err, n, compressed) + return 0, err + } + return n, nil + } + + return pf.ReadAt(b, f.Offset) +} + +type FileSystem struct { + Files map[string]File `json:"files"` + fp *os.File +} + +func (fs *FileSystem) ToJSON() (string, error) { + data, err := json.Marshal(fs) + if err != nil { + return "", fmt.Errorf("failed to marshal FileSystem to JSON: %v", err) + } + return string(data), nil +} + +func FromJSON(jsonStr string) (*FileSystem, error) { + var fs FileSystem + err := json.Unmarshal([]byte(jsonStr), &fs) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal JSON to FileSystem: %v", err) + } + return &fs, nil +} + +func (fs *FileSystem) AddFile(name string, size int64, offset int64) { + if fs.Files == nil { + fs.Files = make(map[string]File) + } + fs.Files[name] = File{Size: size, Offset: offset} +} + +func (fs *FileSystem) GetFile(name string) (File, bool) { + file, exists := fs.Files[name] + return file, exists +} + +func (fs *FileSystem) ListFiles() []string { + keys := make([]string, 0, len(fs.Files)) + for k := range fs.Files { + keys = append(keys, k) + } + return keys +} + +func (fs *FileSystem) ReadFile(name string) ([]byte, int, error) { + err := fs.openContainerFile() + if err != nil { + return []byte{}, 0, fmt.Errorf("failed to open container file: %v", err) + } + file, exists := fs.GetFile(name) + if !exists { + return []byte{}, 0, fmt.Errorf("file %s not found", name) + } + + b := make([]byte, file.Size) + len, err := file.Read(fs.fp, b) + return b, len, err +} +func (fs *FileSystem) openContainerFile() error { + if fs.fp != nil { + return nil + } + exePath, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to get executable path: %v", err) + } + fp, err := os.Open(exePath) + if err != nil { + return fmt.Errorf("failed to open executable file: %v", err) + } + fs.fp = fp + return nil +} +func (fs *FileSystem) Close() error { + if fs.fp != nil { + err := fs.fp.Close() + fs.fp = nil + return err + } + return nil +} + +func GetFileSystem() (*FileSystem, error) { + exePath, err := os.Executable() + if err != nil { + return nil, fmt.Errorf("failed to get executable path: %v", err) + } + fp, err := os.Open(exePath) + if err != nil { + return nil, fmt.Errorf("failed to open executable file: %v", err) + } + defer fp.Close() + // Seek to the end to find the JSON metadata + stat, err := fp.Stat() + if err != nil { + return nil, fmt.Errorf("failed to stat executable file: %v", err) + } + size := stat.Size() + if size < 8 { + return nil, fmt.Errorf("executable file is too small to contain metadata") + } + _, err = fp.Seek(-8, io.SeekEnd) + if err != nil { + return nil, fmt.Errorf("failed to seek to end of executable file: %v", err) + } + var fsSize uint64 + err = binary.Read(fp, binary.LittleEndian, &fsSize) + if err != nil { + return nil, fmt.Errorf("failed to decode metadata size: %v", err) + } + fmt.Println("fsSize:", fsSize) + if fsSize == 0 || fsSize > uint64(size-8) { + return nil, fmt.Errorf("invalid metadata size: %d", fsSize) + } + _, err = fp.Seek(-(8 + int64(fsSize)), io.SeekEnd) + if err != nil { + return nil, fmt.Errorf("failed to seek to metadata position: %v", err) + } + jsonData := make([]byte, fsSize) + _, err = fp.Read(jsonData) + if err != nil { + return nil, fmt.Errorf("failed to read metadata: %v", err) + } + var fs FileSystem + err = json.Unmarshal(jsonData, &fs) + if err != nil { + return nil, fmt.Errorf("failed to decode JSON metadata: %v", err) + } + return &fs, nil +} diff --git a/exmaple/compress.text b/exmaple/compress.text new file mode 100644 index 0000000..dafe5c6 --- /dev/null +++ b/exmaple/compress.text @@ -0,0 +1,94 @@ +package main + +import ( + "bytes" + "compress/flate" + "fmt" +) + +func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +} +func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +} +func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +} +func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +} +func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +}func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +}func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +}func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +}func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +}func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +}func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +}func compress(data []byte) []byte { + var buf bytes.Buffer + w, _ := flate.NewWriter(&buf, flate.BestCompression) + w.Write(data) + w.Close() + return buf.Bytes() +} + +func main() { + data := []byte("1234567890abcdefghij") // 20 字节随机内容 + compressed := compress(data) + + fmt.Printf("原始: %d 字节\n", len(data)) + fmt.Printf("压缩后: %d 字节\n", len(compressed)) + fmt.Printf("比率: %.2f\n", float64(len(compressed))/float64(len(data))) +} \ No newline at end of file diff --git a/exmaple/main.go b/exmaple/main.go new file mode 100644 index 0000000..6137a68 --- /dev/null +++ b/exmaple/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "git.kingecg.top/kingecg/gopacker/data" +) + +func main() { + fs, err := data.GetFileSystem() + if err != nil { + fmt.Println("Error:", err) + return + } + defer fs.Close() + files := fs.ListFiles() + fmt.Println("Files:", files) + content, length, err := fs.ReadFile("res.text") + if err != nil { + fmt.Println("Error reading file:", err) + return + } + fmt.Printf("File content (%d bytes):\n%s\n", length, string(content)) + scontent, slen, serr := fs.ReadFile("compress.text") + if serr != nil { + fmt.Println("Error reading file:", serr) + return + + } + fmt.Printf("File content (%d bytes):\n%s\n", slen, string(scontent)) + +} diff --git a/exmaple/res.text b/exmaple/res.text new file mode 100644 index 0000000..1cf9ef9 --- /dev/null +++ b/exmaple/res.text @@ -0,0 +1 @@ +This is a text file. \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..73ff0d0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.kingecg.top/kingecg/gopacker + +go 1.23.1