Port Said Computer Systems
1993 · C:\PORTSAID\PORTSAID.EXE
Loading PORTSAID.EXE, 19 KB
The start-up screen on every PC that Port Said Computer Systems sold. They paid him with his first mouse.
The story
Yasser made this boot screen as a gift for Port Said Computer Systems, and every computer the company sold showed it when it started. The company thanked him with his very first mouse, a rare thing in Egypt in 1993. He built it on the picture code from his KemYam splash, carried into C.
The screen every PC showed
Every computer that Port Said Computer Systems sold greeted its owner with this screen. It is a small film, and his own code draws every frame.
The four pieces of the red PCS logo fly in from the right. The company's Arabic name, بورسعيد للحاسبات, opens like a curtain, falls and bounces into place. Then the English name appears beneath it.
He wrote it in Turbo C++, with the drawing done in inline assembly: 2,318 bytes of his code and 24,993 bytes of pictures, all in one file. Drag the bar to any of its 659 draws, or pick a part and watch it again.
Four tiles, four straight lines
The logo is four tiles, 64 by 79 pixels each. Each one flies on a straight line in 100 steps: two from the top right corner, two from below the bottom edge.
The line is plain school math. Divide the distance by the number of steps, then add that much to x and y at every step. The steps are decimals, 5.12 pixels across for each row down, so the path stays perfectly straight. It is the way a CSS transition moves an element from one point to another.
Each step, x is cut to a whole pixel and then to a whole byte, x ÷ 8, because the screen stores eight pixels in a byte. So the tiles glide down a row at a time and across in hops of eight pixels. Nothing is erased between steps. The first tile paints a red ribbon across the screen, and the tile that follows the same path rolls it back up behind it. Step through his loop below and watch the numbers.
- 0595
sub sp,24h; room for x, y, dx and dy - 05A0
sub ax,[bp+0Ah]; x1 - x0 - 05BA
fdivr qword [bp-18h]; dx = (x1 - x0) / n - 05C7
sub ax,di; y1 - y0 - 05E0
fdivr qword [bp-20h]; dy = (y1 - y0) / n - 05F4
fstp qword [bp-08h]; x = x0 - 0601
fstp qword [bp-10h]; y = y0 - 0605
xor si,si; i = 0 - 060Bstep:
fld qword [bp-08h]; x, then _ftol cuts it to a whole number - 0615
fld qword [bp-10h]; y, cut the same way - 0621
imul dx; y × 80 bytes a row - 062B
idiv bx; x ÷ 8: the byte - 062E
add dx,ax; where the tile lands - 0643
call 04FA; copy the tile, four layers - 064D
fadd qword [bp-18h]; x += dx - 0659
fadd qword [bp-20h]; y += dy - 0666
call 0311; wait - 066A
inc si; i++ - 066E
jle step; until i passes n
-
void fly(word pic, int x0, int y0, int x1, int y1, -
int w, int h, int n, int wait) { -
double dx = (double)(x1 - x0) / n; -
double dy = (double)(y1 - y0) / n; -
double x = x0, y = y0; -
for (int i = 0; i <= n; i++) { -
int at = (int)y * 80 + (int)x / 8; -
copy(pic, at, w, h);// all four layers -
x += dx; -
y += dy; -
delay(wait); -
} -
}
- i
- - the step, 0 to 100
- x
- 576.0 a decimal, in the math unit
- y
- 0.0 a decimal
- dx
- -5.12 added every step
- AX
- 0 the last whole number
Logo tile 4 flies from the top right corner, (576, 0), toward (64, 100), in 100 steps. The function is C; Turbo C++ wrote this start.
For engineers: decimals in a tiny program
The decimals are C doubles. Turbo C++ compiles each math instruction as a call to Borland's floating-point emulator, which uses the math chip when the PC has one and does the work in software when it does not, so the logo flies on every PC. The emulator needs ten addresses that DOS fills in when the program loads, a relocation table, so this tiny-model program ships as an EXE that starts like a .COM, at offset 100h.
A curtain, a drop, a bounce
The Arabic name enters in three acts. It opens at the top of the screen, falls to its place, and bounces four times, each bounce smaller than the last.
The name has settled at row 123, after bounces of 13, 6, 4 and 1 rows.
The curtain takes 80 draws, one more byte of every row each time. Even rows slide in from the left, odd rows from the right, and they meet as a whole name. It is the comb effect from his KemYam splash, now a C function.
The bounce has no physics engine. It is nine straight moves, each with numbers he chose: down to row 123, up 13 rows, down, up 6, down, up 4, down, up 1, down. Each bounce is lower and quicker than the one before, so the name slows as it settles, like an ease-out curve drawn by hand.
Pictures inside the code
There are no picture files. All six pictures sit inside the program, between its functions, and the code points at each one by its own address.
His code points at six pictures. Scan it to find them.
His code says mov ax,0A08h / xor dx,dx / mov bx,10h / div bx: divide the picture’s place by 16.
0A08h ÷ 16 = 0A0h, remainder 8. He moves his starting point in memory forward by 0A0h and keeps 8 as the step inside it. That leaves the decoder a whole 64 KB window to read the picture from.
Its first two words say 80 bytes (640 pixels) by 58 rows. 9,400 packed bytes become 18,560: four color layers of 4,640.
His code says mov ax,2F07h / xor dx,dx / mov bx,10h / div bx: divide the picture’s place by 16.
2F07h ÷ 16 = 2F0h, remainder 7. He moves his starting point in memory forward by 2F0h and keeps 7 as the step inside it. That leaves the decoder a whole 64 KB window to read the picture from.
Its first two words say 56 bytes (448 pixels) by 33 rows. 5,574 packed bytes become 7,392: four color layers of 1,848.
His code says mov ax,6375h / xor dx,dx / mov bx,10h / div bx: divide the picture’s place by 16.
6375h ÷ 16 = 637h, remainder 5. He moves his starting point in memory forward by 637h and keeps 5 as the step inside it. That leaves the decoder a whole 64 KB window to read the picture from.
Its first two words say 8 bytes (64 pixels) by 79 rows. 2,417 packed bytes become 2,528: four color layers of 632.
His code says mov ax,598Eh / xor dx,dx / mov bx,10h / div bx: divide the picture’s place by 16.
598Eh ÷ 16 = 598h, remainder 14. He moves his starting point in memory forward by 598h and keeps 14 as the step inside it. That leaves the decoder a whole 64 KB window to read the picture from.
Its first two words say 8 bytes (64 pixels) by 79 rows. 2,534 packed bytes become 2,528: four color layers of 632.
His code says mov ax,4FA7h / xor dx,dx / mov bx,10h / div bx: divide the picture’s place by 16.
4FA7h ÷ 16 = 4FAh, remainder 7. He moves his starting point in memory forward by 4FAh and keeps 7 as the step inside it. That leaves the decoder a whole 64 KB window to read the picture from.
Its first two words say 8 bytes (64 pixels) by 79 rows. 2,534 packed bytes become 2,528: four color layers of 632.
His code says mov ax,45C0h / xor dx,dx / mov bx,10h / div bx: divide the picture’s place by 16.
45C0h ÷ 16 = 45Ch, remainder 0. He moves his starting point in memory forward by 45Ch and keeps 0 as the step inside it. That leaves the decoder a whole 64 KB window to read the picture from.
Its first two words say 8 bytes (64 pixels) by 79 rows. 2,534 packed bytes become 2,528: four color layers of 632.
A PC address of the time has two parts, like a street and a house number: a segment and an offset. His code asks which street it is running on, then splits each picture's place in two. 6375h becomes the street plus 637h, and house number 5. The house number stays small, so the decoder has room to read the whole picture. This pair is a far pointer.
Each picture starts with its width and height, then his packed layers. The Arabic name packs from 18,560 bytes to 9,400. With the pictures inside, the program is one file: copy it to a new PC and nothing can go missing.
| 1991 to 1993 | 2026 | Why it is the same idea |
|---|---|---|
| Pictures stored inside the program, between its functions | Images inlined in a JavaScript bundle as data: URLs | One file to ship, and nothing to lose on the way. |
| A straight line with a decimal step added every frame | A CSS transition or a gsap.to tween with linear easing | Start, end and a number of steps; the math fills in the rest. |
| Nine moves, smaller and smaller, to make a bounce | A keyframe animation with an ease-out bounce | Smaller and smaller rebounds read as weight settling. |
Code that moved house
The heart of this boot screen came from his KemYam splash. He carried his assembly into C functions, and 153 bytes arrived exactly as he first wrote them.
Picture decoder, 22 identical bytes. The heart of his run-length decoder: read a byte, copy it, or repeat the next one.
lodsb · cmp al,37h · je · stosb · jmp · lodsb · cmp al,0 · je · xor ch,ch · mov cl,al · lodsb · rep stosb · jmp
Layer copy, 18 identical bytes. Pick a color layer through the Map Mask, copy a strip into it.
mov dx,3C4h · mov al,2 · out dx,al · inc dx · mov al,1 · out dx,al · push cx · push si · push di · rep movsb · pop di · pop si · pop cx
Refresh wait, 8 identical bytes. Wait for the screen to begin its next refresh.
in al,dx · and al,8 · je · pop dx · pop ax · loop
Key wait, 15 identical bytes. Wait for a key, then switch back to text mode.
mov ah,11h · int 16h · jz · mov ah,0 · int 16h · mov ax,3 · int 10h
Spark, line 1, 24 identical bytes. Move the cursor out of the way and start the spark along his name.
mov ah,2 · mov dx,0300h · int 10h · mov ax,0B800h · mov ds,ax · mov di,33h · mov cx,19h · push cx · mov al,[di] · mov byte [di],0Fh
Spark, line 2, 12 identical bytes. The spark on the second line, 26 letters.
mov di,0D3h · mov cx,1Ah · push cx · mov al,[di] · mov byte [di],0Fh
His signature, 54 identical bytes. His sign-off text, letter for letter.
" YASSER DOT DESIGN. $<c>1993 YASSER MOHAMED ALI$"
In the KemYam splash the decoder finds its input in fixed memory cells. Here it is a C function. Turbo C++ writes the entry and the exit, and his inline assembly reads its four arguments from the stack frame. The middle, 22 bytes, is the same loop. It is what you do today when you move tested code into a shared package with a new interface.
The layer copy, the refresh wait, the key wait and his signature with its spark came along the same way. To see how the decoder, the color layers and the spark work, step through them on the KemYam splash page.
Every Byte of PORTSAID.EXE
Each square is one byte of the program in memory, colored by what it is. Tap a part to open it in the full map, down to the single instruction.
18,715 bytes on the disk, packed by DIET
47,232 bytes in memory, 27,317 of them his own code
- Picture53%
- Borland library37%
- Code4.9%
- Data3.0%
- Start-up code1.1%
- Empty space0.6%
- Fix-up list<0.1%
- Header<0.1%
The biggest parts, his first
- Arabic name picture9,401 bytesYasser
- English name picture5,575 bytesYasser
- Logo tile 12,535 bytesYasser
- Logo tile 22,535 bytesYasser
- Logo tile 32,535 bytesYasser
- Logo tile 42,418 bytesYasser
Mapped by hand from the program’s own bytes.