
/*
    Post-fix: a tool to make bootable CD-ROM image (ISO file) bootable by ISOLINUX
    (C) 2003 by Mikhail Kupchik.  Must be distributed under terms of GNU GPL v2.
*/

#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>

const char* process_iso( int fs );

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main( int argc, char* argv[] )
{
    if( argc == 2 )
    {
	int filestream = open( argv[1], O_RDWR | O_BINARY );
	if( filestream != -1 )
	{
	    const char* status = process_iso( filestream );
	    close( filestream );
            if( 0 != status )
            {
                fprintf( stderr, "%s (%s): %s\n", argv[0], argv[1], status );
                return -1;
            }
            else
                return 0;
        }
	else
        {
            fprintf( stderr, "%s: could not open input file %s: ",
                argv[0], argv[1] );
            perror( "" );
            return -errno;
        }
    }
    else
    {
        fprintf( stderr, "Boot info table correction utility.\n"
            "(a tool to make CD-ROM image bootable by ISOLINUX)\n"
            "(C) 2003 by Mikhail Kupchik. See GNU GPL v2 for distribution terms and conditions.\n"
            "Usage:\n"
	    "%s <image file>\n", argv[0] );
        return 0;
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const char* process_iso( int fs )
{
    lseek( fs, 16 * 2048, SEEK_SET );     /* seek volume descriptor set */
    char buffer[2048];                    /* buffer for volume descriptor */
    long pvd_lba = 0, bvd_lba = 0;        /* primary and boot volume descriptor LBAs */

    for( long this_lba = 16; ; ++this_lba )
    {
	if( 2048 != read( fs, buffer, 2048 ) )
	    return "Couldn't read ISO file";

//        printf( "vdes = %d\n", (int)( *buffer ) );

//        static const char signature[] = "CD001";
//        if( 0 != strncmp( signature, buffer+1, sizeof(signature) ) )
//	    return "Bad ISO file: corrupt volume descriptor signature";

	if( *buffer == 1 )            /* primary volume descriptor */
	{
	    if( pvd_lba )
		return "Bad ISO file: more than one primary volume descriptor";
	    else
		pvd_lba = this_lba;
	}
	else if( *buffer == 0 )       /* boot volume descriptor */
	{
	    if( bvd_lba )
		return "Bad ISO file: more than one boot volume descriptor";
	    else
		bvd_lba = this_lba;
	}
	else if( *buffer == 0xFF )       /* volume descriptor set terminator */
	    break; /* for */
    }

    lseek( fs, bvd_lba * 2048, SEEK_SET );
    if( 2048 != read( fs, buffer, 2048 ) )
	return "Couldn't read ISO file";

    if( buffer[6] != 1 )
	return "Bad ISO file: unknown boot volume descriptor version";
    static const char signature[] = "EL TORITO SPECIFICATION";
    if( 0 != strncmp( buffer+7, signature, sizeof(signature) ) )
	return "Bad ISO file: unknown boot system identifier (non-eltorito disk)";

    long bootcat_lba = *( (long*)( buffer+0x47 ) );
    lseek( fs, bootcat_lba * 2048, SEEK_SET );
    if( 2048 != read( fs, buffer, 2048 ) )
	return "Couldn't read ISO file";

    if( buffer[0] != 1 || buffer[0x1E] != 0x55 || buffer[0x1F] != 0xAA )
	return "Bad ISO file: boot validation entry is corrupted";
    if( buffer[1] != 0 )
	return "Bad ISO file: boot platform ID is not 80x86";

    const char* boot_entry = &buffer[0x20];
    while( boot_entry[0] == 0 ) boot_entry += 0x20;
    if( boot_entry[0] != 0x88 )
	return "Bad ISO file: unknown boot flag in boot entry";
    if( boot_entry[1] != 0 )
	return "Bad ISO file: must be `no-emulation' mode boot image";

    long boot_sectors_count = *( (short*)(boot_entry+6) );
    if( boot_sectors_count == 0 )
	return "Bad ISO file: no boot sectors to load";

    long bootfile_lba = *( (long*)(boot_entry+8) );
    *( (short*)(boot_entry+6) ) = 4;      /* load 4 sectors */

    lseek( fs, bootcat_lba * 2048, SEEK_SET );       /* save modified boot catalog */
    if( 2048 != write( fs, buffer, 2048 ) )
	return "Couldn't write ISO file";
    lseek( fs, bootfile_lba * 2048, SEEK_SET );      /* load bootfile */
    if( 2048 != read( fs, buffer, 2048 ) )
	return "Couldn't read ISO file";

    long checksum = 0;
    for( int i = 64 >> 2; i < (2048 >> 2); ++i )
	checksum += ((long*)buffer)[i];
    for( int sn = 1; sn < boot_sectors_count; sn++ )
    {
	char local_buf[2048];
	if( 2048 != read( fs, local_buf, 2048 ) )
	    return "Couldn't read ISO file";
	for( int i = 0; i < (2048 >> 2); ++i )
	    checksum += ((long*)local_buf)[i];
    }

    *( (long*)(buffer+8) ) = pvd_lba;                        /* LBA of primary volume descriptor */
    *( (long*)(buffer+0xC) ) = bootfile_lba;                 /* LBA of boot file */
    *( (long*)(buffer+0x10) ) = boot_sectors_count * 2048;   /* Boot file length */
    *( (long*)(buffer+0x14) ) = checksum;                    /* Checksum of boot file */
    lseek( fs, bootfile_lba * 2048, SEEK_SET );       /* save modified boot file */
    if( 2048 != write( fs, buffer, 2048 ) )
	return "Couldn't write ISO file";

    return 0;
}
