Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

miniwin: Win32 over SDL

miniwin is to this project what DevilutionX's miniwin was to Diablo: a re-implementation of just the part of Win32 the game calls, so the game's source needn't change. The headers are in port/include/ (windows.h, mmsystem.h, Borland's dir.h/dos.h; everything in namespace miniwin); the implementation is port/miniwin/.

The model

One program on one screen, as the game expects of Windows 95 on a 640×480, 256-colour display.

  • One host thread. Win32 threads are fibers scheduled cooperatively (threads.cpp), as on a uniprocessor: a thread runs until it waits or wakes a thread of higher priority, which then runs at once (the game's file worker, above normal priority, relies on this to finish its call before its caller looks).
  • service() does what Windows does behind the program's back: pumps SDL events into the message queue, fires multimedia timers, mixes audio, and presents the screen. The functions a program waits or polls in call it: the message functions, Sleep and the waits, and the time functions. It runs at most about once a millisecond and gives the host a turn every 16 ms (hostYield).
  • The screen is an 8-bit framebuffer shown through a system palette (screen.cpp, palette.cpp); GDI draws into it or into bitmaps in software.

Files

FileCovers
miniwin.cppstartup, shutdown, service()
user.cppUSER32: windows, the message queue (posted messages, then WM_PAINT, then WM_TIMER), timers, hooks, input state, cursors, metrics and system colours. Every retrieved message passes WH_GETMESSAGE hooks first
gdi.cppGDI objects and device contexts, bitmaps and DIB sections (all surfaces are 8-bit or monochrome; handles are the objects' addresses)
blit.cppcolour matching, raster operations, BitBlt, StretchBlt, StretchDIBits, PatBlt, ScrollDC, GetPixel (on a palette device, GDI works in pixel values)
draw.cppFillRect, FillRgn, InvertRect, Rectangle, Ellipse, Polygon, lines
region.cppregions as bands of rectangles, combined band by band
palette.cppthe system palette and logical palettes; realising puts colours where Windows does (static colours matched exactly, the rest in free entries from the first), animating PC_RESERVED entries in place
text.cppthe game's TrueType font (CornerStone) with stb_truetype: unhinted, no antialiasing (a 256-colour display's text had none), GDI's layout rules, Windows-1252
screen.cppthe SDL window: the framebuffer scaled to the window in whole multiples where they fit, the game's cursor drawn into the image so it scales with it; SDL input to window messages and key state; screenshots
threads.cppWin32 threads and fibers on host fibers; priority-based wake-ups; events and waits
kernel.cppKERNEL32 and ADVAPI32's registry: errors, version (Windows 95), modules, memory, atoms, time
files.cppdrives, paths, directories (see below)
mmsystem.cppWINMM: waveOut, midiOut, multimedia timers
midi.cppthe General MIDI synthesizer (TinySoundFont)
borland.cppBorland runtime extras: itoa, case-blind compares, getdisk/chdir, gettime
internal.hwhat the parts share (and the model, in its header comment)

Files and drives

Each drive letter main() adds is a host directory: C: the installed game and its saves, D: the CD (--drive C=…, --cdrom D=…,LABEL,SERIAL). Windows paths are matched against the host's names without regard to case, as Windows does, one component at a time; there is one current directory, which may be on any drive. uv run port lays C: out from assets/zoombi32/installed/ (the settings mohawk.w32, the font CORNER.TTF, the saved-game list Zoombini.who, a Zoombi32.CFG that points at D:) and D: from assets/ packed back into archives.

Sound

Sound plays by the wall clock: service() mixes as many frames as time has passed from every open waveOut device (each through an SDL audio stream that converts format and rate) and queues them, so the game sees buffers finish in real time even where the host isn't playing yet (a browser waits for the user's first click). Finished headers are reported through the device's callback as a driver would from its interrupt. midiOut hands channel messages to the synth; the engine sequences MIDI itself, so the synth only sees what a MIDI port would. The game's MIDI map (MIDIMAP.DAT, mohawk.w32's "unknown device (port)") treats the device as a General MIDI port: drums on channel 10, channels 6, 7 and 16 muted.

Unsupported calls

A Win32 function the game calls that miniwin doesn't implement is reported once on the console (miniwin::unsupported), so new gaps show up while testing.

Adding to miniwin

Implement only what the game calls. Match Windows 95's behaviour where the game relies on it: 16-bit 0xFFFF counts and device ids, a click activating a window. Put code that differs per host in port/host/, never in miniwin, and never include miniwin's headers there.