Analysis model: gpt-5.5 xhigh
Fyvush by Jamm - Technical Dissection
Scope
This is a static technical dissection of Fyvush by Jamm, released for MS-DOS
in the Assembly 1994 PC 64KB intro competition. The Assembly 1994 result file
places it third in that competition, after AirFrame by Prime and Space Jam
by Fascination.
Public references:
- Assembly 1994 results: https://archive.scene.org/pub/parties/1994/assembly94/results.txt
- Scene.org archive: https://archive.scene.org/pub/parties/1994/assembly94/in64/fyvush.zip
- Scene.org description: https://archive.scene.org/pub/parties/1994/assembly94/in64/fyvush.diz
This is not a source-level reconstruction. It is based on the public archive, the included information text, the MZ/RNC executable layout, the RNC-expanded image, and static disassembly of the unpacked runtime.
Offset notation:
original EXE+0x...means a file offset inside the packedFYVUSH.EXE.expanded+0x...means a physical offset inside the RNC-expanded image.- The main code enters at
expanded+0x2a2a8. - The main code segment base is effectively
expanded+0x28a50, with entry28a5:1858. Some near-call targets printed by a simple 16-bit disassembler wrap at 64 KB. For example, a target printed as0x2186ffrom the main scene list is physicallyexpanded+0x3186f. I use physical expanded offsets below.
Examined Files
Archive contents:
FILE_ID.DIZ 314 bytes
FYVUSH.EXE 49,902 bytes
INPHO.TXT 12,276 bytes
JAMMDIST.LST 668 bytes
Hashes:
0527155653ee573aaa5394464a420b735cae91c39fd0763a381fa79786a75bb7 fyvush.zip
0845ca2be7bee74c8c6abb6f4667074eae26e8a1f81b8f589161cdef8c993e40 fyvush.diz / FILE_ID.DIZ
d6e7b18e561b03fbb0d82de7650bc0939f60362fa8c54a7d3699514c1cd73f72 FYVUSH.EXE
301b287e5eb81bca88008c1a4bc57621b0919fffe26a2fb7c040753243e01e2f INPHO.TXT
62c170dd084c3bf7c149839f6103fdf237f559f6aa843b9b668d2396436eba83 JAMMDIST.LST
8a475ba80342085f1b5052fecfa200895e43da35e303b69d2e551aff3e441862 RNC-expanded payload
The info text credits:
- Rubber Square: Hellraiser cube code.
- Galvados: all other code.
- Criman: graphics and design.
- Mordicus: music.
The same text describes a 386/40 target, a fast compatible VGA card, about 550 KB of conventional memory, and Gravis UltraSound with 256 KB. It also says the intro is fully assembler, that the tune was made with ProTracker for GUS, and that the raster interrupt is used only in the Hellraiser cube. The code confirms the overall shape: this is a tight real-mode VGA/GUS intro, not a protected-mode framework.
Executable And Packing
FYVUSH.EXE is an MZ executable with a tiny loader/depacker tail and one RNC
method-1 block beginning immediately after the 32-byte MZ header.
file size: 49,902 bytes
MZ header size: 32 bytes
entry point: CS:IP 0bea:000c
entry file offset: 0xbecc
relocations: 0
RNC block offset: 0x20
RNC method: 1
RNC packed size: 48,772 bytes
RNC unpacked size: 529,296 bytes
The expanded payload starts with deliberate noise: an anti-ripper taunt and a
large number of fake M.K. ProTracker signatures. A simple string scan sees
thousands of module-looking markers near the front, but that is not the code
entry and not a trustworthy module boundary.
The real loader tail at original EXE+0xbec0 contains the far entry and stack
values that are applied after decompression:
raw entry: 28a5:1858 -> expanded+0x2a2a8
raw stack: 2885:0200
The RNC method-1 depacker at original EXE+0xbf1a..0xc14b is a compact
bitstream/Huffman decoder. Its structure is the normal RNC method-1 shape:
- Build three Huffman decode tables on the stack.
- Pull bitfields through a small carry/shift bitreader.
- Decode literal runs, match offsets, and match lengths.
- Copy either direct literal bytes or backreferences into the destination.
- After the unpacked block is present, apply the relocation table at loader
cs:0x28c. - Restore the saved stack and jump through the relocated far pointer.
The important practical point is that expanded+0x0 is data/decoy text. The
first real top-level code I found is the far target expanded+0x2a2a8.
Startup Path
Startup is plain DOS/real-mode setup. At expanded+0x2a2a8 it saves the
initial ES, switches DS to segment 09e8, and asks DOS to resize the program
memory block:
AH=4Ah, BX=83d6h, DX=1943h, INT 21h
Then it runs a small machine/environment check sequence. One check uses
smsw ax; if protected/v86 mode appears to be active, it prints a warning and
lets the user abort. It sets GUS defaults in the player segment:
GS = 3fe6
GS:[0f88] = 0036h
GS:[0f70] = 0220h
The ULTRASND= parser is at expanded+0x2a17a. On success it prints the GUS
port message and later calls the resident player code through far calls in
segment 3fe6, for example 3fe6:2360, 3fe6:2383, 3fe6:24de,
3fe6:2258, 3fe6:22fe, and 3fe6:24d1.
Before the scene chain starts, it blanks the VGA framebuffer, starts the music path, and masks the interrupt controllers:
save PIC masks from ports a1h and 21h
out 21h, feh
out a1h, ffh
The fixed top-level scene order is:
expanded+0x2ac89 first logo / wave / quad transition
expanded+0x2b13d point and line table scene
expanded+0x2f490 Hellraiser cube / raster section
expanded+0x2b43f plane-separated texture columns
expanded+0x2e053 rotozoom / texture slab and wave lines
expanded+0x3186f 3D object / vector scene
expanded+0x2f617 timer-driven textured polygon scene
expanded+0x2e626 line wipe plus second rotozoom phase
expanded+0x2eaec vertical wave/slice scene
expanded+0x2f15c final text and fade
After the last scene it restores the saved PIC masks, stops/restores the GUS player, switches to BIOS text mode 3, prints the exit text, and terminates.
Shared VGA Helpers
Several shared routines are reached through wrapped 16-bit calls. The useful physical locations are:
expanded+0x3210f fill A000 with ECX dwords of EAX, starting at DI
expanded+0x32124 set CRTC display start from BX via registers 0Ch/0Dh
expanded+0x32136 set sequencer map mask plus graphics-controller state
expanded+0x3215a select VGA sequencer map-mask register
expanded+0x32165 clipped Bresenham line draw in planar memory
expanded+0x322dc draw four connected edges from points at 4e2a..4e38
expanded+0x3234f set one DAC color from stack arguments
expanded+0x32368 wait for vertical retrace start
expanded+0x3237a wait for display-enable edge using 3DAh bit 0
expanded+0x3238c BIOS mode 13h plus unchained/tweaked VGA setup
expanded+0x3240b blank all 256 DAC entries
expanded+0x32124 is the page flip. It writes high and low bytes of the start
address to CRTC registers 0Ch and 0Dh. The scenes flip by toggling offsets such
as 0x4b00, 0x2580, or 0x3e80, depending on the mode and buffer height.
The clipped line drawer at expanded+0x32165 is a normal integer Bresenham,
but adapted for unchained VGA:
- Reject the segment if both endpoints are outside the clip rectangle.
- Compute signed
dx,dy, major/minor axis, step signs, and error terms. - Choose ES as
a000h + (page_offset >> 4). - For each point, set the sequencer map mask from
x & 3. - Store one byte at
y * 0x50 + (x >> 2). - Advance the major axis and conditionally step the minor axis from the Bresenham error accumulator.
The polygon/span helpers are more interesting because they exploit VGA write masks:
expanded+0x31f7acomputes scanline edge intersections into0x39b0.expanded+0x31b36fills 1-bit/8-pixel spans using the graphics controller bit mask. It handles first and last partial bytes separately, then usesstosb,stosw, andrep stosdfor the aligned middle.expanded+0x31c36fills 4-plane spans with sequencer map masks. It converts x coordinates tox >> 2, uses first/last plane masks, sets the map mask through port3c5h, and bulk-fills the middle with all planes enabled.expanded+0x31d22is the simpler chunky byte span writer used when ES points at a linear work buffer instead of A000 planar memory.
This common raster layer explains why many visual parts look different but
share the same state fields: 39a0..39a6 are clip bounds, 4e04/4e26 describe
scanline start/count, 4e44 is page offset, 4e47 is draw color, and
4e2a.. holds projected points.
Opening Scene: Logo, Wave Columns, Rotating Quad
The first scene starts at expanded+0x2ac89. It sets DS to 09e8, initializes
clip bounds, enters BIOS mode 13h, applies the tweaked VGA setup, clears A000,
and blanks the DAC.
The early loop is a timed reveal:
- Wait for retrace via port
3dah. - Increment a scene counter at
ds:0730. - Call
expanded+0x2ac1cuntil the counter reaches0x20. - Call
expanded+0x2aba6for the main logo/reveal operation. - Upload a palette table from
ds:0430.
The first genuinely hot renderer is expanded+0x2a773. It is a column wave or
tunnel-like copier:
DS = 09e8
ES = a3ea
GS = 6097
phase = ds:0840
source table = gs:[06ca + index]
destination = es:[di + row offsets]
The core math uses a sine/motion table at ds:0740, multiplies by 0xc8, and
uses shrd ...,9 to keep a fixed-point scale. The inner loop advances a
fixed-point source coordinate, takes the high byte as an index, clamps it
against a rough -100..200 range, and writes word pairs into a work segment.
It is unrolled enough that each step writes two adjacent pixels while the
source index slides through the table.
After that, the scene writes a small bordered/wipe rectangle directly in A000.
It walks di by one 320-byte scanline and writes paired words around
di+0x3b75, producing a rectangular transition without invoking the general
polygon engine.
The last part is a four-point rotating projection:
expanded+0x2adc7reads sine/cosine values from the current phaseds:0860.- Four points from
ds:084eare rotated with signedimuland scaled usingshrd ...,9. - Projection divides by a changing depth value at
ds:0866. - Screen center is approximately
(0xb5, 0x61). - Projected pairs are written to
ds:4e2a.
expanded+0x2ae8a clears the current page, calls the projector, draws through
the shared polygon/line path, flips with expanded+0x32124, and toggles the
page offset with ds:4e44 ^= 0x3e80.
Point And Line Table Scene
The second scene starts at expanded+0x2b13d. It uses the attribute controller
and clears the full planar framebuffer. Before drawing, it builds lookup tables:
expanded+0x2afc8fills a mirrored quadratic table at0x0bfefromcx * cx >> 6.expanded+0x2aff3derives scaled tables at0x0a30and0x0ad0.
The generator at expanded+0x2b034 writes 0x6f x/y pairs to 0x39b0. It
uses several phase counters (0xbe8, 0xbea, 0xbec, 0xbee) plus an
amplitude at 0xbf0. The generated point strip is handed to the common
polygon/line machinery by setting fields such as 4e04, 4e26, and 4e47,
then calling the scanline/draw helper.
Conceptually this scene is not a texture effect. It is a table-driven vector oscillator: compute many y/x positions from phase-shifted tables, draw connected spans/lines, flip page, advance phases, repeat.
Hellraiser Cube And Raster Timing
The Hellraiser cube scene begins at expanded+0x2f490. This is the one part
the info text calls out as raster-interrupt-related. There are two different
timing mechanisms:
- A polling raster routine used in the cube frames.
- A later PIT IRQ0 hook used by the following scene.
The cube's per-frame raster routine is expanded+0x2f3fa. It is synchronized
by polling VGA status, not by a permanent ISR:
DS = ES = 3f56
for cx = 0..0x15d:
wait for 3DAh bit 0 active
value = table[si++]
wait for 3DAh bit 0 inactive
out 3D4h, AX where AL=13h and AH=value
CRTC register 13h is the logical screen offset/pitch register. Changing it per scanline bends the displayed memory layout. The visual result is the "Hellraiser" deformation: the cube/table data is ordinary memory, but the VGA scanout is being steered line by line.
The scene masks interrupts around the timed section with cli/sti, calls the
GUS player periodically, and decrements a frame countdown. It is highly
ET4000-era VGA friendly: tight polling of 3dah and immediate CRTC writes
assume a responsive VGA implementation.
The separate timer hook is installed by expanded+0x2f5b7. Because the active
CS base is expanded+0x28a50, the installed vector cs:6b50 physically lands
at expanded+0x2f5a0. The ISR body is tiny:
push ax
out 20h, 20h ; EOI to master PIC
inc word ptr cs:6b65 ; tick accumulator
call 3fe6:1c07 ; music/update callback
pop ax
iret
The hook programs PIT channel 0 mode 3 with divisor 0x4dae, saves the old
INT 08h vector at cs:6b61, and restores it in expanded+0x2f5e1.
Plane-Separated Texture Columns
The fourth scene starts at expanded+0x2b43f. Its hot loop at
expanded+0x2b582..0x2b77e is a vertical column renderer tuned for planar VGA.
The outer loop selects a VGA plane:
out 3c4h, index 2
out 3c5h, plane_mask
Then it computes di = page_offset + 0x0976 and walks columns. The source
coordinate is a combination of:
- phase words at
ds:00e0andds:00e2, - wave tables at
ds:1160andds:1260, - an offset table addressed through FS,
- texture bytes addressed through GS.
The inner column is unrolled as repeated source samples and vertical stores:
sample = gs:[computed_texture_index]
es:[di + 000h] = sample
es:[di + 050h] = sample
es:[di + 0a0h] = sample
...
es:[di + 5a0h] = sample
The pitch is 0x50 bytes because unchained 320-wide planar VGA stores one
byte per four horizontal pixels per plane. Rendering one plane at a time lets
the code use straight byte stores after the map mask is selected. The cost is
four passes, but the inner loop stays branch-light and aligned to the memory
layout.
Rotozoom And Wave-Line Scene
expanded+0x2e053 is a larger scene built around the texture mapper at
expanded+0x2e3d8. It prepares coordinates, palettes, and line tables, then
uses a two-pass planar mapper.
The mapper's setup:
- DS is the main state segment
09e8. - GS is a texture segment around
0x0ecf. - ES is computed as
a000h + (page_offset >> 4) + row_page. - Sine/angle values come from tables around
1e4c. - Fixed-point increments are placed in
2250/2252. - Fixed-point bases are placed in
224c/224e.
The core texture sample uses high bytes of two fixed-point accumulators:
bl = high(u)
bh = high(v)
pixel = gs:[bx]
Then it writes two pixels as a word. It first draws planes 0/1 with map mask
0302h, then planes 2/3 with map mask 0c02h. The row loop is unrolled four
word writes at a time and uses a logical pitch of 0x50, matching the same
Mode X memory geometry as the column renderer.
The palette side is also active:
expanded+0x2e55fbuilds palette tables at0x2262.expanded+0x2e592copies 0x180 palette bytes into a working table.expanded+0x2e5a6clamps palette values againstds:225a.expanded+0x2e5c4uploads the DAC table and decrements the clamp.
The companion line generator is expanded+0x2e5e0. For nine phase offsets it
fills 100 words at 0x39b0 from a base line table and a wave table at
0x1a40, then calls the shared scanline/line function. This is why the scene
combines a texture-slab look with overlaid moving line/wave geometry.
3D Object Scene
The call printed as 0x2186f is physically expanded+0x3186f. It is a compact
3D object/vector part using the same shared polygon layer.
Setup:
- DS is
09e8. - The callback pointer at
4e42is set to a fill routine. 4e28 = 4, so four vertices are expected for the primary polygon.4e47 = 0x0f, the bright draw color.- Page offset toggles with
4e44 ^= 0x4b00.
The object transform in this region uses a 3x3 fixed-point matrix. Routines
around expanded+0x307fe..0x30aa6 load sine values through GS, build matrix
terms with signed 32-bit multiplies and sar 9/sar 18 scaling, transform
eight 3D points, and project them with:
screen_x = center_x + ((x << 8) / z)
screen_y = center_y - ((y << 8) / z)
Projected points are stored around 0x037e/0x0380. The renderer then assembles
quads from an index table, determines face orientation with a 2D cross product,
selects an FS texture/pattern segment for visible faces, and draws through the
span/line helpers.
There is also a self-modifying patch near the end of the scene: it writes NOPs over bytes around the current code segment before switching to another VGA setup. In context it looks like a timed transition patch rather than an obfuscation layer.
Timer-Driven Textured Polygon Scene
expanded+0x2f617 uses the PIT hook described earlier. The main loop drains
the accumulated timer count:
xchg cx, cs:6b65
while cx != 0:
call expanded+0x2f86f ; advance three phase counters
ds:03e7++
if ds:03e7 >= 0x03c0:
ds:00ae += 2
It then updates the 3D transform, assembles six quads from projected points,
rejects backfacing polygons using the signed cross product at
expanded+0x2f798, updates a 0x48-byte DAC palette from face/brightness data,
and calls the texture mapper at expanded+0x2f893.
expanded+0x2f893 is one of the classic inner loops in this intro. It computes
vertical edge interpolation between polygon endpoints, derives affine texture
increments, selects the VGA plane from x & 3, and writes a heavily unrolled
column:
advance u/v fixed point
texture_byte = fs:[texture_index]
es:[di + 0000h] = texture_byte
es:[di + 0050h] = texture_byte
es:[di + 00a0h] = texture_byte
...
es:[di + 3e30h] = texture_byte
The repeated +0x50 stride is again the 320-pixel planar pitch. The code uses
32-bit registers in real mode for affine accumulators, rotates words to expose
fraction/integer halves, and relies on unrolled stores rather than a tiny inner
loop. The result is a fast planar textured face renderer that spends most of
its cycles doing predictable adds, ADCs, one texture read, and one byte write.
The auxiliary motion routines at expanded+0x30511, 0x30591, 0x30611, and
0x306aa interpolate endpoints and table indices. They reset counters, compute
fixed-point deltas with signed division, and then let the frame loop add those
deltas until the next segment is reached.
Line Wipe And Second Rotozoom Phase
expanded+0x2e626 first clears/sets up the screen, waits about 60 retraces,
then uses the line engine to draw a simple triangular/line structure. It copies
the generated points through buffers at 0x1950 and 0x1860, then drives two
line waves using expanded+0x2e5e0.
The loop has a wipe behavior:
ds:1b46grows and is multiplied by0x50to become a screen byte offset.- A black dword fill clears a page slice with
expanded+0x3210f. - Two wave-line passes are drawn with different bases.
- The page is shown through
expanded+0x32124. - The visible page toggles with
4e44 ^= 0x4b00.
After the wipe it switches back into the texture/palette machinery from the
earlier rotozoom scene, reusing 0x2e55f, 0x2e592, 0x2e5a6, 0x2e5c4,
0x2e360, and 0x2e3d8. This part is less a separate engine and more a second
scripted pass through the same texture slab core with new palette timing.
Vertical Wave/Slice Scene
expanded+0x2eaec starts by setting a mode 0Dh-derived planar setup at
expanded+0x2e874, copies a 0x640-byte source block from segment 3a42 to
3aa6, and builds two scaling tables:
expanded+0x2e949creates a 0x80-entry table at0x2b7c.- The same routine creates a 0x100-entry table at
0x297c.
The per-frame renderer is expanded+0x2e991. It fills 0xf0 x-pairs in
0x39b0. Each pair is the sum of two table-driven waves, shifted down by ten
bits, biased by the current plane offset, and then separated into left and
right x coordinates. After all scanlines are prepared it calls the planar span
fill helper with color/state 4e46/4e47 = 2.
There is a small raster color trick at the end of the draw call:
wait for 3DAh bit 0 edge
write attribute-controller index 33h
write low three bits of the current phase
That means part of the movement is not in framebuffer memory at all; the attribute controller is being nudged in sync with display timing, changing how the already-written pixels are interpreted.
The palette transition at expanded+0x2ea73 blends 12 DAC bytes between two
small palettes. It uses old * (256-t) >> 8 plus new * t >> 8, increments
the blend counter to 0x100, and later advances a separate fade counter once
the scene reaches its exit threshold.
Final Scene And Text Fade
The final sequence starts at expanded+0x2f15c. It sets up text/glyph state,
calls expanded+0x2ecde to build a 0x40-entry sine-derived table, and enters a
page-flipped loop that updates music, draws, flips, and polls keyboard port
60h for Escape.
The bitmapped text renderer is expanded+0x2eca0. It is an 8x16 glyph blitter
using the VGA graphics-controller bit mask:
- Select the target VGA plane/mode through
expanded+0x32136. - Select graphics-controller register 8, the bit mask.
- Load the glyph row from the font pointer at
ds:2ef8. - Shift it by the x bit offset.
- Output the low and high mask bytes to
3cfh. - Touch
es:[di+1]andes:[di]withxchgto trigger masked writes. - Advance by
0x28bytes per glyph row.
The main reveal iterates 0x1e glyph positions from 0x31e0, computes byte and
bit offsets, plots the glyph on both visible and hidden pages, and then flips.
Fade-out is done from the real DAC:
- Read the current palette through
3c7h/3c9hinto0x3102. - Duplicate it to
0x3132. - For each frame, scale every original component by
bp / 64usingmul bpandshrd ax,dx,6. - Upload the scaled palette and redraw/flip.
- Decrement
bpuntil black.
This is a good example of the intro's style: the visual state is often a mix of framebuffer data, VGA plane state, DAC state, and timing-side effects rather than one clean chunky framebuffer.
Technical Character
Fyvush is a 64K intro built around small, specialized real-mode kernels:
- RNC compression hides a roughly 529 KB expanded image inside a 49 KB MZ file.
- The startup path is direct DOS, VGA, PIC, PIT, and GUS manipulation.
- Most drawing is tuned for unchained planar VGA, not for mode 13h chunky convenience.
- Page flipping is CRTC start-address flipping, not memory copying.
- Several effects use the same span/line layer with different generated point lists.
- The expensive visuals are dominated by unrolled texture columns, affine fixed-point stepping, map-mask writes, and scanline-synchronized VGA register changes.
- The Hellraiser cube is the standout hardware trick: it bends scanout by updating CRTC offset/pitch per scanline.
The code is dense but internally consistent. Once the RNC decoys and 16-bit call-wrap addresses are accounted for, the intro resolves into a handful of well-reused primitives: page flip, retrace wait, palette upload/blend, line scan conversion, planar span fill, planar texture columns, and table-driven motion.