#! /usr/bin/perl

# relocate boot catalog
#
# Copyright (c) 2007 Steffen Winterfeldt
#
# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# This is free software: you are free to change and redistribute it.
#
#
# mkisofs/genisoimage leave one sector empty (or fill it with version info
# if the ISODEBUG environment variable is set) before starting the path
# table.
#
# We use this space to move the boot catalog there.
#
# usage:
#
#  relocate_catalog cd_image.iso
#

sub read_sector;
sub write_sector;

$iso = shift;

die "$iso: $!\n" unless open ISO, "+<$iso";

$vol_descr = read_sector 0x10;
$vol_id = substr($vol_descr, 0, 7);
die "$iso: no iso9660 fs\n" if $vol_id ne "\x01CD001\x01";
$path_table = unpack "V", substr($vol_descr, 0x08c, 4);
die "$iso: strange path table location: $path_table\n" if $path_table < 0x11;

$new_location = $path_table - 1;

$eltorito_descr = read_sector 0x11;
$eltorito_id = substr($eltorito_descr, 0, 0x1e);
die "$iso: not bootable\n" if $eltorito_id ne "\x00CD001\x01EL TORITO SPECIFICATION";
$boot_catalog = unpack "V", substr($eltorito_descr, 0x47, 4);
die "$iso: strange boot catalog location: $boot_catalog\n" if $boot_catalog < 0x12;

$vol_descr2 = read_sector $new_location - 1;
$vol_id2 = substr($vol_descr2, 0, 7);
if($vol_id2 ne "\xffCD001\x01") {
  undef $new_location;
  for($i = 0x12; $i < 0x40; $i++) {
    $vol_descr2 = read_sector $i;
    $vol_id2 = substr($vol_descr2, 0, 7);
    if($vol_id2 eq "\x00TEA01\x01" || $boot_catalog == $i + 1) {
      $new_location = $i + 1;
      last;
    }
  }
}

die "$iso: unexpected layout\n" unless defined $new_location;

if($boot_catalog == $new_location) {
  print "boot catalog already relocated\n";
  exit 0;
}

$version_descr = read_sector $new_location;
die "$iso: unexpected layout\n" if $version_descr ne ("\x00" x 0x800) && substr($version_descr, 0, 4) ne "MKI ";

$boot_catalog_data = read_sector $boot_catalog;

# now reloacte to $new_location

substr($eltorito_descr, 0x47, 4) = pack "V", $new_location;

write_sector $new_location, $boot_catalog_data;

write_sector 0x11, $eltorito_descr;

printf "boot catalog relocated: %d -> %d\n", $boot_catalog, $new_location;

close ISO;


sub read_sector
{
  my $buf;

  die "$iso: seek error!\n" unless seek ISO, $_[0] * 0x800, 0;
  die "$iso: read error\n" if sysread(ISO, $buf, 0x800) != 0x800;

  return $buf;
}

sub write_sector
{
  die "$iso: seek error!\n" unless seek ISO, $_[0] * 0x800, 0;
  die "$iso: write error\n" if syswrite(ISO, $_[1], 0x800) != 0x800;
}

