Ars Longa, Vita Brevis

Март 28, 2008

Преобразование образов NRG в ISO

Рубрика: C/C++
Метки: , , , ,
Vladimir @ 3:07 дп
RSS 2.0

Быстрый способ преобразования NRG-файла в ISO

Позавчера возникла проблема: нужно было срочно переставить Windows XP (ибо VMWare так больно упала, что Windows пал смертью храбрых). Всё хорошо, но вот родного инсталляционного диска под рукой не было, а инсталлятор жил на NTFS-разделе (кстати, DOS-драйвер для чтения NTFS почему-то оказался бессильным). По счастью, на FAT32-диске нашелся образ загрузочного диска. Одна проблема: образ был сохранен Nero и записан в формате NRG. И, как на зло, не было ничего, что понимает NRG-образы. А так как Internet тоже не было (какой там Internet при загрузке с Реаниматора!), пришлось думать, как можно с ограниченными средствами преобразовать NRG-образ в ISO.

По счастью, я когда-то задавался этим вопросом — нужно было достать файлы из NRG, но записывать диск не хотелось. Тогда вот и поверхностно познакомился с форматом NRG. Но, к сожалению, тогда я это забросил. Но тестовые ISO и NRG-файлы остались.

Сразу отмечу (хотя выяснил это я позже), что все сказанное ниже относится только к образам CD-дисков, записанных методом Disk-at-Once (DAO). Если диск был записан в режиме Track-at-Once (TAO), то приведённое ниже преобразование к нему применять нельзя. Разница между режимами DAO и TAO прекрасно описана в Wikipedia в статье "Optical disc recording modes". Обычно загрузочные диски пишутся в режиме DAO.

Продолжим.
Первое, на что я сразу же обратил внимание — NRG-образ был примерно на 300 КиБ больше, чем ISO-образ. Что вполне логично, если рассматривать NRG-файл как ISO-образ плюс метаданные. Простое сравнение показало, что у NRG-файла есть какой-то непонятный заголовок размером 300 КиБ, состоящий из одних нулей.

В моём случае NRG-файл был длиннее на 144 байта. Хвост выглядел следующим образом:

002B916800: 43 55 45 58 00 00 00 20 │ 41 00 00 00 FF FF FF 6A
002B916810: 41 01 00 00 FF FF FF 6A │ 41 01 01 00 00 00 00 00
002B916820: 41 AA 01 00 00 05 71 97 │ 44 41 4F 58 00 00 00 40
002B916830: 00 00 00 40 00 00 00 00 │ 00 00 00 00 00 00 00 00
002B916840: 00 00 00 01 01 01 00 00 │ 00 00 00 00 00 00 00 00
002B916850: 00 00 08 00 00 00 00 01 │ 00 00 00 00 00 00 00 00
002B916860: 00 00 00 00 00 04 B0 00 │ 00 00 00 00 2B 91 68 00
002B916870: 53 49 4E 46 00 00 00 04 │ 00 00 00 01 4D 54 59 50
002B916880: 00 00 00 04 00 00 00 01 │ 45 4E 44 21 00 00 00 00
002B916890: 4E 45 52 35 00 00 00 00 │ 2B 91 68 00

Блоки, выделенные красным цветом, имеют следующие текстовые значения: CUEX, DAOX, SINF, MTYP, END!, NER5. По структуре очень напоминает IFF — Interchange File Format (например, подобный подход используется в PNG-файлах). Считая, что CUEX, DAOX и пр. — это имена блоков (chunk), то логично предположить, что следом за именем идет длина блока, а за длиной — сам блок. Еще одно совпадение выделено зелёным цветом: адрес самого первого блока. Однако порядок байт в адресе не Intel'овский (little endian), а Mototrolla'овский (big endian). Логично предположить, что смещение будет 8-байтным (иначе не будет работать для образов больше 4 ГиБ). Таким образом, чтобы узнать смещение первого блока, нужно прочитать последние 8 байт образа (на забывая, что они в формате big endian). Для подстраховки: перед смещением идет сигнатура NER5, перед которой находится блок END! (END! и четыре нуля).

В результате получили простой алгоритм: читаем последние 8 байт, узнаём смещение первого блока. Это смещение будет длиной ISO-образа с "заголовком" Nero в 300 КиБ. Вычитаем из смещения 307200 (те самые 300 КиБ), получаем размер ISO-образа. Далее читаем это количество байт с позиции 307200 исходного файла и записываем в ISO-файл. Всё просто! :-)

Упрощённая версия кода на языке C будет выглядеть следующим образом (в целях упрощения кода я убрал все проверки на ошибки):

    fseek(in, -sizeof(id), SEEK_END);
 
    read = fread(&id, 1, sizeof(id), in);
    if (0x3552454Eul == id[0]) {    /* Nero v2 footer */
        id[1] = ((id[1] & 0x000000FF) << 24) |
                ((id[1] & 0x0000FF00) << 8) |
                ((id[1] & 0x00FF0000) >> 8) |
                ((id[1] & 0xFF000000) >> 24);
 
        id[2] = ((id[2] & 0x000000FF) << 24) |
                ((id[2] & 0x0000FF00) << 8) |
                ((id[2] & 0x00FF0000) >> 8) |
                ((id[2] & 0xFF000000) >> 24);
 
        bytes_to_copy = id[2] | ((uint64_t)(id[1]) << 32);
    }
    else if (0x4F52454E == id[1]) { /* Nero v1 footer */
        bytes_to_copy = id[2];
        bytes_to_copy = ((bytes_to_copy & 0x000000FF) << 24) |
                        ((bytes_to_copy & 0x0000FF00) << 8) |
                        ((bytes_to_copy & 0x00FF0000) >> 8) |
                        ((bytes_to_copy & 0xFF000000) >> 24);
    }
 
    bytes_to_copy -= 512*600;
    fseek(in, 512*600, SEEK_SET);
 
    while (bytes_to_copy > 0) {
        to_read = (bytes_to_copy < READ_BUFFER_SIZE) ? bytes_to_copy : READ_BUFFER_SIZE;
        read    = fread(buf, 1, to_read, in);
        if (0 == read) {
            break;
        }
 
        written = fwrite(buf, 1, read, out);
        if (written != read) {
            break;
        }
 
        bytes_to_copy -= read;
    }

Примечание: в код добавлена поддержка старых образов Nero.

Полный исходный код nrg2iso можно скачать отсюда, скомпилированный EXE-файл для Windows (7680 байт) лежит здесь. Файл зависит только от KERNEL32.DLL и msvcrt.dll (они есть в каждой версии Windows).

MD5 от nrg2iso.exe — 6cba4b141839031d91af14cac261650a.

PS — исходник написан на ANSI C и, как следствие, должен компилироваться всеми компиляторами, поддерживающими стандарт ANSI C.

Чтобы скомпилировать файл в gcc, достаточно команды

gcc nrg2iso.c -ansi -O3 -o nrg2iso

Комментариев нет »

Комментариев нет.

RSS комментариев к этой записи. URL обратной ссылки

Оставить комментарий