WinDriver/win31drv/neload.c
2026-02-21 18:01:54 -06:00

962 lines
31 KiB
C

// ============================================================================
// neload.c - NE (New Executable) format loader
//
// Loads Windows 3.x 16-bit DLLs/drivers into protected mode memory
// using DPMI to allocate LDT descriptors for 16-bit code/data segments.
// ============================================================================
#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;
}