package main import ( "encoding/binary" "io/fs" "os" "path/filepath" "strconv" "log" "fmt" ) type fileData struct { name string content []byte } type cpkg struct { packageName string files []fileData } func NewCpkg(packageName string) *cpkg { return &cpkg{ packageName: packageName, files: make([]fileData, 0), } } func (c *cpkg) AddFile(fsys fs.FS, path string) error { file, err := fs.ReadFile(fsys, path) if err != nil { return err } c.files = append(c.files, fileData{name: path, content: file}) return nil } // cpkg file header format: // 1 byte: filename length // n bytes: filename // 4 bytes: file size (uint32) func (c *cpkg) createCpkgFileHeader(file fileData) []byte { headerBytes := make([]byte, 0) headerBytes = append(headerBytes, byte(len(file.name))) headerBytes = append(headerBytes, []byte(file.name)...) // file size (uint32 - 4 bytes) fileSizeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(fileSizeBytes, uint32(len(file.content))) headerBytes = append(headerBytes, fileSizeBytes...) return headerBytes } // cpkg header format: // 4 bytes: magic number "Cpkg" // 2 bytes: version // 4 bytes: file count func (c *cpkg) createCpkgHeader() []byte { headerBytes := make([]byte, 0) headerBytes = append(headerBytes, []byte("Cpkg")...) // version (uint16 - 2 bytes) versionBytes := make([]byte, 2) binary.LittleEndian.PutUint16(versionBytes, 1) headerBytes = append(headerBytes, versionBytes...) // file count (uint32 - 4 bytes) fileCountBytes := make([]byte, 4) binary.LittleEndian.PutUint32(fileCountBytes, uint32(len(c.files))) headerBytes = append(headerBytes, fileCountBytes...) return headerBytes } func (c *cpkg) Create() (bool, error) { file, err := os.Create(c.packageName + ".cpkg") if err != nil { return false, err } defer file.Close() // Write main header header := c.createCpkgHeader() n, err := file.Write(header) if err != nil { return false, fmt.Errorf("failed to write header: %w", err) } if n != len(header) { return false, fmt.Errorf("incomplete header write: wrote %d of %d bytes", n, len(header)) } // Write each file for i, fileData := range c.files { // Write file header fileHeader := c.createCpkgFileHeader(fileData) n, err := file.Write(fileHeader) if err != nil { return false, fmt.Errorf("failed to write header for file %d (%s): %w", i, fileData.name, err) } if n != len(fileHeader) { return false, fmt.Errorf("incomplete header write for file %d (%s): wrote %d of %d bytes", i, fileData.name, n, len(fileHeader)) } // Write file content n, err = file.Write(fileData.content) if err != nil { return false, fmt.Errorf("failed to write content for file %d (%s): %w", i, fileData.name, err) } if n != len(fileData.content) { return false, fmt.Errorf("incomplete content write for file %d (%s): wrote %d of %d bytes", i, fileData.name, n, len(fileData.content)) } log.Printf("Wrote file %d: %s (%d bytes)", i, fileData.name, len(fileData.content)) } return true, nil } func (c *cpkg) DebugPrint() { log.Println("Package Name:", c.packageName) idx := 0 for _, file := range c.files { log.Println("File " + strconv.Itoa(idx) + " header") log.Println(c.createCpkgFileHeader(file)) idx++ } } func (c *cpkg) Unpack(fsys fs.FS) error { file, err := os.Open(c.packageName + ".cpkg") if err != nil { return err } defer file.Close() // Read main header (10 bytes) header := make([]byte, 10) _, err = file.Read(header) if err != nil { return fmt.Errorf("failed to read header: %w", err) } // Verify magic number magicNumber := string(header[:4]) if magicNumber != "Cpkg" { return fmt.Errorf("invalid cpkg file: wrong magic number") } // Verify version version := binary.LittleEndian.Uint16(header[4:6]) if version != 1 { return fmt.Errorf("unsupported cpkg file version: %d", version) } // Read file count fileCount := binary.LittleEndian.Uint32(header[6:10]) log.Printf("Unpacking %d files from %s.cpkg", fileCount, c.packageName) // Clear existing files array c.files = make([]fileData, 0, fileCount) // Read each file sequentially for i := 0; i < int(fileCount); i++ { // Read filename length (1 byte) fileNameLengthBuf := make([]byte, 1) n, err := file.Read(fileNameLengthBuf) if err != nil { return fmt.Errorf("failed to read filename length for file %d: %w", i, err) } if n != 1 { return fmt.Errorf("incomplete read of filename length for file %d: got %d bytes, expected 1", i, n) } fileNameLength := fileNameLengthBuf[0] // Read filename fileNameBuf := make([]byte, fileNameLength) n, err = file.Read(fileNameBuf) if err != nil { return fmt.Errorf("failed to read filename for file %d: %w", i, err) } if n != int(fileNameLength) { return fmt.Errorf("incomplete read of filename for file %d: got %d bytes, expected %d", i, n, fileNameLength) } fileName := string(fileNameBuf) // Read file size (4 bytes) fileSizeBuf := make([]byte, 4) n, err = file.Read(fileSizeBuf) if err != nil { return fmt.Errorf("failed to read file size for file %d: %w", i, err) } if n != 4 { return fmt.Errorf("incomplete read of file size for file %d: got %d bytes, expected 4", i, n) } fileSize := binary.LittleEndian.Uint32(fileSizeBuf) // Read file content fileContent := make([]byte, fileSize) n, err = file.Read(fileContent) if err != nil { return fmt.Errorf("failed to read content for file %d (%s): %w", i, fileName, err) } if n != int(fileSize) { return fmt.Errorf("incomplete read of content for file %d (%s): got %d bytes, expected %d", i, fileName, n, fileSize) } log.Printf("Read file %d: %s (%d bytes)", i, fileName, fileSize) c.files = append(c.files, fileData{name: fileName, content: fileContent}) } // Write files to filesystem in subdirectory with the package name err = os.MkdirAll(c.packageName, 0755) if err != nil { return fmt.Errorf("failed to create output directory: %w", err) } for _, fileData := range c.files { outputPath := filepath.Join(c.packageName, fileData.name) // Create parent directories if they don't exist parentDir := filepath.Dir(outputPath) err = os.MkdirAll(parentDir, 0755) if err != nil { return fmt.Errorf("failed to create directory for file %s: %w", fileData.name, err) } err = os.WriteFile(outputPath, fileData.content, 0644) if err != nil { return fmt.Errorf("failed to write file %s: %w", fileData.name, err) } log.Printf("Wrote file: %s", outputPath) } log.Printf("Successfully unpacked %d files to %s/", len(c.files), c.packageName) return nil }