#!/usr/bin/perl
# Post-process an ISO 9660 image generated with mkisofs/genisoimage
# to allow "hybrid booting" as a CD-ROM or as a hard disk.
#
# This is isohybrid-bg2 - written by Andreas Loibl <andreas@andreas-loibl.de>
#
# It works for ISO images with BURG or GRUB2. For ISO images with
# syslinux/isolinux use isohybrid (written by H. Peter Anvin)
#
# isohybrid-bg2 is based on isohybrid:
## -----------------------------------------------------------------------
##
##   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.
##
## -----------------------------------------------------------------------

# 512byte boot.img
$mbr='eb639000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800100000000000000fffaeb07f6c2807502b280ea747c000031c08ed88ed0bc0020fba0647c3cff740288c252be807de81c01be057cf6c2807448b441bbaa55cd135a52723d81fb55aa753783e101743231c0894404408844ff894402c7041000668b1e5c7c66895c08668b1e607c66895c0cc744060070b442cd137205bb0070eb76b408cd13730df6c2800f84d800be8b7de98200660fb6c68864ff40668944040fb6d1c1e20288e888f4408944080fb6c2c0e80266890466a1607c6609c0754e66a15c7c6631d266f73488d131d266f774043b44087d37fec188c530c0c1e80208c188d05a88c6bb00708ec331dbb80102cd13721e8cc3601eb900018edb31f6bf00808ec6fcf3a51f61ff265a7cbe867deb03be957de83400be9a7de82e00cd18ebfe47525542200047656f6d0048617264204469736b005265616400204572726f720d0a00bb0100b40ecd10ac3c0075f4c3000000000000000000000000000024120f0900bebd7d31c0cd13468a0c80f900750fbeda7de8d2ffeb9c466c6f70707900bb0070b80102b500b600cd1372d7b601b54fe9fbfe000000000000000055aa';

($file) = @ARGV;
open(FILE, "+< $file\0") or die "$0: cannot open $file: $!\n";
binmode FILE;

# search pattern to find location of boot.isohybrid on ISO image
$pattern="ACRITOX!"x64;
$p=0;
while($p++ < 0xffff)
{
	seek(FILE, $p*512, SEEK_SET) or die "$0: $file: $!\n";
	read(FILE, $sector, 512) == 512 or die "$0: $file: read error\n";
	if($sector eq $pattern)
	{
		seek(FILE, 0, SEEK_SET) or die "$0: $file: $!\n";
		print FILE pack('H*',$mbr);
		$boot_offset = hex(substr($mbr,2,2));
		seek(FILE, $boot_offset - 0x7, SEEK_SET) or die "$0: $file: $!\n";
		print FILE pack("V", $p+1);
		seek(FILE, $p*512 + 0x3f4, SEEK_SET) or die "$0: $file: $!\n";
		print FILE pack("V", $p+2);
		last;
	}
}
if($p>0xffff) {
	die "$0: error: $file does not contain boot.isohybrid in its first 32MB!\n";
}

# 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;
}

#
# 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;
}


$mbr = pack("VV", $de_lba*4, 0);
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 the partition table
seek(FILE, 432, SEEK_SET) or die "$0: $file: $!\n";
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;