完成packer

This commit is contained in:
kingecg 2025-09-13 18:23:56 +08:00
commit e57ee27b95
9 changed files with 559 additions and 0 deletions

28
.vscode/launch.json vendored Normal file
View File

@ -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}"
}
]
}

21
Makefile Normal file
View File

@ -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

16
cmd/packer/counter.go Normal file
View File

@ -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
}

198
cmd/packer/main.go Normal file
View File

@ -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!")
}

167
data/data.go Normal file
View File

@ -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
}

94
exmaple/compress.text Normal file
View File

@ -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)))
}

31
exmaple/main.go Normal file
View File

@ -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))
}

1
exmaple/res.text Normal file
View File

@ -0,0 +1 @@
This is a text file.

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.kingecg.top/kingecg/gopacker
go 1.23.1