// ============================================================================ // 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 #include #include #include #include #include #include #include #include #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; }