Start page: ARABART home

DOS for modern developers

Yasser wrote his programs for a PC with no browser, no app store and no internet. If you build for the web today, you already know most of the ideas. In 1993 they had other names.

Seven ideas. Each one starts from something you use every day, and each one is a toy you can drive.

Memory is one big array

Picture the whole PC as one JavaScript array of 1,048,576 bytes. Your program, DOS, the screen and the BIOS all live in it, and any of them can write anywhere.

There are no tabs and no sandbox. A browser keeps every tab in its own box. A DOS PC runs one program at a time, in real mode, and lets it touch everything. Drag the white pointer from 0 to 1 MB, then press Write. No error, no permission prompt: one changed byte.

That freedom is what made his programs fast. ARABART paints letters by writing straight into the screen’s memory, the video memory, which starts 640 KB into that array. KVC rewrites the table DOS uses to route its calls, to cut a virus off. The rule was simple: know exactly what lives where.

MEMORY one array, 1,048,576 bytes
WATCH

memory[0xA1F4C]byte 663,372 of 1,048,576

The graphics screen

Not RAM at all: the graphics card answers here. Write a byte and pixels change on the monitor, at once.

In the 640 × 480 mode: pixels 96 to 103 of row 100.

ARABART, PESM, The Brave Sun and both editors draw here.

Then and now
1991 to 19932026Why it is the same idea
One array of 1 MB, shared by everyone Each app gets its own private memory Today the operating system keeps programs apart.
Write to A0000h and the screen changes Write into a canvas’s ImageData Pixels are numbers in an array that becomes the picture.
640 KB for DOS and your program The memory one browser tab may use IBM kept the top 384 KB for the screen and the chips.

Two numbers make one address

The processor’s numbers stop at 65,535. Memory goes past a million. So every address is two numbers: a base, and an offset from that base.

Think of buffer.subarray(base * 16), then an index into that slice. The base is the segment. The processor multiplies it by 16, which in hex just adds a zero at the end, and adds the offset.

So A000h becomes A0000h, plus 1F4Ch is A1F4Ch: the byte that holds pixel (100, 100) on his 640 × 480 screen. Many pairs reach the same byte, the way two relative paths can name one file. Drag both sliders and watch the address form.

ADDRESS segment × 16 + offset

A000:1F4C is address A1F4Ch, in The graphics screen.

memory[(0xA000 << 4) + 0x1F4C]  // = memory[0xA1F4C]
WHERE IT LANDS

The graphics screen

The byte for pixel (100, 100) in the 640 × 480 mode: row 100 × 80 bytes, plus 100 ÷ 8.

For engineers: near and far pointers in his programs

One segment is a 64 KB window. A near pointer is 2 bytes, only the offset: an index inside one array. A far pointer is 4 bytes, segment and offset: a reference to any array plus an index into it. Borland C chose the defaults for a whole program with its memory model.

  • Tiny, one segment for everything: the Port Said Computer Systems boot screen.
  • Small, one segment of code and one of data: WINKEY.
  • Compact, near code and far data: KVC.
  • Large, far code and far data: The Brave Sun, Digi-Test and both editors. EDITOR.EXE keeps up to 601 lines of 204 bytes, 122,604 bytes of text, more than one window can hold.

The step from segment to segment is 16 bytes, a paragraph. So B800:0000 and B000:8000 are the same byte, B8000h: the first letter of the color text screen.

Event handlers, built into the chip

You know addEventListener. The PC has the same idea in hardware. A table of 256 handlers sits at the very start of memory, with a number for every kind of event.

When a key goes down, the keyboard chip fires interrupt 9. The processor drops what it is doing, looks up entry 9 in the interrupt table, runs the code it finds there, and comes back. Your program never notices.

Programs use the same table to ask for help. Put a function number in a register and run int 21h: a system call into DOS, like a fetch() to the operating system. INT 10h asks the video BIOS, INT 16h the keyboard. Press a button and follow the yellow chip.

EVENTS

Something happens

Your program asks

Or type on your keyboard while a button here has focus.

INTERRUPT TABLE at address 0
EntryWhereFor
08h0000:0020clock tick
09h0000:0024key press
10h0000:0040video service
16h0000:0058keyboard service
21h0000:0084DOS

Entry n sits at n × 4. Five of the 256 entries.

HANDLERS
  • BIOS clock codeon the BIOS chip
  • BIOS keyboard codeon the BIOS chip
  • Video BIOSon the graphics card
  • DOSin memory, loaded at boot
Keyboard buffer
empty
Clock ticks
0
Screen
 
Pixels drawn
0
HOW IT WENT
  1. You press Y. The keyboard chip raises its hand: interrupt 9.
  2. The processor pauses your program and notes where it was, like pushing a frame on the call stack.
  3. It reads entry 09h of the table, at 0000:0024, and jumps to the address it finds there.
  4. The BIOS keyboard code reads the key and keeps Y in the keyboard buffer.
  5. It returns, and your program carries on exactly where it stopped. It never noticed.
THE SAME IDEA IN JAVASCRIPT
  1. const table = new Array(256); // at address 0
  2. table[0x08] = biosClock; // filled in at power-on
  3. table[0x09] = biosKeyboard;
  4. table[0x10] = videoBios;
  5. table[0x21] = dos; // filled in when DOS starts
  6. // Events: the hardware calls the table for you
  7. clock.on('tick', () => table[0x08]());
  8. keyboard.on('key', k => table[0x09](k));
  9. // Requests: your program calls it, a system call
  10. table[0x21]({ ah: 0x09, text: 'Hello$' });
  11. table[0x10]({ ah: 0x0C, x, y, color: 14 });
Then and now
1991 to 19932026Why it is the same idea
Interrupt 9, a key press A keydown event The hardware calls your handler. Nobody has to keep checking.
int 21h with a function number A system call One numbered door into the operating system.
The interrupt table A map from event names to handlers Change an entry, and a different function answers.
For engineers: what int n does, step by step

int n pushes three values on the stack: the flags, CS and IP, which together say where your program was. Then it loads CS:IP from the 4 bytes at 0000:(n × 4), offset first, then segment. iret pops all three back. It is a function call through a pointer table, with the return address saved for you.

DOS function 35h reads an entry and function 25h writes one, so a program can hook a service politely. To stay in memory after it ends, a program calls DOS function 31h or the older INT 27h. ARABART uses INT 27h.

A program that ends, and stays

DOS runs one program at a time. So how do you run a background service? You write a TSR: a program that ends, but stays in memory.

A TSR starts, writes its own address into the interrupt table, then ends. DOS keeps its code in memory. From then on, every program that calls that interrupt reaches the TSR first. Today we would call it a daemon, or middleware in front of an API: it can answer, or call next().

His ARABART is one. Run it once and 30,110 bytes stay in memory, five fonts included, in front of the video service. A call for its own function, number 36h, gets Arabic. Any other call passes on, untouched, in three instructions. Run it below, run DEMO.EXE, then try to run ARABART twice.

MEMORY

ARABART stays in memory after it ends, so every program after it can call it.

C:\ARABART

C:\ARABART>ARABART

ARABIC-ART Ver 4.15 Copyright (c)1993 YASSER MOHAMED ALI.

C:\ARABART>DEMO

DEMO.EXE writes Arabic through ARABART, so run ARABART first.

INT 10h the video service
  1. A callint 10h
  2. ARABARTis it 36h?
  3. Video BIOSthe card’s own code
THE SAME IDEA IN JAVASCRIPT
  1. // ARABART, written as middleware in front of the video service
  2. videoService.use((call, next) => {
  3. if (call.ah !== 0x36) return next(call); // not mine: pass it on
  4. drawArabic(call); // 36h: join and draw
  5. });
  6. // It runs once. The program ends; the handler stays.

Before it loads, ARABART reads the video service’s entry and looks for its own name tag, DALYASSER. If the tag is already there, it prints “This utility is already active in your RAM.” and stops. One copy is enough. In modern terms, that is a single-instance lock.

Sixteen colors, four layers

On a canvas, a pixel is four bytes: red, green, blue and alpha. On his VGA screen, a pixel is four bits, and each bit lives in a different layer.

The 640 × 480 mode shows 307,200 pixels in 16 colors. The card keeps them as four bit planes of 38,400 bytes: blue, green, red, and one that makes a color bright. All four answer at the same address, A0000h, like four layers of one image in a design tool. Stack the four bits of a pixel and you get its color number, from 0 to 15.

SCREEN 640 × 480, 16 colors

Tap a pixel of the word to pick its color.

ONE COLOR, FOUR BITS
I
R
G
B

Decimal 14 Hex Eh

14 yellow

  1. 00000, black
  2. 00011, blue
  3. 00102, green
  4. 00113, cyan
  5. 01004, red
  6. 01015, magenta
  7. 01106, brown
  8. 01117, light gray
  9. 10008, dark gray
  10. 10019, light blue
  11. 101010, light green
  12. 101111, light cyan
  13. 110012, light red
  14. 110113, pink
  15. 111014, yellow
  16. 111115, white

The word is drawn by his own ARABART engine, ported pixel for pixel. Flip a color’s bits and watch which planes carry it. Switch a plane off and every color on the screen loses that bit at once.

Planes are also why his drawing code is fast. A program can tell the card one color for all four planes, then paint up to eight pixels with a single write. ARABART does exactly that, with the card’s Set/Reset register. See it on the ARABART page.

An EXE is a manifest, the code, and a list of fixes

A .COM file is raw code that DOS copies into memory and runs, like a plain script. An .EXE starts with a header: a small manifest, much like package.json.

The header says where the program starts, where its stack goes and how much memory it wants. Then it lists fix-ups. A program cannot know where DOS will put it, yet some instructions need real addresses. So the linker writes down every spot that holds one, and DOS adds the real load address to each. A browser does the same when it resolves a relative URL against the page.

Here is his antivirus, KVC.EXE, unpacked. Seven of its instructions hold the same number, 0378h: where its data starts, counted from its own first byte. Step through the load, then drag the load address and watch all seven follow. Bigger programs have more to fix: The Brave Sun lists 1,746. And every Windows .exe you download today still begins with the same two letters, MZ.

KVC.EXE the manifest
{
  "magic":  "MZ",         // I am an EXE
  "fixups": 7,            // listed after this header
  "start":  "0000:0000",  // counted from my load address
  "stack":  "0406:0100",  // counted from it too
  "memory": "all of it"   // as much as DOS can give
}
MEMORY where DOS puts it
  1. 0000mov dx, 0378h 1378h
  2. 005Amov di, 0378h 1378h
  3. 009Fmov ax, 0378h 1378h
  4. 00C9mov ax, 0378h 1378h
  5. 0103mov ax, 0378h 1378h
  6. 0D31mov ax, 0378h 1378h
  7. 0FD3mov ax, 0378h 1378h

Starts at 1000:0000, stack at 1406:0100

HELP

Last, DOS points the stack where the header said and jumps to the start, counted from the same load address. KVC runs, sure of where its data is.

THE SAME IDEA IN JAVASCRIPT
// Only the loader knows the start, so it patches each listed spot:
for (const at of header.fixups)
  image.setUint16(at, image.getUint16(at, true) + loadSegment, true);
// Today the same job: a URL resolved against where the code loaded
new URL('./data.json', import.meta.url);

Zip for programs, unzipped in memory

Every one of his program files is packed with DIET 1.00: compressed, with a small unpacker at the front. Run it, and the unpacker rebuilds the real program in memory, then jumps to its first instruction.

Think of a self-extracting zip that never touches the disk, or gzip on a JavaScript bundle where the code unzips itself. The method is the classic LZ77 kind: store a byte as it is, or say “go back this far and copy that many”. Every repeat becomes one small instruction.

Step through it on the Basmallah, stored the way his program PESM stores it: in the keys of an Arabic keyboard, so fsl is بسم. His engine draws the Arabic as each key comes back. On floppy disks of 360 KB to 1.44 MB, every byte saved was room for more.

PACKED what the file stores
  1. fcopy as is
  2. scopy as is
  3. lcopy as is
  4. ␣copy as is
  5. hcopy as is
  6. gcopy as is
  7. gcopy as is
  8. icopy as is
  9. ←5 ×3go back 5, copy 3
  10. vcopy as is
  11. pcopy as is
  12. lcopy as is
  13. kcopy as is
  14. ←7 ×5go back 7, copy 5
  15. dcopy as is
  16. lcopy as is

16 instructions: 14 letters stored as they are, and 2 copies of letters already rebuilt.

UNPACKED rebuilt in memory
  1. f
  2. s
  3. l
  4. ␣
  5. h
  6. g
  7. g
  8. i
  9. ␣
  10. h
  11. g
  12. v
  13. p
  14. l
  15. k
  16. ␣
  17. h
  18. g
  19. v
  20. p
  21. d
  22. l

All 22 keys are back: بسم الله الرحمن الرحيم. Notice the copy that rebuilt hgvp: الرح, shared by الرحمن and الرحيم.

THE UNPACKER, IN JAVASCRIPT
for (const t of packed) {
  if (t.literal) out.push(t.byte);
  else for (let i = 0; i < t.len; i++)
    out.push(out[out.length - t.dist]);
}
jumpTo(out);  // then run the program it rebuilt
HIS 17 FILES packed, then unpacked
  • MAIN.EXEDigi-Test 79,358→ 202,720
  • SUN.EXEThe Brave Sun 47,709→ 93,792
  • EDIT.EXEText Editor: Sample GUI 36,526→ 79,200
  • NAMES.COMKemyam: Group Names 13,090→ 59,402
  • PORTSAID.EXEPort Said Computer Systems 18,715→ 47,232
  • EDITOR.EXEText Editor: Demo 20,565→ 37,392
  • ARABART.EXEARABART 11,183→ 30,144
  • DEMO.EXEARABART: Demo 18,129→ 28,128
  • EDITOR.EXEARABART: Editor 17,492→ 27,088
  • DEMO.COMArabic Commercial 4,864→ 25,899
  • ARAB-ART.EXEThe Brave Sun 2,061→ 22,240
  • KVC.EXEKVC: Kemyam Virus Capture 11,000→ 16,928
  • FORM_X.EXEFormula Solver 3,546→ 9,744
  • WINKEY.EXEWINKEY 4,220→ 7,056
  • PESM.EXEArabic Basmallah 3,739→ 6,144
  • SCREEN.COMPort Said IT Department 1,672→ 3,504
  • TAHER.COMTAHER 2,050→ 2,346

All together: 295,919 bytes on disk become 698,959 in memory.

This is also why the site had to unpack each program before it could read it. Every Byte shows both layers of every file: the packed bytes on the disk, and the program DIET rebuilds in memory.

See every idea at work

Each idea on this page runs inside one of his programs, and every program runs in your browser.