Boot CD and retro play in one tweet
A few years ago, I created a boot floppy and a retro game that fit into one tweet. Since then, Twitter has doubled the length of tweets, so I decided to create a bootable CD. It runs on a slightly improved version of tron.
perl -E'say"A"x46422,"BDRDAwMQFFTCBUT1JJVE8gU1BFQ0lGSUNBVElPTg","A"x54,"Ew","A"x2634,"/0NEMDAxAQ","A"x2721,"BAAAAYQ","A"x30,"SVVVqogAAAAAAAEAF","A"x2676,"LMBaACgB76gfbgTAM0Qv8D4uYAI86qqgcc+AXP45GA8SHIRPFB3DTeYSEhyBSwCa8CwicMB3rSG/sHNFbRFJjAke9rrwQ","A"x2638'|base64 -D>cd.iso
Tweet code creates a bootable disk image-ROM drive the CD:
cd.iso. You can download the code in qemu or your favorite virtual machine - and play with the arrow keys. You can even burn an iso to a CD-R and boot onto a real computer.
To manually create a CD image, you first need to get a basic understanding of ISO 9660 . Unfortunately, ISO documents are usually expensive. However, ISO 9660 is the same as ECMA 119 , so the specifications can be read for free.
ISO 9660 has many extensions such as UDF, El Torito, RockRidge, Joliet, etc. For boot images, only El Torito is important to us . The El Torito specification, in my opinion, is poorly written. There are errors (for example, the last line in table 7), it is easy to forget that the values are hexadecimal (no prefix is specified
0x), numbers are not sorted in an intuitive order, etc. Fortunately, the document is quite small.
To create a boot disk, we start by writing down 17 empty sectors, followed by a Volume Descriptor Set. Each sector is 2048 bytes.
Note. The ISO-9660 specification says that the Volume Descriptor Set begins with sector 16. El Torito specification requires the start of a boot record in sector 17. Technically, you should put a dummy volume handle in sector 16, but everything works fine without it.
We write the first volume descriptor:
0x00// Type (0 = boot record)'CD001'// Identifier0x01// Version'EL TORITO SPECIFICATION'// Boot System Identifier9 x 0x00// Padding32 x 0x00// Unused0x130x000x000x00// Boot Catalog address (in absolute sectors)1973 x 0x00// Unused
The following sector hosts the Volume Descriptor Set Terminator:
0xff// Type (255 = terminator)'CD001'// Identifier0x01// Version2041 x 0x00// Unused
The volume descriptors are followed by the Boot Catalog. El Torito supports different emulation modes. A CD-ROM can emulate a boot diskette, boot HDD, etc. I did not install the emulation, that is, the BIOS will load a certain number of sectors - and our bootloader will take it.
The checksum is calculated so that all 16-bit values in the record are summed to 0 (mod 65536).
The first entry in the boot directory (check entry):
0x01// Header ID0x00// Platform ID (0 = Intel x86)0x000x00// Reserved'a'// ID string23 x 0x00// Padding cksum cksum // Checksum (2 bytes)0x550xaa// Key bytes
The second entry (default):
0x88 // Boot Indicator (0x88 = bootable) 0x00 // Boot Media Type (0 = no emulation) 0x00 0x00 // Loadsegment0x00 // SystemType0x00 // Unused0x010x00 // Numberof sectors toload0x140x000x000x00 // Virtual disk address (inabsolute sectors) 20 x 0x00 // Unused
Then zeros to the end of the sector:
1984 x 0x00// Unused
The next sector is our bootloader and retro game:
; to compile: ; nasm bootloader.asm -o bootloader.img [bits 16] ; Pragma, tells the assembler that we ; are in16bit mode (which is the state ; of x86 when booting from a floppy). [org 0x7C00] ; Pragma, tell the assembler where the ; code will be loaded. mov bl, 1 ; Starting direction for the worm. push 0xa000 ; Load address of VRAM into es. pop es restart_game: mov si, 320*100+160 ; worm's starting position, center of ; screen ; Set video mode. Mode 13h is VGA (1 byte per pixel with the actual ; color stored in a palette), 320x200 total size. mov ax, 0x0013 int 0x10 ; Draw borders. We assume the default palette will work for us. ; We also assume that starting at the bottom and drawing 2176 pixels ; wraps around and ends up drawing the top + bottom borders. mov di, 320*199 mov cx, 2176 rep draw_loop: stosb ; draw right border stosb ; draw left border add di, 318 jnc draw_loop ; notice the jump in the middle of the ; rep stosb instruction. game_loop: ; We read the keyboard input from port0x60. This also reads bytes from ; the mouse, so we need to only handle [up (0x48), left (0x4b), ; right (0x4d), down (0x50)] in al, 0x60 cmp al, 0x48 jb kb_handle_end cmp al, 0x50 ja kb_handle_end ; At the end bx contains offset displacement (+1, -1, +320, -320) ; based on pressed/released keypad key. I bet there are a few bytes ; to shave around here given the bounds check above. aaa cbw dec ax dec ax jc kb_handle sub al, 2 imul ax, ax, byte -0x50 kb_handle: mov bx, ax kb_handle_end: add si, bx ; The original code used set pallete command (10h/0bh) towaitfor ; the vertical retrace. Today's computers are however too fast, so ; we use int 15h 86h instead. This also shaves a few bytes. ; Note: you'll have to tweak cx+dx if you are running this on a virtual ; machine vs real hardware. Casual testing seems to show that virtual machines ; wait ~3-4x longer than physical hardware. mov ah, 0x86 inc cl int 0x15 ; Draw worm and check for collision with parity ; (even parity=collision). xor [es:si], ah ; Go back to the main game loop. jpo game_loop ; We hit a wall or the worm. Restart the game. jmp restart_game TIMES 2048 - ($ - $$) db 0 ; Fill the rest of the sector with0
Then I wrote a script to compile the bootloader, build the image and generate a tweet. Finally, I burned the CD and checked that everything works on real hardware .