Analysis model: gpt-5.5 xhigh
Black Glass ][ - Renaissance
Subject
Black Glass ][ is a 1992 PC vector demo by Renaissance. The archive names the
main executable as rbgdemo2.exe; the bundled documentation calls it
"Black Glass ][" Vector Demo - Renaissance 7/18/92.
Credits from the documentation:
- Code: Tran
- Music: Mosaic and C.C.Catch
- Music system: a new Renaissance Digital/FM driver and
.670music player
The production expects a 386 or better, VGA, DOS in real mode, and optionally a
Sound Blaster or Sound Blaster Pro. The docs explicitly warn against memory
managers and mention the SYSTEM ALREADY IN V86 MODE!!! failure path. That is
consistent with the code: it switches the machine into protected mode itself and
does not want EMM386/QEMM/Windows already controlling CR0.
Files And Hashes
Archive: rbgdemo2.zip
1111bc87f329dd807b74ed42a9bf2bf8117e6617a71d15bd37430c1d65c6036a rbgdemo2.zip
056fab530b943c3607a70a970c63d7f7afe7f8d6d543d6d4290c10fb27ec2aff cbt.com
ae4377ea5ad7ace3149992f111f8384a81a71b1d658cd2fe453291ab14b26d6f demosite.com
36baa8637b0d371e75dce4363318922d91987c7575c4b311d29362c05490ada7 muzik0.670
39a1d183036cf40c60a05861559f3bb9d324a2e783fdec54b6b5aed55ff7dbea muzik1.670
cdc3009b68e3510f8d92842dd22b179a6abce83980ed15d3d2e61fc74e49753c othermuz.bat
4387c26075a6e1aa382e2e09f4b3841c0b9c878b010e6d033737f6e24ba5126d rbgdemo2.doc
0dbd84ffc05336f77c841df5228ce5bc60710ef10e3472b0310e1b2b2b0aca9c rbgdemo2.exe
a5c7579b8ba0d51325e09d9b00a0eff8d4eb918a8165827c096edb8bc909ad07 ren-92.nfo
The main EXE load image after the first XOR stage:
328cb148db906b6ff22822bd215dd8ef90316596cd413f15d9217daa672060ce rbgdemo2_decrypted_load.bin
The .670 files are not ordinary MOD/S3M/XM data. They are custom data for the
Renaissance music player. othermuz.bat simply starts the same executable with
muzik1.670, while the default path is muzik0.670.
EXE Shape
The executable is a compact MZ program with a tiny relocation table and a custom loader at the entry point.
MZ image size: 31478 bytes
header paragraphs: 2
load image: 31446 bytes
relocations: 1
min alloc: 6412 paragraphs
entry CS:IP: 1956:000e
entry in load file: 0x7a4e
overlay: none
That minalloc value matters. The program asks DOS for a large block, then uses
that memory for its protected-mode runtime, depacked data, page buffers, music
buffers, and low-memory transfer buffers.
The decrypted load image starts with a custom LZ depacker, not with application logic. The real entry near the end of the MZ load image runs a bytewise XOR decryptor over the program body, applies the MZ relocation, restores the original stack, and far-jumps to the now-readable code at the front.
First Stage: XOR Shell
The MZ entry at load offset 0x7a4e is a very small decrypt-and-jump shell:
7a4e: cli
7a4f: mov ax,es
7a51: add ax,0010h
7a54: mov es,ax
7a56: mov ss,ax
7a58: mov sp,0001h
7a5b: mov ax,cs
7a5d: mov ds,ax
7a5f: xor bx,bx
7a61: mov dx,642ah
outer_cell:
7a64: cmp bx,0f000h
7a68: jb short_cell
7a6a: sub bx,0f000h
7a6e: mov ax,es
7a70: add ax,0f00h
7a73: mov es,ax
short_cell:
7a75: mov cx,0010h
byte_loop:
7a78: mov al,es:[bx]
7a7b: xor al,dh
7a7d: add dh,dl
7a7f: ror dl,1
7a81: mov es:[bx],al
7a84: add bx,sp
7a86: loop byte_loop
7a88: dec word [000ch]
7a8c: jne outer_cell
The odd-looking add bx,sp is just bx++, because the shell deliberately set
sp to 1. The counter at [000c] is the MZ page count. Instead of calculating a
byte count directly, the shell walks the load image in 16-byte cells and uses
the header's page count as a loop budget.
The key schedule is tiny but stateful:
al = encrypted byte
al ^= dh
dh += dl
dl = ror(dl, 1)
Initial DX is 0x642a, so the first XOR byte is 0x64. Because DH is
mutated by the rotating DL, the key stream is not a fixed single-byte XOR.
This is not cryptographic, but it is enough to stop strings and opcodes from
showing up in a casual strings or linear disassembly pass.
The segment repair is also deliberate:
cmp bx,0f000h
jb short_cell
sub bx,0f000h
add es,0f00h
It advances the physical address by 64K while avoiding an offset wrap through
0xffff. 0xf000 offset plus 0x0f00 paragraphs is the same physical
addressing progression.
After decryption the shell manually performs the single MZ relocation:
7a8e: mov cx,[0002] ; relocation count
7a92: mov si,0092h ; relocation table
7a95: mov dx,[0000] ; load segment delta
reloc_loop:
7a9b: lodsw ; offset
7a9c: mov bx,ax
7a9e: lodsw ; segment
7a9f: add ax,dx
7aa1: mov es,ax
7aa3: add es:[bx],dx
7aa6: loop reloc_loop
Then it restores the original MZ stack and transfers into the decrypted program:
7aa8: mov sp,[0008]
7aac: mov ax,[000a]
7aaf: add ax,dx
7ab1: mov ss,ax
7ab3: add [0006],dx
7ab7: sub dx,0010h
7aba: mov ds,dx
7abc: mov es,dx
...
7acd: ljmp [cs:0004]
So the load chain is:
DOS loads MZ -> tiny XOR shell -> decrypted LZ depacker -> protected-mode runtime
Second Stage: Custom LZ Depacker
The decrypted image begins with the depacker. Its setup reserves stack/output space below the load area, copies a small helper block, and returns through a far transfer into the copied routine:
0000: mov ax,209bh
0003: mov dx,079ah
0009: cmp ax,[0002]
000f: sub ax,0020h
0013: mov ss,ax
0016: sub ax,0025h
0019: mov es,ax
001c: mov cx,0122h
0022: mov si,0144h
0026: rep movsw
0028: retf
The main stream uses a 16-bit flag bucket in BP and DL=0x10 as a bit count:
00ba: lodsw
00bb: xchg bp,ax
00bc: mov dl,10h
literal_loop:
00be: jb copy_match
00c0: movsb
00c1: shr bp,1
00c3: dec dx
00c4: je 00ba
00c6: jae literal_loop
The branch condition is fed by the carry from shr bp,1. A clear flag bit means
literal byte: copy from compressed input at DS:SI to output at ES:DI. A set
flag bit enters the match decoder.
The short copy tail is:
013a: lodsb
013b: mov bl,al
013d: push si
013e: mov si,di
0140: sub si,bx
0142: cli
0143: rep es movsb
0146: sti
0147: pop si
0148: jmp 00c1
This is classic LZSS shape:
- Read a backward distance into
BX. - Temporarily point
SIatDI - distance. - Copy
CXbytes inside the output buffer. - Restore compressed-stream
SI. - Consume the next flag bit.
The cli/sti around rep movsb is not for speed. It prevents an interrupt
from observing or disturbing the mixed segment state while DS:SI is borrowed
as an output-window pointer. That is a recurring style in this demo: small
inner loops are allowed to bend global assumptions as long as interrupts are
blocked for the critical window.
The depacker has small decode tables at CS:0206, CS:0211, and CS:022c.
Those tables classify match forms and lengths so the stream can encode common
short matches compactly while still supporting larger offsets. There is also a
64K boundary repair path around offset 0x01bb; it adjusts ES and repairs
DS:SI when the output pointer crosses a segment edge. That is the same basic
problem solved by the XOR shell, but now the copy loop must preserve both the
compressed input pointer and the output history pointer.
Real-Mode Checks
After depacking, the startup code is still real-mode code. It checks VGA, CPU class, protected/V86 state, and memory before touching CR0.
The VGA test asks BIOS for display-combination information:
0306: mov ax,1a00h
0309: int 10h
030c: cmp bl,07h
030f: jb fail
0311: cmp bl,0ch
0314: ja fail
0316: ret
The accepted BL range corresponds to VGA-class display combinations. On
failure it prints the embedded VGA error text with DOS AH=09h and exits with
AX=4cffh.
The CPU test uses the old pushf/popf flags trick: it tries to alter high flag bits that are only mutable on 386-class CPUs. That avoids executing 386-only protected-mode setup on a 286.
The V86/protected-mode test is direct:
0344: smsw ax
0347: test al,1
0349: jne fail_already_pm
034b: ret
If PE is already set in the machine status word, the program prints the
SYSTEM ALREADY IN V86 MODE style message and exits. This is why memory
managers are forbidden: the demo wants to own the mode switch.
Memory is measured from both conventional and extended-memory BIOS calls:
0351: int 12h ; conventional KB
0353: cmp ax,027fh
0358: movzx eax,ax
035c: shl eax,0ah
0360: mov [0c69h],eax
0372: mov ah,88h ; extended KB
0374: int 15h
0376: mov [0c54h],ax
...
037e: shl eax,0ah
0382: add eax,100000h
0388: mov [0c71h],eax
The second path converts extended kilobytes to bytes and adds 1MB, producing a physical top-of-memory address for the protected-mode allocator.
Protected-Mode Switch
The switch is compact and conventional for a 1992 DOS demo that does its own 386 protected mode:
039e: cli
039f: cld
03a0: mov ax,cs
03a2: mov ds,ax
03a4: mov ss,ax
03a6: mov sp,0200h
...
046d: cli
046e: lgdt [0210h]
0473: mov eax,cr0
0476: or al,1
0478: mov cr0,eax
047b: jmp 0008h:09d6h
The far jump after setting CR0 is mandatory. It reloads CS with a protected
mode selector and flushes the prefetch queue. Selector 0008h is the 32-bit
code descriptor.
The exit path reverses the operation:
0520: lidt [0216h]
0525: mov eax,cr0
0528: and al,0feh
052a: mov cr0,eax
052d: jmp 0823h:0534h
The important detail is the loaded real-mode-compatible IDT just before clearing PE. It prevents an interrupt from landing in a protected-mode IDT while the CPU is being brought back to DOS.
Real-Mode Bridge: int 30h
The protected-mode body uses a small service bridge rather than inlining every
BIOS/DOS transition. The call sites populate a request area around 0x06c0,
select a service in AL, and execute int 30h.
For video mode changes:
92b527: mov word [06c0h],0013h
92b533: mov al,10h
92b535: int 30h
92b53a: mov word [06c0h],0003h
92b545: mov al,10h
92b547: int 30h
AL=10h means "run BIOS video interrupt 10h with the prepared register image".
The first call sets mode 13h; the second restores text mode 3.
For DOS file I/O:
92f2aa: mov word [06c0h],3d02h
92f2b3: mov al,21h
92f2b5: int 30h
92f397: mov word [06c0h],3f00h
92f3a0: mov al,21h
92f3a2: int 30h
The request words are normal DOS functions: open read/write (3d02h) and read
file (3f00h). The bridge is the compatibility layer between the 32-bit demo
core and real-mode DOS/BIOS.
The large-read helper is careful about physical address placement. If a target range is below the bridgeable low-memory window, it reads directly. If not, it reads chunks through a low staging buffer and copies them upward. That lets the demo keep most resources in high protected-mode memory while still using DOS file services that cannot DMA into arbitrary 32-bit linear addresses.
Allocators
The primary bump allocator lives near 0x92b278. It maintains current and limit
pointers around [06e5] and [06e9], subtracts the flat base adjustment stored
at [06dd], and returns a usable linear pointer.
The secondary allocator at 0x92b2a7 uses [06ed] and [06f1]. The split
matches the runtime design: one arena is for persistent decompressed/runtime
data, and another is for temporary or video-side work buffers. There is no
general free list. This demo streams forward through memory in the way many DOS
demos do: allocate everything in the order the show needs it, reset or exit when
done.
Keyboard IRQ
The keyboard handler at 0x92b3b2 talks directly to the AT keyboard controller:
out 64h,0adh ; disable keyboard
in al,60h ; read scancode
...
out 20h,20h ; PIC EOI
It decodes make/break state, updates modifier bytes around [0b58], stores the
last key around [0b59], and acknowledges IRQ1. Direct keyboard IRQ handling
avoids BIOS keyboard latency and works while the demo is in protected mode with
its own interrupt layout.
.670 Music Format
The two bundled music files start like this:
muzik0.670: 06 0b 09 09 09 00 e9 28 00 00 ...
muzik1.670: 08 19 14 01 12 00 38 30 00 00 ...
The parser at 0x92f030 treats the first six bytes as counts and flags:
byte 0: stored at [3463]
byte 1: length of first table
byte 2: count of 4-byte records
byte 3: count of 16-byte records
byte 4: count of 11-byte records
byte 5: stored at [3466]
dword at +6: payload-relative offset
data at +0x0a: first table
The parser then walks the record areas and rebases offsets against the loaded file pointer. The 16-byte record loop is especially clear:
92f09f: mov [edx],ebx
92f0a1: mov [edx+8],ebx
92f0a4: add [edx+0ch],ebx
92f0a7: add ebx,[edx+4]
92f0aa: mov [edx+4],ebx
92f0ad: add edx,10h
92f0b0: dec al
92f0b2: jne 92f09f
This is not decoding samples yet. It is converting a compact file-relative layout into live memory pointers:
- Store the current payload pointer in fields 0 and 8.
- Rebase the field at 12 by adding the current payload pointer.
- Advance the payload pointer by the record's size field at 4.
- Replace field 4 with the new end pointer.
After this pass, playback code can chase pointers directly without adding a file base every time an instrument, pattern, or sample fragment is touched.
Sound Hardware Probe
The sound probe around 0x92ea97 caches its result in [3497], masks and
unmasks PIC lines while testing IRQs, and sets feature bits in [32dd].
OPL detection uses the AdLib/Sound Blaster FM port pair at 0x388/0x389.
The write helper at 0x92dc70 is the standard two-step OPL write:
out 388h,register
delay by repeated in al,dx
out 389h,value
delay again
The delays are required because OPL registers are slow. A fast 386 can easily write the value before the chip is ready unless the code burns I/O cycles.
Sound Blaster DSP probing starts at base port 0x220 and steps by 0x10.
The probe writes DSP commands D3h, E0h, and C6h, then expects AL=39h as
the response pattern. IRQ probing installs temporary handlers on candidate
vectors, unmasks the PIC line, sends DSP command F2h, and observes which stub
sets the detection byte. The stubs do only the minimum: mark the IRQ and send
EOI.
The playback setup around 0x92ede1 also programs the PIT and DMA:
out 43h,36h
out 40h,00h
out 40h,50h
...
out 83h,09h
The PIT divisor bytes 00h,50h are loaded for channel 0 after command 36h.
The DMA page write to 83h and the nearby buffer address 0x9fdd0 show the
driver preparing an 8-bit DMA playback buffer. In other words, the .670
system is a mixed FM plus digital playback path, not merely an FM music player.
VGA Mode Setup
The video setup near 0x94487f starts from BIOS mode 13h, then reprograms VGA
registers into an unchained planar layout:
Sequencer 04h: clear bit 3, set bit 2
Sequencer 01h: set bit 5
Graphics 05h: clear bit 4
Graphics 06h: clear bit 1
CRTC 14h: clear bit 6
CRTC 17h: set bit 6
That is the usual family of Mode X / unchained 256-color setup:
- Four VGA planes become independently addressable.
- CPU writes select a plane through Sequencer map mask.
- A 320x200 screen consumes 16000 bytes per plane.
- Extra VGA memory can hold additional pages or work surfaces.
The clear loop then writes 0x1000 dwords per selected memory area, giving a
16KB clear per plane-sized page.
Chunky-To-Planar Loader
One of the cleanest VGA inner loops is at 0x944a54. It first blacks the DAC by
writing 0x300 zero bytes, then converts an interleaved chunky source image
into planar VGA memory.
The core shape is:
esi = 8200h
edi = [1915fh] ; active VGA page base
dx = 3c5h ; sequencer data port
for mask in 1,2,4,8:
out dx,mask
cx = 3e80h ; 16000 bytes = 320*200/4
plane_loop:
lodsb
add esi,3
stosb
mov [edi+3fffh],al
mov [edi+7fffh],al
loop plane_loop
sub esi,0f9ffh
sub edi,3e80h
The lodsb plus add esi,3 samples every fourth byte from the chunky source.
The four plane passes therefore read:
pass 0: source bytes 0, 4, 8, ...
pass 1: source bytes 1, 5, 9, ...
pass 2: source bytes 2, 6, 10, ...
pass 3: source bytes 3, 7, 11, ...
The rewind is the clever part:
bytes consumed per pass = 0x3e80 * 4 = 0xfa00
sub esi,0xf9ff
net advance = 1 byte
So each plane starts exactly one chunky byte later than the previous plane. The
sub edi,0x3e80 cancels the apparent forward stosb movement so each pass
writes the same byte offsets, only with a different VGA plane selected.
The two extra stores:
mov [edi+3fffh],al
mov [edi+7fffh],al
duplicate pixels into additional page banks. That gives the later page-flip and symmetry code prefilled copies without re-running the chunky conversion.
Palette Uploads And Fades
The palette reader/writer pair uses the normal VGA DAC ports:
3c7h: DAC read index
3c8h: DAC write index
3c9h: DAC data
Small palette ranges are saved and restored:
DAC 00h: 12h bytes
DAC 14h: 03h bytes
DAC 07h: 03h bytes
DAC 38h: 18h bytes
The vblank wait at 0x944958 is the standard two-phase status-port wait:
wait_end:
in al,3dah
test al,08h
jne wait_end
wait_start:
in al,3dah
test al,08h
je wait_start
This synchronizes palette or CRTC changes to vertical retrace. It first waits until any current retrace has ended, then waits for the next retrace to begin. That avoids catching the tail end of a retrace and racing into visible scanout.
The full palette streamer at 0x944b0c uploads four chunks of 0xc0 bytes at
DAC indices 0x00, 0x40, 0x80, and 0xc0. Each chunk is 64 RGB entries.
The fade-up loop at 0x944b67 increments every component toward a target:
al = current component
compare al with target
setne bh
adc al,0
or bl,bh
store al
The adc al,0 is a compact conditional increment. The compare sets carry when
current is below target, and setne records whether any component still differs.
At the end of a pass, BL says whether another fade frame is needed.
Fade-down at 0x944bab mirrors this with sbb al,0, decrementing non-zero
components toward black.
Page Flip And Raster Timing
The page/raster timing routine at 0x944bd9 coordinates CRTC start address,
page state, a small script ring, and two live DAC entries.
The important operations are:
1. Wait until not in vblank.
2. Write CRTC start high register 0Ch using [1916f].
3. Toggle bit 40h in [1916f].
4. Toggle active page pointer [1915f] with [1916b].
5. Advance ring pointers [19170]/[19174].
6. Wait until timer/frame counter [32d8] reaches at least 2.
7. Wait for vblank start.
8. Write two DAC entries: AH and AH+80h.
The CRTC write uses port 3d4h index 0ch, so the demo is changing the display
start address rather than copying a full frame every time. The page pointer XOR
keeps CPU drawing and CRTC display in opposite buffers.
The two DAC writes use small jmp $+2 style delays between byte writes. That is
not decorative. VGA DAC ports can be timing-sensitive on old hardware, and the
delay also gives a stable raster color mark when the routine is used for timing
effects.
Object Workspace
The workspace builder at 0x944c8d allocates 0x5140 bytes and copies four
0x1040 byte blocks from packed data offsets:
0x8840
0x99c0
0xdc00
0xec40
The destination is a contiguous object/sprite workspace. The end pointer is
stored at [191a7]. The block size 0x1040 is slightly larger than 4096, which
fits the later glyph/object routines that treat these as planar-ish packed
frames with row or alignment slop.
Symmetric Planar Plot Loop
The plotter at 0x944cd5 is one of the more interesting visual inner loops. It
does about 0xd0 plotted samples per call, stepping through a source workspace
with modular arithmetic and mirroring writes into VGA pages.
The loop state:
EBP = 0xd0 iterations
SI = horizontal-ish phase
DI = row/block phase
[191c7] = destination phase/base adjustment
[191cb] = mirrored vertical/base adjustment
[1915f] = active VGA page base
[19167] = alternate page/bank offset
Per iteration:
si += 0bh
if si >= 140h:
si -= 140h
di += 140h
if di >= 1040h:
di -= 1040h
plane = 1 << (si & 3)
out 3c5h,plane
al = [workspace_end + di + si]
dest = ((si + di + [191c7]) >> 2) + [1915f]
[dest] = al
[dest + mirror_bank] = al
mirrored_al = al + 80h
mirrored_dest = computed from [191cb]
[mirrored_dest] = mirrored_al
The plane selection comes from the low two bits of SI, because unchained VGA
stores one visible pixel per plane at each byte address. The byte address uses
>> 2 because four adjacent screen pixels share one byte offset, one pixel per
plane.
The si += 0x0b step is a cheap pseudo-diagonal traversal. Since 11 is
relatively prime to many powers-of-two-ish widths and does not share factors
with 320, it walks the source in a scattered but deterministic order. That gives
the glass/sparkle feel without needing a random number generator.
Adding 0x80 for the mirrored write deliberately shifts into the upper half of
the palette. The same shape can therefore be drawn twice with related geometry
but different color ramps.
Object Script
The object script routine at 0x944d84 consumes byte commands from [191ac].
Command format:
00h..fdh: draw/select object frame
feh: mode command
ffh: reset script pointer to 19376h
The normal object path:
al = *script++
edx = [al*4 + 19379h] ; object descriptor/frame table
frame = *script++
src = workspace + frame * 1040h
dst = active object buffer
ecx = 410h dwords
rep movsd
0x410 dwords is 0x1040 bytes, exactly one workspace frame. So a visible
object command is a table lookup plus one fixed-size frame copy.
After copying the frame it loads offsets from [191b7], updates [191c7] and
[191cb], clears two phase counters, and sets [191cf]=0x28. That last value
is a duration/countdown used by the plot/update layer.
The 0xfe mode command uses a compact mode table:
load mode from [191b1 + index]
store it at [1a52d]
wrap when mode reaches 7
load duration into [191ab]
load callback pointer into [1919f]
This is why the demo can present several named modes from the command line without separate codepaths for every look. The mode byte changes tables, palette behavior, offsets, or callbacks while the same inner loops keep running.
Font And Text Pipeline
The font code is more elaborate than a BIOS-font blitter. It builds lookup tables, measures proportional widths, packs glyph masks, and writes planar VGA glyph data.
The lookup builder at 0x92b929 takes a font structure in EDX and a font ID in
AH. It stores:
[font+8] = default width
[font+4] = inverse character lookup table
Then it maps supported character codes to glyph indexes. The width calculator at
0x92b9d9 walks a string, maps each character to a glyph ID, and adds either
the glyph-specific width or the default width. The draw path at 0x92b98f
performs the same scan but calls the active renderer through [1140].
The glyph bit-packer at 0x944e81 packs a 15-row by 19-column source glyph:
for y in 0..14:
for x in 0..18:
read source pixel
setne bit
shl al,cl
or [edi],al
btr cx,2
setb al
add edi,eax
The btr cx,2 trick tests and clears bit 2 of the bit position. Every four
pixels it advances the packed destination byte. That is exactly the packing
needed for four planar pixels per byte offset.
The shift/roll routine at 0x944f28 then uses rcl al,1 over 15 rows. This
creates shifted variants while preserving carry between bytes. The planar
writer at 0x944f47 performs four plane passes, writes 19x15 bytes per pass,
and uses Sequencer map masks through port 3c5h.
The setup routine at 0x944fb1 builds six glyph blocks from source offsets:
0x000
0x14a
0x294
0x3de
0x528
0x672
Those are pre-shifted or styled variants used by the text effects.
Graphics Initializer
The high-level graphics initializer at 0x94501d ties the pieces together:
1. Enter the tweaked VGA planar mode.
2. Build the character table from font data at 0x17f00.
3. Convert physical A0000h through the flat-base adjustment [06dd].
4. Store active page base in [1915f] and [19163].
5. Compute the page XOR mask [1916b].
6. Store the alternate page/bank offset in [19167].
7. Set viewport width/height: [0d60]=320, [0d64]=200.
8. Build glyph variants.
9. Build object workspace.
10. Convert chunky image data into planar VGA pages.
This ordering is practical. The demo does not try to draw anything until the font, object frames, palette, page pointers, and planar background are all in their runtime forms.
Visible Text And Modes
The runtime contains a small command-line/mode interface:
Syntax: RBGDEMO2 [muzik filename]
Select mode:
0) Normal mode.
1) Happy mode.
2) Evil mode.
3) Bubbly mode.
4) A mode for the kiddies.
5) Muzikal mode.
6) Creepy mode.
7) You're being watched mode.
It also contains title/credit text, welcome text, and explanatory text about the
.670 player. The command-line parameter is the music filename; if none is
given, the default string points at MUZIK0.670.
Clean Boot Helper
cbt.com is a tiny helper rather than part of the visual demo. It contains
path strings for autoexec and config variants and a minimal generated config
containing only files=20 and buffers=30. Its purpose is to help produce a
plain real-mode boot environment where the demo can switch modes itself.
That utility confirms the design assumption found in the executable: the demo is not trying to cooperate with a DOS extender or memory manager. It wants a clean machine, owns the GDT/IDT, owns IRQs it needs, and leaves only through its own real-mode restore path.
Runtime Memory Capture Notes
A guest RAM capture found multiple runtime copies of the important code and strings:
RBGDEMO2 near 0x929d35, 0x92a138, 0x9437e7
MUZIK0.670 near 0x9438b2
Not enough memory near 0x9437a2
must have VGA near 0x92a465
a 386 near 0x92a499
Hardware signatures in the capture line up with the static decode:
lgdt / CR0 switch: around 0x92a6b2
real-mode exit switch: around 0x92a762
VGA DAC/CRTC code: around 0x9448xx..0x944cxx
OPL/SB/DMA code: around 0x92dcxx..0x92eexx
file loader / .670 parse around 0x92f0xx..0x92f3xx
The original XOR shell also appears in memory at 0x94a5be, matching the load
offset 0x7a4e when using base 0x942b70. That helped align the runtime carve
with the decrypted MZ load image.
What The Demo Is Doing
The architecture is typical of a strong early-1990s PC demo, but the implementation is compact and confident:
- A tiny obfuscating MZ shell decrypts the loader.
- A custom LZ depacker expands the protected-mode runtime and data.
- The startup code verifies VGA, 386 CPU, real-mode ownership, and memory.
- The demo switches to its own 386 protected-mode environment.
- DOS and BIOS calls go through an
int 30hreal-mode bridge. - VGA is reprogrammed from mode 13h into unchained planar 256-color mode.
- Background/image data is converted from chunky source bytes into four VGA planes using a tight stride-4 loader.
- Palette fades and raster changes are synchronized to port
3dah. - The object and text systems prebuild packed planar-friendly data.
- The main glass/text effects are driven by table scripts, modular stepping, page flips, and palette-bank tricks rather than heavyweight per-pixel math.
- The music system parses a custom
.670format and drives both FM and digital Sound Blaster paths.
The most important inner-loop theme is that the demo spends CPU time up front to put everything into hardware-friendly shapes. Once running, the effect loops mostly do table walks, fixed-size block copies, VGA plane selects, byte stores, and palette updates. That is exactly the right tradeoff for a 386/VGA demo: prepare aggressively, then make the frame loop small enough to hit timing.