1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeASARs = exports.generateAsarIntegrity = exports.detectAsarMode = exports.AsarMode = void 0;
const asar = require("@electron/asar");
const child_process_1 = require("child_process");
const crypto = require("crypto");
const fs = require("fs-extra");
const path = require("path");
const minimatch = require("minimatch");
const os = require("os");
const debug_1 = require("./debug");
const LIPO = 'lipo';
var AsarMode;
(function (AsarMode) {
AsarMode[AsarMode["NO_ASAR"] = 0] = "NO_ASAR";
AsarMode[AsarMode["HAS_ASAR"] = 1] = "HAS_ASAR";
})(AsarMode = exports.AsarMode || (exports.AsarMode = {}));
// See: https://github.com/apple-opensource-mirror/llvmCore/blob/0c60489d96c87140db9a6a14c6e82b15f5e5d252/include/llvm/Object/MachOFormat.h#L108-L112
const MACHO_MAGIC = new Set([
// 32-bit Mach-O
0xfeedface,
0xcefaedfe,
// 64-bit Mach-O
0xfeedfacf,
0xcffaedfe,
]);
const MACHO_UNIVERSAL_MAGIC = new Set([
// universal
0xcafebabe,
0xbebafeca,
]);
exports.detectAsarMode = async (appPath) => {
debug_1.d('checking asar mode of', appPath);
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
if (!(await fs.pathExists(asarPath))) {
debug_1.d('determined no asar');
return AsarMode.NO_ASAR;
}
debug_1.d('determined has asar');
return AsarMode.HAS_ASAR;
};
exports.generateAsarIntegrity = (asarPath) => {
return {
algorithm: 'SHA256',
hash: crypto
.createHash('SHA256')
.update(asar.getRawHeader(asarPath).headerString)
.digest('hex'),
};
};
function toRelativePath(file) {
return file.replace(/^\//, '');
}
function isDirectory(a, file) {
return Boolean('files' in asar.statFile(a, file));
}
function checkSingleArch(archive, file, allowList) {
if (allowList === undefined || !minimatch(file, allowList, { matchBase: true })) {
throw new Error(`Detected unique file "${file}" in "${archive}" not covered by ` +
`allowList rule: "${allowList}"`);
}
}
exports.mergeASARs = async ({ x64AsarPath, arm64AsarPath, outputAsarPath, singleArchFiles, }) => {
debug_1.d(`merging ${x64AsarPath} and ${arm64AsarPath}`);
const x64Files = new Set(asar.listPackage(x64AsarPath).map(toRelativePath));
const arm64Files = new Set(asar.listPackage(arm64AsarPath).map(toRelativePath));
//
// Build set of unpacked directories and files
//
const unpackedFiles = new Set();
function buildUnpacked(a, fileList) {
for (const file of fileList) {
const stat = asar.statFile(a, file);
if (!('unpacked' in stat) || !stat.unpacked) {
continue;
}
if ('files' in stat) {
continue;
}
unpackedFiles.add(file);
}
}
buildUnpacked(x64AsarPath, x64Files);
buildUnpacked(arm64AsarPath, arm64Files);
//
// Build list of files/directories unique to each asar
//
for (const file of x64Files) {
if (!arm64Files.has(file)) {
checkSingleArch(x64AsarPath, file, singleArchFiles);
}
}
const arm64Unique = [];
for (const file of arm64Files) {
if (!x64Files.has(file)) {
checkSingleArch(arm64AsarPath, file, singleArchFiles);
arm64Unique.push(file);
}
}
//
// Find common bindings with different content
//
const commonBindings = [];
for (const file of x64Files) {
if (!arm64Files.has(file)) {
continue;
}
// Skip directories
if (isDirectory(x64AsarPath, file)) {
continue;
}
const x64Content = asar.extractFile(x64AsarPath, file);
const arm64Content = asar.extractFile(arm64AsarPath, file);
if (x64Content.compare(arm64Content) === 0) {
continue;
}
if (MACHO_UNIVERSAL_MAGIC.has(x64Content.readUInt32LE(0)) &&
MACHO_UNIVERSAL_MAGIC.has(arm64Content.readUInt32LE(0))) {
continue;
}
if (!MACHO_MAGIC.has(x64Content.readUInt32LE(0))) {
throw new Error(`Can't reconcile two non-macho files ${file}`);
}
commonBindings.push(file);
}
//
// Extract both
//
const x64Dir = await fs.mkdtemp(path.join(os.tmpdir(), 'x64-'));
const arm64Dir = await fs.mkdtemp(path.join(os.tmpdir(), 'arm64-'));
try {
debug_1.d(`extracting ${x64AsarPath} to ${x64Dir}`);
asar.extractAll(x64AsarPath, x64Dir);
debug_1.d(`extracting ${arm64AsarPath} to ${arm64Dir}`);
asar.extractAll(arm64AsarPath, arm64Dir);
for (const file of arm64Unique) {
const source = path.resolve(arm64Dir, file);
const destination = path.resolve(x64Dir, file);
if (isDirectory(arm64AsarPath, file)) {
debug_1.d(`creating unique directory: ${file}`);
await fs.mkdirp(destination);
continue;
}
debug_1.d(`xopying unique file: ${file}`);
await fs.mkdirp(path.dirname(destination));
await fs.copy(source, destination);
}
for (const binding of commonBindings) {
const source = await fs.realpath(path.resolve(arm64Dir, binding));
const destination = await fs.realpath(path.resolve(x64Dir, binding));
debug_1.d(`merging binding: ${binding}`);
child_process_1.execFileSync(LIPO, [source, destination, '-create', '-output', destination]);
}
debug_1.d(`creating archive at ${outputAsarPath}`);
const resolvedUnpack = Array.from(unpackedFiles).map((file) => path.join(x64Dir, file));
let unpack;
if (resolvedUnpack.length > 1) {
unpack = `{${resolvedUnpack.join(',')}}`;
}
else if (resolvedUnpack.length === 1) {
unpack = resolvedUnpack[0];
}
await asar.createPackageWithOptions(x64Dir, outputAsarPath, {
unpack,
});
debug_1.d('done merging');
}
finally {
await Promise.all([fs.remove(x64Dir), fs.remove(arm64Dir)]);
}
};
//# sourceMappingURL=asar-utils.js.map
|