πŸ”¬ Technical Deep Dives β€” Castle of the Winds
_
β–‘
βœ•
← Home Binary Format Segments Data Structures Algorithms RNG

This section documents the results of reverse-engineering CASTLE1.EXE at the binary level β€” disassembling the original 16-bit NE executable to reconstruct the dungeon generation algorithm, map data structures, and core game logic. Rick Saada passed away, and no source code is known to exist publicly. This work is archival in nature.

The analysis was performed using custom NE segment parsing, disassembly of x86 real-mode code, and pattern matching against known MSVC runtime signatures. All segment offsets and addresses below are from the original CASTLE1.EXE v1.1A.

CASTLE1.EXE is a 16-bit New Executable (NE) binary targeting Windows 3.x. NE is the predecessor to the PE format used by modern Windows executables. Key facts from the file header:

FieldValueNotes
FormatNENew Executable β€” Windows 3.x / OS/2 1.x format
Version string1.1AFound in data segment
Copyright1989–1993 SaadaSoftFrom data segment string table
CompilerMicrosoft C (MSVC)Confirmed by R6000–R6021 runtime error strings. Not Turbo Pascal as sometimes claimed.
Align shift4Segment alignment = 16 bytes (2^4)
Code segments32Segmented 16-bit model
Data segmentSeg 32 @ 0xC0E0Contains BSS + initialized data
Seg File Offset Size Identified Role
10x32C09,522Window procedures (WndProc entry points)
20x5A8013,874Color animation, game initialization
30x93C011,348Math library (div/mul/memmove)
70x1568011,610DUNGEONWNDPROC + entity list traversal
80x185F04,952Player movement, map reads
90x19A506,248Tile type reads (14 refs to DS:0xCE2)
110x1C6409,936DUNGEON GENERATION (27 tile write refs)
120x1EDF08,140Map rendering support
130x20F3021,408String formatting (printf-style)
190x2FD6013,605Entity movement (map writes at DS:0x1A50, 0x1ABB)
220x372B014,702Map interaction (9 refs), combat resolution
320xC0E020,158Data segment (BSS + initialized data, strings)
πŸ—ΊοΈ Map Array β€” DS:0xCE2

Discovered via rep stosb at seg11:0x1B04 zeroing 0x3000 = 12,288 bytes = 64Γ—64Γ—3. Three bytes per cell:


[+0]Tile type. Bit 0 = floor. 0x12 = stairs↓. 0x13 = stairs↑.
[+1]Flags. 0x20 = room. 0x0C = corridor. 0x40 = door junction.
[+2]Entity ID of occupying monster/item.

bx = row Γ— 192 + col Γ— 3
(shl row, 6 β†’ Γ— 3 in ASM)

πŸ“‹ Level Descriptor Table β€” DS:0x3D90

Runtime BSS (zeroed in file). 36 bytes (0x24) per level entry. Accessed via:

ax = 0x24
imul [0x96] ; [0x96] = current level


[+0x3D88]Level difficulty / monster density parameter
[+0x3D92/93]Down stairs column/row coordinates
[+0x3D96/97]Up stairs column/row coordinates

Entity table at DS:0x4962 β€” 510 bytes (0x1FE), zeroed alongside the map. Entity pointer lookup: [0x4960 + entity_id Γ— 2].

The main generation function in seg11 orchestrates the entire level build. After post-generation population calls at 0x1B90 (monsters), 0x1DBC (items), and 0x1E02 (traps), the level is fully populated and ready to enter.

; InitMap (seg11:0x1B04) rep stosb DS:0xCE2, 0, 0x3000 ; zero tile array (64*64*3 bytes) rep stosb DS:0x4962, 0, 0x1FE ; zero entity table ; GenerateLevel (seg11:~0x0A3D) mov bx, [level_descriptor] mov ax, [bx + 0x3D88] ; difficulty β†’ monster threshold call InitMap ; Room placement loop (seg11:0x0BCE) for attempt in range(maxRooms * 3): col = rand(W/3) + rand(W/3) + 1 row = rand(H/3) + rand(H/3) + 1 w = rand(8) + 4 h = rand(8) + 4 if col+w >= W-1 or row+h >= H-1: continue if not CanPlaceRoom(col, row, w, h): continue ; seg11:0x164E DrawRoom(col, row, w, h) ; seg11:0x0E30 if prevRoom and rand(100) < monsterThresh: DrawCorridor(prev.cx, prev.cy, cx, cy) ; seg11:0x1A88 roomCount++ if roomCount >= maxRooms: break ; Post-generation (seg11:0x0E14) call 0x1B90 ; populate monsters call 0x1DBC ; populate items call 0x1E02 ; populate traps

CotW uses the standard Microsoft C rand() implementation β€” a linear congruential generator. The multiplier and increment were confirmed by matching against the MSVC runtime error strings found in the data segment. The same LCG is used for all random decisions in the game: room placement, corridor drawing, monster generation, item drops, and combat rolls.

; MS C rand() β€” LCG matching MSVC runtime (confirmed from data segment) state = (state * 214013 + 2531011) & 0xFFFFFFFF return (state >> 16) & 0x7FFF ; Python equivalent: class MSCRand: def next(self): self.state = (self.state * 214013 + 2531011) & 0xFFFFFFFF return (self.state >> 16) & 0x7FFF

All terrain tiles, monster icons, and object graphics are stored as standard Windows 3.1 .ICO resource files embedded in the executable. The icon format was chosen deliberately β€” it meant Paul Canniff could create all art using standard Windows tools, and it gave the game native monochrome support for free (every icon has both a color and a monochrome version).

Multi-tile graphics (ball spell effects, town buildings) are raw bitmaps embedded in the executable's data segment. The entire palette is the Windows standard 16 colors plus transparency β€” no custom palette, no dithering.

The map rendering path scales a 64Γ—64 tile view into the available window size. Rick Saada acknowledged in the Steam release notes that the original scaling routine "does not always work correctly" β€” visible as slight tile artifacts at non-integer scaling factors. The simplified map view (for performance on slow machines) avoids the scaler entirely and renders a cleaner, smaller overview.

CotW was built using the Windows 3.x API as a first-class game engine rather than bypassing it. Window scrollbars drive viewport scrolling. Icon resources hold all tile and entity graphics. The window message loop handles turn processing. This approach was intentional β€” Saada was learning Windows programming as he built the game, and leaning on the OS gave him a functional framework without writing a custom engine from scratch.

The segment structure reflects this: seg1 contains the WndProc entry points that handle Windows messages. seg7 contains DUNGEONWNDPROC specifically β€” the window procedure for the dungeon viewport. Mouse input, keyboard shortcuts, and all UI interactions flow through standard Windows message handling.

The result is a game that integrates almost seamlessly with the Windows 3.x desktop: it can be minimized, resized (within limits), and runs alongside other applications without conflict. For 1989, this was genuinely unusual for a game.

technical.html
NE format  Β·  32 segments  Β·  MSVC compiler  Β·  16-bit x86