Boot CD and retro play in one tweet

Original author: Alok Menghrajani
  • Transfer

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
          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
          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.
          ; 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.
          dec       ax
          dec       ax
          jc        kb_handle
          sub       al, 2
          imul      ax, ax, byte -0x50
          mov       bx, ax
          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 .

Also popular now: