nativefier/src/helpers/upgrade/executableHelpers.ts

210 lines
5.9 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
import * as log from 'loglevel';
import { NativefierOptions } from '../../../shared/src/options/model';
import { getVersionString } from './rceditGet';
import { fileExists } from '../fsHelpers';
type ExecutableInfo = {
arch?: string;
};
function getExecutableBytes(executablePath: string): Uint8Array {
return fs.readFileSync(executablePath);
}
function getExecutableArch(
exeBytes: Uint8Array,
platform: string,
): string | undefined {
switch (platform) {
case 'linux':
// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
switch (exeBytes[0x12]) {
case 0x03:
return 'ia32';
case 0x28:
return 'armv7l';
case 0x3e:
return 'x64';
case 0xb7:
return 'arm64';
default:
return undefined;
}
case 'darwin':
case 'mas':
// https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h
switch ((exeBytes[0x04] << 8) + exeBytes[0x05]) {
case 0x0700:
return 'x64';
case 0x0c00:
return 'arm64';
default:
return undefined;
}
case 'windows':
// https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#COFF_Header
switch ((exeBytes[0x7d] << 8) + exeBytes[0x7c]) {
case 0x014c:
return 'ia32';
case 0x8664:
return 'x64';
case 0xaa64:
return 'arm64';
default:
return undefined;
}
default:
return undefined;
}
}
function getExecutableInfo(
executablePath: string,
platform: string,
): ExecutableInfo | undefined {
if (!fileExists(executablePath)) {
return undefined;
}
const exeBytes = getExecutableBytes(executablePath);
return {
arch: getExecutableArch(exeBytes, platform),
};
}
export function getOptionsFromExecutable(
appResourcesDir: string,
priorOptions: NativefierOptions,
): NativefierOptions {
const newOptions: NativefierOptions = { ...priorOptions };
if (!newOptions.name) {
throw new Error(
'Can not extract options from executable with no name specified.',
);
}
const name: string = newOptions.name;
let executablePath: string | undefined = undefined;
const appRoot = path.resolve(path.join(appResourcesDir, '..', '..'));
const children = fs.readdirSync(appRoot, { withFileTypes: true });
const looksLikeMacOS =
children.filter((c) => c.name === 'MacOS' && c.isDirectory()).length > 0;
const looksLikeWindows =
children.filter((c) => c.name.toLowerCase().endsWith('.exe') && c.isFile())
.length > 0;
const looksLikeLinux =
children.filter((c) => c.name.toLowerCase().endsWith('.so') && c.isFile())
.length > 0;
if (looksLikeMacOS) {
log.debug('This looks like a MacOS app...');
if (newOptions.platform === undefined) {
newOptions.platform =
children.filter((c) => c.name === 'Library' && c.isDirectory()).length >
0
? 'mas'
: 'darwin';
}
executablePath = path.join(
appRoot,
'MacOS',
fs.readdirSync(path.join(appRoot, 'MacOS'))[0],
);
} else if (looksLikeWindows) {
log.debug('This looks like a Windows app...');
if (newOptions.platform === undefined) {
newOptions.platform = 'windows';
}
executablePath = path.join(
appRoot,
children.filter(
(c) =>
c.name.toLowerCase() === `${name.toLowerCase()}.exe` && c.isFile(),
)[0].name,
);
if (newOptions.appVersion === undefined) {
// https://github.com/electron/electron-packager/blob/f1c159f4c844d807968078ea504fba40ca7d9c73/src/win32.js#L46-L48
newOptions.appVersion = getVersionString(
executablePath,
'ProductVersion',
);
log.debug(
`Extracted app version from executable: ${
newOptions.appVersion as string
}`,
);
}
if (newOptions.buildVersion === undefined) {
//https://github.com/electron/electron-packager/blob/f1c159f4c844d807968078ea504fba40ca7d9c73/src/win32.js#L50-L52
newOptions.buildVersion = getVersionString(executablePath, 'FileVersion');
if (newOptions.appVersion == newOptions.buildVersion) {
newOptions.buildVersion = undefined;
} else {
log.debug(
`Extracted build version from executable: ${
newOptions.buildVersion as string
}`,
);
}
}
if (newOptions.appCopyright === undefined) {
// https://github.com/electron/electron-packager/blob/f1c159f4c844d807968078ea504fba40ca7d9c73/src/win32.js#L54-L56
newOptions.appCopyright = getVersionString(
executablePath,
'LegalCopyright',
);
log.debug(
`Extracted app copyright from executable: ${
newOptions.appCopyright as string
}`,
);
}
} else if (looksLikeLinux) {
log.debug('This looks like a Linux app...');
if (newOptions.platform === undefined) {
newOptions.platform = 'linux';
}
executablePath = path.join(
appRoot,
children.filter((c) => c.name == name && c.isFile())[0].name,
);
}
if (!executablePath || !newOptions.platform) {
throw Error(
`Could not find executablePath or platform of app in ${appRoot}`,
);
}
log.debug(`Executable path: ${executablePath}`);
if (newOptions.arch === undefined) {
const executableInfo = getExecutableInfo(
executablePath,
newOptions.platform,
);
if (!executableInfo) {
throw new Error(
`Could not get executable info for executable path: ${executablePath}`,
);
}
newOptions.arch = executableInfo.arch;
log.debug(`Extracted arch from executable: ${newOptions.arch as string}`);
}
if (newOptions.platform === undefined || newOptions.arch == undefined) {
throw Error(`Could not determine platform / arch of app in ${appRoot}`);
}
return newOptions;
}