OmniRoute/scripts/native-binary-compat.mjs

167 lines
4.5 KiB
JavaScript

import { existsSync, openSync, readSync, closeSync } from "node:fs";
export const PUBLISHED_BUILD_PLATFORM = "linux";
export const PUBLISHED_BUILD_ARCH = "x64";
const HEADER_SIZE = 4096;
const MAX_FAT_ARCH_COUNT = 30;
function mapElfMachine(machine) {
switch (machine) {
case 62:
return "x64";
case 183:
return "arm64";
default:
return null;
}
}
function mapMachCpuType(cpuType) {
switch (cpuType) {
case 0x01000007:
return "x64";
case 0x0100000c:
return "arm64";
default:
return null;
}
}
function mapPeMachine(machine) {
switch (machine) {
case 0x8664:
return "x64";
case 0xaa64:
return "arm64";
default:
return null;
}
}
function readUInt16(buffer, offset, littleEndian) {
return littleEndian ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
}
function readUInt32(buffer, offset, littleEndian) {
return littleEndian ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
}
const ELF_MAGIC = 0x7f454c46;
function detectElfTarget(buffer) {
if (buffer.length < 20) return null;
if (buffer.readUInt32BE(0) !== ELF_MAGIC) return null;
const littleEndian = buffer[5] !== 2;
const arch = mapElfMachine(readUInt16(buffer, 18, littleEndian));
if (!arch) return null;
return { platform: "linux", architectures: [arch] };
}
const THIN_MACH_MAGIC = new Map([
[0xfeedface, false],
[0xfeedfacf, false],
[0xcefaedfe, true],
[0xcffaedfe, true],
]);
const FAT_MACH_MAGIC = new Map([
[0xcafebabe, false],
[0xcafebabf, false],
[0xbebafeca, true],
[0xbfbafeca, true],
]);
function detectMachTarget(buffer) {
if (buffer.length < 8) return null;
const magic = buffer.readUInt32BE(0);
if (THIN_MACH_MAGIC.has(magic)) {
const littleEndian = THIN_MACH_MAGIC.get(magic);
const arch = mapMachCpuType(readUInt32(buffer, 4, littleEndian));
if (!arch) return null;
return { platform: "darwin", architectures: [arch] };
}
if (!FAT_MACH_MAGIC.has(magic)) return null;
const littleEndian = FAT_MACH_MAGIC.get(magic);
const isFat64 = magic === 0xcafebabf || magic === 0xbfbafeca;
const archCount = readUInt32(buffer, 4, littleEndian);
if (archCount > MAX_FAT_ARCH_COUNT) return null;
const entrySize = isFat64 ? 32 : 20;
const architectures = new Set();
for (let index = 0; index < archCount; index += 1) {
const offset = 8 + index * entrySize;
if (offset + 4 > buffer.length) break;
const arch = mapMachCpuType(readUInt32(buffer, offset, littleEndian));
if (arch) architectures.add(arch);
}
if (architectures.size === 0) return null;
return { platform: "darwin", architectures: [...architectures] };
}
function detectPeTarget(buffer) {
if (buffer.length < 0x40) return null;
if (buffer.readUInt16LE(0) !== 0x5a4d) return null;
const peHeaderOffset = buffer.readUInt32LE(0x3c);
if (peHeaderOffset + 6 > buffer.length) return null;
if (buffer.readUInt32LE(peHeaderOffset) !== 0x00004550) return null;
const arch = mapPeMachine(buffer.readUInt16LE(peHeaderOffset + 4));
if (!arch) return null;
return { platform: "win32", architectures: [arch] };
}
export function detectNativeBinaryTarget(buffer) {
return detectElfTarget(buffer) ?? detectMachTarget(buffer) ?? detectPeTarget(buffer);
}
export function readNativeBinaryTarget(binaryPath) {
if (!existsSync(binaryPath)) return null;
let fd;
try {
fd = openSync(binaryPath, "r");
const buffer = Buffer.alloc(HEADER_SIZE);
const bytesRead = readSync(fd, buffer, 0, HEADER_SIZE, 0);
return detectNativeBinaryTarget(buffer.subarray(0, bytesRead));
} catch (err) {
console.warn(` ⚠️ Could not read native binary at ${binaryPath}: ${err.message}`);
return null;
} finally {
if (fd !== undefined) closeSync(fd);
}
}
export function isNativeBinaryCompatible(
binaryPath,
{ runtimePlatform = process.platform, runtimeArch = process.arch, dlopen = process.dlopen } = {}
) {
const target = readNativeBinaryTarget(binaryPath);
if (target) {
if (
(target.platform !== runtimePlatform &&
!(target.platform === "linux" && runtimePlatform === "android")) ||
!target.architectures.includes(runtimeArch)
) {
return false;
}
} else if (runtimePlatform !== PUBLISHED_BUILD_PLATFORM || runtimeArch !== PUBLISHED_BUILD_ARCH) {
return false;
}
try {
dlopen({ exports: {} }, binaryPath);
return true;
} catch (err) {
console.warn(` ⚠️ Native binary dlopen failed: ${err.message}`);
return false;
}
}