Add comprehensive README covering architecture, API usage, build instructions, tested drivers, binary patching details, and DGROUP layout. Expand file header comments in all library sources and headers to document module responsibilities, data flow, and key constraints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
982 lines
32 KiB
C
982 lines
32 KiB
C
// ============================================================================
|
|
// neload.c - NE (New Executable) format loader
|
|
//
|
|
// Loads Windows 3.x 16-bit DLLs/drivers (.DRV files) into protected
|
|
// mode memory. Each NE segment gets a DPMI LDT descriptor (16-bit
|
|
// code or data), and the segment data is loaded from the file.
|
|
//
|
|
// Loading steps:
|
|
// 1. Read and validate MZ (DOS stub) + NE headers
|
|
// 2. Parse the module reference table (imported DLL names)
|
|
// 3. Parse the resident name table (module name + named exports)
|
|
// 4. Parse the entry table (ordinal -> segment:offset mappings)
|
|
// 5. Allocate memory and LDT descriptors for each segment
|
|
// 6. Load segment data from the file
|
|
// 7. Process relocations (internal refs, imports by ordinal/name)
|
|
//
|
|
// Import resolution is delegated to a caller-supplied callback
|
|
// (ImportResolverT) which returns a 16:16 far pointer for each
|
|
// imported function. If the callback returns FARPTR16_NULL, the
|
|
// loader patches in a stub address and logs a warning.
|
|
//
|
|
// Relocations are applied as fixups to the loaded segment data.
|
|
// Supported fixup types: LOBYTE, SEGMENT, FAR_ADDR (seg:off),
|
|
// OFFSET, and additive fixups. Chained relocations (linked lists
|
|
// within the segment) are followed to completion.
|
|
// ============================================================================
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <inttypes.h>
|
|
#include <dpmi.h>
|
|
#include <go32.h>
|
|
#include <sys/movedata.h>
|
|
#include <sys/nearptr.h>
|
|
|
|
#include "neload.h"
|
|
#include "wintypes.h"
|
|
#include "log.h"
|
|
|
|
// Forward declarations
|
|
static bool readHeaders(NeModuleT *mod, FILE *fp);
|
|
static bool loadSegments(NeModuleT *mod, FILE *fp, ImportResolverT resolver);
|
|
static bool allocateSegment(NeModuleT *mod, int segIdx, uint32_t size, bool isCode);
|
|
static bool loadSegmentData(NeModuleT *mod, int segIdx, FILE *fp, uint32_t fileOffset, uint32_t fileSize);
|
|
static bool processRelocations(NeModuleT *mod, int segIdx, FILE *fp, ImportResolverT resolver);
|
|
static bool parseEntryTable(NeModuleT *mod, FILE *fp);
|
|
static bool parseModuleReferences(NeModuleT *mod, FILE *fp);
|
|
static bool parseResidentNames(NeModuleT *mod, FILE *fp);
|
|
static void freeSegment(LoadedSegT *seg);
|
|
static uint16_t makeDescriptor16(uint32_t base, uint32_t limit, bool isCode);
|
|
static void dbgPrint(const char *fmt, ...);
|
|
|
|
static bool gDebug = false;
|
|
|
|
|
|
static void dbgPrint(const char *fmt, ...)
|
|
{
|
|
if (!gDebug) {
|
|
return;
|
|
}
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
logErrV(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
|
|
bool neLoadModule(NeModuleT *mod, const char *filePath, ImportResolverT resolver)
|
|
{
|
|
memset(mod, 0, sizeof(NeModuleT));
|
|
|
|
FILE *fp = fopen(filePath, "rb");
|
|
if (!fp) {
|
|
logErr("neload: cannot open '%s'\n", filePath);
|
|
return false;
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
// Step 1: Read and validate MZ + NE headers
|
|
if (!readHeaders(mod, fp)) {
|
|
goto done;
|
|
}
|
|
|
|
// Step 2: Parse module reference table (imported module names)
|
|
if (!parseModuleReferences(mod, fp)) {
|
|
goto done;
|
|
}
|
|
|
|
// Step 3: Parse resident name table (module name + exports by name)
|
|
if (!parseResidentNames(mod, fp)) {
|
|
goto done;
|
|
}
|
|
|
|
// Step 4: Parse entry table (export ordinals -> segment:offset)
|
|
if (!parseEntryTable(mod, fp)) {
|
|
goto done;
|
|
}
|
|
|
|
// Step 5: Load segments, apply relocations
|
|
if (!loadSegments(mod, fp, resolver)) {
|
|
goto done;
|
|
}
|
|
|
|
// Resolve auto data segment selector
|
|
if (mod->neHeader.autoDataSegIndex > 0 &&
|
|
mod->neHeader.autoDataSegIndex <= mod->segmentCount) {
|
|
mod->autoDataSel = mod->segments[mod->neHeader.autoDataSegIndex - 1].selector;
|
|
}
|
|
|
|
mod->loaded = true;
|
|
ok = true;
|
|
|
|
done:
|
|
fclose(fp);
|
|
if (!ok) {
|
|
neUnloadModule(mod);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
|
|
void neUnloadModule(NeModuleT *mod)
|
|
{
|
|
for (uint16_t i = 0; i < mod->segmentCount; i++) {
|
|
freeSegment(&mod->segments[i]);
|
|
}
|
|
mod->segmentCount = 0;
|
|
mod->loaded = false;
|
|
}
|
|
|
|
|
|
bool neLookupExport(const NeModuleT *mod, uint16_t ordinal, uint16_t *seg, uint16_t *off, uint16_t *sel)
|
|
{
|
|
if (ordinal == 0 || ordinal >= NE_MAX_EXPORTS) {
|
|
return false;
|
|
}
|
|
if (mod->exports[ordinal].segIndex == 0) {
|
|
return false;
|
|
}
|
|
|
|
ExportEntryT *e = (ExportEntryT *)&mod->exports[ordinal];
|
|
uint16_t sIdx = e->segIndex - 1;
|
|
|
|
if (sIdx >= mod->segmentCount) {
|
|
return false;
|
|
}
|
|
|
|
if (seg) {
|
|
*seg = e->segIndex;
|
|
}
|
|
if (off) {
|
|
*off = e->offset;
|
|
}
|
|
if (sel) {
|
|
*sel = mod->segments[sIdx].selector;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
uint16_t neLookupExportByName(const NeModuleT *mod, const char *name, const char *filePath)
|
|
{
|
|
// We need to re-read the resident name table from the file to search by name.
|
|
// The first entry is the module name; subsequent entries are exports.
|
|
FILE *fp = fopen(filePath, "rb");
|
|
if (!fp) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t tableOff = mod->neHeaderFileOffset + mod->neHeader.resNameTableOffset;
|
|
fseek(fp, tableOff, SEEK_SET);
|
|
|
|
uint16_t ordinal = 0;
|
|
while (1) {
|
|
uint8_t nameLen;
|
|
if (fread(&nameLen, 1, 1, fp) != 1 || nameLen == 0) {
|
|
break;
|
|
}
|
|
|
|
char entryName[256];
|
|
if (fread(entryName, 1, nameLen, fp) != nameLen) {
|
|
break;
|
|
}
|
|
entryName[nameLen] = '\0';
|
|
|
|
uint16_t ord;
|
|
if (fread(&ord, 2, 1, fp) != 1) {
|
|
break;
|
|
}
|
|
|
|
if (strcasecmp(entryName, name) == 0) {
|
|
ordinal = ord;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
return ordinal;
|
|
}
|
|
|
|
|
|
void neDumpModule(const NeModuleT *mod)
|
|
{
|
|
logErr("=== NE Module: %s ===\n", mod->moduleName);
|
|
logErr(" Segments: %u\n", mod->segmentCount);
|
|
logErr(" Auto data segment: %u\n", mod->neHeader.autoDataSegIndex);
|
|
logErr(" Module flags: 0x%04X", mod->neHeader.moduleFlags);
|
|
if (mod->neHeader.moduleFlags & NE_FFLAGS_DLL) {
|
|
logErr(" (DLL)");
|
|
}
|
|
logErr("\n");
|
|
logErr(" Target OS: 0x%02X\n", mod->neHeader.targetOS);
|
|
logErr(" Expected Windows version: %u.%u\n",
|
|
mod->neHeader.expectedWinVer >> 8,
|
|
mod->neHeader.expectedWinVer & 0xFF);
|
|
|
|
logErr(" Segments:\n");
|
|
for (uint16_t i = 0; i < mod->segmentCount; i++) {
|
|
LoadedSegT *s = (LoadedSegT *)&mod->segments[i];
|
|
logErr(" [%u] %s sel=0x%04X base=0x%08" PRIX32 " size=%" PRIu32,
|
|
i + 1,
|
|
s->isCode ? "CODE" : "DATA",
|
|
s->selector,
|
|
s->linearAddr,
|
|
s->size);
|
|
if (s->flags & NE_SEGF_PRELOAD) {
|
|
logErr(" PRELOAD");
|
|
}
|
|
if (s->flags & NE_SEGF_MOVEABLE) {
|
|
logErr(" MOVEABLE");
|
|
}
|
|
logErr("\n");
|
|
}
|
|
|
|
logErr(" Module references:\n");
|
|
for (uint16_t i = 0; i < mod->modRefCount; i++) {
|
|
logErr(" [%u] %s\n", i + 1, mod->modRefNames[i]);
|
|
}
|
|
|
|
logErr(" Exports:\n");
|
|
for (uint16_t i = 1; i < NE_MAX_EXPORTS; i++) {
|
|
if (mod->exports[i].segIndex != 0) {
|
|
logErr(" ord %u -> seg %u : 0x%04X (sel 0x%04X)\n",
|
|
i,
|
|
mod->exports[i].segIndex,
|
|
mod->exports[i].offset,
|
|
mod->exports[i].selector);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================================
|
|
// Internal implementation
|
|
// ============================================================================
|
|
|
|
static bool readHeaders(NeModuleT *mod, FILE *fp)
|
|
{
|
|
// Read MZ header
|
|
MzHeaderT mz;
|
|
if (fread(&mz, sizeof(mz), 1, fp) != 1) {
|
|
logErr("neload: failed to read MZ header\n");
|
|
return false;
|
|
}
|
|
if (mz.signature != MZ_SIGNATURE) {
|
|
logErr("neload: not an MZ executable (sig=0x%04X)\n", mz.signature);
|
|
return false;
|
|
}
|
|
|
|
// Read NE header
|
|
mod->neHeaderFileOffset = mz.neHeaderOffset;
|
|
fseek(fp, mz.neHeaderOffset, SEEK_SET);
|
|
|
|
if (fread(&mod->neHeader, sizeof(NeHeaderT), 1, fp) != 1) {
|
|
logErr("neload: failed to read NE header\n");
|
|
return false;
|
|
}
|
|
if (mod->neHeader.signature != NE_SIGNATURE) {
|
|
logErr("neload: not an NE executable (sig=0x%04X)\n",
|
|
mod->neHeader.signature);
|
|
return false;
|
|
}
|
|
|
|
// Validate
|
|
if (mod->neHeader.segmentCount > NE_MAX_SEGMENTS) {
|
|
logErr("neload: too many segments (%u, max %u)\n",
|
|
mod->neHeader.segmentCount, NE_MAX_SEGMENTS);
|
|
return false;
|
|
}
|
|
|
|
mod->segmentCount = mod->neHeader.segmentCount;
|
|
mod->sectorAlignShift = mod->neHeader.sectorAlignShift;
|
|
if (mod->sectorAlignShift == 0) {
|
|
mod->sectorAlignShift = 9; // Default: 512-byte sectors
|
|
}
|
|
|
|
dbgPrint("neload: NE header at 0x%08" PRIX32 ", %u segments, %u align shift\n",
|
|
mod->neHeaderFileOffset, mod->segmentCount, mod->sectorAlignShift);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool parseModuleReferences(NeModuleT *mod, FILE *fp)
|
|
{
|
|
if (mod->neHeader.moduleRefCount == 0) {
|
|
return true;
|
|
}
|
|
|
|
mod->modRefCount = mod->neHeader.moduleRefCount;
|
|
if (mod->modRefCount > NE_MAX_MODREFS) {
|
|
logErr("neload: too many module references (%u, max %u)\n",
|
|
mod->modRefCount, NE_MAX_MODREFS);
|
|
return false;
|
|
}
|
|
|
|
// Read module reference table (array of offsets into imported name table)
|
|
uint32_t modRefTableOff = mod->neHeaderFileOffset + mod->neHeader.modRefTableOffset;
|
|
fseek(fp, modRefTableOff, SEEK_SET);
|
|
|
|
uint16_t nameOffsets[NE_MAX_MODREFS];
|
|
if (fread(nameOffsets, 2, mod->modRefCount, fp) != mod->modRefCount) {
|
|
logErr("neload: failed to read module reference table\n");
|
|
return false;
|
|
}
|
|
|
|
// Resolve each offset to a name from the imported name table
|
|
uint32_t importNameTableOff = mod->neHeaderFileOffset + mod->neHeader.importNameTableOffset;
|
|
|
|
for (uint16_t i = 0; i < mod->modRefCount; i++) {
|
|
uint32_t nameOff = importNameTableOff + nameOffsets[i];
|
|
fseek(fp, nameOff, SEEK_SET);
|
|
|
|
uint8_t nameLen;
|
|
if (fread(&nameLen, 1, 1, fp) != 1) {
|
|
logErr("neload: failed to read module name length\n");
|
|
return false;
|
|
}
|
|
|
|
if (nameLen > 31) {
|
|
nameLen = 31;
|
|
}
|
|
if (fread(mod->modRefNames[i], 1, nameLen, fp) != nameLen) {
|
|
logErr("neload: failed to read module name\n");
|
|
return false;
|
|
}
|
|
mod->modRefNames[i][nameLen] = '\0';
|
|
|
|
dbgPrint("neload: module ref [%u] = '%s'\n", i + 1, mod->modRefNames[i]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool parseResidentNames(NeModuleT *mod, FILE *fp)
|
|
{
|
|
uint32_t tableOff = mod->neHeaderFileOffset + mod->neHeader.resNameTableOffset;
|
|
fseek(fp, tableOff, SEEK_SET);
|
|
|
|
bool firstEntry = true;
|
|
while (1) {
|
|
uint8_t nameLen;
|
|
if (fread(&nameLen, 1, 1, fp) != 1 || nameLen == 0) {
|
|
break;
|
|
}
|
|
|
|
char name[256];
|
|
if (fread(name, 1, nameLen, fp) != nameLen) {
|
|
break;
|
|
}
|
|
name[nameLen] = '\0';
|
|
|
|
uint16_t ordinal;
|
|
if (fread(&ordinal, 2, 1, fp) != 1) {
|
|
break;
|
|
}
|
|
|
|
if (firstEntry) {
|
|
// First entry is the module name (ordinal 0)
|
|
memcpy(mod->moduleName, name, sizeof(mod->moduleName) - 1);
|
|
mod->moduleName[sizeof(mod->moduleName) - 1] = '\0';
|
|
dbgPrint("neload: module name = '%s'\n", mod->moduleName);
|
|
firstEntry = false;
|
|
} else {
|
|
dbgPrint("neload: resident name '%s' = ordinal %u\n", name, ordinal);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool parseEntryTable(NeModuleT *mod, FILE *fp)
|
|
{
|
|
uint32_t tableOff = mod->neHeaderFileOffset + mod->neHeader.entryTableOffset;
|
|
uint32_t tableEnd = tableOff + mod->neHeader.entryTableSize;
|
|
|
|
fseek(fp, tableOff, SEEK_SET);
|
|
|
|
uint16_t currentOrdinal = 1;
|
|
|
|
while (ftell(fp) < (long)tableEnd) {
|
|
uint8_t bundleCount;
|
|
uint8_t indicator;
|
|
|
|
if (fread(&bundleCount, 1, 1, fp) != 1 || bundleCount == 0) {
|
|
break; // End of entry table
|
|
}
|
|
if (fread(&indicator, 1, 1, fp) != 1) {
|
|
break;
|
|
}
|
|
|
|
if (indicator == 0x00) {
|
|
// Empty bundle - skip these ordinals
|
|
currentOrdinal += bundleCount;
|
|
continue;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < bundleCount; i++) {
|
|
if (currentOrdinal >= NE_MAX_EXPORTS) {
|
|
logErr("neload: export ordinal %u exceeds max\n", currentOrdinal);
|
|
currentOrdinal++;
|
|
continue;
|
|
}
|
|
|
|
if (indicator == 0xFF) {
|
|
// Moveable segment entry
|
|
NeMoveableEntryT entry;
|
|
if (fread(&entry, sizeof(entry), 1, fp) != 1) {
|
|
return false;
|
|
}
|
|
mod->exports[currentOrdinal].segIndex = entry.segIndex;
|
|
mod->exports[currentOrdinal].offset = entry.offset;
|
|
mod->exports[currentOrdinal].flags = entry.flags;
|
|
mod->exportCount++;
|
|
|
|
dbgPrint("neload: entry ord %u -> seg %u : 0x%04X (moveable)\n",
|
|
currentOrdinal, entry.segIndex, entry.offset);
|
|
} else {
|
|
// Fixed segment entry (indicator = segment number)
|
|
NeFixedEntryT entry;
|
|
if (fread(&entry, sizeof(entry), 1, fp) != 1) {
|
|
return false;
|
|
}
|
|
mod->exports[currentOrdinal].segIndex = indicator;
|
|
mod->exports[currentOrdinal].offset = entry.offset;
|
|
mod->exports[currentOrdinal].flags = entry.flags;
|
|
mod->exportCount++;
|
|
|
|
dbgPrint("neload: entry ord %u -> seg %u : 0x%04X (fixed)\n",
|
|
currentOrdinal, indicator, entry.offset);
|
|
}
|
|
|
|
currentOrdinal++;
|
|
}
|
|
}
|
|
|
|
dbgPrint("neload: %u export entries parsed\n", mod->exportCount);
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool loadSegments(NeModuleT *mod, FILE *fp, ImportResolverT resolver)
|
|
{
|
|
// Read segment table
|
|
uint32_t segTableOff = mod->neHeaderFileOffset + mod->neHeader.segmentTableOffset;
|
|
fseek(fp, segTableOff, SEEK_SET);
|
|
|
|
NeSegEntryT segTable[NE_MAX_SEGMENTS];
|
|
if (fread(segTable, sizeof(NeSegEntryT), mod->segmentCount, fp) != mod->segmentCount) {
|
|
logErr("neload: failed to read segment table\n");
|
|
return false;
|
|
}
|
|
|
|
// Load each segment
|
|
for (uint16_t i = 0; i < mod->segmentCount; i++) {
|
|
NeSegEntryT *se = &segTable[i];
|
|
bool isCode = !(se->flags & NE_SEGF_DATA);
|
|
|
|
// Determine segment size
|
|
uint32_t fileLen = se->fileLength;
|
|
if (fileLen == 0 && se->fileSectorOffset != 0) {
|
|
fileLen = 0x10000; // 64K
|
|
}
|
|
|
|
uint32_t allocSize = se->minAllocSize;
|
|
if (allocSize == 0) {
|
|
allocSize = 0x10000; // 64K
|
|
}
|
|
if (allocSize < fileLen) {
|
|
allocSize = fileLen;
|
|
}
|
|
|
|
// Allocate memory and create descriptor
|
|
if (!allocateSegment(mod, i, allocSize, isCode)) {
|
|
logErr("neload: failed to allocate segment %u\n", i + 1);
|
|
return false;
|
|
}
|
|
|
|
mod->segments[i].flags = se->flags;
|
|
mod->segments[i].fileSize = fileLen;
|
|
|
|
// Load data from file
|
|
if (se->fileSectorOffset != 0 && fileLen > 0) {
|
|
uint32_t fileOffset = (uint32_t)se->fileSectorOffset << mod->sectorAlignShift;
|
|
if (!loadSegmentData(mod, i, fp, fileOffset, fileLen)) {
|
|
logErr("neload: failed to load segment %u data\n", i + 1);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
dbgPrint("neload: loaded seg %u: %s size=%" PRIu32 " filelen=%" PRIu32 " sel=0x%04X base=0x%08" PRIX32 "\n",
|
|
i + 1, isCode ? "CODE" : "DATA",
|
|
allocSize, fileLen,
|
|
mod->segments[i].selector,
|
|
mod->segments[i].linearAddr);
|
|
}
|
|
|
|
// Resolve export selectors now that segments are loaded
|
|
for (uint16_t i = 1; i < NE_MAX_EXPORTS; i++) {
|
|
if (mod->exports[i].segIndex > 0 &&
|
|
mod->exports[i].segIndex <= mod->segmentCount) {
|
|
mod->exports[i].selector = mod->segments[mod->exports[i].segIndex - 1].selector;
|
|
}
|
|
}
|
|
|
|
// Process relocations for each segment
|
|
for (uint16_t i = 0; i < mod->segmentCount; i++) {
|
|
if (segTable[i].flags & NE_SEGF_HASRELOC) {
|
|
if (!processRelocations(mod, i, fp, resolver)) {
|
|
logErr("neload: relocation failed for segment %u\n", i + 1);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool allocateSegment(NeModuleT *mod, int segIdx, uint32_t size, bool isCode)
|
|
{
|
|
LoadedSegT *seg = &mod->segments[segIdx];
|
|
|
|
// Allocate memory using DJGPP's malloc (extended memory, flat model)
|
|
// The memory is accessible via the flat DS selector, but we also need
|
|
// a 16-bit selector pointing to it.
|
|
uint8_t *mem = (uint8_t *)calloc(1, size);
|
|
if (!mem) {
|
|
logErr("neload: failed to allocate %" PRIu32 " bytes for segment %u\n", size, segIdx + 1);
|
|
return false;
|
|
}
|
|
|
|
// In DJGPP, pointer values are offsets from the DS base.
|
|
// The true linear address = pointer + __djgpp_base_address.
|
|
// We store the pointer value (for C access) but use the linear
|
|
// address when setting up the LDT descriptor base.
|
|
uint32_t ptrVal = (uint32_t)mem;
|
|
uint32_t linearAddr = ptrVal + __djgpp_base_address;
|
|
|
|
// Create a 16-bit LDT descriptor for this segment
|
|
uint16_t sel = makeDescriptor16(linearAddr, size - 1, isCode);
|
|
if (sel == 0) {
|
|
free(mem);
|
|
return false;
|
|
}
|
|
|
|
seg->linearAddr = ptrVal; // Store DJGPP pointer (for C access via cast)
|
|
seg->selector = sel;
|
|
seg->size = size;
|
|
seg->isCode = isCode;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static uint16_t makeDescriptor16(uint32_t base, uint32_t limit, bool isCode)
|
|
{
|
|
int sel = __dpmi_allocate_ldt_descriptors(1);
|
|
if (sel < 0) {
|
|
logErr("neload: failed to allocate LDT descriptor\n");
|
|
return 0;
|
|
}
|
|
|
|
if (__dpmi_set_segment_base_address(sel, base) < 0) {
|
|
logErr("neload: failed to set segment base\n");
|
|
__dpmi_free_ldt_descriptor(sel);
|
|
return 0;
|
|
}
|
|
|
|
if (__dpmi_set_segment_limit(sel, limit) < 0) {
|
|
logErr("neload: failed to set segment limit\n");
|
|
__dpmi_free_ldt_descriptor(sel);
|
|
return 0;
|
|
}
|
|
|
|
// Set access rights for 16-bit segment
|
|
// Code: present, DPL 3, code, readable, non-conforming = 0xFA
|
|
// Data: present, DPL 3, data, writable = 0xF2
|
|
// High byte: G=0, D=0 (16-bit), 0, AVL=0 = 0x00
|
|
uint16_t rights = isCode ? 0x00FA : 0x00F2;
|
|
if (__dpmi_set_descriptor_access_rights(sel, rights) < 0) {
|
|
logErr("neload: failed to set access rights (0x%04X)\n", rights);
|
|
__dpmi_free_ldt_descriptor(sel);
|
|
return 0;
|
|
}
|
|
|
|
return (uint16_t)sel;
|
|
}
|
|
|
|
|
|
static bool loadSegmentData(NeModuleT *mod, int segIdx, FILE *fp, uint32_t fileOffset, uint32_t fileSize)
|
|
{
|
|
LoadedSegT *seg = &mod->segments[segIdx];
|
|
|
|
fseek(fp, fileOffset, SEEK_SET);
|
|
|
|
// Read directly into the allocated memory (flat model, so pointer works)
|
|
uint8_t *dest = (uint8_t *)seg->linearAddr;
|
|
if (fread(dest, 1, fileSize, fp) != fileSize) {
|
|
logErr("neload: short read loading segment %u data\n", segIdx + 1);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool processRelocations(NeModuleT *mod, int segIdx, FILE *fp, ImportResolverT resolver)
|
|
{
|
|
LoadedSegT *seg = &mod->segments[segIdx];
|
|
|
|
// Relocation data follows the segment data in the file.
|
|
// The segment table entry gives us the file offset; relocation data
|
|
// starts at fileOffset + fileLength.
|
|
uint32_t segTableOff = mod->neHeaderFileOffset + mod->neHeader.segmentTableOffset;
|
|
fseek(fp, segTableOff + segIdx * sizeof(NeSegEntryT), SEEK_SET);
|
|
|
|
NeSegEntryT se;
|
|
if (fread(&se, sizeof(se), 1, fp) != 1) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t fileOffset = (uint32_t)se.fileSectorOffset << mod->sectorAlignShift;
|
|
uint32_t fileLen = se.fileLength;
|
|
if (fileLen == 0 && se.fileSectorOffset != 0) {
|
|
fileLen = 0x10000;
|
|
}
|
|
|
|
// Relocation records follow the segment data
|
|
uint32_t relocOff = fileOffset + fileLen;
|
|
fseek(fp, relocOff, SEEK_SET);
|
|
|
|
// First word is the count of relocation records
|
|
uint16_t relocCount;
|
|
if (fread(&relocCount, 2, 1, fp) != 1) {
|
|
return false;
|
|
}
|
|
|
|
dbgPrint("neload: segment %u has %u relocations\n", segIdx + 1, relocCount);
|
|
|
|
uint8_t *segData = (uint8_t *)seg->linearAddr;
|
|
|
|
for (uint16_t i = 0; i < relocCount; i++) {
|
|
NeRelocT rec;
|
|
if (fread(&rec, sizeof(rec), 1, fp) != 1) {
|
|
logErr("neload: failed to read relocation %u/%u\n", i + 1, relocCount);
|
|
return false;
|
|
}
|
|
|
|
uint8_t targetType = rec.flags & NE_RELF_TARGET_MASK;
|
|
bool additive = (rec.flags & NE_RELF_ADDITIVE) != 0;
|
|
|
|
// Resolve the target address
|
|
uint16_t targetSel = 0;
|
|
uint16_t targetOff = 0;
|
|
bool resolved = false;
|
|
|
|
switch (targetType) {
|
|
case NE_RELF_INTERNALREF: {
|
|
// Internal reference: target1 = segment index (1-based)
|
|
// For moveable segments, target2 is an entry table ordinal.
|
|
// For fixed segments, target2 is the offset.
|
|
uint16_t tSegIdx = rec.target1;
|
|
if (rec.target1 == 0xFF) {
|
|
// Moveable reference via entry table
|
|
uint16_t ordinal = rec.target2;
|
|
if (ordinal > 0 && ordinal < NE_MAX_EXPORTS &&
|
|
mod->exports[ordinal].segIndex > 0) {
|
|
tSegIdx = mod->exports[ordinal].segIndex;
|
|
targetOff = mod->exports[ordinal].offset;
|
|
targetSel = mod->segments[tSegIdx - 1].selector;
|
|
resolved = true;
|
|
}
|
|
} else if (tSegIdx > 0 && tSegIdx <= mod->segmentCount) {
|
|
targetSel = mod->segments[tSegIdx - 1].selector;
|
|
targetOff = rec.target2;
|
|
resolved = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NE_RELF_IMPORTORD: {
|
|
// Import by ordinal: target1 = module ref index (1-based)
|
|
// target2 = ordinal number
|
|
uint16_t modIdx = rec.target1;
|
|
uint16_t ordinal = rec.target2;
|
|
|
|
if (modIdx > 0 && modIdx <= mod->modRefCount && resolver) {
|
|
FarPtr16T addr = resolver(mod->modRefNames[modIdx - 1], ordinal, NULL);
|
|
if (addr.segment != 0 || addr.offset != 0) {
|
|
targetSel = addr.segment;
|
|
targetOff = addr.offset;
|
|
resolved = true;
|
|
}
|
|
dbgPrint("neload: RELOC seg %u off 0x%04X srcType=%u: %s.%u -> %04X:%04X\n",
|
|
segIdx + 1, rec.srcOffset, rec.srcType,
|
|
mod->modRefNames[modIdx - 1], ordinal,
|
|
targetSel, targetOff);
|
|
}
|
|
|
|
if (!resolved) {
|
|
dbgPrint("neload: UNRESOLVED import %s.%u in seg %u at 0x%04X\n",
|
|
(modIdx > 0 && modIdx <= mod->modRefCount) ?
|
|
mod->modRefNames[modIdx - 1] : "???",
|
|
ordinal, segIdx + 1, rec.srcOffset);
|
|
// Patch in a dummy value (INT 3 / breakpoint)
|
|
targetSel = mod->segments[0].selector; // Point to first code seg
|
|
targetOff = 0;
|
|
resolved = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NE_RELF_IMPORTNAME: {
|
|
// Import by name: target1 = module ref index (1-based)
|
|
// target2 = offset into imported names table
|
|
uint16_t modIdx = rec.target1;
|
|
uint16_t nameOff16 = rec.target2;
|
|
|
|
// Read the function name from the imported names table
|
|
char funcName[64] = "";
|
|
uint32_t importNameTableOff = mod->neHeaderFileOffset +
|
|
mod->neHeader.importNameTableOffset;
|
|
long savedPos = ftell(fp);
|
|
fseek(fp, importNameTableOff + nameOff16, SEEK_SET);
|
|
|
|
uint8_t nameLen;
|
|
if (fread(&nameLen, 1, 1, fp) == 1 && nameLen < 64) {
|
|
fread(funcName, 1, nameLen, fp);
|
|
funcName[nameLen] = '\0';
|
|
}
|
|
fseek(fp, savedPos, SEEK_SET);
|
|
|
|
if (modIdx > 0 && modIdx <= mod->modRefCount && resolver) {
|
|
FarPtr16T addr = resolver(mod->modRefNames[modIdx - 1], 0, funcName);
|
|
if (addr.segment != 0 || addr.offset != 0) {
|
|
targetSel = addr.segment;
|
|
targetOff = addr.offset;
|
|
resolved = true;
|
|
}
|
|
}
|
|
|
|
if (!resolved) {
|
|
dbgPrint("neload: UNRESOLVED import %s.%s in seg %u at 0x%04X\n",
|
|
(modIdx > 0 && modIdx <= mod->modRefCount) ?
|
|
mod->modRefNames[modIdx - 1] : "???",
|
|
funcName, segIdx + 1, rec.srcOffset);
|
|
targetSel = mod->segments[0].selector;
|
|
targetOff = 0;
|
|
resolved = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case NE_RELF_OSFIXUP: {
|
|
// OS fixup (floating point emulation, etc.)
|
|
// target1 = fixup type (1=FIARQQ, 2=FJARQQ, etc.)
|
|
// We just patch in NOPs or a far return.
|
|
targetSel = mod->segments[0].selector;
|
|
targetOff = 0;
|
|
resolved = true;
|
|
dbgPrint("neload: OS fixup type %u at seg %u offset 0x%04X\n",
|
|
rec.target1, segIdx + 1, rec.srcOffset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!resolved) {
|
|
logErr("neload: failed to resolve reloc in seg %u at 0x%04X\n",
|
|
segIdx + 1, rec.srcOffset);
|
|
continue;
|
|
}
|
|
|
|
// Apply the fixup to the segment data.
|
|
// NE relocations can be chained: the word at srcOffset contains
|
|
// the offset of the next fixup location (forming a linked list),
|
|
// UNLESS the relocation is additive.
|
|
uint32_t fixupOff = rec.srcOffset;
|
|
|
|
if (additive) {
|
|
// Single fixup, add to existing value
|
|
switch (rec.srcType) {
|
|
case NE_RELOC_LOBYTE:
|
|
if (fixupOff < seg->size) {
|
|
segData[fixupOff] += (uint8_t)targetOff;
|
|
}
|
|
break;
|
|
case NE_RELOC_OFFSET:
|
|
if (fixupOff + 1 < seg->size) {
|
|
uint16_t *p = (uint16_t *)(segData + fixupOff);
|
|
*p += targetOff;
|
|
}
|
|
break;
|
|
case NE_RELOC_SEGMENT:
|
|
if (fixupOff + 1 < seg->size) {
|
|
uint16_t *p = (uint16_t *)(segData + fixupOff);
|
|
*p += targetSel;
|
|
}
|
|
break;
|
|
case NE_RELOC_FAR_ADDR:
|
|
if (fixupOff + 3 < seg->size) {
|
|
uint16_t *pOff = (uint16_t *)(segData + fixupOff);
|
|
uint16_t *pSeg = (uint16_t *)(segData + fixupOff + 2);
|
|
*pOff += targetOff;
|
|
*pSeg += targetSel;
|
|
}
|
|
break;
|
|
case NE_RELOC_OFFSET32:
|
|
if (fixupOff + 3 < seg->size) {
|
|
uint32_t *p = (uint32_t *)(segData + fixupOff);
|
|
*p += ((uint32_t)targetSel << 16) | targetOff;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
// Chained fixups: follow the linked list
|
|
int chainLimit = 4096; // Safety limit
|
|
int chainCount = 0;
|
|
while (fixupOff != 0xFFFF && chainLimit-- > 0) {
|
|
if (fixupOff + 1 >= seg->size) {
|
|
break;
|
|
}
|
|
|
|
uint16_t nextOff;
|
|
chainCount++;
|
|
|
|
switch (rec.srcType) {
|
|
case NE_RELOC_LOBYTE:
|
|
if (fixupOff < seg->size) {
|
|
nextOff = segData[fixupOff]; // Next in chain
|
|
segData[fixupOff] = (uint8_t)targetOff;
|
|
} else {
|
|
nextOff = 0xFFFF;
|
|
}
|
|
break;
|
|
|
|
case NE_RELOC_OFFSET: {
|
|
uint16_t *p = (uint16_t *)(segData + fixupOff);
|
|
nextOff = *p;
|
|
*p = targetOff;
|
|
break;
|
|
}
|
|
|
|
case NE_RELOC_SEGMENT: {
|
|
uint16_t *p = (uint16_t *)(segData + fixupOff);
|
|
nextOff = *p;
|
|
*p = targetSel;
|
|
break;
|
|
}
|
|
|
|
case NE_RELOC_FAR_ADDR: {
|
|
if (fixupOff + 3 >= seg->size) {
|
|
nextOff = 0xFFFF;
|
|
break;
|
|
}
|
|
uint16_t *pOff = (uint16_t *)(segData + fixupOff);
|
|
uint16_t *pSeg = (uint16_t *)(segData + fixupOff + 2);
|
|
nextOff = *pOff; // Chain is in offset field
|
|
*pOff = targetOff;
|
|
*pSeg = targetSel;
|
|
break;
|
|
}
|
|
|
|
case NE_RELOC_OFFSET32: {
|
|
if (fixupOff + 3 >= seg->size) {
|
|
nextOff = 0xFFFF;
|
|
break;
|
|
}
|
|
uint16_t *p16 = (uint16_t *)(segData + fixupOff);
|
|
nextOff = *p16;
|
|
uint32_t *p32 = (uint32_t *)(segData + fixupOff);
|
|
*p32 = ((uint32_t)targetSel << 16) | targetOff;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
dbgPrint("neload: unknown reloc srcType 0x%02X\n", rec.srcType);
|
|
nextOff = 0xFFFF;
|
|
break;
|
|
}
|
|
|
|
fixupOff = nextOff;
|
|
}
|
|
|
|
if (chainCount > 1) {
|
|
dbgPrint("neload: chain: %d links patched\n", chainCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static void freeSegment(LoadedSegT *seg)
|
|
{
|
|
if (seg->selector != 0) {
|
|
__dpmi_free_ldt_descriptor(seg->selector);
|
|
seg->selector = 0;
|
|
}
|
|
if (seg->linearAddr != 0) {
|
|
free((void *)seg->linearAddr);
|
|
seg->linearAddr = 0;
|
|
}
|
|
seg->size = 0;
|
|
}
|
|
|
|
|
|
bool neExtendSegment(NeModuleT *mod, int segIdx, uint32_t extraSize, uint32_t *oldSizeOut)
|
|
{
|
|
if (segIdx < 0 || segIdx >= mod->segmentCount) {
|
|
return false;
|
|
}
|
|
|
|
LoadedSegT *seg = &mod->segments[segIdx];
|
|
uint32_t oldSize = seg->size;
|
|
uint32_t newSize = oldSize + extraSize;
|
|
|
|
// 16-bit segments are limited to 64K
|
|
if (newSize > 0x10000) {
|
|
logErr("neload: cannot extend segment %d beyond 64K (old=%" PRIu32 " extra=%" PRIu32 ")\n",
|
|
segIdx + 1, oldSize, extraSize);
|
|
return false;
|
|
}
|
|
|
|
uint8_t *newMem = (uint8_t *)realloc((void *)seg->linearAddr, newSize);
|
|
if (!newMem) {
|
|
logErr("neload: realloc failed extending segment %d\n", segIdx + 1);
|
|
return false;
|
|
}
|
|
|
|
// Zero the new space
|
|
memset(newMem + oldSize, 0, extraSize);
|
|
|
|
uint32_t newPtrVal = (uint32_t)newMem;
|
|
uint32_t newLinAddr = newPtrVal + __djgpp_base_address;
|
|
|
|
seg->linearAddr = newPtrVal;
|
|
seg->size = newSize;
|
|
|
|
// Update LDT descriptor base (may have moved) and limit
|
|
__dpmi_set_segment_base_address(seg->selector, newLinAddr);
|
|
__dpmi_set_segment_limit(seg->selector, newSize - 1);
|
|
|
|
if (oldSizeOut) {
|
|
*oldSizeOut = oldSize;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Enable debug output for the NE loader
|
|
void neSetDebug(bool enable)
|
|
{
|
|
gDebug = enable;
|
|
}
|