Analysis model: gpt-5.5 xhigh

Summer Holiday by Sorcerers - Technical Dissection

Scope

This covers sholiday.exe, the single-file MS-DOS demo Summer Holiday by Sorcerers, released in May 1989. Pouet lists it as an MS-DOS demo, and the scene.org mirror contains one executable in sholiday.zip.

The archive used here:

c00c24db8b7b5e798a9b4ed1f6aeff7ee24b4579da7842d3b2bfe4fc911005ad  sholiday.zip
ccb09a83205bb0f472d9b9eb6a81760709d098477bf20ab1a7e91563267c85f7  sholiday.exe

This is very different from the more famous 1992-1995 DOS demos. It is not a packed multi-part VGA engine. It is a normal real-mode MZ executable, most likely built with Borland/Turbo Pascal-style runtime and Graph/Crt units. The interesting low-level code is still there, but it sits beside a lot of runtime library and an embedded Borland BGI driver.

Executable Layout

The executable is a regular segmented MZ image:

file size:       98,592 bytes
header size:        880 bytes
loaded image:    97,712 bytes
relocations:        211
entry point:     CS:IP 0000:090C  (file offset 0C7C)
stack:           SS:SP 1820:4000

The relocation count is the first important clue: this is not a tiny packed stub. It is a linked program with far calls, far data pointers, runtime tables, and embedded resource blocks.

The binary contains the string:

BGI Device Driver (CGA) 2.00 - Mar 21 1988

So several of the low-level port sequences in the later address ranges belong to the linked BGI graphics driver, not necessarily to custom demo code. That matters when reading this program: the demo author code mostly calls library entry points; the linked driver implements the CGA/Herc/EGA-ish register and bitplane details.

Program Entry

The entry at 0000:090C is compact and mostly dispatches to library/runtime code:

090c: call far 141e:0000   ; runtime init
0911: call far 133d:011d   ; CRT/video init
0916: call far 0fbb:158c   ; graph/BGI init path

091b: push bp
091c: mov  bp,sp
...
0950: call 0043            ; DESQview guard
0958: call 0000            ; key-drain loop
095b: call far 133d:0038   ; video mode/setup
0960: call 0092            ; opening star/text loop
0963: call far 133d:0000   ; video cleanup/setup
0968: call 0000            ; key-drain loop
096b: call 0315            ; main picture/sound sequence
096e: call 0000            ; key-drain loop
0971: call far 0fbb:0ec2   ; graph cleanup/setup
0976: call 080b            ; final card sequence
0979: call far 0c3b:0000   ; final speaker bitstream

The far call at 141E:0000 is runtime startup. It initializes heap/stack state, saves interrupt vectors, and installs handlers for runtime-critical interrupts. The code saves 18 vectors using DOS int 21h AH=35h, then installs its own handlers through AH=25h.

The graphics initialization wrapper at 02AE creates local driver/mode words with values 1 and 3, passes them to the Graph unit, and checks the result. On error it prints a Pascal string, Graphics init error:, followed by the BGI error text and exits.

DESQview Guard

The short routine at 0043 builds the signature words ED and QS on the stack, calls a runtime helper with interrupt number 21h, and checks whether a sentinel byte was changed to FFh. If not, it prints:

Sorry, you have to take DESQview off!

Then it exits through the runtime termination call. That is an early example of the demo refusing to run under a multitasker/virtualized DOS environment.

Opening Star/Text Loop

The routine at 0092 implements the opening moving-character screen. It keeps 15 object slots on the stack:

The loop exits when the runtime KeyPressed function reports a key. Otherwise it erases and redraws each slot. The two Pascal one-character strings are at CS:008E and CS:0090:

008e: length 1, " "
0090: length 1, FAh

The object loop is:

for i = 1..15:
    draw_string(" ", old_x[i], y[i])

    x[i] -= speed[i]
    if x[i] underflowed:
        y[i] = random(18h) + 1
        speed[i] = random(1Eh) + 0Fh
        x[i] = 0316h

    draw_string(FAh, x[i], y[i])

The movement is therefore row-based character motion, not a pixel scroller. It matches the 1989 PC feel: the program leans on text/graph library output and simple per-object state rather than a handcrafted full-screen framebuffer.

Direct Text-Mode Helpers

The linked CRT-style code has a useful direct-video path. It tracks the active text page, screen width, video segment, and whether direct writes are allowed. When it can write directly, it computes:

DI = ((row * columns) + column) * 2
pitch = columns * 2

The clear/scroll helper at 13849 can either call BIOS int 10h AH=06h or do the work itself in video memory. The direct path has a CGA-snow avoidance branch when writing to B800h: before each word copy or store, it waits on status port 3DAh.

One inner loop copies existing character cells:

mov dx,03dah
row_copy:
    mov cl,width
cell_copy:
    cli
wait_1:
    in  al,dx
    shr al,1
    jc  wait_1
wait_2:
    in  al,dx
    shr al,1
    jnc wait_2
    movsw
    sti
    loop cell_copy
    add di,row_skip
    add si,row_skip

The fill half writes space/attribute words in the same style:

mov ax,attribute_space
row_fill:
    mov cl,width
cell_fill:
    cli
    ; same 3DAh timing wait
    stosw
    sti
    loop cell_fill

If direct timing is not enabled, the same routine falls back to plain rep movsw and rep stosw. This is one of the places where the code is more low-level than the high-level Pascal structure suggests.

Main Picture And Sound Sequence

The main routine at 0315 starts in text memory. It sets a destination pointer to B800:0000, a source pointer to embedded data around image offset 498Bh, and copies 4000h bytes. That fills all of the CGA text memory area, not just one 80x25 page.

The setup sequence is:

dst = B800:0000
src = 0000:498B
copy 4000h bytes
delay 01F4h

play bitstream at 0000:898B, length 0612h
delay 05DCh

play embedded bitstream from the 0BD6h sound routine
delay 0DACh

After that, it enters a graphics/BGI animation loop. Each iteration restores a prepared background, uses table data around 24E2h to select changing positions/values, draws through BGI calls, and advances a long timeline counter at [BP-12h] every third frame. That counter wraps at 1FDBh.

At three exact counter values, it plays more speaker bitstreams:

counter 0A61h -> source 8F9Dh, length 05C2h
counter 0B27h -> source 955Fh, length 0EBCh
counter 0B8Ah -> source A41Bh, length 1944h

The loop also exits on keypress. Before leaving, it clears byte 0040:0017, the BIOS keyboard flag byte. During the loop it writes changing high-nibble values to that same byte from an eight-entry table. The code does not send a keyboard-controller LED command, so this should be read as BIOS flag manipulation, not direct hardware LED driving.

The Speaker Bitstream Player

The best custom inner loop in Summer Holiday is the PC-speaker player. There are several copies/variants:

All three use the same idea. They save the old timer interrupt vector from the IVT entry at 0000:0020, install their ISR by writing a new far pointer there, program PIT channel 0 through ports 43h/40h, busy-wait until the ISR marks the stream complete, then restore the old timer vector and PIT state.

The install path is:

; save old INT 08 vector
xor ax,ax
mov ds,ax
mov si,0020h
movsw cs:[old_vector], [si]
movsw cs:[old_vector+2], [si]

; install new INT 08 vector
mov es,ax
mov di,0020h
cli
stosw              ; offset of ISR
mov ax,cs
stosw              ; segment of ISR

; PIT channel 0, mode 3
mov al,36h
out 43h,al
mov al,80h         ; or 48h in the final player
out 40h,al
xor al,al
out 40h,al
sti

The ISR treats each byte as eight 1-bit speaker samples. It preserves the original port 61h value with speaker bits cleared, and also stores a variant with bit 1 set. On each interrupt it tests the current stream byte's high bit and writes one of those two values to port 61h:

test byte [current_byte],80h
jz   low

high:
    mov al,[port61_on]
    out 61h,al
    jmp shifted

low:
    mov al,[port61_off]
    out 61h,al

shifted:
    shl byte [current_byte],1
    inc byte [bit_index]
    cmp byte [bit_index],7
    jbe done

After eight bits, it advances the byte stream. A byte value of FFh is a control marker: the following word is loaded as a hold/silence counter, the stream pointer skips three bytes, and the current output byte is replaced with zero until the counter expires.

if ++stream_ptr >= end:
    done_flag = 1
else:
    al = *stream_ptr
    if al == FFh:
        hold_counter = *(stream_ptr + 1)
        stream_ptr += 3
        al = 0
    current_byte = al
    bit_index = 0

out 20h,20h        ; PIC EOI
iret

This is closer to a 1-bit PCM streamer than a musical note player. The PIT rate defines the sample bit rate, the stream bytes provide the waveform, and FFh + word compresses stretches of silence or repeated low output.

Embedded BGI Driver And Graphics Primitives

The linked BGI driver contains much of the direct graphics hardware work. For example, the driver code writes CGA/Herc/EGA-style registers and contains helpers that program Graphics Controller and Sequencer registers:

mov dx,03ceh
mov al,05h
out dx,al
inc dx
mov al,02h
out dx,al

mov dx,03c4h
mov ax,0f02h
out dx,ax

Another driver helper writes bitplane bytes by changing the sequencer map mask for each plane:

mov dx,03c4h
mov al,02h
out dx,al
inc dx

mov al,01h
out dx,al
mov es:[di],plane0_byte

mov al,02h
out dx,al
mov es:[di],plane1_byte

mov al,04h
out dx,al
mov es:[di],plane2_byte

mov al,08h
out dx,al
mov es:[di],plane3_byte

Those are real hardware loops, but they are library driver loops. The demo's own effect code calls them through the BGI API rather than implementing a complete custom rasterizer.

Final Card Sequence

The routine at 080B is the closing screen/card player. It sets a bright text attribute, then emits a run of fixed 81-byte blocks:

0532h, 0583h, 05D4h, 0625h, 0676h,
06C7h, 0718h, 0769h, 07BAh, then back to 0532h

Each block starts with 50h (P) and then contains spaces and FEh block characters. The routine copies one block into the runtime text buffer and prints it, repeating the same two far calls for every card:

push ds:2AF6h      ; destination text buffer
push cs:card       ; current 81-byte card
push 0
call far 141e:1125 ; string/block copy helper
call far 141e:10c7 ; emit/write helper

After the cards, the entry path calls the final 0C3B:0000 speaker player. That final copy uses the same ISR design as the earlier sample player but a different PIT divisor byte (48h instead of 80h) and a larger embedded stream ending around 0944h in its segment.

What The Code Says About The Demo

Summer Holiday is pioneering PC demo code, but technically it is still very close to application-programming practice:

That makes it valuable for a different reason than later classics. It shows the transition point: demo presentation logic built on familiar DOS/Pascal tools, with just enough direct hardware programming added where the standard library could not provide timing, sound, or flicker-free text output.

Sources