const { promisify } = require('util'); const { exec } = require('child_process'); const { promises: fs } = require('fs'); const execAsync = promisify(exec); const debug = require('debug')('read-binary-file-arch'); // https://nodejs.org/api/process.html#processarch const SUPPORTED_ARCH = [ 'arm', 'arm64', 'ia32', 'loong64', 'mips', 'mipsel', 'ppc', 'ppc64', 'riscv64', 's390', 's390x', 'x64', ]; async function readPEArch(filePath) { const DOS_HEADER_PE_OFFSET = 0x3c; const COFF_HEADER_MACHINE_OFFSET = 4; // Offset after 'PE\0\0' const BUFFER_SIZE = 1024; // Enough to cover DOS header, PE header, and COFF header const buffer = Buffer.alloc(BUFFER_SIZE); const fileHandle = await fs.open(filePath, 'r'); await fileHandle.read(buffer, 0, BUFFER_SIZE, 0); // Find the PE header offset from the DOS header const peOffset = buffer.readUInt32LE(DOS_HEADER_PE_OFFSET); // Read the machine type from the COFF header let machineType; try { machineType = buffer.readUInt16LE( peOffset + COFF_HEADER_MACHINE_OFFSET ); } catch (error) { debug('read error:', error.message); throw new Error('Invalid PE file'); } // Mapping of machine types to architectures const MACHINE_TYPES = { 0x014c: 'ia32', 0x8664: 'x64', 0x01c0: 'arm', 0x01c4: 'arm', // ARMv7 Thumb-2 LE 0xaa64: 'arm64', }; const arch = MACHINE_TYPES[machineType]; debug('win32 arch:', arch); await fileHandle.close(); return arch; } async function getArchUsingFileCommand(filePath) { const { stdout } = await execAsync(`file "${filePath}"`); const output = stdout.trim(); debug('file command output:', output); const resultStart = filePath.length + 1; // skip 'filename:' const result = output.substring(resultStart).trim(); debug('result:', result); // 'file' architectures => Node architectures const FILE_ARCH = { x64: 'x64', 'x86-64': 'x64', x86_64: 'x64', x86: 'ia32', i386: 'ia32', arm64: 'arm64', arm: 'arm', aarch64: 'arm64', ARMv7: 'arm', ppc_7400: 'ppc', // PE32 executable (console) Intel 80386, for MS Windows 'Intel 80386': 'ia32', // ELF 64-bit LSB executable, IA-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-ia64.so.2, for GNU/Linux 2.6.16 'IA-64': 'x64', // ELF 32-bit MSB executable, PowerPC or cisco 4500, version 1 (SYSV), dynamically linked, interpreter /lib/ld.so.1, for GNU/Linux 2.6.32 PowerPC: 'ppc', // ELF 32-bit MSB executable, MIPS, MIPS-IV version 1 (SYSV), dynamically linked, interpreter /lib/ld.so.1, for GNU/Linux 2.4.1 MIPS: 'mips', }; const fileArchRegexStr = Object.keys(FILE_ARCH) .map((arch) => `(?:${arch})`) .join('|'); const fileArchRegex = new RegExp(fileArchRegexStr); const fileArchMatch = result.match(fileArchRegex); debug('archMatch:', fileArchMatch && fileArchMatch[0]); const fileArch = fileArchMatch ? fileArchMatch[0] : null; const arch = FILE_ARCH[fileArch]; return arch; } async function readBinaryFileArch(filePath) { const stat = await fs.stat(filePath); if (!stat.isFile()) { throw new Error(`${filePath} is not a file.`); } let arch; if (process.platform === 'win32') { // Windows only supports reading the architecture of valid PE files since // 'file' is not available. arch = await readPEArch(filePath); } else { arch = await getArchUsingFileCommand(filePath); } return SUPPORTED_ARCH.includes(arch) ? arch : null; } module.exports = { readBinaryFileArch };