Files
opencoreC/tools/elf2opc.py
T

123 lines
3.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""
elf2opc - OpenCore Binary Converter
Converts standard 64-bit ELF executables into native .opc (OpenCore Binary) format.
"""
import sys
import struct
import os
OPC_MAGIC = 0x0143504F # 'O', 'P', 'C', '\x01'
OPC_VERSION = 1
OPC_ARCH_X86_64 = 0x8664
PT_LOAD = 1
def convert_elf_to_opc(elf_path, opc_path, preferred_core=0xFF, caps=0xFFFFFFFF):
with open(elf_path, "rb") as f:
elf_data = f.read()
if len(elf_data) < 64:
raise ValueError("ELF file too small")
if elf_data[0:4] != b"\x7fELF":
raise ValueError("Invalid ELF magic")
if elf_data[4] != 2: # 64-bit
raise ValueError("Not a 64-bit ELF")
# Parse ELF64 Header
# 0x10: e_type(2), e_machine(2), e_version(4), e_entry(8), e_phoff(8), e_shoff(8)
e_type, e_machine, e_version, e_entry, e_phoff, e_shoff = struct.unpack_from("<HHIQQQ", elf_data, 0x10)
e_flags, e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx = struct.unpack_from("<IHHHHHH", elf_data, 0x30)
if e_machine != 0x3E:
raise ValueError(f"Unsupported architecture machine {e_machine:#x} (expected x86_64: 0x3e)")
# Read Program Headers
load_segments = []
for i in range(e_phnum):
ph_offset = e_phoff + i * e_phentsize
p_type, p_flags, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, p_align = struct.unpack_from(
"<IIQQQQQQ", elf_data, ph_offset
)
if p_type == PT_LOAD and p_memsz > 0:
seg_bytes = elf_data[p_offset : p_offset + p_filesz]
load_segments.append({
"flags": p_flags,
"vaddr": p_vaddr,
"filesz": p_filesz,
"memsz": p_memsz,
"align": p_align,
"data": seg_bytes
})
if not load_segments:
raise ValueError("No PT_LOAD segments found in ELF")
header_fmt = "<IHHQQIBBH"
header_size = struct.calcsize(header_fmt)
seg_fmt = "<IIQQQQQ"
seg_header_size = struct.calcsize(seg_fmt)
total_headers_size = header_size + len(load_segments) * seg_header_size
# Calculate file offsets for segment bodies
current_data_offset = total_headers_size
for seg in load_segments:
# Align offset if needed
seg["out_offset"] = current_data_offset
current_data_offset += len(seg["data"])
# Build OPC Header
opc_header = struct.pack(
header_fmt,
OPC_MAGIC,
OPC_VERSION,
OPC_ARCH_X86_64,
e_entry,
64 * 1024, # 64 KiB stack
caps, # Capabilities mask
preferred_core, # Preferred core
len(load_segments), # num_segments
0 # reserved
)
# Build Segments Table
opc_segments = bytearray()
for seg in load_segments:
# Map ELF flags (PF_X=1, PF_W=2, PF_R=4) directly
opc_segments += struct.pack(
seg_fmt,
1, # seg_type = LOAD
seg["flags"], # flags
seg["out_offset"], # file_offset
seg["vaddr"], # vaddr
seg["filesz"], # filesz
seg["memsz"], # memsz
seg["align"] # align
)
# Write output .opc file
with open(opc_path, "wb") as out:
out.write(opc_header)
out.write(opc_segments)
for seg in load_segments:
out.write(seg["data"])
print(f"[elf2opc] Converted '{elf_path}' -> '{opc_path}'")
print(f" Entry RIP: {e_entry:#x}, Segments: {len(load_segments)}, Size: {current_data_offset} bytes")
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: elf2opc <input.elf> <output.opc> [preferred_core] [caps_mask]")
sys.exit(1)
pref_core = int(sys.argv[3]) if len(sys.argv) > 3 else 0xFF
caps_val = int(sys.argv[4], 0) if len(sys.argv) > 4 else 0xFFFFFFFF
convert_elf_to_opc(sys.argv[1], sys.argv[2], pref_core, caps_val)