UNPKG

asarmor

Version:

Protect asar archive files from extraction

141 lines (140 loc) 5.51 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.close = exports.open = void 0; const promises_1 = __importDefault(require("fs/promises")); const fs_1 = __importDefault(require("fs")); const _1 = require("."); // eslint-disable-next-line @typescript-eslint/no-var-requires const pickle = require('chromium-pickle-js'); const headerSizeOffset = 8; async function readHeaderSize(handle) { const sizeBuffer = Buffer.alloc(headerSizeOffset); const { bytesRead } = await handle.read(sizeBuffer, 0, headerSizeOffset, null); if (bytesRead !== headerSizeOffset) throw new Error('Unable to read header size!'); const sizePickle = pickle.createFromBuffer(sizeBuffer); return sizePickle.createIterator().readUInt32(); } async function readArchive(filePath) { const handle = await promises_1.default.open(filePath, 'r'); // Read header size const headerSize = await readHeaderSize(handle); // Read header const headerBuffer = Buffer.alloc(headerSize); const { bytesRead } = await handle.read(headerBuffer, 0, headerSize, null); await handle.close(); if (bytesRead !== headerSize) throw new Error('Unable to read header!'); const headerPickle = pickle.createFromBuffer(headerBuffer); const header = JSON.parse(headerPickle.createIterator().readString()); // Returning archive object return { headerSize, header, }; } /** * Read and parse an asar archive. * * This can take a while depending on the size of the file. */ async function read(filePath) { const { size: fileSize } = await promises_1.default.stat(filePath); if (fileSize > 2147483648) console.warn('Warning: archive is larger than 2GB. This might take a while.'); const archive = await readArchive(filePath); return archive; } class Asarmor { constructor(archivePath, archive) { this.filePath = archivePath; this.archive = archive; } /** * Apply a patch to the asar archive. */ patch(patch) { if (!patch) { this.patch((0, _1.createBloatPatch)()); return this.archive; } if (patch.header) this.archive.header.files = { ...patch.header.files, ...this.archive.header.files, }; if (!patch.headerSize && patch.header) { this.archive.headerSize = Buffer.from(JSON.stringify(this.archive.header)).length; } else if (patch.headerSize) { this.archive.headerSize = patch.headerSize; } return this.archive; } /** * Write modified asar archive to given absolute file path. */ async write(outputPath) { // Convert header back to string const headerPickle = pickle.createEmpty(); headerPickle.writeString(JSON.stringify(this.archive.header)); // Read new header size const headerBuffer = headerPickle.toBuffer(); const sizePickle = pickle.createEmpty(); sizePickle.writeUInt32(headerBuffer.length); const sizeBuffer = sizePickle.toBuffer(); // Write everything to output file :D const tmp = outputPath + '.tmp'; // create temp file bcs we can't read & write the same file at the same time const writeStream = fs_1.default.createWriteStream(tmp, { flags: 'w' }); writeStream.write(sizeBuffer); writeStream.write(headerBuffer); // write unmodified contents const fd = await promises_1.default.open(this.filePath, 'r'); const originalHeaderSize = await readHeaderSize(fd); await fd.close(); const readStream = fs_1.default.createReadStream(this.filePath, { start: headerSizeOffset + originalHeaderSize, }); readStream.pipe(writeStream); readStream.on('close', () => readStream.unpipe()); return new Promise((resolve, reject) => { writeStream.on('close', () => { fs_1.default.renameSync(tmp, outputPath); resolve(outputPath); }); writeStream.on('error', reject); }); } async createBackup(options) { const backupPath = (options === null || options === void 0 ? void 0 : options.backupPath) || this.filePath + '.bak'; if (!fs_1.default.existsSync(backupPath) || (options === null || options === void 0 ? void 0 : options.overwrite)) await promises_1.default.copyFile(this.filePath, backupPath); } async restoreBackup(options) { const backupPath = (options === null || options === void 0 ? void 0 : options.backupPath) || this.filePath + '.bak'; if (fs_1.default.existsSync(backupPath)) { await promises_1.default.copyFile(backupPath, this.filePath); if (options === null || options === void 0 ? void 0 : options.remove) await promises_1.default.unlink(backupPath); } } } exports.default = Asarmor; /** * Open and prepare asar archive file for modifications. */ async function open(asarFilePath) { const archive = await read(asarFilePath); return new Asarmor(asarFilePath, archive); } exports.open = open; /** * Alias of `asarmor.write()`. */ async function close(asarmor, outputfilePath) { return asarmor.write(outputfilePath); } exports.close = close;