use crate::emulator::emulator::mmu::{Mmu, Perm};
use crate::emulator::win32::{Registry, THUNK_BASE};
use crate::format::ne::NeFile;
pub const WIN16_SEG_BASE: u32 = 0x0010_0000;
pub const WIN16_SEG_STRIDE: u32 = 0x0001_0000;
pub const IMPORT_SELECTOR: u16 = 0xF000;
pub const PSP_SELECTOR: u16 = 0xF100;
pub const PSP_BASE: u32 = 0x000F_0000;
pub const SENTINEL_SELECTOR: u16 = 0xFFF8;
#[derive(Debug, thiserror::Error)]
pub enum NeLoadError {
#[error("NE parse failed: {0}")]
Parse(#[from] crate::format::ne::Error),
#[error("segment {seg} data [{off:#x}..+{len:#x}] runs past the file ({file_len} bytes)")]
SegmentOutOfRange {
seg: usize,
off: usize,
len: usize,
file_len: usize,
},
#[error("memory map failed at {addr:#x}: {detail}")]
Map { addr: u32, detail: String },
}
#[derive(Debug, Clone)]
pub struct NeImage {
pub module_name: String,
pub entry_cs: u16,
pub entry_ip: u16,
pub init_ss: u16,
pub init_sp: u16,
pub auto_data: u16,
pub selectors: Vec<(u16, u32)>,
pub unresolved: Vec<(String, String)>,
}
pub fn load_ne(
mmu: &mut Mmu,
registry: &mut Registry,
bytes: &[u8],
) -> Result<NeImage, NeLoadError> {
let ne = NeFile::parse(bytes)?;
let h = &ne.header;
let mut selectors: Vec<(u16, u32)> = Vec::new();
for (i, seg) in ne.segments.iter().enumerate() {
let segnum = u16::try_from(i + 1).unwrap_or(u16::MAX);
let base = WIN16_SEG_BASE + (i as u32) * WIN16_SEG_STRIDE;
mmu.map(base, WIN16_SEG_STRIDE, Perm::R | Perm::W | Perm::X);
if let Some(file_off) = seg.file_offset(h) {
let off = file_off as usize;
let len = seg.data_len() as usize;
let data = bytes
.get(off..off + len)
.ok_or(NeLoadError::SegmentOutOfRange {
seg: segnum as usize,
off,
len,
file_len: bytes.len(),
})?;
mmu.write_initializer(base, data)
.map_err(|t| NeLoadError::Map {
addr: base,
detail: t.to_string(),
})?;
}
selectors.push((segnum, base));
}
selectors.push((IMPORT_SELECTOR, THUNK_BASE));
mmu.map(PSP_BASE, 0x1000, Perm::R | Perm::W);
let _ = mmu.store8(PSP_BASE + 0x80, 0); let _ = mmu.store8(PSP_BASE + 0x81, 0x0D); selectors.push((PSP_SELECTOR, PSP_BASE));
let mut unresolved: Vec<(String, String)> = Vec::new();
for (i, seg) in ne.segments.iter().enumerate() {
if !seg.has_relocations() {
continue;
}
let Some(file_off) = seg.file_offset(h) else {
continue;
};
let base = WIN16_SEG_BASE + (i as u32) * WIN16_SEG_STRIDE;
let reloc_off = file_off as usize + seg.data_len() as usize;
apply_segment_relocs(mmu, registry, &ne, bytes, reloc_off, base, &mut unresolved);
}
Ok(NeImage {
module_name: ne.module_name().unwrap_or("?").to_string(),
entry_cs: (h.cs_ip >> 16) as u16,
entry_ip: (h.cs_ip & 0xFFFF) as u16,
init_ss: (h.ss_sp >> 16) as u16,
init_sp: (h.ss_sp & 0xFFFF) as u16,
auto_data: h.auto_data_seg,
selectors,
unresolved,
})
}
const RT_OFFSET_LOBYTE: u8 = 0;
const RT_SEGMENT: u8 = 2; const RT_FAR_PTR: u8 = 3; const RT_OFFSET: u8 = 5; const SRC_INTERNAL: u8 = 0;
const SRC_IMPORT_ORDINAL: u8 = 1;
const FLAG_ADDITIVE: u8 = 0x04;
fn apply_segment_relocs(
mmu: &mut Mmu,
registry: &mut Registry,
ne: &NeFile,
bytes: &[u8],
reloc_off: usize,
base: u32,
unresolved: &mut Vec<(String, String)>,
) {
let Some(count) = read_u16(bytes, reloc_off) else {
return;
};
let mut rec = reloc_off + 2;
for _ in 0..count {
let Some(record) = bytes.get(rec..rec + 8) else {
break;
};
rec += 8;
let addr_type = record[0] & 0x0F;
let src_type = record[1] & 0x03;
let additive = record[1] & FLAG_ADDITIVE != 0;
let loc = u16::from_le_bytes([record[2], record[3]]);
let target = match src_type {
SRC_INTERNAL => {
let seg = record[4];
if seg == 0xFF {
let ordinal = u16::from_le_bytes([record[6], record[7]]);
match ne.entries.iter().find(|e| e.ordinal == ordinal) {
Some(e) => Some((u16::from(e.segment), e.offset)),
None => continue,
}
} else {
let off = u16::from_le_bytes([record[6], record[7]]);
Some((u16::from(seg), off))
}
}
SRC_IMPORT_ORDINAL => {
let mod_idx = u16::from_le_bytes([record[4], record[5]]) as usize;
let ordinal = u16::from_le_bytes([record[6], record[7]]);
let module = ne
.imported_modules
.get(mod_idx.wrapping_sub(1))
.cloned()
.unwrap_or_default();
let dll = module.to_ascii_lowercase();
let name = format!("@{ordinal}");
let thunk = match registry.resolve(&dll, &name) {
Some(addr) => addr,
None => {
unresolved.push((module, name.clone()));
registry.register_unknown_fallback(&dll, &name)
}
};
Some((IMPORT_SELECTOR, (thunk - THUNK_BASE) as u16))
}
_ => None,
};
let Some((sel, off)) = target else {
continue;
};
if additive {
patch_one(mmu, base, u32::from(loc), addr_type, sel, off);
} else {
patch_chain(mmu, base, loc, addr_type, sel, off);
}
}
}
fn patch_chain(mmu: &mut Mmu, base: u32, start: u16, addr_type: u8, sel: u16, off: u16) {
let mut cur = start;
for _ in 0..0x1_0000 {
let here = base.wrapping_add(u32::from(cur));
let next = mmu.load16(here).unwrap_or(0xFFFF);
patch_one(mmu, base, u32::from(cur), addr_type, sel, off);
if next == 0xFFFF {
break;
}
cur = next;
}
}
fn patch_one(mmu: &mut Mmu, base: u32, loc: u32, addr_type: u8, sel: u16, off: u16) {
let at = base.wrapping_add(loc);
match addr_type {
RT_FAR_PTR => {
let _ = mmu.store16(at, off);
let _ = mmu.store16(at.wrapping_add(2), sel);
}
RT_SEGMENT => {
let _ = mmu.store16(at, sel);
}
RT_OFFSET => {
let _ = mmu.store16(at, off);
}
RT_OFFSET_LOBYTE => {
let _ = mmu.store8(at, off as u8);
}
_ => {}
}
}
fn read_u16(bytes: &[u8], at: usize) -> Option<u16> {
bytes
.get(at..at + 2)
.map(|s| u16::from_le_bytes([s[0], s[1]]))
}