Сергей Новодворский
Источник http://www.hare.ru/
Файлы метаданных V7 (*.md) представляют собой структурированные хранилища (structured storage), организованные по правилам файловой системы от Microsoft. В терминологии OLE2 сами дисковые файлы носят название "составной файл" (compound file).
Compound file состоит из целого числа блоков данных, размер каждого блока равен 512 байт, т.е. соответствует одному дисковому сектору, поэтому в дальнейшем я буду пользоваться термином "сектор" для обозначения блока размером 512 байт.
Нумерация секторов в составном файле начинается с -1: -1,0,1,2…
В стуктуру составного файла входят следующие области данных:
Нам придется работать с двоичными данными и переводить числа из шестнадцетеричного в десятичный формат (учитывая, что числа в файле хранятся в шестнадцатеричном формате начиная с младшего байта). Для этого в разделе деклараций нашего VBA-модуля создадим две UDT-структуры:
и напишем функцию для перемещения содержимого из одной структуры в другую
Объявим там же константу со значением размера сектора и байтовый массив, в котором будет хранится наш составной файл:
Далее пишем функцию, которая считает составной файл целиком в байтовый массив
Если функция вернула True, значит файл считан в массив fileBuf().
Заголовок составного файла
Заголовок представляет собой запись размером 80 байтов в секторе номер -1. Нас будут интересовать следующие поля заголовка:
Что сие означает – будет рассказано ниже, а пока что в разделе деклараций модуля создаем UDT структуру:
объявляем переменную, которая будет содержать означенную выше структуру
и пишем функцию, которая заполнит стуктуру заголовка составного файла
Если функция вернула True, значит заголовок считан и наш файл является составным файлом OLE.
FAT
FAT представляет собой последовательный список 4-байтовых "строчек" DWORD, каждая из которых соответствует порядковому номеру сектора в файле, начиная с сектора под номером 0 (сектор -1 вообще не учитывается). Значение строчки – это номер сектора, следующего за текущим. Отрицательное содержимое "строчки" означает следующее:
Номер строчки для FAT малых блоков и каталога – это не порядковые номера секторов, а порядковый номер записи (размером 64 байта или 128 байт соответственно) от начала области, где располагаются собственно малые блоки или каталог. Первая запись имеет номер 0.
Но вернемся к FAT больших блоков. Первый сектор расположен в файле по абсолютному адресу, который мы определили из заголовка файла (смещение +4Ch) и записали в HeaderMD.StartBBD. Но непосредственно в сектор может поместиться только 128 номеров секторов (т.е. файл в принципе не может быть больше 512 + 128 Х 512 = 66048 байт), а где же тогда искать продолжение FAT?
Вот это был секрет за семью замками и только методом проб и ошибок была обнаружена область, которая из себя представляет FAT для FAT. Находится она в секторе -1 и занимает оставшуюся от заголовка файла область начиная с 81 байта (+50h) и до конца сектора. Каждая строчка этой области указывает на номер сектора, в котором расположен следующий сектор FAT( поэтому FAT для FAT это не совсем FAT, т.к. значение строчки указывает не на следующий за текущим номер сектора, а прямо адресует к сектору, где расположен следующий "кусочек" настоящего FAT).
Таким образом мы имеем 109 секторов FAT, каждый из которых адресует 128 секторов составного файла, т.е. максимальный размер файла может составлять 512 + (109 Х 128 Х 512) = 7 143 936 байт, но физические-то файлы больше!
Методом всё того же тыка был обнаружен еще один сектор "FAT для FAT", номер которого находится в заголовке файла (смещение +44h), причем если значение равно -1 (FFFFFFFFh), то такого сектора в файле нет.
Теперь, когда мы разобрались с местом жительства FAT больших блоков, можно написать программу, которая вытянет весь FAT в массив. Причем для удобства создадим UDT-структуру (этого можно и не делать), которая будет содержать ссылку на следующий номер сектора и абсолютный номер текущего сектора файла. В области деклараций модуля объявляем
И пишем функцию:
Если функция вернула True, значит FAT считан. Функцию можно разбить на две, но это не существенно.
После того, как мы получили из составного файла FAT больших блоков, пришла пора приступить к получению FAT малых блоков. Номер стартового сектора FAT малых блоков указан в заголовке файла (смещение +3С), мы его записали в HeaderMD.StartSBD. Этот номер равен индексу массива FAT больших блоков, и дальше по цепочке вытягиваем все сектора, в которых расположен FAT малых блоков.
Сейчас самое время написать функцию, которая будет выдавать нам номер следующего сектора. Функция возвращает ноль, если текущий сектор последний.
Почему нужна была структура FATSTRUCTURE? Если для FAT больших блоков можно было умножением индекса массива на размер сектора плюс 512 узнать абсолютный адрес сектора, то номера в FAT малых блоков означают относительные номера 64-байтовых записей от начала области данных малых блоков и, используя указанную структуру, мы будем в процессе создания массива FAT малых блоков сразу записывать абсолютные адреса для каждого малого блока.
Но для этого надо определить абсолютный адрес сектора, с которого начинается область данных малых блоков. Адрес этого сектора находится в 128-байтовой записи объекта каталога, являющегося корнем каталога (Root Entry) по смещению +74h, а сам объект представлен в заголовке файла (смещение +30h) как стартовый сектор каталога (об объектах каталога речь пойдет позже).
В разделе деклараций объявляем массив FAT малых блоков:
и пишем функцию для получения FAT из файла
и сразу пишем функцию, которая будет возвращать номер следующего малого блока или 0, если текущий блок последний:
Каталог
Каталог представляет собой описание структуры объектов составного файла, упорядоченных в виде дерева. Сам объект представлен 128-байтной записью со следующими полями (только те, что нас интересуют):
Как видим, объекты каталога представляют собой связанный список, каждый элемент которого имеет ссылку на предыдущий,следующий и подчиненный объекты, в свою очередь подчиненные также имеют своих предудыщих,следующих и подчиненных. Отсутствие какого-либо из перечисленных обозначается как -1.
Единственное, чего не имеет объект, так это своего собственного номера. А нумеруются они начиная с нуля 128-байтными "кусочками" относительно области данных каталога, номер стартового сектора этой области находится в заголовке файла (смещение +30h), а сама область вытягивается из FAT больших блоков.
Еще одно существенное замечание (оно не касается стартового объекта каталога Root Entry) –если размер объекта (смещение +78h) больше или равен 4096 байтам (1000h), то номер стартового сектора (смещение +74h) указывает на FAT больших блоков, в противном случае – на FAT малых блоков. Стартовый объект всегда находится в FAT больших блоков (ведь, как было показано выше, там живет адрес области данных малых блоков).
Для построения дерева из связанного списка существует много алгоритмов, предложу свой (не претендую ни на что! ;-) – просто он как-то сразу заработал.
Создадим модуль класса, назовем его clsNode и напишем следующий код
В разделе деклараций нашего модуля (не класса!) объявим массив, который будет содержать ссылки на абсолютные адреса объектов каталога, а так же объявим наш класс:
и напишем функцию, которая достанет из файла структуру каталога
Теперь у нас есть структура каталога, оба FAT и, зная стартовый адрес любого объекта, можно вытянуть из составного файла весь объект (не забывая, что исходя из размера объекта надо использовать FAT больших или малых блоков).
* * *
В работе использованы материалы из статьи К.Е. Климентьева "Внутренний формат документов MS Word" и собственный опыт автора.
Пример реализации методики доступа к MD на VBA (Excel)