ISOLINUX: support for hybrid mode (CD-ROM/USB key)
authorH. Peter Anvin <hpa@zytor.com>
Fri, 5 Sep 2008 21:49:42 +0000 (14:49 -0700)
committerH. Peter Anvin <hpa@zytor.com>
Fri, 5 Sep 2008 21:49:42 +0000 (14:49 -0700)
Still a work in progress.

Signed-off-by: H. Peter Anvin <hpa@zytor.com>
.gitignore
core/isolinux.asm
mbr/Makefile
mbr/isohdpfx.S [new file with mode: 0644]
utils/Makefile
utils/isohybrid.in [new file with mode: 0644]

index d4724d8..7657c3d 100644 (file)
@@ -34,6 +34,7 @@
 /linux/syslinux
 /linux/syslinux-nomtools
 /utils/gethostip
+/utils/isohybrid
 /utils/mkdiskimage
 /version.h
 /version.mk
index f93b24e..060a3cf 100644 (file)
@@ -118,10 +118,13 @@ ImageDwords       resd 1                  ; isolinux.bin size, dwords
 InitStack      resd 1                  ; Initial stack pointer (SS:SP)
 DiskSys                resw 1                  ; Last INT 13h call
 ImageSectors   resw 1                  ; isolinux.bin size, sectors
+GetlinsecPtr   resw 1                  ; The sector-read pointer
 DiskError      resb 1                  ; Error code for disk I/O
-DriveNumber            resb 1                  ; CD-ROM BIOS drive number
+DriveNumber    resb 1                  ; CD-ROM BIOS drive number
 ISOFlags       resb 1                  ; Flags for ISO directory search
 RetryCount      resb 1                 ; Used for disk access retries
+bsSecPerTrack  resw 1                  ; Used in hybrid mode
+bsHeads                resw 1                  ; Used in hybrid mode
 
 _spec_start    equ $
 
@@ -225,12 +228,46 @@ bi_file:  dd 0                            ; LBA of boot file
 bi_length:     dd 0xdeadbeef                   ; Length of boot file
 bi_csum:       dd 0xdeadbeef                   ; Checksum of boot file
 bi_reserved:   times 10 dd 0xdeadbeef          ; Reserved
+bi_end:
+
+               ; Custom entry point for the hybrid-mode disk.
+               ; The following values will have been pushed onto the
+               ; entry stack:
+               ;       - CBIOS Heads 
+               ;       - CBIOS Sectors
+               ;       - CBIOS flag
+               ;       - DX (including drive number)
+               ;       - DI
+               ;       - ES
+               ;       (top of stack)
+%ifndef DEBUG_MESSAGES
+_hybrid_signature:
+               dd 0x7078c0fb                   ; An arbitrary number...
+
+_start_hybrid:
+               pop ax
+               mov si,getlinsec_ebios
+               and ax,ax
+               jnz .ebios
+               mov si,getlinsec_cbios
+.ebios:
+               pop word [cs:bsSecPerTrack]
+               pop word [cs:bsHeads]
+
+               pop dx
+               pop di
+               pop es
+               jmp _start_common
+%endif
 
-_start1:       mov [cs:InitStack],sp           ; Save initial stack pointer
+_start1:
+               mov si,getlinsec_cdrom
+_start_common:
+               mov [cs:InitStack],sp   ; Save initial stack pointer
                mov [cs:InitStack+2],ss
                xor ax,ax
                mov ss,ax
-               mov sp,StackBuf                 ; Set up stack
+               mov sp,StackBuf         ; Set up stack
                push es                 ; Save initial ES:DI -> $PnP pointer
                push di
                mov ds,ax
@@ -238,8 +275,10 @@ _start1:   mov [cs:InitStack],sp           ; Save initial stack pointer
                mov fs,ax
                mov gs,ax
                sti
-
                cld
+
+               mov [GetlinsecPtr],si
+
                ; Show signs of life
                mov si,syslinux_banner
                call writestr_early
@@ -253,7 +292,7 @@ _start1:    mov [cs:InitStack],sp           ; Save initial stack pointer
                ; 64-2048
                ;
 initial_csum:  xor edi,edi
-               mov si,_start1
+               mov si,bi_end
                mov cx,(SECTOR_SIZE-64) >> 2
 .loop:         lodsd
                add edi,eax
@@ -285,6 +324,10 @@ initial_csum:      xor edi,edi
                ; Other nonzero fields
                inc word [dsp_sectors]
 
+               ; Are we just pretending to be a CD-ROM?
+               cmp word [GetlinsecPtr],getlinsec_cdrom
+               jne found_drive                 ; If so, no spec packet...
+
                ; Now figure out what we're actually doing
                ; Note: use passed-in DL value rather than 7Fh because
                ; at least some BIOSes will get the wrong value otherwise
@@ -333,7 +376,7 @@ found_drive:
 %endif
 
                ; No such luck.  Get the Boot Record Volume, assuming single
-               ; session disk, and that we're the first entry in the chain
+               ; session disk, and that we're the first entry in the chain.
                mov eax,17                      ; Assumed address of BRV
                mov bx,trackbuf
                call getonesec
@@ -356,7 +399,7 @@ set_file:
 found_file:
                ; Set up boot file sizes
                mov eax,[bi_length]
-               sub eax,SECTOR_SIZE-3
+               sub eax,SECTOR_SIZE-3           ; ... minus sector loaded
                shr eax,2                       ; bytes->dwords
                mov [ImageDwords],eax           ; boot file dwords
                add eax,(2047 >> 2)
@@ -685,16 +728,191 @@ getonesec:
 ;
 ; Get linear sectors - EBIOS LBA addressing, 2048-byte sectors.
 ;
-; Note that we can't always do this as a single request, because at least
-; Phoenix BIOSes has a 127-sector limit.  To be on the safe side, stick
-; to 32 sectors (64K) per request.
-;
 ; Input:
 ;      EAX     - Linear sector number
 ;      ES:BX   - Target buffer
 ;      BP      - Sector count
 ;
-getlinsec:
+getlinsec:     jmp word [cs:GetlinsecPtr]
+
+%ifndef DEBUG_MESSAGES
+
+;
+; First, the variants that we use when actually loading off a disk
+; (hybrid mode.)  These are adapted versions of the equivalent routines
+; in ldlinux.asm.
+;
+
+;
+; getlinsec_ebios:
+;
+; getlinsec implementation for floppy/HDD EBIOS (EDD)
+;
+getlinsec_ebios:
+               shl eax,2                       ; Convert to HDD sectors
+               shl bp,2
+
+.loop:
+                push bp                         ; Sectors left
+.retry2:
+               call maxtrans                   ; Enforce maximum transfer size
+               movzx edi,bp                    ; Sectors we are about to read
+               mov cx,retry_count
+.retry:
+
+               ; Form DAPA on stack
+               push edx
+               push eax
+               push es
+               push bx
+               push di
+               push word 16
+               mov si,sp
+               pushad
+                mov dl,[DriveNumber]
+               push ds
+               push ss
+               pop ds                          ; DS <- SS
+                mov ah,42h                      ; Extended Read
+               int 13h
+               pop ds
+               popad
+               lea sp,[si+16]                  ; Remove DAPA
+               jc .error
+               pop bp
+               add eax,edi                     ; Advance sector pointer
+               sub bp,di                       ; Sectors left
+                shl di,9                       ; 512-byte sectors
+                add bx,di                      ; Advance buffer pointer
+                and bp,bp
+                jnz .loop
+
+                ret
+
+.error:
+               ; Some systems seem to get "stuck" in an error state when
+               ; using EBIOS.  Doesn't happen when using CBIOS, which is
+               ; good, since some other systems get timeout failures
+               ; waiting for the floppy disk to spin up.
+
+               pushad                          ; Try resetting the device
+               xor ax,ax
+               mov dl,[DriveNumber]
+               int 13h
+               popad
+               loop .retry                     ; CX-- and jump if not zero
+
+               ;shr word [MaxTransfer],1       ; Reduce the transfer size
+               ;jnz .retry2
+
+               ; Total failure.  Try falling back to CBIOS.
+               mov word [GetlinsecPtr], getlinsec_cbios
+               ;mov byte [MaxTransfer],63      ; Max possibe CBIOS transfer
+
+               pop bp
+               jmp getlinsec_cbios.loop
+
+;
+; getlinsec_cbios:
+;
+; getlinsec implementation for legacy CBIOS
+;
+getlinsec_cbios:
+               shl eax,2                       ; Convert to HDD sectors
+               shl bp,2
+
+.loop:
+               push edx
+               push eax
+               push bp
+               push bx
+
+               movzx esi,word [bsSecPerTrack]
+               movzx edi,word [bsHeads]
+               ;
+               ; Dividing by sectors to get (track,sector): we may have
+               ; up to 2^18 tracks, so we need to use 32-bit arithmetric.
+               ;
+               div esi
+               xor cx,cx
+               xchg cx,dx              ; CX <- sector index (0-based)
+                                       ; EDX <- 0
+               ; eax = track #
+               div edi                 ; Convert track to head/cyl
+
+               ; We should test this, but it doesn't fit...
+               ; cmp eax,1023
+               ; ja .error
+
+               ;
+               ; Now we have AX = cyl, DX = head, CX = sector (0-based),
+               ; BP = sectors to transfer, SI = bsSecPerTrack,
+               ; ES:BX = data target
+               ;
+
+               call maxtrans                   ; Enforce maximum transfer size
+
+               ; Must not cross track boundaries, so BP <= SI-CX
+               sub si,cx
+               cmp bp,si
+               jna .bp_ok
+               mov bp,si
+.bp_ok:
+
+               shl ah,6                ; Because IBM was STOOPID
+                                       ; and thought 8 bits were enough
+                                       ; then thought 10 bits were enough...
+               inc cx                  ; Sector numbers are 1-based, sigh
+               or cl,ah
+               mov ch,al
+               mov dh,dl
+               mov dl,[DriveNumber]
+               xchg ax,bp              ; Sector to transfer count
+               mov ah,02h              ; Read sectors
+               mov bp,retry_count
+.retry:
+               pushad
+               int 13h
+               popad
+               jc .error
+.resume:
+               movzx ecx,al            ; ECX <- sectors transferred
+               shl ax,9                ; Convert sectors in AL to bytes in AX
+               pop bx
+               add bx,ax
+               pop bp
+               pop eax
+               pop edx
+               add eax,ecx
+               sub bp,cx
+               jnz .loop
+               ret
+
+.error:
+               dec bp
+               jnz .retry
+
+               xchg ax,bp              ; Sectors transferred <- 0
+               shr word [MaxTransfer],1
+               jnz .resume
+               jmp disk_error
+
+;
+; Truncate BP to MaxTransfer
+;
+maxtrans:
+               cmp bp,[MaxTransfer]
+               jna .ok
+               mov bp,[MaxTransfer]
+.ok:           ret
+
+%endif
+
+;
+; This is the variant we use for real CD-ROMs:
+; LBA, 2K sectors, some special error handling.
+;
+getlinsec_cdrom:
                mov si,dapa                     ; Load up the DAPA
                mov [si+4],bx
                mov bx,es
@@ -774,6 +992,7 @@ xint13:             mov byte [RetryCount],retry_count
 ; kaboom: write a message and bail out.  Wait for a user keypress,
 ;        then do a hard reboot.
 ;
+disk_error:
 kaboom:
                RESET_STACK_AND_SEGS AX
                mov si,err_bootfailed
@@ -833,11 +1052,13 @@ MaxTransfer      dw 32                           ; Max sectors per transfer
 rl_checkpt     equ $                           ; Must be <= 800h
 
 rl_checkpt_off equ ($-$$)
-;%ifndef DEPEND
-;%if rl_checkpt_off > 0x800
-;%error "Sector 0 overflow"
-;%endif
-;%endif
+%ifndef DEPEND
+%if rl_checkpt_off > 0x800
+; This only works for NASM 2.03+, but it's really nice then...
+%assign SPILL rl_checkpt_off-0x800
+%error Sector 0 overflow by SPILL bytes
+%endif
+%endif
 
 ; ----------------------------------------------------------------------------
 ;  End of code and data that have to be in the first sector
index 7952e60..a57b394 100644 (file)
 topdir = ..
 include $(topdir)/MCONFIG.embedded
 
-all:   mbr.bin gptmbr.bin
+all:   mbr.bin gptmbr.bin isohdpfx.bin
 
 .PRECIOUS: %.o
 %.o: %.S
        $(CC) $(SFLAGS) -Wa,-a=$*.lst -c -o $@ $<
 
-mbr.elf: mbr.o mbr.ld
+.PRECIOUS: %.elf
+%.elf: %.o mbr.ld
        $(LD) $(LDFLAGS) -T mbr.ld -e _start -o $@ $<
 
 mbr.bin: mbr.elf checksize.pl
        $(OBJCOPY) -O binary $< $@
        $(PERL) checksize.pl mbr.bin 440
 
-mbr_bin.c: mbr.bin
+isohdpfx.bin: isohdpfx.elf checksize.pl
+       $(OBJCOPY) -O binary $< $@
+       $(PERL) checksize.pl mbr.bin 432
 
-gptmbr.elf: gptmbr.o mbr.ld
-       $(LD) $(LDFLAGS) -T mbr.ld -e _start -o $@ $<
+mbr_bin.c: mbr.bin
 
 gptmbr.bin: gptmbr.elf checksize.pl
        $(OBJCOPY) -O binary $< $@
diff --git a/mbr/isohdpfx.S b/mbr/isohdpfx.S
new file mode 100644 (file)
index 0000000..f2bc7dc
--- /dev/null
@@ -0,0 +1,212 @@
+/* -----------------------------------------------------------------------
+ *
+ *   Copyright 2007-2008 H. Peter Anvin - All Rights Reserved
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * Modified MBR code used on an ISO image in hybrid mode.
+ *
+ * This doesn't follow the El Torito spec at all -- it is just a stub
+ * loader of a hard-coded offset, but that's good enough to load
+ * ISOLINUX.
+ */
+       
+       .code16
+       .text
+
+HYBRID_MAGIC                   = 0x7078c0fb
+isolinux_hybrid_signature      = 0x7c00+64
+isolinux_start_hybrid          = 0x7c00+64+4
+
+       .globl  bootsec
+/* Important: the top 6 words on the stack are passed to isolinux.bin */
+stack          = 0x7c00
+driveno                = (stack-6)
+sectors                = (stack-8)
+heads          = (stack-10)
+secpercyl      = (stack-14)
+
+BIOS_page = 0x462
+
+       /* gas/ld has issues with doing this as absolute addresses... */
+       .section ".bootsec", "a", @nobits
+       .globl  bootsec
+bootsec:
+       .space  512
+
+       .text
+       .globl  _start
+_start:
+
+       cli
+       xorw    %ax, %ax
+       movw    %ax, %ds
+       movw    %ax, %ss
+       movw    $stack, %sp
+       movw    %sp, %si
+       pushw   %es             /* es:di -> $PnP header */
+       pushw   %di
+       pushw   %dx             /* dl -> drive number */
+       movw    %ax, %es
+       sti
+       cld
+
+       /* Copy down to 0:0x600 */
+       movw    $_start, %di
+       movw    $(512/2), %cx
+       rep; movsw
+
+       ljmpw   $0, $next
+
+next:
+       /* Check to see if we have EBIOS */
+       pushw   %dx             /* drive number */
+       movb    $0x41, %ah      /* %al == 0 already */
+       movw    $0x55aa, %bx
+       xorw    %cx, %cx
+       xorb    %dh, %dh
+       stc
+       int     $0x13
+       jc      1f
+       cmpw    $0xaa55, %bx
+       jne     1f
+       shrw    %cx             /* Bit 0 = fixed disk subset */
+       jnc     1f
+
+       /* We have EBIOS; patch in the following code at
+          read_sector_cbios: movb $0x42, %ah ;  jmp read_common */
+       movl    $0xeb42b4+((read_common-read_sector_cbios-4) << 24), \
+               (read_sector_cbios)
+
+1:
+       popw    %dx
+
+       /* Get (C)HS geometry */
+       movb    $0x08, %ah
+       int     $0x13
+       andw    $0x3f, %cx      /* Sector count */
+       pushw   %cx             /* Save sectors on the stack */
+       movzbw  %dh, %ax        /* dh = max head */
+       incw    %ax             /* From 0-based max to count */
+       pushw   %ax             /* Save heads on the stack */
+       mulw    %cx             /* Heads*sectors -> sectors per cylinder */
+
+       /* Save sectors/cylinder on the stack */
+       pushw   %dx             /* High word */
+       pushw   %ax             /* Low word */
+
+       /*
+        * Load sectors.  We do this one at a time mostly to avoid
+        * pitfalls and to share code with the stock MBR code.
+        */
+       movw    $0x7c00, %bx
+       movw    $4, %cx         /* Sector count */
+       movl    (lba_offset), %eax
+
+2:
+       call    read_sector
+       jc      disk_error
+       incl    %eax
+       addw    $512, %bx
+       loopw   2b
+
+       /*
+        * Okay, that actually worked... update the stack pointer
+        * and jump into isolinux.bin...
+        */
+       cmpl    $HYBRID_MAGIC,(isolinux_hybrid_signature)
+       jne     bad_signature
+       
+       cli
+       movw    $heads, %sp
+       jmp     isolinux_start_hybrid
+
+bad_signature:
+       call    error
+       .ascii  "isolinux.bin missing or corrupt.\r\n"
+
+/*
+ * read_sector: read a single sector pointed to by %eax to %es:%bx.
+ * CF is set on error.  All registers saved.
+ */
+read_sector:
+       pushal
+       xorl    %edx, %edx
+       pushl   %edx    /* MSW of LBA */
+       pushl   %eax    /* LSW of LBA */
+       pushw   %es     /* Buffer segment */
+       pushw   %bx     /* Buffer offset */
+       pushw   $1      /* Sector count */
+       pushw   $16     /* Size of packet */
+       movw    %sp, %si
+
+       /* This chunk is skipped if we have ebios */
+       /* Do not clobber %eax before this chunk! */
+       /* This also relies on %bx and %edx as set up above. */
+read_sector_cbios:
+       divl    (secpercyl)
+       shlb    $6, %ah
+       movb    %ah, %cl
+       movb    %al, %ch
+       xchgw   %dx, %ax
+       divb    (sectors)
+       movb    %al, %dh
+       orb     %ah, %cl
+       incw    %cx     /* Sectors are 1-based */
+       movw    $0x0201, %ax
+
+read_common:
+       movb    (driveno), %dl
+       int     $0x13
+       addw    $16, %sp        /* Drop DAPA */
+       popal
+       ret
+
+disk_error:
+       call    error
+       .ascii  "Operating system load error.\r\n"
+
+/*
+ * Print error messages.  This is invoked with "call", with the
+ * error message at the return address.
+ */
+error:
+       popw    %si
+2:
+       lodsb
+       movb    $0x0e, %ah
+       movb    (BIOS_page), %bh
+       movb    $0x07, %bl
+       int     $0x10           /* May destroy %bp */
+       cmpb    $10, %al        /* Newline? */
+       jne     2b
+
+       int     $0x18           /* Boot failure */
+die:
+       hlt
+       jmp     die
+
+       /* Address of pointer to isolinux.bin */
+lba_offset = _start+432
index 64f8236..de8c999 100644 (file)
@@ -20,7 +20,7 @@ include $(topdir)/MCONFIG
 CFLAGS   = -W -Wall -Os -fomit-frame-pointer -D_FILE_OFFSET_BITS=64
 LDFLAGS  = -O2 -s
 
-TARGETS         = mkdiskimage gethostip
+TARGETS         = mkdiskimage isohybrid gethostip
 ASIS     = keytab-lilo lss16toppm md5pass ppmtolss16 sha1pass syslinux2ansi
 
 all: $(TARGETS)
@@ -32,6 +32,10 @@ mkdiskimage: mkdiskimage.in ../mbr/mbr.bin bin2hex.pl
        $(PERL) bin2hex.pl < ../mbr/mbr.bin | cat mkdiskimage.in - > $@
        chmod a+x $@
 
+isohybrid: isohybrid.in ../mbr/isohdpfx.bin bin2hex.pl
+       $(PERL) bin2hex.pl < ../mbr/isohdpfx.bin | cat isohybrid.in - > $@
+       chmod a+x $@
+
 gethostip: gethostip.o
        $(CC) $(LDFLAGS) -o $@ $^
 
diff --git a/utils/isohybrid.in b/utils/isohybrid.in
new file mode 100644 (file)
index 0000000..a0487ca
--- /dev/null
@@ -0,0 +1,165 @@
+#!/usr/bin/perl
+## -----------------------------------------------------------------------
+##
+##   Copyright 2002-2008 H. Peter Anvin - All Rights Reserved
+##
+##   This program is free software; you can redistribute it and/or modify
+##   it under the terms of the GNU General Public License as published by
+##   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
+##   Boston MA 02111-1307, USA; either version 2 of the License, or
+##   (at your option) any later version; incorporated herein by reference.
+##
+## -----------------------------------------------------------------------
+
+#
+# Post-process an ISO 9660 image generated with mkisofs/genisoimage
+# to allow "hybrid booting" as a CD-ROM or as a hard disk.
+#
+
+use bytes;
+use integer;
+use Fcntl;
+use Errno;
+use Cwd;
+use IO::Handle;                        # For flush()
+
+# Use this fake geometry (zipdrive-style...)
+$h = 64; $s = 32;
+
+sub get_random() {
+    # Get a 32-bit random number
+    my $rfd, $rnd;
+    my $rid;
+
+    if (sysopen($rfd, '/dev/urandom', O_RDONLY) &&
+       sysread($rfd, $rnd, 4) == 4) {
+       $rid = unpack("V", $rnd);
+    }
+
+    close($rfd) if (defined($rfd));
+    return $rid if (defined($rid));
+
+    # This sucks but is better than nothing...
+    return ($$+time()) & 0xffffffff;
+}
+
+
+($file) = @ARGV;
+open(FILE, "+< $file\0") or die "$0: cannot open $file: $!\n";
+binmode FILE;
+
+#
+# First, actually figure out where mkisofs hid isolinux.bin
+#
+seek(FILE, 17*2048, SEEK_SET) or die "$0: $file: $!\n";
+read(FILE, $boot_record, 2048) == 2048 or die "$0: $file: read error\n";
+($br_sign, $br_cat_offset) = unpack("a71V", $boot_record);
+if ($br_sign ne ("\0CD001\1EL TORITO SPECIFICATION" . ("\0" x 41))) {
+    die "$0: $file: no boot record found\n";
+}
+seek(FILE, $br_cat_offset*2048, SEEK_SET) or die "$0: $file: $!\n";
+read(FILE, $boot_cat, 2048) == 2048 or die "$0: $file: read error\n";
+
+# We must have a Validation Entry followed by a Default Entry...
+# no fanciness allowed for the Hybrid mode [XXX: might relax this later]
+@ve = unpack("v16", $boot_cat);
+$cs = 0;
+for ($i = 0; $i < 16; $i++) {
+    $cs += $ve[$i];
+}
+if ($ve[0] != 0x0001 || $ve[15] != 0xaa55 || $cs & 0xffff) {
+    die "$0: $file: invalid boot catalog\n";
+}
+($de_boot, $de_media, $de_seg, $de_sys, $de_mbz1, $de_count, 
+ $de_lba, $de_mbz2) = unpack("CCvCCvVv", substr($boot_cat, 32, 32));
+if ($de_boot != 0x88 || $de_media != 0 ||
+    ($de_segment != 0 && $de_segment != 0x7c0) || $de_count != 4) {
+    die "$0: $file: unexpected boot catalog parameters\n";
+}
+
+# Get the total size of the image
+(@imgstat = stat(FILE)) or die "$0: $file: $!\n";
+$imgsize = $imgstat[7];
+if (!$imgsize) {
+    die "$0: $file: cannot determine length of file\n";
+}
+# Target image size: round up to a multiple of $h*$s*512
+$cylsize = $h*$s*512;
+$frac = $imgsize % $cylsize;
+$padding = ($frac > 0) ? $cylsize - $frac : 0;
+$imgsize += $padding;
+$c = $imgsize/$cylsize;
+if ($c > 1024) {
+    print STDERR "Warning: more than 1024 cylinders ($c).\n";
+    print STDERR "Not all BIOSes will be able to boot this device.\n";
+    $cc = 1024;
+} else {
+    $cc = $c;
+}
+
+# Now $de_lba should contain the CD sector number for isolinux.bin
+seek(FILE, 0, SEEK_SET) or die "$0: $file: $!\n";
+
+# Print the MBR and partition table
+$mbr = '';
+while ( $line = <DATA> ) {
+    chomp $line;
+    foreach $byte ( split(/\s+/, $line) ) {
+       $mbr .= chr(hex($byte));
+    }
+}
+if ( length($mbr) > 432 ) {
+    die "$0: Bad MBR code\n";
+}
+
+$mbr .= "\0" x (432 - length($mbr));
+
+$mbr .= pack("VV", $de_lba*4, 0); # Offset 432: LBA of isolinux.bin
+if (defined($id)) {
+    $id = to_int($id);
+} else {
+    $id = get_random();
+}
+$mbr .= pack("V", $id);                # Offset 440: MBR ID
+$mbr .= "\0\0";                        # Offset 446: actual partition table
+
+# Print partition table
+$psize = $c*$h*$s-$s;
+$bhead   = 0;
+$bsect   = 1;
+$bcyl    = 0;
+$ehead   = $h-1;
+$esect   = $s + ((($cc-1) & 0x300) >> 2);
+$ecyl    = ($cc-1) & 0xff;
+$fstype  = 0x83;               # Linux (any better ideas?)
+$pentry  = 1;
+if ( $c > 1024 ) {
+    $fstype = 0x0e;
+} elsif ( $psize > 65536 ) {
+    $fstype = 0x06;
+} else {
+    $fstype = 0x04;
+}
+for ( $i = 1 ; $i <= 4 ; $i++ ) {
+    if ( $i == $pentry ) {
+       $mbr .= pack("CCCCCCCCVV", 0x80, $bhead, $bsect, $bcyl, $fstype,
+                    $ehead, $esect, $ecyl, $s, $psize);
+    } else {
+       $mbr .= "\0" x 16;
+    }
+}
+$mbr .= "\x55\xaa";
+
+print FILE $mbr;
+
+# Pad the image to a fake cylinder boundary
+seek(FILE, $imgstat[7], SEEK_SET) or die "$0: $file: $!\n";
+if ($padding) {
+    print FILE "\0" x $padding;
+}
+
+# Done...
+close(FILE);
+
+exit 0;
+__END__