#!/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 this fake geometry (zipdrive-style...)
$h = 64; $s = 32;

sub get_random() {
    # Get a 32-bit random number
    my $rfd, $rnd;
    my $rid;

    if (open($rfd, "< /dev/urandom\0") && read($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";
}

# Now $de_lba should contain the CD sector number for isolinux.bin
seek(FILE, $de_lba*2048+0x40, SEEK_SET) or die "$0: $file: $!\n";
read(FILE, $ibsig, 4);
if ($ibsig ne "\xfb\xc0\x78\x70") {
    die "$0: $file: bootloader is missing isolinux.bin hybrid signature\n".
	"Note: isolinux-debug.bin does not support hybrid booting\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;
}

# Print the MBR and partition table
seek(FILE, 0, SEEK_SET) or die "$0: $file: $!\n";

$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;
$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;			# First partition slot

for ( $i = 1 ; $i <= 4 ; $i++ ) {
    if ( $i == $pentry ) {
	$mbr .= pack("CCCCCCCCVV", 0x80, $bhead, $bsect, $bcyl, $fstype,
		     $ehead, $esect, $ecyl, 0, $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__
fa 31 c0 8e d8 8e d0 bc 0 7c 89 e6 6 57 52 8e c0 fb fc bf 0 6 b9 0 1 f3 a5 ea 20 6 0 0 52 b4 41 bb aa
55 31 c9 30 f6 f9 cd 13 72 14 81 fb 55 aa 75 e 83 e1 1 74 9 66 c7 6 b4 6 b4 42 eb 15 5a 51 b4 8 cd 13 83
e1 3f 51 f b6 c6 40 50 f7 e1 52 50 bb 0 7c b9 4 0 66 a1 b0 7 e8 40 0 72 74 66 40 80 c7 2 e2 f4 66 81 3e
40 7c fb c0 78 70 75 7 fa bc f4 7b e9 c6 75 e8 79 0 69 73 6f 6c 69 6e 75 78 2e 62 69 6e 20 6d 69 73 73 69 6e
67 20 6f 72 20 63 6f 72 72 75 70 74 2e d a 66 60 66 31 d2 66 52 66 50 6 53 6a 1 6a 10 89 e6 66 f7 36 f0 7b
c0 e4 6 88 e1 88 c5 92 f6 36 f6 7b 88 c6 8 e1 41 b8 1 2 8a 16 fa 7b cd 13 8d 64 10 66 61 c3 e8 1e 0 4f 70
65 72 61 74 69 6e 67 20 73 79 73 74 65 6d 20 6c 6f 61 64 20 65 72 72 6f 72 2e d a 5e ac b4 e 8a 3e 62 4 b3
7 cd 10 3c a 75 f1 cd 18 f4 eb fd 
