| 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; |
| } |
| } |
|
|