#!/usr/bin/env python3 """ leimage.py -- minimal loader for DOS/4G "LE" (Linear Executable) images, as produced by Watcom C/C++32 + the Rational/Tenberry DOS/4G(W) extender. Path A tooling for dpl3-revive: lets us inspect FLYK.EXE (and the other HPDAVE binaries) -- object table, names tables, debug-info presence -- and reconstruct the flat code/data images for capstone disassembly. Usage: python leimage.py header FLYK.EXE # LE header fields python leimage.py names FLYK.EXE # resident + non-resident name tables python leimage.py objects FLYK.EXE # object table + page reconstruction sanity python leimage.py strings FLYK.EXE PAT # locate a symbol/string + file offset """ import sys, struct def rd(f, off, n): f.seek(off); return f.read(n) class LE: def __init__(self, path): self.path = path with open(path, 'rb') as f: mz = f.read(0x40) assert mz[:2] == b'MZ', "not an MZ file" self.lfanew = struct.unpack_from(' {'PRESENT' if self.debug_len else 'none'}") def objects(self): with open(self.path,'rb') as f: data = rd(f, self.obj_tab_off, 24*self.n_objects) for i in range(self.n_objects): vsize, base, flags, pgidx, pgcnt, resv = struct.unpack_from('<6I', data, i*24) fl = [] if flags & 0x1: fl.append('READ') if flags & 0x2: fl.append('WRITE') if flags & 0x4: fl.append('EXEC') if flags & 0x2000: fl.append('32BIT') print(f"obj{i+1} vsize={vsize:#010x} base={base:#010x} flags={flags:#06x} [{'|'.join(fl)}] " f"pagemap[{pgidx}..{pgidx+pgcnt-1}] ({pgcnt} pages)") def image(self, objno): """Reconstruct the flat linear image of object `objno` (1-based). Assumes sequential, non-iterated pages (true for DOS/4GW LE).""" with open(self.path, 'rb') as f: objs = rd(f, self.obj_tab_off, 24 * self.n_objects) vsize, base, flags, pgidx, pgcnt, _ = struct.unpack_from('<6I', objs, (objno-1)*24) out = bytearray() for k in range(pgidx, pgidx + pgcnt): # k = global 1-based page number length = self.last_page if k == self.n_pages else self.page_size out += rd(f, self.data_pages_off + (k-1)*self.page_size, length) out = out[:vsize] + bytes(max(0, vsize - len(out))) # trim / zero-fill to vsize return base, bytes(out) def _read_names(self, off, hardlen=None): """Read a chain of length-prefixed name entries: len(1), chars, ordinal(2).""" out = [] with open(self.path,'rb') as f: f.seek(off) blob = f.read(hardlen if hardlen else 0x8000) p = 0 while p < len(blob): ln = blob[p]; p += 1 if ln == 0: break name = blob[p:p+ln]; p += ln if p+2 > len(blob): break ordv = struct.unpack_from('