use super::{arg_dword, HostState, Registry, StubFn, Win32Error};
use crate::emulator::{Cpu, Mmu};
use std::collections::BTreeSet;
pub const SENTINEL_HDC: u32 = 0xDEAD_C011;
fn gdi_hdcs_mut(state: &mut HostState) -> &mut BTreeSet<u32> {
state.gdi_hdcs.get_or_insert_with(BTreeSet::new)
}
pub fn register(registry: &mut Registry) {
registry.register("gdi32.dll", "BitBlt", stub_bitblt as StubFn, 9);
registry.register(
"gdi32.dll",
"CreateCompatibleDC",
stub_create_compatible_dc as StubFn,
1,
);
registry.register("gdi32.dll", "DeleteDC", stub_delete_dc as StubFn, 1);
registry.register(
"gdi32.dll",
"GetDeviceCaps",
stub_get_device_caps as StubFn,
2,
);
registry.register(
"gdi32.dll",
"GetNearestColor",
stub_get_nearest_color as StubFn,
2,
);
registry.register("gdi32.dll", "GetObjectA", stub_get_object_a as StubFn, 3);
registry.register(
"gdi32.dll",
"GetSystemPaletteEntries",
stub_get_system_palette_entries as StubFn,
4,
);
registry.register("gdi32.dll", "SelectObject", stub_select_object as StubFn, 2);
registry.register(
"gdi32.dll",
"StretchDIBits",
stub_stretch_dibits as StubFn,
13,
);
registry.register("gdi32.dll", "DeleteObject", stub_delete_object as StubFn, 1);
}
fn stub_delete_object(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_bitblt(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_create_compatible_dc(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
gdi_hdcs_mut(state).insert(SENTINEL_HDC);
Ok(SENTINEL_HDC)
}
fn stub_delete_dc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let h = arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("DeleteDC", t))?;
gdi_hdcs_mut(state).remove(&h);
Ok(1)
}
const DRIVERVERSION: u32 = 0;
const TECHNOLOGY: u32 = 2;
const HORZSIZE: u32 = 4;
const VERTSIZE: u32 = 6;
const HORZRES: u32 = 8;
const VERTRES: u32 = 10;
const BITSPIXEL: u32 = 12;
const PLANES: u32 = 14;
const NUMBRUSHES: u32 = 16;
const NUMPENS: u32 = 18;
const NUMFONTS: u32 = 22;
const NUMCOLORS: u32 = 24;
const RASTERCAPS: u32 = 38;
const LOGPIXELSX: u32 = 88;
const LOGPIXELSY: u32 = 90;
const SIZEPALETTE: u32 = 104;
const NUMRESERVED: u32 = 106;
const COLORRES: u32 = 108;
fn stub_get_device_caps(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hdc = arg_dword(cpu, mmu, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetDeviceCaps", t))?;
let idx = arg_dword(cpu, mmu, 1)
.map_err(|t| crate::win32::trap_to_win32_local("GetDeviceCaps", t))?;
Ok(match idx {
DRIVERVERSION => 0x0400,
TECHNOLOGY => 1, HORZSIZE => 320,
VERTSIZE => 240,
HORZRES => 1024,
VERTRES => 768,
BITSPIXEL => 32,
PLANES => 1,
NUMBRUSHES => 0,
NUMPENS => 16,
NUMFONTS => 0,
NUMCOLORS => 0,
RASTERCAPS => 0x0000_19F1,
LOGPIXELSX => 96,
LOGPIXELSY => 96,
SIZEPALETTE => 0,
NUMRESERVED => 20,
COLORRES => 24,
_ => 0,
})
}
fn stub_get_nearest_color(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hdc = arg_dword(cpu, mmu, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetNearestColor", t))?;
let color = arg_dword(cpu, mmu, 1)
.map_err(|t| crate::win32::trap_to_win32_local("GetNearestColor", t))?;
Ok(color)
}
fn stub_get_object_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_get_system_palette_entries(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_select_object(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hdc =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("SelectObject", t))?;
let h =
arg_dword(cpu, mmu, 1).map_err(|t| crate::win32::trap_to_win32_local("SelectObject", t))?;
Ok(h)
}
fn stub_stretch_dibits(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hdc = arg_dword(cpu, mmu, 0)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _x_dest = arg_dword(cpu, mmu, 1)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _y_dest = arg_dword(cpu, mmu, 2)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _dest_width = arg_dword(cpu, mmu, 3)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let dest_height = arg_dword(cpu, mmu, 4)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _x_src = arg_dword(cpu, mmu, 5)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _y_src = arg_dword(cpu, mmu, 6)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _src_width = arg_dword(cpu, mmu, 7)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _src_height = arg_dword(cpu, mmu, 8)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _lp_bits = arg_dword(cpu, mmu, 9)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _lpbmi = arg_dword(cpu, mmu, 10)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _i_usage = arg_dword(cpu, mmu, 11)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
let _rop = arg_dword(cpu, mmu, 12)
.map_err(|t| crate::win32::trap_to_win32_local("StretchDIBits", t))?;
Ok(dest_height)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emulator::mmu::Perm;
use crate::emulator::regs::Reg32;
fn make_env() -> (Cpu, Mmu, Registry, HostState) {
let mut mmu = Mmu::new();
mmu.map(0x4000, 0x4000, Perm::R | Perm::W);
mmu.map(0x9000, 0x1000, Perm::R | Perm::W);
let mut cpu = Cpu::new();
cpu.regs.set_esp(0x9F00);
let mut registry = Registry::new();
registry.register_kernel32();
registry.register_gdi32();
let state = HostState::new(0x4000, 0x8000);
(cpu, mmu, registry, state)
}
fn call(
cpu: &mut Cpu,
mmu: &mut Mmu,
registry: &Registry,
state: &mut HostState,
dll: &str,
name: &str,
args: &[u32],
) -> Result<(), crate::Error> {
for a in args.iter().rev() {
cpu.push32(mmu, *a)?;
}
cpu.push32(mmu, 0xDEAD_DEAD)?;
cpu.regs.eip = registry.resolve(dll, name).expect("registered");
crate::win32::dispatch_stub(cpu, mmu, registry, state)
}
#[test]
fn create_compatible_dc_returns_sentinel() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"gdi32.dll",
"CreateCompatibleDC",
&[0],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), SENTINEL_HDC);
}
#[test]
fn create_then_delete_dc_roundtrips() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"gdi32.dll",
"CreateCompatibleDC",
&[0],
)
.unwrap();
let h = cpu.regs.get32(Reg32::Eax);
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"gdi32.dll",
"DeleteDC",
&[h],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 1);
}
#[test]
fn get_device_caps_bitspixel_is_32() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"gdi32.dll",
"GetDeviceCaps",
&[SENTINEL_HDC, BITSPIXEL],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 32);
}
#[test]
fn get_nearest_color_is_identity() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"gdi32.dll",
"GetNearestColor",
&[SENTINEL_HDC, 0x12_3456],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0x12_3456);
}
#[test]
fn select_object_returns_input_unchanged() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"gdi32.dll",
"SelectObject",
&[SENTINEL_HDC, 0xCAFE_BABE],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0xCAFE_BABE);
}
#[test]
fn stretch_dibits_returns_dest_height() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"gdi32.dll",
"StretchDIBits",
&[
SENTINEL_HDC,
0,
0,
352,
288,
0,
0,
352,
288,
0,
0,
0,
0x00CC_0020,
],
)
.unwrap();
assert_eq!(
cpu.regs.get32(Reg32::Eax),
288,
"StretchDIBits should echo DestHeight as the scanline count"
);
}
}