962 lines
31 KiB
C
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;
|
|
}
|