Skip to content

Commit

Permalink
feat: add support for auto-merging ElectronAsarIntegrity values
Browse files Browse the repository at this point in the history
  • Loading branch information
MarshallOfSound committed Sep 15, 2021
1 parent fe8d99e commit 36b58a8
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 16 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@types/debug": "^4.1.5",
"@types/fs-extra": "^9.0.4",
"@types/node": "^14.14.7",
"@types/plist": "^3.0.2",
"husky": "^4.3.0",
"lint-staged": "^10.5.1",
"prettier": "^2.1.2",
Expand All @@ -38,10 +39,11 @@
},
"dependencies": {
"@malept/cross-spawn-promise": "^1.1.0",
"asar": "^3.0.3",
"asar": "^3.1.0",
"debug": "^4.3.1",
"dir-compare": "^2.4.0",
"fs-extra": "^9.0.1"
"fs-extra": "^9.0.1",
"plist": "^3.0.4"
},
"husky": {
"hooks": {
Expand Down
12 changes: 12 additions & 0 deletions src/asar-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as asar from 'asar';
import * as crypto from 'crypto';
import * as fs from 'fs-extra';
import * as path from 'path';
import { d } from './debug';
Expand All @@ -19,3 +21,13 @@ export const detectAsarMode = async (appPath: string) => {
d('determined has asar');
return AsarMode.HAS_ASAR;
};

export const generateAsarIntegrity = (asarPath: string) => {
return {
algorithm: 'SHA256' as const,
hash: crypto
.createHash('SHA256')
.update(asar.getRawHeader(asarPath).headerString)
.digest('hex'),
};
};
3 changes: 3 additions & 0 deletions src/file-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const MACHO_PREFIX = 'Mach-O ';
export enum AppFileType {
MACHO,
PLAIN,
INFO_PLIST,
SNAPSHOT,
APP_CODE,
}
Expand Down Expand Up @@ -50,6 +51,8 @@ export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
fileType = AppFileType.MACHO;
} else if (p.endsWith('.bin')) {
fileType = AppFileType.SNAPSHOT;
} else if (path.basename(p) === 'Info.plist') {
fileType = AppFileType.INFO_PLIST;
}

files.push({
Expand Down
53 changes: 43 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { spawn } from '@malept/cross-spawn-promise';
import * as asar from 'asar';
import * as crypto from 'crypto';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import * as plist from 'plist';
import * as dircompare from 'dir-compare';

import { AppFile, AppFileType, getAllAppFiles } from './file-utils';
import { AsarMode, detectAsarMode } from './asar-utils';
import { AsarMode, detectAsarMode, generateAsarIntegrity } from './asar-utils';
import { sha } from './sha';
import { d } from './debug';

Expand Down Expand Up @@ -172,6 +175,9 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
}
}

const generatedIntegrity: Record<string, { algorithm: 'SHA256'; hash: string }> = {};
let didSplitAsar = false;

/**
* If we have an ASAR we just need to check if the two "app.asar" files have the same hash,
* if they are, same as above, we can leave one there and call it a day. If they're different
Expand All @@ -188,11 +194,10 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
);

if (x64AsarSha !== arm64AsarSha) {
didSplitAsar = true;
d('x64 and arm64 asars are different');
await fs.move(
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar'),
);
const x64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar');
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), x64AsarPath);
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
if (await fs.pathExists(x64Unpacked)) {
await fs.move(
Expand All @@ -201,9 +206,10 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
);
}

const arm64AsarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar');
await fs.copy(
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar'),
arm64AsarPath,
);
const arm64Unpacked = path.resolve(
opts.arm64AppPath,
Expand Down Expand Up @@ -234,13 +240,40 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
);
pj.main = 'index.js';
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
await asar.createPackage(
entryAsar,
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
);
const asarPath = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar');
await asar.createPackage(entryAsar, asarPath);

generatedIntegrity['Resources/app.asar'] = generateAsarIntegrity(asarPath);
generatedIntegrity['Resources/app-x64.asar'] = generateAsarIntegrity(x64AsarPath);
generatedIntegrity['Resources/app-arm64.asar'] = generateAsarIntegrity(arm64AsarPath);
} else {
d('x64 and arm64 asars are the same');
generatedIntegrity['Resources/app.asar'] = generateAsarIntegrity(
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
);
}
}

const plistFiles = x64Files.filter((f) => f.type === AppFileType.INFO_PLIST);
for (const plistFile of plistFiles) {
const x64PlistPath = path.resolve(opts.x64AppPath, plistFile.relativePath);
const arm64PlistPath = path.resolve(opts.arm64AppPath, plistFile.relativePath);

const { ElectronAsarIntegrity: x64Integrity, ...x64Plist } = plist.parse(
await fs.readFile(x64PlistPath, 'utf8'),
) as any;
const { ElectronAsarIntegrity: arm64Integrity, ...arm64Plist } = plist.parse(
await fs.readFile(arm64PlistPath, 'utf8'),
) as any;
if (JSON.stringify(x64Plist) !== JSON.stringify(arm64Plist)) {
throw new Error(
`Expected all Info.plist files to be identical when ignoring integrity when creating a universal build but "${plistFile.relativePath}" was not`,
);
}

const mergedPlist = { ...x64Plist, ElectronAsarIntegrity: generatedIntegrity };

await fs.writeFile(path.resolve(tmpApp, plistFile.relativePath), plist.build(mergedPlist));
}

for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) {
Expand Down
39 changes: 35 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==

"@types/plist@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/plist/-/plist-3.0.2.tgz#61b3727bba0f5c462fe333542534a0c3e19ccb01"
integrity sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==
dependencies:
"@types/node" "*"
xmlbuilder ">=11.0.1"

"@types/retry@^0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
Expand Down Expand Up @@ -497,10 +505,10 @@ asap@^2.0.0:
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=

asar@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/asar/-/asar-3.0.3.tgz#1fef03c2d6d2de0cbad138788e4f7ae03b129c7b"
integrity sha512-k7zd+KoR+n8pl71PvgElcoKHrVNiSXtw7odKbyNpmgKe7EGRF9Pnu3uLOukD37EvavKwVFxOUpqXTIZC5B5Pmw==
asar@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/asar/-/asar-3.1.0.tgz#70b0509449fe3daccc63beb4d3c7d2e24d3c6473"
integrity sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==
dependencies:
chromium-pickle-js "^0.2.0"
commander "^5.0.0"
Expand Down Expand Up @@ -559,6 +567,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=

base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==

bcrypt-pbkdf@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
Expand Down Expand Up @@ -3711,6 +3724,14 @@ please-upgrade-node@^3.2.0:
dependencies:
semver-compare "^1.0.0"

plist@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.4.tgz#a62df837e3aed2bb3b735899d510c4f186019cbe"
integrity sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==
dependencies:
base64-js "^1.5.1"
xmlbuilder "^9.0.7"

prepend-http@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
Expand Down Expand Up @@ -5037,6 +5058,16 @@ xdg-basedir@^3.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=

xmlbuilder@>=11.0.1:
version "15.1.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==

xmlbuilder@^9.0.7:
version "9.0.7"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=

xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
Expand Down

0 comments on commit 36b58a8

Please sign in to comment.