Skip to content
lj7788Public
forked from electron/asar

About

Simple extensive tar-like archive format with indexing

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

asarpro - Electron Archive

Test npm version

English | 简体中文

ASAR is a simple extensive archive format. It concatenates all files together without compression (like tar) while having random access support.

Features

  • Support random access
  • Use JSON to store file information
  • Very easy to write a parser
  • Store the contents of duplicated files only once

CLI

Install

This module requires Node 22.12.0 or later.

npm i -g asarpro

The package installs the asarpro command.

Usage

$ asarpro --help

  Usage: asarpro [options] [command]

  Commands:

    pack|p <dir> <output>
       create asar archive

    list|l <archive>
       list files of asar archive

    extract-file|ef <archive> <filename>
       extract one file from archive

    extract|e <archive> <dest>
       extract archive

    replace|r <archive> <filename> <source>
       replace a file inside archive with contents of source file


  Options:

    -h, --help     output usage information
    -V, --version  output the version number

Pack

asarpro pack app app.asar

# leave files matching a glob unpacked (written next to the archive, in app.asar.unpacked/)
asarpro pack app app.asar --unpack "*.node"

# exclude hidden files
asarpro pack app app.asar --exclude-hidden

# control the order files are stored in (can improve app startup)
asarpro pack app app.asar --ordering order.txt
Excluding multiple resources from being packed

Given:

    app
(a) ├── x1
(b) ├── x2
(c) ├── y3
(d) │   ├── x1
(e) │   └── z1
(f) │       └── x2
(g) └── z4
(h)     └── w1

Exclude: a, b

asarpro pack app app.asar --unpack-dir "{x1,x2}"

Exclude: a, b, d, f

asarpro pack app app.asar --unpack-dir "**/{x1,x2}"

Exclude: a, b, d, f, h

asarpro pack app app.asar --unpack-dir "{**/x1,**/x2,z4/w1}"

List

asarpro list app.asar

# also show whether each entry is packed or unpacked
asarpro list --is-pack app.asar
pack   : /a.txt
pack   : /sub
unpack : /sub/b.bin

Extract

# extract the whole archive into ./dest
asarpro extract app.asar dest

# extract a single file, written to the current directory using its basename
asarpro extract-file app.asar path/inside/b.bin   # -> ./b.bin

Replacing a file inside an archive

asarpro replace app.asar path/inside/archive.js new-file.js

The new contents are appended to the archive and the entry is re-pointed at them, so the rest of the archive is left untouched (including entries that share contents through deduplication). Old bytes stay as dead space until the archive is repacked with asarpro pack.

Programmatic usage

For full API usage, see the API documentation.

Function Description
createPackage(src, dest) Pack directory src into archive dest
createPackageWithOptions(src, dest, options) Pack with unpack, unpackDir, ordering, pattern, transform, dot / globOptions options
createPackageFromFiles(src, dest, filenames, metadata?) Pack an explicit list of files
createPackageFromStreams(dest, streams) Pack from stream descriptors (AsarStreamType)
listPackage(archive, options) List entries; { isPack: true } also marks each as packed/unpacked
statFile(archive, filename, followLinks?) Stat one entry
extractFile(archive, filename, followLinks?) Read one entry as a Buffer
extractAll(archive, dest) Extract all entries into dest
replaceFile(archive, filename, source) Replace one entry in place
getRawHeader(archive) Read the raw header
uncache(archive) / uncacheAll() Drop cached archive state

createPackage* are async, the rest are synchronous.

Example

import { createPackage } from 'asarpro';

const src = 'some/path/';
const dest = 'name.asar';

await createPackage(src, dest);
console.log('done.');

Please note that there is currently no error handling provided!

Replace a file

import { replaceFile } from 'asarpro';

replaceFile('app.asar', 'path/inside/archive.js', 'new-file.js');

The new contents are appended to the end of the data section and the entry is re-pointed at them, so every other entry keeps its offset — including entries that share contents through deduplication. Old bytes stay as dead space until the archive is repacked with createPackage.

Entries that were unpacked to disk are overwritten in place under app.asar.unpacked instead.

Throws if filename does not exist, refers to a directory, or source cannot be read. The archive is rewritten through a temporary file and renamed, so it never leaves a half-written archive behind.

Deduplication

Files with identical contents are stored once and shared: the first copy is written into the archive and every other copy's header entry points at that same offset. Nothing changes for readers — each file still has its own entry, size, integrity hash, and executable bit — but archives with duplicated contents (a common shape for bundled node_modules) get smaller and pack faster, since the redundant bytes are never written.

Unpacked files (unpack / unpackDir) are always written out in full, because they live on disk outside the archive.

Transform

You can pass in a transform option, that is a function, which either returns nothing, or a stream.Transform. The latter will be used on files that will be in the .asar file to transform them (e.g. compress).

import { createPackageWithOptions } from 'asarpro';

const src = 'some/path/';
const dest = 'name.asar';

function transform (filename) {
  return new CustomTransformStream()
}

await createPackageWithOptions(src, dest, { transform: transform });
console.log('done.');

Format

Asar uses Pickle to safely serialize binary value to file.

The format of asar is very flat:

| UInt32: header_size | String: header | Bytes: file1 | ... | Bytes: file42 |

The header_size and header are serialized with Pickle class, and header_size's Pickle object is 8 bytes.

The header is a JSON string, and the header_size is the size of header's Pickle object.

Structure of header is something like this:

{
   "files": {
      "tmp": {
         "files": {}
      },
      "usr" : {
         "files": {
           "bin": {
             "files": {
               "ls": {
                 "offset": "0",
                 "size": 100,
                 "executable": true,
                 "integrity": {
                   "algorithm": "SHA256",
                   "hash": "...",
                   "blockSize": 1024,
                   "blocks": ["...", "..."]
                 }
               },
               "cd": {
                 "offset": "100",
                 "size": 100,
                 "executable": true,
                 "integrity": {
                   "algorithm": "SHA256",
                   "hash": "...",
                   "blockSize": 1024,
                   "blocks": ["...", "..."]
                 }
               }
             }
           }
         }
      },
      "etc": {
         "files": {
           "hosts": {
             "offset": "200",
             "size": 32,
             "integrity": {
                "algorithm": "SHA256",
                "hash": "...",
                "blockSize": 1024,
                "blocks": ["...", "..."]
              }
           }
         }
      }
   }
}

offset and size records the information to read the file from archive, the offset starts from 0 so you have to manually add the size of header_size and header to the offset to get the real offset of the file.

Files with identical contents share a single copy in the archive, so more than one entry can point at the same offset.

offset is a UINT64 number represented in string, because there is no way to precisely represent UINT64 in JavaScript Number. size is a JavaScript Number that is no larger than Number.MAX_SAFE_INTEGER, which has a value of 9007199254740991 and is about 8PB in size. We didn't store size in UINT64 because file size in Node.js is represented as Number and it is not safe to convert Number to UINT64.

integrity is an object consisting of a few keys:

  • A hashing algorithm, currently only SHA256 is supported.
  • A hex encoded hash value representing the hash of the entire file.
  • An array of hex encoded hashes for the blocks of the file (i.e. for a blockSize of 4KB, this array contains the hash of every block if you split the file into N 4KB blocks).
  • A integer value blockSize representing the size in bytes of each block in the blocks hashes above.

About

Simple extensive tar-like archive format with indexing

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages