diff --git a/asar/asar.go b/asar/asar.go index 71aabd3..1d906d8 100644 --- a/asar/asar.go +++ b/asar/asar.go @@ -2,7 +2,6 @@ package asar import ( "errors" - "fmt" "io" "os" "path/filepath" @@ -87,9 +86,7 @@ func CreatePackageFromFiles(src, dest string, filenames []string, metadata map[s } else { line = strings.TrimSpace(line) } - if strings.HasPrefix(line, "/") { - line = line[1:] - } + line = strings.TrimPrefix(line, "/") comps := strings.Split(line, string(os.PathSeparator)) cur := src for _, c := range comps { @@ -204,7 +201,6 @@ func CreatePackageFromFiles(src, dest string, filenames []string, metadata map[s return err } } - fmt.Println("文件条目:", len(files), "链接条目:", len(links)) // 写入 header tmpFS := &Filesystem{} tmpFS.SetHeader(root, 0) @@ -375,8 +371,6 @@ func isOutOf(base, p string) bool { return strings.HasPrefix(filepath.ToSlash(r), "..") } -func mustStat(p string) os.FileInfo { fi, _ := os.Lstat(p); return fi } - func mustRealpath(p string) string { a, _ := filepath.EvalSymlinks(p) if a == "" { diff --git a/asar/crawl.go b/asar/crawl.go index 4691303..24226a8 100644 --- a/asar/crawl.go +++ b/asar/crawl.go @@ -1,70 +1,86 @@ package asar import ( - "io" - "os" - "path/filepath" + "io" + "os" + "path/filepath" ) // CrawledFileType 表示爬取到的条目信息 type CrawledFileType struct { - Type string // file | directory | link - Stat os.FileInfo + Type string // file | directory | link + Stat os.FileInfo } // DetermineFileType 判断文件类型 func DetermineFileType(filename string) (*CrawledFileType, error) { - fi, err := os.Lstat(filename) - if err != nil { return nil, err } - if fi.Mode()&os.ModeSymlink != 0 { return &CrawledFileType{Type: "link", Stat: fi}, nil } - if fi.IsDir() { return &CrawledFileType{Type: "directory", Stat: fi}, nil } - return &CrawledFileType{Type: "file", Stat: fi}, nil + fi, err := os.Lstat(filename) + if err != nil { + return nil, err + } + if fi.Mode()&os.ModeSymlink != 0 { + return &CrawledFileType{Type: "link", Stat: fi}, nil + } + if fi.IsDir() { + return &CrawledFileType{Type: "directory", Stat: fi}, nil + } + return &CrawledFileType{Type: "file", Stat: fi}, nil } // Crawl 递归遍历目录,返回路径列表与元数据 func Crawl(root string, includeDot bool) ([]string, map[string]*CrawledFileType, error) { - meta := map[string]*CrawledFileType{} - files := make([]string, 0) - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if err != nil { return err } - // 过滤隐藏(任一路径段以 . 开头) - if !includeDot && path != root { - rel, _ := filepath.Rel(root, path) - parts := filepath.SplitList(filepath.ToSlash(rel)) - // SplitList 不适合通用,这里按 '/' - parts = splitBySlash(rel) - for _, p := range parts { - if len(p) > 0 && p[0] == '.' { - if info.IsDir() { return filepath.SkipDir } - return nil - } - } - } - if path == root { return nil } - typ, err := DetermineFileType(path) - if err != nil { return err } - meta[path] = typ - files = append(files, path) - return nil - }) - if err != nil { return nil, nil, err } - return files, meta, nil + meta := map[string]*CrawledFileType{} + files := make([]string, 0) + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // 过滤隐藏(任一路径段以 . 开头) + if !includeDot && path != root { + rel, _ := filepath.Rel(root, path) + parts := filepath.SplitList(filepath.ToSlash(rel)) + // SplitList 不适合通用,这里按 '/' + parts = splitBySlash(rel) + for _, p := range parts { + if len(p) > 0 && p[0] == '.' { + if info.IsDir() { + return filepath.SkipDir + } + return nil + } + } + } + if path == root { + return nil + } + typ, err := DetermineFileType(path) + if err != nil { + return err + } + meta[path] = typ + files = append(files, path) + return nil + }) + if err != nil { + return nil, nil, err + } + return files, meta, nil } // StreamGenerator 为生成读取流的函数类型 type StreamGenerator func() (io.ReadCloser, error) func splitBySlash(s string) []string { - s = filepath.ToSlash(s) - out := make([]string, 0) - cur := 0 - b := []byte(s) - for i := 0; i < len(b); i++ { - if b[i] == '/' { - out = append(out, string(b[cur:i])) - cur = i + 1 - } - } - out = append(out, string(b[cur:])) - return out + s = filepath.ToSlash(s) + out := make([]string, 0) + cur := 0 + b := []byte(s) + for i := 0; i < len(b); i++ { + if b[i] == '/' { + out = append(out, string(b[cur:i])) + cur = i + 1 + } + } + out = append(out, string(b[cur:])) + return out } diff --git a/asar/disk.go b/asar/disk.go index 79e7635..e70c568 100644 --- a/asar/disk.go +++ b/asar/disk.go @@ -1,75 +1,87 @@ package asar import ( - "encoding/json" - "errors" - "io" - "os" - "path/filepath" - "strconv" + "encoding/json" + "errors" + "io" + "os" + "path/filepath" + "strconv" ) // FileRecord 文件记录结构(与 Node 版保持一致) type FileRecord struct { - FilesystemFileEntry - Integrity struct { - Hash string `json:"hash"` - Algorithm string `json:"algorithm"` - Blocks []string `json:"blocks"` - BlockSize int `json:"blockSize"` - } `json:"integrity"` + FilesystemFileEntry + Integrity struct { + Hash string `json:"hash"` + Algorithm string `json:"algorithm"` + Blocks []string `json:"blocks"` + BlockSize int `json:"blockSize"` + } `json:"integrity"` } // DirectoryRecord 目录记录结构 type DirectoryRecord struct { - Files map[string]any `json:"files"` + Files map[string]any `json:"files"` } // ArchiveHeader 头部信息 type ArchiveHeader struct { - Header FilesystemEntry - HeaderString string - HeaderSize int + Header FilesystemEntry + HeaderString string + HeaderSize int } // ReadArchiveHeaderSync 同步读取 ASAR 头(解析 JSON) func ReadArchiveHeaderSync(archivePath string) (ArchiveHeader, error) { - f, err := os.Open(archivePath) - if err != nil { return ArchiveHeader{}, err } - defer f.Close() - sizeBuf := make([]byte, 8) - if _, err := io.ReadFull(f, sizeBuf); err != nil { return ArchiveHeader{}, errors.New("unable to read header size") } - sizePickle := NewPickleFromBuffer(sizeBuf) - size := int(sizePickle.NewIterator().ReadUInt32()) - headerBuf := make([]byte, size) - if _, err := io.ReadFull(f, headerBuf); err != nil { return ArchiveHeader{}, errors.New("unable to read header") } - headerPickle := NewPickleFromBuffer(headerBuf) - headerStr := headerPickle.NewIterator().ReadString() - hdr, err := decodeHeader([]byte(headerStr)) - if err != nil { return ArchiveHeader{}, err } - return ArchiveHeader{Header: hdr, HeaderString: headerStr, HeaderSize: size}, nil + f, err := os.Open(archivePath) + if err != nil { + return ArchiveHeader{}, err + } + defer f.Close() + sizeBuf := make([]byte, 8) + if _, err := io.ReadFull(f, sizeBuf); err != nil { + return ArchiveHeader{}, errors.New("unable to read header size") + } + sizePickle := NewPickleFromBuffer(sizeBuf) + size := int(sizePickle.NewIterator().ReadUInt32()) + headerBuf := make([]byte, size) + if _, err := io.ReadFull(f, headerBuf); err != nil { + return ArchiveHeader{}, errors.New("unable to read header") + } + headerPickle := NewPickleFromBuffer(headerBuf) + headerStr := headerPickle.NewIterator().ReadString() + hdr, err := decodeHeader([]byte(headerStr)) + if err != nil { + return ArchiveHeader{}, err + } + return ArchiveHeader{Header: hdr, HeaderString: headerStr, HeaderSize: size}, nil } var filesystemCache = map[string]*Filesystem{} // ReadFilesystemSync 读取并缓存文件系统头 func ReadFilesystemSync(archivePath string) (*Filesystem, error) { - if fsys, ok := filesystemCache[archivePath]; ok && fsys != nil { return fsys, nil } - header, err := ReadArchiveHeaderSync(archivePath) - if err != nil { return nil, err } - fsys := NewFilesystem(archivePath) - fsys.SetHeader(header.Header, header.HeaderSize) - filesystemCache[archivePath] = fsys - return fsys, nil + if fsys, ok := filesystemCache[archivePath]; ok && fsys != nil { + return fsys, nil + } + header, err := ReadArchiveHeaderSync(archivePath) + if err != nil { + return nil, err + } + fsys := NewFilesystem(archivePath) + fsys.SetHeader(header.Header, header.HeaderSize) + filesystemCache[archivePath] = fsys + return fsys, nil } // UncacheFilesystem 清理指定缓存 func UncacheFilesystem(archivePath string) bool { - if _, ok := filesystemCache[archivePath]; ok { - filesystemCache[archivePath] = nil - return true - } - return false + if _, ok := filesystemCache[archivePath]; ok { + filesystemCache[archivePath] = nil + return true + } + return false } // UncacheAll 清理所有缓存 @@ -77,108 +89,164 @@ func UncacheAll() { filesystemCache = map[string]*Filesystem{} } // ReadFileSync 读取单个文件内容(根据文件条目信息) func ReadFileSync(fsys *Filesystem, filename string, info *FilesystemFileEntry) ([]byte, error) { - buffer := make([]byte, info.Size) - if info.Size <= 0 { return buffer, nil } - if info.Unpacked { - return os.ReadFile(filepath.Join(fsys.GetRootPath()+".unpacked", filename)) - } - fd, err := os.Open(fsys.GetRootPath()) - if err != nil { return nil, err } - defer fd.Close() - offset := int64(8 + fsys.GetHeaderSize()) - off, _ := strconv.ParseInt(info.Offset, 10, 64) - offset += off - if _, err := fd.ReadAt(buffer, offset); err != nil && err != io.EOF { return nil, err } - return buffer, nil + buffer := make([]byte, info.Size) + if info.Size <= 0 { + return buffer, nil + } + if info.Unpacked { + return os.ReadFile(filepath.Join(fsys.GetRootPath()+".unpacked", filename)) + } + fd, err := os.Open(fsys.GetRootPath()) + if err != nil { + return nil, err + } + defer fd.Close() + offset := int64(8 + fsys.GetHeaderSize()) + off, _ := strconv.ParseInt(info.Offset, 10, 64) + offset += off + if _, err := fd.ReadAt(buffer, offset); err != nil && err != io.EOF { + return nil, err + } + return buffer, nil } // createFilesystemWriteStream 创建输出文件并写入 size 与 header pickle func createFilesystemWriteStream(fsys *Filesystem, dest string) (*os.File, error) { - headerPickle := NewEmptyPickle() - // 将 header 序列化为 JSON - bs, err := json.Marshal(fsys.GetHeader()) - if err != nil { return nil, err } - headerPickle.WriteString(string(bs)) - headerBuf := headerPickle.ToBuffer() - - sizePickle := NewEmptyPickle() - sizePickle.WriteUInt32(uint32(len(headerBuf))) - sizeBuf := sizePickle.ToBuffer() - - out, err := os.Create(dest) - if err != nil { return nil, err } - if _, err := out.Write(sizeBuf); err != nil { out.Close(); return nil, err } - if _, err := out.Write(headerBuf); err != nil { out.Close(); return nil, err } - return out, nil + headerPickle := NewEmptyPickle() + // 将 header 序列化为 JSON + bs, err := json.Marshal(fsys.GetHeader()) + if err != nil { + return nil, err + } + headerPickle.WriteString(string(bs)) + headerBuf := headerPickle.ToBuffer() + + sizePickle := NewEmptyPickle() + sizePickle.WriteUInt32(uint32(len(headerBuf))) + sizeBuf := sizePickle.ToBuffer() + + out, err := os.Create(dest) + if err != nil { + return nil, err + } + if _, err := out.Write(sizeBuf); err != nil { + out.Close() + return nil, err + } + if _, err := out.Write(headerBuf); err != nil { + out.Close() + return nil, err + } + return out, nil } // createSymlink 在 .unpacked 中创建符号链接 func createSymlink(dest, filepathRel, link string) error { - base := dest + ".unpacked" - if err := os.MkdirAll(filepath.Join(base, filepath.Dir(filepathRel)), 0o755); err != nil { return err } - target := filepath.Join(base, filepathRel) - if err := os.Symlink(link, target); err != nil { return err } - return nil + base := dest + ".unpacked" + if err := os.MkdirAll(filepath.Join(base, filepath.Dir(filepathRel)), 0o755); err != nil { + return err + } + target := filepath.Join(base, filepathRel) + if err := os.Symlink(link, target); err != nil { + return err + } + return nil } // --------- 头解析 --------- func decodeHeader(bs []byte) (FilesystemEntry, error) { - var m map[string]any - if err := json.Unmarshal(bs, &m); err != nil { return nil, err } - return parseEntry(m) + var m map[string]any + if err := json.Unmarshal(bs, &m); err != nil { + return nil, err + } + return parseEntry(m) } func parseEntry(m map[string]any) (FilesystemEntry, error) { - // 目录 - if files, ok := m["files"]; ok { - dir := &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}} - if u, ok := m["unpacked"].(bool); ok { dir.Unpacked = u } - fm, ok := files.(map[string]any) - if !ok { return dir, nil } - for name, child := range fm { - childMap, ok := child.(map[string]any) - if !ok { continue } - ce, err := parseEntry(childMap) - if err != nil { return nil, err } - dir.Files[name] = ce - } - return dir, nil - } - // 链接 - if link, ok := m["link"].(string); ok { - l := &FilesystemLinkEntry{Link: link} - if u, ok := m["unpacked"].(bool); ok { l.Unpacked = u } - return l, nil - } - // 文件 - f := &FilesystemFileEntry{} - if u, ok := m["unpacked"].(bool); ok { f.Unpacked = u } - if e, ok := m["executable"].(bool); ok { f.Executable = e } - if off, ok := m["offset"].(string); ok { f.Offset = off } - if sz, ok := m["size"].(float64); ok { f.Size = int(sz) } - if integ, ok := m["integrity"].(map[string]any); ok { - var fi FileIntegrity - if alg, ok := integ["algorithm"].(string); ok { fi.Algorithm = alg } - if hash, ok := integ["hash"].(string); ok { fi.Hash = hash } - if bs, ok := integ["blockSize"].(float64); ok { fi.BlockSize = int(bs) } - if blks, ok := integ["blocks"].([]any); ok { - fi.Blocks = make([]string, 0, len(blks)) - for _, b := range blks { if s, ok := b.(string); ok { fi.Blocks = append(fi.Blocks, s) } } - } - f.Integrity = fi - } - return f, nil + // 目录 + if files, ok := m["files"]; ok { + dir := &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}} + if u, ok := m["unpacked"].(bool); ok { + dir.Unpacked = u + } + fm, ok := files.(map[string]any) + if !ok { + return dir, nil + } + for name, child := range fm { + childMap, ok := child.(map[string]any) + if !ok { + continue + } + ce, err := parseEntry(childMap) + if err != nil { + return nil, err + } + dir.Files[name] = ce + } + return dir, nil + } + // 链接 + if link, ok := m["link"].(string); ok { + l := &FilesystemLinkEntry{Link: link} + if u, ok := m["unpacked"].(bool); ok { + l.Unpacked = u + } + return l, nil + } + // 文件 + f := &FilesystemFileEntry{} + if u, ok := m["unpacked"].(bool); ok { + f.Unpacked = u + } + if e, ok := m["executable"].(bool); ok { + f.Executable = e + } + if off, ok := m["offset"].(string); ok { + f.Offset = off + } + if sz, ok := m["size"].(float64); ok { + f.Size = int(sz) + } + if integ, ok := m["integrity"].(map[string]any); ok { + var fi FileIntegrity + if alg, ok := integ["algorithm"].(string); ok { + fi.Algorithm = alg + } + if hash, ok := integ["hash"].(string); ok { + fi.Hash = hash + } + if bs, ok := integ["blockSize"].(float64); ok { + fi.BlockSize = int(bs) + } + if blks, ok := integ["blocks"].([]any); ok { + fi.Blocks = make([]string, 0, len(blks)) + for _, b := range blks { + if s, ok := b.(string); ok { + fi.Blocks = append(fi.Blocks, s) + } + } + } + f.Integrity = fi + } + return f, nil } // CopyFile 将 srcRoot 下的相对路径 filename 拷贝到 dest.unpacked 下,并保持权限 func CopyFile(destUnpacked string, srcRoot string, filename string) error { - srcFile := filepath.Join(srcRoot, filename) - targetFile := filepath.Join(destUnpacked, filename) - bs, err := os.ReadFile(srcFile) - if err != nil { return err } - if err := os.MkdirAll(filepath.Dir(targetFile), 0o755); err != nil { return err } - fi, err := os.Stat(srcFile) - if err != nil { return err } - return os.WriteFile(targetFile, bs, fi.Mode()) + srcFile := filepath.Join(srcRoot, filename) + targetFile := filepath.Join(destUnpacked, filename) + bs, err := os.ReadFile(srcFile) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(targetFile), 0o755); err != nil { + return err + } + fi, err := os.Stat(srcFile) + if err != nil { + return err + } + return os.WriteFile(targetFile, bs, fi.Mode()) } diff --git a/asar/filesystem.go b/asar/filesystem.go index 7993cfd..03d0581 100644 --- a/asar/filesystem.go +++ b/asar/filesystem.go @@ -1,38 +1,38 @@ package asar import ( - "errors" - "io" - "os" - "path/filepath" - "strconv" + "errors" + "io" + "os" + "path/filepath" + "strconv" ) // EntryMetadata 表示通用条目元数据 type EntryMetadata struct { - Unpacked bool `json:"unpacked,omitempty"` + Unpacked bool `json:"unpacked,omitempty"` } // FilesystemDirectoryEntry 目录条目 type FilesystemDirectoryEntry struct { - Files map[string]FilesystemEntry `json:"files"` - EntryMetadata + Files map[string]FilesystemEntry `json:"files"` + EntryMetadata } // FilesystemFileEntry 文件条目 type FilesystemFileEntry struct { - Unpacked bool `json:"unpacked"` - Executable bool `json:"executable"` - Offset string `json:"offset"` - Size int `json:"size"` - Integrity FileIntegrity `json:"integrity"` - EntryMetadata + Unpacked bool `json:"unpacked"` + Executable bool `json:"executable"` + Offset string `json:"offset"` + Size int `json:"size"` + Integrity FileIntegrity `json:"integrity"` + EntryMetadata } // FilesystemLinkEntry 符号链接条目 type FilesystemLinkEntry struct { - Link string `json:"link"` - EntryMetadata + Link string `json:"link"` + EntryMetadata } // FilesystemEntry 泛型条目 @@ -40,21 +40,21 @@ type FilesystemEntry interface{} // Filesystem 表示 ASAR 文件系统头与构建状态 type Filesystem struct { - src string - header FilesystemEntry - headerSize int - offset int64 + src string + header FilesystemEntry + headerSize int + offset int64 } // NewFilesystem 创建新的文件系统对象 func NewFilesystem(src string) *Filesystem { - abs, _ := filepath.Abs(src) - return &Filesystem{ - src: abs, - header: &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}}, - headerSize: 0, - offset: 0, - } + abs, _ := filepath.Abs(src) + return &Filesystem{ + src: abs, + header: &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}}, + headerSize: 0, + offset: 0, + } } // GetRootPath 返回根路径 @@ -67,196 +67,235 @@ func (fsys *Filesystem) GetHeader() FilesystemEntry { return fsys.header } func (fsys *Filesystem) GetHeaderSize() int { return fsys.headerSize } // SetHeader 设置头与头大小 -func (fsys *Filesystem) SetHeader(h FilesystemEntry, size int) { fsys.header = h; fsys.headerSize = size } +func (fsys *Filesystem) SetHeader(h FilesystemEntry, size int) { + fsys.header = h + fsys.headerSize = size +} // searchNodeFromDirectory 按目录路径查找或创建节点 func (fsys *Filesystem) searchNodeFromDirectory(p string) FilesystemEntry { - json := fsys.header - parts := filepath.SplitList(filepath.ToSlash(p)) - // filepath.SplitList 不适用于一般路径分割,这里手动按 '/' - parts = splitPath(p) - for _, dir := range parts { - if dir == "." || dir == "" { continue } - dirEntry, ok := json.(*FilesystemDirectoryEntry) - if !ok { panic("unexpected directory state: " + p) } - if dirEntry.Files == nil { dirEntry.Files = map[string]FilesystemEntry{} } - child, exists := dirEntry.Files[dir] - if !exists { - child = &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}} - dirEntry.Files[dir] = child - } - json = child - } - return json + json := fsys.header + parts := filepath.SplitList(filepath.ToSlash(p)) + // filepath.SplitList 不适用于一般路径分割,这里手动按 '/' + parts = splitPath(p) + for _, dir := range parts { + if dir == "." || dir == "" { + continue + } + dirEntry, ok := json.(*FilesystemDirectoryEntry) + if !ok { + panic("unexpected directory state: " + p) + } + if dirEntry.Files == nil { + dirEntry.Files = map[string]FilesystemEntry{} + } + child, exists := dirEntry.Files[dir] + if !exists { + child = &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}} + dirEntry.Files[dir] = child + } + json = child + } + return json } // searchNodeFromPath 按完整路径查找或创建节点 func (fsys *Filesystem) searchNodeFromPath(p string) FilesystemEntry { - rel, _ := filepath.Rel(fsys.src, p) - if rel == "." || rel == "" { return fsys.header } - name := filepath.Base(rel) - dir := filepath.Dir(rel) - node := fsys.searchNodeFromDirectory(dir).(*FilesystemDirectoryEntry) - if node.Files == nil { node.Files = map[string]FilesystemEntry{} } - child, ok := node.Files[name] - if !ok { - child = &FilesystemFileEntry{} - node.Files[name] = child - } - return child + rel, _ := filepath.Rel(fsys.src, p) + if rel == "." || rel == "" { + return fsys.header + } + name := filepath.Base(rel) + dir := filepath.Dir(rel) + node := fsys.searchNodeFromDirectory(dir).(*FilesystemDirectoryEntry) + if node.Files == nil { + node.Files = map[string]FilesystemEntry{} + } + child, ok := node.Files[name] + if !ok { + child = &FilesystemFileEntry{} + node.Files[name] = child + } + return child } // InsertDirectory 插入目录节点,支持 unpack 标记 func (fsys *Filesystem) InsertDirectory(p string, shouldUnpack bool) map[string]FilesystemEntry { - rel, _ := filepath.Rel(fsys.src, p) - parent := fsys.searchNodeFromDirectory(filepath.Dir(rel)).(*FilesystemDirectoryEntry) - name := filepath.Base(rel) - child, ok := parent.Files[name] - var node *FilesystemDirectoryEntry - if ok { - if d, isDir := child.(*FilesystemDirectoryEntry); isDir { - node = d - } else { - // 覆盖为目录 - node = &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}} - parent.Files[name] = node - } - } else { - node = &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}} - parent.Files[name] = node - } - if shouldUnpack { node.Unpacked = true } - if node.Files == nil { node.Files = map[string]FilesystemEntry{} } - return node.Files + rel, _ := filepath.Rel(fsys.src, p) + parent := fsys.searchNodeFromDirectory(filepath.Dir(rel)).(*FilesystemDirectoryEntry) + name := filepath.Base(rel) + child, ok := parent.Files[name] + var node *FilesystemDirectoryEntry + if ok { + if d, isDir := child.(*FilesystemDirectoryEntry); isDir { + node = d + } else { + // 覆盖为目录 + node = &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}} + parent.Files[name] = node + } + } else { + node = &FilesystemDirectoryEntry{Files: map[string]FilesystemEntry{}} + parent.Files[name] = node + } + if shouldUnpack { + node.Unpacked = true + } + if node.Files == nil { + node.Files = map[string]FilesystemEntry{} + } + return node.Files } // InsertFile 插入文件节点并更新偏移、完整性等信息 func (fsys *Filesystem) InsertFile(p string, streamGenerator func() (ioReadSeeker, error), shouldUnpack bool, fileStat os.FileInfo) error { - dirNode := fsys.searchNodeFromDirectory(filepath.Dir(p)).(*FilesystemDirectoryEntry) - // 确保文件节点类型为文件 - rel, _ := filepath.Rel(fsys.src, p) - parent := fsys.searchNodeFromDirectory(filepath.Dir(rel)).(*FilesystemDirectoryEntry) - name := filepath.Base(rel) - // 直接覆盖为文件条目,确保类型正确 - node := &FilesystemFileEntry{} - parent.Files[name] = node - if shouldUnpack || dirNode.Unpacked { - node.Size = int(fileStat.Size()) - node.Unpacked = true - r, err := streamGenerator() - if err != nil { return err } - integ, err := GetFileIntegrity(r) - if err != nil { return err } - node.Integrity = integ - return nil - } - size := int(fileStat.Size()) - if uint64(size) > uint64(^uint32(0)) { - return errors.New(p + ": file size can not be larger than 4.2GB") - } - node.Size = size - node.Offset = int64ToString(fsys.offset) - r, err := streamGenerator() - if err != nil { return err } - integ, err := GetFileIntegrity(r) - if err != nil { return err } - node.Integrity = integ - // 可执行位(非 Windows) - if isExecutable(fileStat) { node.Executable = true } - fsys.offset += int64(size) - return nil + dirNode := fsys.searchNodeFromDirectory(filepath.Dir(p)).(*FilesystemDirectoryEntry) + // 确保文件节点类型为文件 + rel, _ := filepath.Rel(fsys.src, p) + parent := fsys.searchNodeFromDirectory(filepath.Dir(rel)).(*FilesystemDirectoryEntry) + name := filepath.Base(rel) + // 直接覆盖为文件条目,确保类型正确 + node := &FilesystemFileEntry{} + parent.Files[name] = node + if shouldUnpack || dirNode.Unpacked { + node.Size = int(fileStat.Size()) + node.Unpacked = true + r, err := streamGenerator() + if err != nil { + return err + } + integ, err := GetFileIntegrity(r) + if err != nil { + return err + } + node.Integrity = integ + return nil + } + size := int(fileStat.Size()) + if uint64(size) > uint64(^uint32(0)) { + return errors.New(p + ": file size can not be larger than 4.2GB") + } + node.Size = size + node.Offset = int64ToString(fsys.offset) + r, err := streamGenerator() + if err != nil { + return err + } + integ, err := GetFileIntegrity(r) + if err != nil { + return err + } + node.Integrity = integ + // 可执行位(非 Windows) + if isExecutable(fileStat) { + node.Executable = true + } + fsys.offset += int64(size) + return nil } // InsertLink 插入符号链接节点,校验链接不越界 func (fsys *Filesystem) InsertLink(p string, shouldUnpack bool, parentPath string, symlink string, src string) (string, error) { - link := fsys.resolveLink(src, parentPath, symlink) - if hasParentOutOf(src, link) { - return "", errors.New(p + ": file \"" + link + "\" links out of the package") - } - node := fsys.searchNodeFromPath(p).(*FilesystemLinkEntry) - dirNode := fsys.searchNodeFromDirectory(filepath.Dir(p)).(*FilesystemDirectoryEntry) - if shouldUnpack || dirNode.Unpacked { node.Unpacked = true } - node.Link = link - return link, nil + link := fsys.resolveLink(src, parentPath, symlink) + if hasParentOutOf(link) { + return "", errors.New(p + ": file \"" + link + "\" links out of the package") + } + node := fsys.searchNodeFromPath(p).(*FilesystemLinkEntry) + dirNode := fsys.searchNodeFromDirectory(filepath.Dir(p)).(*FilesystemDirectoryEntry) + if shouldUnpack || dirNode.Unpacked { + node.Unpacked = true + } + node.Link = link + return link, nil } // ListFiles 列出所有路径,可选是否包含 pack/unpack 前缀 func (fsys *Filesystem) ListFiles(isPack bool) []string { - files := make([]string, 0) - var fill func(base string, meta FilesystemEntry) - fill = func(base string, meta FilesystemEntry) { - dir, ok := meta.(*FilesystemDirectoryEntry) - if !ok { return } - for childPath, childMeta := range dir.Files { - full := filepath.ToSlash(filepath.Join(base, childPath)) - if isPack { - state := "pack " - if hasUnpacked(childMeta) { state = "unpack" } - files = append(files, state+" : "+full) - } else { - files = append(files, full) - } - fill(full, childMeta) - } - } - fill("/", fsys.header) - return files + files := make([]string, 0) + var fill func(base string, meta FilesystemEntry) + fill = func(base string, meta FilesystemEntry) { + dir, ok := meta.(*FilesystemDirectoryEntry) + if !ok { + return + } + for childPath, childMeta := range dir.Files { + full := filepath.ToSlash(filepath.Join(base, childPath)) + if isPack { + state := "pack " + if hasUnpacked(childMeta) { + state = "unpack" + } + files = append(files, state+" : "+full) + } else { + files = append(files, full) + } + fill(full, childMeta) + } + } + fill("/", fsys.header) + return files } // GetNode 获取任意路径的条目(可解析符号链接) func (fsys *Filesystem) GetNode(p string, followLinks bool) FilesystemEntry { - node := fsys.searchNodeFromDirectory(filepath.Dir(p)) - name := filepath.Base(p) - if lnk, ok := node.(*FilesystemLinkEntry); ok && followLinks { - return fsys.GetNode(filepath.Join(lnk.Link, name), followLinks) - } - if name != "" { - dir := node.(*FilesystemDirectoryEntry) - return dir.Files[name] - } - return node + node := fsys.searchNodeFromDirectory(filepath.Dir(p)) + name := filepath.Base(p) + if lnk, ok := node.(*FilesystemLinkEntry); ok && followLinks { + return fsys.GetNode(filepath.Join(lnk.Link, name), followLinks) + } + if name != "" { + dir := node.(*FilesystemDirectoryEntry) + return dir.Files[name] + } + return node } // GetFile 获取文件条目(可解析符号链接) func (fsys *Filesystem) GetFile(p string, followLinks bool) (FilesystemEntry, error) { - info := fsys.GetNode(p, followLinks) - if info == nil { return nil, errors.New("\"" + p + "\" was not found in this archive") } - if lnk, ok := info.(*FilesystemLinkEntry); ok && followLinks { - return fsys.GetFile(lnk.Link, followLinks) - } - return info, nil + info := fsys.GetNode(p, followLinks) + if info == nil { + return nil, errors.New("\"" + p + "\" was not found in this archive") + } + if lnk, ok := info.(*FilesystemLinkEntry); ok && followLinks { + return fsys.GetFile(lnk.Link, followLinks) + } + return info, nil } // resolveLink 计算符号链接的相对路径 func (fsys *Filesystem) resolveLink(src, parentPath, symlink string) string { - target := filepath.Join(parentPath, symlink) - link, _ := filepath.Rel(src, target) - return filepath.ToSlash(link) + target := filepath.Join(parentPath, symlink) + link, _ := filepath.Rel(src, target) + return filepath.ToSlash(link) } // 辅助函数 func splitPath(p string) []string { - p = filepath.ToSlash(p) - if p == "." || p == "" { return nil } - parts := make([]string, 0) - for _, s := range bytesSplit(p, '/') { - if s != "" { parts = append(parts, s) } - } - return parts + p = filepath.ToSlash(p) + if p == "." || p == "" { + return nil + } + parts := make([]string, 0) + for _, s := range bytesSplit(p, '/') { + if s != "" { + parts = append(parts, s) + } + } + return parts } func bytesSplit(s string, sep byte) []string { - b := []byte(s) - out := make([]string, 0) - start := 0 - for i := 0; i < len(b); i++ { - if b[i] == sep { - out = append(out, string(b[start:i])) - start = i + 1 - } - } - out = append(out, string(b[start:])) - return out + b := []byte(s) + out := make([]string, 0) + start := 0 + for i := 0; i < len(b); i++ { + if b[i] == sep { + out = append(out, string(b[start:i])) + start = i + 1 + } + } + out = append(out, string(b[start:])) + return out } func int64ToString(v int64) string { return fmtInt64(v) } @@ -264,28 +303,28 @@ func int64ToString(v int64) string { return fmtInt64(v) } func fmtInt64(v int64) string { return strconv.FormatInt(v, 10) } func isExecutable(fi os.FileInfo) bool { - return (fi.Mode() & 0o100) != 0 && os.PathSeparator == '/' + return (fi.Mode()&0o100) != 0 && os.PathSeparator == '/' } func hasUnpacked(e FilesystemEntry) bool { - switch t := e.(type) { - case *FilesystemDirectoryEntry: - return t.Unpacked - case *FilesystemFileEntry: - return t.Unpacked - case *FilesystemLinkEntry: - return t.Unpacked - default: - return false - } + switch t := e.(type) { + case *FilesystemDirectoryEntry: + return t.Unpacked + case *FilesystemFileEntry: + return t.Unpacked + case *FilesystemLinkEntry: + return t.Unpacked + default: + return false + } } -func hasParentOutOf(base, rel string) bool { - // 如果 rel 以 ".." 开头则认为越界 - return len(rel) >= 2 && rel[:2] == ".." +func hasParentOutOf(rel string) bool { + // 如果 rel 以 ".." 开头则认为越界 + return len(rel) >= 2 && rel[:2] == ".." } // ioReadSeeker 为通用读取接口 type ioReadSeeker interface { - io.Reader + io.Reader } diff --git a/asar/integrity.go b/asar/integrity.go index 40bd0a7..2c4fadb 100644 --- a/asar/integrity.go +++ b/asar/integrity.go @@ -1,57 +1,56 @@ package asar import ( - "crypto/sha256" - "encoding/hex" - "io" + "crypto/sha256" + "encoding/hex" + "io" ) const ( - // ALGORITHM 哈希算法标识 - ALGORITHM = "SHA256" - // BLOCK_SIZE 分块大小(4MB) - BLOCK_SIZE = 4 * 1024 * 1024 + // ALGORITHM 哈希算法标识 + ALGORITHM = "SHA256" + // BLOCK_SIZE 分块大小(4MB) + BLOCK_SIZE = 4 * 1024 * 1024 ) // FileIntegrity 表示文件完整性信息 type FileIntegrity struct { - Algorithm string `json:"algorithm"` - Hash string `json:"hash"` - BlockSize int `json:"blockSize"` - Blocks []string `json:"blocks"` + Algorithm string `json:"algorithm"` + Hash string `json:"hash"` + BlockSize int `json:"blockSize"` + Blocks []string `json:"blocks"` } // GetFileIntegrity 计算输入流的完整性信息,包含整文件哈希与分块哈希 func GetFileIntegrity(r io.Reader) (FileIntegrity, error) { - h := sha256.New() - blocks := make([]string, 0) - buf := make([]byte, BLOCK_SIZE) - for { - n, err := io.ReadFull(r, buf) - if err == io.ErrUnexpectedEOF { - if n > 0 { - hb := sha256.Sum256(buf[:n]) - blocks = append(blocks, hex.EncodeToString(hb[:])) - h.Write(buf[:n]) - } - break - } - if err == io.EOF { - break - } - if err != nil { - return FileIntegrity{}, err - } - hb := sha256.Sum256(buf[:n]) - blocks = append(blocks, hex.EncodeToString(hb[:])) - h.Write(buf[:n]) - } - sum := h.Sum(nil) - return FileIntegrity{ - Algorithm: ALGORITHM, - Hash: hex.EncodeToString(sum), - BlockSize: BLOCK_SIZE, - Blocks: blocks, - }, nil + h := sha256.New() + blocks := make([]string, 0) + buf := make([]byte, BLOCK_SIZE) + for { + n, err := io.ReadFull(r, buf) + if err == io.ErrUnexpectedEOF { + if n > 0 { + hb := sha256.Sum256(buf[:n]) + blocks = append(blocks, hex.EncodeToString(hb[:])) + h.Write(buf[:n]) + } + break + } + if err == io.EOF { + break + } + if err != nil { + return FileIntegrity{}, err + } + hb := sha256.Sum256(buf[:n]) + blocks = append(blocks, hex.EncodeToString(hb[:])) + h.Write(buf[:n]) + } + sum := h.Sum(nil) + return FileIntegrity{ + Algorithm: ALGORITHM, + Hash: hex.EncodeToString(sum), + BlockSize: BLOCK_SIZE, + Blocks: blocks, + }, nil } - diff --git a/asar/pickle.go b/asar/pickle.go index 574b781..7a6c496 100644 --- a/asar/pickle.go +++ b/asar/pickle.go @@ -1,199 +1,197 @@ package asar import ( - "encoding/binary" + "encoding/binary" ) const ( - sizeInt32 = 4 - sizeUint32 = 4 - sizeInt64 = 8 - sizeUint64 = 8 - sizeFloat = 4 - sizeDouble = 8 - payloadUnit = 64 + sizeInt32 = 4 + sizeUint32 = 4 + payloadUnit = 64 ) // alignInt 将整数按 alignment 对齐到上一个倍数 func alignInt(i, alignment int) int { - return i + ((alignment - (i % alignment)) % alignment) + return i + ((alignment - (i % alignment)) % alignment) } // Pickle 表示 Chromium 的二进制打包结构,支持基本类型的写入与读取 // Pickle 的前 4 字节为 payload 大小(uint32 LE),随后为按 4 字节对齐的 payload type Pickle struct { - header []byte - headerSize int - capacityAfterHeader int - writeOffset int + header []byte + headerSize int + capacityAfterHeader int + writeOffset int } // NewEmptyPickle 创建一个空的 Pickle 用于写入 func NewEmptyPickle() *Pickle { - p := &Pickle{ - header: make([]byte, 4), - headerSize: 4, - capacityAfterHeader: 0, - writeOffset: 0, - } - p.resize(payloadUnit) - p.setPayloadSize(0) - return p + p := &Pickle{ + header: make([]byte, 4), + headerSize: 4, + capacityAfterHeader: 0, + writeOffset: 0, + } + p.resize(payloadUnit) + p.setPayloadSize(0) + return p } // NewPickleFromBuffer 从完整的 Pickle 缓冲读取结构(含 header 和 payload) func NewPickleFromBuffer(buf []byte) *Pickle { - p := &Pickle{ - header: buf, - } - payload := int(p.getPayloadSize()) - p.headerSize = len(buf) - payload - if p.headerSize < 0 || p.headerSize != alignInt(p.headerSize, sizeUint32) { - p.headerSize = 0 - p.header = []byte{} - } - p.capacityAfterHeader = int(9_007_199_254_740_992) // CAPACITY_READ_ONLY - p.writeOffset = 0 - return p + p := &Pickle{ + header: buf, + } + payload := int(p.getPayloadSize()) + p.headerSize = len(buf) - payload + if p.headerSize < 0 || p.headerSize != alignInt(p.headerSize, sizeUint32) { + p.headerSize = 0 + p.header = []byte{} + } + p.capacityAfterHeader = int(9_007_199_254_740_992) // CAPACITY_READ_ONLY + p.writeOffset = 0 + return p } // ToBuffer 导出当前 Pickle 的有效字节切片 func (p *Pickle) ToBuffer() []byte { - return p.header[:p.headerSize+p.getPayloadSize()] + return p.header[:p.headerSize+p.getPayloadSize()] } // Iterator 提供对 Pickle 的顺序读取能力 type Iterator struct { - payload []byte - payloadOffset int - readIndex int - endIndex int + payload []byte + payloadOffset int + readIndex int + endIndex int } // NewIterator 创建读取迭代器 func (p *Pickle) NewIterator() *Iterator { - it := &Iterator{ - payload: p.header, - payloadOffset: p.headerSize, - readIndex: 0, - endIndex: p.getPayloadSize(), - } - return it + it := &Iterator{ + payload: p.header, + payloadOffset: p.headerSize, + readIndex: 0, + endIndex: p.getPayloadSize(), + } + return it } // ReadUInt32 读取一个 uint32(LE) func (it *Iterator) ReadUInt32() uint32 { - var v uint32 - it.readBytes(sizeUint32, func(b []byte) { v = binary.LittleEndian.Uint32(b) }) - return v + var v uint32 + it.readBytes(sizeUint32, func(b []byte) { v = binary.LittleEndian.Uint32(b) }) + return v } // ReadInt32 读取一个 int32(LE) func (it *Iterator) ReadInt32() int32 { - var v int32 - it.readBytes(sizeInt32, func(b []byte) { v = int32(binary.LittleEndian.Uint32(b)) }) - return v + var v int32 + it.readBytes(sizeInt32, func(b []byte) { v = int32(binary.LittleEndian.Uint32(b)) }) + return v } // ReadString 读取一个以长度前缀的字符串(utf8) func (it *Iterator) ReadString() string { - l := int(it.ReadInt32()) - var s []byte - it.readBytes(l, func(b []byte) { s = append([]byte(nil), b...) }) - return string(s) + l := int(it.ReadInt32()) + var s []byte + it.readBytes(l, func(b []byte) { s = append([]byte(nil), b...) }) + return string(s) } func (it *Iterator) readBytes(length int, fn func([]byte)) { - if length > it.endIndex-it.readIndex { - it.readIndex = it.endIndex - panic("pickle: insufficient payload to read") - } - off := it.payloadOffset + it.readIndex - fn(it.payload[off : off+length]) - it.advance(length) + if length > it.endIndex-it.readIndex { + it.readIndex = it.endIndex + panic("pickle: insufficient payload to read") + } + off := it.payloadOffset + it.readIndex + fn(it.payload[off : off+length]) + it.advance(length) } func (it *Iterator) advance(size int) { - aligned := alignInt(size, sizeUint32) - if it.endIndex-it.readIndex < aligned { - it.readIndex = it.endIndex - } else { - it.readIndex += aligned - } + aligned := alignInt(size, sizeUint32) + if it.endIndex-it.readIndex < aligned { + it.readIndex = it.endIndex + } else { + it.readIndex += aligned + } } // WriteUInt32 写入一个 uint32(LE) func (p *Pickle) WriteUInt32(v uint32) bool { - return p.writeBytes(func(buf []byte) { binary.LittleEndian.PutUint32(buf, v) }, sizeUint32) + return p.writeBytes(func(buf []byte) { binary.LittleEndian.PutUint32(buf, v) }, sizeUint32) } // WriteInt32 写入一个 int32(LE) func (p *Pickle) WriteInt32(v int32) bool { - return p.writeBytes(func(buf []byte) { binary.LittleEndian.PutUint32(buf, uint32(v)) }, sizeInt32) + return p.writeBytes(func(buf []byte) { binary.LittleEndian.PutUint32(buf, uint32(v)) }, sizeInt32) } // WriteString 写入一个以长度前缀的字符串(utf8) func (p *Pickle) WriteString(s string) bool { - if !p.WriteInt32(int32(len(s))) { - return false - } - // 原样写入字符串并进行 4 字节对齐填充 - return p.writeRawBytes([]byte(s)) + if !p.WriteInt32(int32(len(s))) { + return false + } + // 原样写入字符串并进行 4 字节对齐填充 + return p.writeRawBytes([]byte(s)) } func (p *Pickle) writeRawBytes(data []byte) bool { - length := len(data) - dataLength := alignInt(length, sizeUint32) - newSize := p.writeOffset + dataLength - if newSize > p.capacityAfterHeader { - p.resize(max(p.capacityAfterHeader*2, newSize)) - } - copy(p.header[p.headerSize+p.writeOffset:], data) - // 对齐填充 0 - end := p.headerSize + p.writeOffset + length - for i := end; i < p.headerSize+p.writeOffset+dataLength; i++ { - p.header[i] = 0 - } - p.setPayloadSize(newSize) - p.writeOffset = newSize - return true + length := len(data) + dataLength := alignInt(length, sizeUint32) + newSize := p.writeOffset + dataLength + if newSize > p.capacityAfterHeader { + p.resize(max(p.capacityAfterHeader*2, newSize)) + } + copy(p.header[p.headerSize+p.writeOffset:], data) + // 对齐填充 0 + end := p.headerSize + p.writeOffset + length + for i := end; i < p.headerSize+p.writeOffset+dataLength; i++ { + p.header[i] = 0 + } + p.setPayloadSize(newSize) + p.writeOffset = newSize + return true } func (p *Pickle) writeBytes(writer func([]byte), length int) bool { - dataLength := alignInt(length, sizeUint32) - newSize := p.writeOffset + dataLength - if newSize > p.capacityAfterHeader { - p.resize(max(p.capacityAfterHeader*2, newSize)) - } - writer(p.header[p.headerSize+p.writeOffset : p.headerSize+p.writeOffset+length]) - // 对齐填充 0 - end := p.headerSize + p.writeOffset + length - for i := end; i < p.headerSize+p.writeOffset+dataLength; i++ { - p.header[i] = 0 - } - p.setPayloadSize(newSize) - p.writeOffset = newSize - return true + dataLength := alignInt(length, sizeUint32) + newSize := p.writeOffset + dataLength + if newSize > p.capacityAfterHeader { + p.resize(max(p.capacityAfterHeader*2, newSize)) + } + writer(p.header[p.headerSize+p.writeOffset : p.headerSize+p.writeOffset+length]) + // 对齐填充 0 + end := p.headerSize + p.writeOffset + length + for i := end; i < p.headerSize+p.writeOffset+dataLength; i++ { + p.header[i] = 0 + } + p.setPayloadSize(newSize) + p.writeOffset = newSize + return true } func (p *Pickle) setPayloadSize(sz int) int { - binary.LittleEndian.PutUint32(p.header[:4], uint32(sz)) - return sz + binary.LittleEndian.PutUint32(p.header[:4], uint32(sz)) + return sz } func (p *Pickle) getPayloadSize() int { - return int(binary.LittleEndian.Uint32(p.header[:4])) + return int(binary.LittleEndian.Uint32(p.header[:4])) } func (p *Pickle) resize(newCapacity int) { - newCapacity = alignInt(newCapacity, payloadUnit) - buf := make([]byte, p.headerSize+newCapacity) - copy(buf, p.header) - p.header = buf - p.capacityAfterHeader = newCapacity + newCapacity = alignInt(newCapacity, payloadUnit) + buf := make([]byte, p.headerSize+newCapacity) + copy(buf, p.header) + p.header = buf + p.capacityAfterHeader = newCapacity } func max(a, b int) int { - if a > b { return a } - return b + if a > b { + return a + } + return b } diff --git a/cmd/asar/main.go b/cmd/asar/main.go index 5976df8..c454bb5 100644 --- a/cmd/asar/main.go +++ b/cmd/asar/main.go @@ -1,97 +1,139 @@ package main import ( - "flag" - "fmt" - "os" - "path/filepath" - "strings" - "github.com/dcboy/go-asar/asar" + "flag" + "fmt" + "github.com/dcboy/go-asar/asar" + "os" + "path/filepath" + "strings" ) // main 解析命令并对齐 node-asar 的子命令与参数 func main() { - if len(os.Args) < 2 { - printHelp() - os.Exit(1) - } - cmd := os.Args[1] - switch cmd { - case "pack", "p": - // 手工解析,支持选项与位置参数交错 - dir, output, opts := parsePackArgs(os.Args[2:]) - if dir == "" || output == "" { fmt.Println("用法: asar pack [options]"); os.Exit(1) } - if err := asar.CreatePackageWithOptions(dir, output, opts); err != nil { - fmt.Println("打包失败:", err) - os.Exit(1) - } - fmt.Println("打包完成:", filepath.Base(output)) - case "list", "l": - archive, isPack := parseListArgs(os.Args[2:]) - if archive == "" { fmt.Println("用法: asar list [-i]"); os.Exit(1) } - files, err := asar.ListPackage(archive, isPack) - if err != nil { fmt.Println("读取失败:", err); os.Exit(1) } - for _, f := range files { fmt.Println(f) } - case "extract-file", "ef": - // extract-file - fs := flag.NewFlagSet("extract-file", flag.ExitOnError) - _ = fs.Parse(os.Args[2:]) - args := fs.Args() - if len(args) < 2 { fmt.Println("用法: asar extract-file "); os.Exit(1) } - archive := args[0] - filename := args[1] - data, err := asar.ExtractFile(archive, filename, true) - if err != nil { fmt.Println("提取失败:", err); os.Exit(1) } - if err := os.WriteFile(filepath.Base(filename), data, 0o644); err != nil { fmt.Println("写入失败:", err); os.Exit(1) } - case "extract", "e": - // extract - fs := flag.NewFlagSet("extract", flag.ExitOnError) - _ = fs.Parse(os.Args[2:]) - args := fs.Args() - if len(args) < 2 { fmt.Println("用法: asar extract "); os.Exit(1) } - archive := args[0] - dest := args[1] - if err := asar.ExtractAll(archive, dest); err != nil { fmt.Println("解压失败:", err); os.Exit(1) } - fmt.Println("解压完成:", dest) - default: - printHelp() - os.Exit(1) - } + if len(os.Args) < 2 { + printHelp() + os.Exit(1) + } + cmd := os.Args[1] + switch cmd { + case "pack", "p": + // 手工解析,支持选项与位置参数交错 + dir, output, opts := parsePackArgs(os.Args[2:]) + if dir == "" || output == "" { + fmt.Println("用法: asar pack [options]") + os.Exit(1) + } + if err := asar.CreatePackageWithOptions(dir, output, opts); err != nil { + fmt.Println("打包失败:", err) + os.Exit(1) + } + fmt.Println("打包完成:", filepath.Base(output)) + case "list", "l": + archive, isPack := parseListArgs(os.Args[2:]) + if archive == "" { + fmt.Println("用法: asar list [-i]") + os.Exit(1) + } + files, err := asar.ListPackage(archive, isPack) + if err != nil { + fmt.Println("读取失败:", err) + os.Exit(1) + } + for _, f := range files { + fmt.Println(f) + } + case "extract-file", "ef": + // extract-file + fs := flag.NewFlagSet("extract-file", flag.ExitOnError) + _ = fs.Parse(os.Args[2:]) + args := fs.Args() + if len(args) < 2 { + fmt.Println("用法: asar extract-file ") + os.Exit(1) + } + archive := args[0] + filename := args[1] + data, err := asar.ExtractFile(archive, filename, true) + if err != nil { + fmt.Println("提取失败:", err) + os.Exit(1) + } + if err := os.WriteFile(filepath.Base(filename), data, 0o644); err != nil { + fmt.Println("写入失败:", err) + os.Exit(1) + } + case "extract", "e": + // extract + fs := flag.NewFlagSet("extract", flag.ExitOnError) + _ = fs.Parse(os.Args[2:]) + args := fs.Args() + if len(args) < 2 { + fmt.Println("用法: asar extract ") + os.Exit(1) + } + archive := args[0] + dest := args[1] + if err := asar.ExtractAll(archive, dest); err != nil { + fmt.Println("解压失败:", err) + os.Exit(1) + } + fmt.Println("解压完成:", dest) + default: + printHelp() + os.Exit(1) + } } // printHelp 打印简单帮助 func printHelp() { - fmt.Println("用法:") - fmt.Println(" asar pack [--ordering --unpack --unpack-dir --exclude-hidden]") - fmt.Println(" asar list [-i | --is-pack]") - fmt.Println(" asar extract-file ") - fmt.Println(" asar extract ") + fmt.Println("用法:") + fmt.Println(" asar pack [--ordering --unpack --unpack-dir --exclude-hidden]") + fmt.Println(" asar list [-i | --is-pack]") + fmt.Println(" asar extract-file ") + fmt.Println(" asar extract ") } // parsePackArgs 解析 pack 子命令参数,支持交错 func parsePackArgs(argv []string) (string, string, asar.CreateOptions) { - var dir, output string - opts := asar.CreateOptions{Dot: true} - for i := 0; i < len(argv); i++ { - a := argv[i] - if a == "--ordering" && i+1 < len(argv) { opts.Ordering = argv[i+1]; i++ } else - if a == "--unpack" && i+1 < len(argv) { opts.Unpack = argv[i+1]; i++ } else - if a == "--unpack-dir" && i+1 < len(argv) { opts.UnpackDir = argv[i+1]; i++ } else - if a == "--exclude-hidden" { opts.Dot = false } else if strings.HasPrefix(a, "-") { - // 忽略未知选项 - } else if dir == "" { dir = a } else if output == "" { output = a } - } - return dir, output, opts + var dir, output string + opts := asar.CreateOptions{Dot: true} + for i := 0; i < len(argv); i++ { + a := argv[i] + if a == "--ordering" && i+1 < len(argv) { + opts.Ordering = argv[i+1] + i++ + } else if a == "--unpack" && i+1 < len(argv) { + opts.Unpack = argv[i+1] + i++ + } else if a == "--unpack-dir" && i+1 < len(argv) { + opts.UnpackDir = argv[i+1] + i++ + } else if a == "--exclude-hidden" { + opts.Dot = false + } else if strings.HasPrefix(a, "-") { + // 忽略未知选项 + } else if dir == "" { + dir = a + } else if output == "" { + output = a + } + } + return dir, output, opts } // parseListArgs 解析 list 子命令参数 func parseListArgs(argv []string) (string, bool) { - var archive string - var isPack bool - for i := 0; i < len(argv); i++ { - a := argv[i] - if a == "-i" || a == "--is-pack" { isPack = true } else if strings.HasPrefix(a, "-") { - } else if archive == "" { archive = a } - } - return archive, isPack + var archive string + var isPack bool + for i := 0; i < len(argv); i++ { + a := argv[i] + if a == "-i" || a == "--is-pack" { + isPack = true + } else if strings.HasPrefix(a, "-") { + } else if archive == "" { + archive = a + } + } + return archive, isPack } diff --git a/cmd/showheader/main.go b/cmd/showheader/main.go index f422b5b..4d977f6 100644 --- a/cmd/showheader/main.go +++ b/cmd/showheader/main.go @@ -1,17 +1,22 @@ package main import ( - "fmt" - "os" - "github.com/dcboy/go-asar/asar" + "fmt" + "github.com/dcboy/go-asar/asar" + "os" ) func main() { - if len(os.Args) < 2 { fmt.Println("用法: showheader "); os.Exit(1) } - hdr, err := asar.GetRawHeader(os.Args[1]) - if err != nil { fmt.Println("读取失败:", err); os.Exit(1) } - fmt.Println("headerSize:", hdr.HeaderSize) - fmt.Println("headerString:") - fmt.Println(hdr.HeaderString) + if len(os.Args) < 2 { + fmt.Println("用法: showheader ") + os.Exit(1) + } + hdr, err := asar.GetRawHeader(os.Args[1]) + if err != nil { + fmt.Println("读取失败:", err) + os.Exit(1) + } + fmt.Println("headerSize:", hdr.HeaderSize) + fmt.Println("headerString:") + fmt.Println(hdr.HeaderString) } - diff --git a/go.mod b/go.mod index b30ae98..a656aba 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,3 @@ module github.com/dcboy/go-asar -go 1.21 - +go 1.24.6