В старые добрые времена MS-DOS, когда hardware были большими, а software маленькими, были таланты (без всяких ковычек!) которые по подобному текстовому дампу говорили:
здесь открывается файл и что-то записывается в конец.
я таких талантов не имел, поэтому добавил в hiew дизассемблер.
Смотреть на дампы всяких файлов данных тоже было интересно но хотелось лучше видеть, что же там за конкретные данные внутри. Для этого я в те ламповые времена написал StructLook или сокращенно STL - смотрелку различных структурированных файлов. (Кто ж знал, что английских букв мало и что MS так назовет свою библиотеку)
Там был простенький описательный язык, который помогал управляться всякими байтовыми и даже битовыми полями.
Например, вот так описывались компоненты Zip-файла:
--- 8< ---
/Zip
ZIPSIG: w 1 Signature(PK)
#if zipsig != "KP"
:ERROR!!! Invalid signature PK
#else
ZIPSUBSIG: w 'zipsubsig directory type
#if( zipsubsig == 0x0403 )
ZIPVER: w 1 Version needed to extract
t16 `zipflags General purpose bit flag
ZIPMTHD: u16 'zipcompres Compression method
td 1 Last mod file time (MS-DOS)
ZIPCRC: d 1 CRC-32
ZIPSIZE: u32 1 Compressed size
ZIPUNCMP: u32 1 Uncompressed size
ZIPFNLN: w 1 Filename length
ZIPXTRALN: w 1 Extra field length
ZIPNAME: c ZIPFNLN filename
ZIPXTRA: c ZIPXTRALN extra field
+ ZIPSIZE
#elseif( zipsubsig == 0x0201 )
ZIPCVER: i8 1 Version made by
ZIPCOS: u8 'ziphost Host operating system
ZIPCVXT: i8 1 Version needed to extract
ZIPCEXOS: i8 1 O/S of version needed for extraction
t16 `ZipFlags General purpose bit flag
ZIPCMTHD: u16 'zipcompres Compression method
td 1 Last mod file time (MS-DOS)
ZIPCCRC: d 1 CRC-32
....
--- 8< ---
И вот так StruсtLook показывал Zip-файл по этому описанию:
Время шло-бежало-летело.
И вот через 30 лет я написал Байтитуру (Bytitura, от Byte(en)+Partitura(it)).
Точнее, она была написана несколько лет назад только для личного использования, отчасти потому что язык описания от StructLook никак не добавлялся, отчасти потому что это была платформа для тренировок со всякими GUI и потоками и поэтому время от времени серьезно переписывалась.
Файлы описания были только как внешние DLL.
Потом смысл своего описателя полностью отпал при наличии той же Lua, и которую я наконец-то добавил в уже коммерческий продукт.
А в этом году добавились .sture-файлы - это текстовые файлы со стандартными С-шными структурами.
И теперь эта связка Lua + .sture-файл помогает достаточно просто описать то что надо увидеть, без расписывания каждого адреса и каждого байта.
И сейчас покажу как это делается на примере все того же Zip-файла.
Он выбран потому что простой, все его знают и внутри него есть разные части.
Начнем со .sture-файла.
В нем записаны С-структуры, которые понадобятся для разбора целевого файла. Имя .sture-файла совпадает с именем скрипта и подхватывается автоматически при выборе целевого файла в диалоге открытия. Один и тот же .sture-файл используется как для парзеров в .lua так и для .btt32
Байтитура знает только стандартные типы:
(signed/unsigned) char/short/int/long/long long для десятичного представления
(signed/unsigned) BYTE/WORD/DWORD/QWORD для шестнадцатиричного
float/double для вещественного
WCHAR для юникода
есть два нюанса (куда же без них!)
char без указания signed/unsigned представляет текст, в т.ч. отдельный символ
BYTE без signed/unsigned отображается как дамп
типы можно переопределять через стандартный typedef, см (1)
в размере массива может быть имя целой переменой из этой же структуры, см (2)
--- 8< ---
typedef unsigned short ushort; (1)
typedef unsigned long ulong; (1)
typedef struct{
DWORD marker; // если будет комментарий то отобразиться именно он
ushort version_needed_to_extract;
WORD general_purpose_bit_flag;
WORD compression_method;
WORD last_mod_file_time;
WORD last_mod_file_date;
DWORD crc_32;
ulong compressed_size; (2a)
ulong uncompressed_size;
ushort file_name_length; (2b)
ushort extra_field_length; (2c)
char file_name[ file_name_length ]; (2b)
BYTE extra_field[ extra_field_length ]; (2c)
BYTE compressed_data[ compressed_size ]; (2a)
}ZIP_LOCAL;
....
--- 8< ---
Теперь к собственно Lua-скрипту.
Минимальный скрипт состоит из трех обязательных функций:
--- 8< ---
-- минимальный скрипт который загрузится
-- но не знает ни про какие файлы
BTT_API_VERSION = 130 -- следите за актуальностью !
-- возвращает версию API которой пользуется
function ApiVersionUsed()
return BTT_API_VERSION
end
-- отвечает хосту знает ли этот файл
function IKnowThisFile( filename )
return -1
end
-- собственно разборщик файла
function ParseFile( file_index, filename, base_address, codesize )
return -1
end
--- 8< ---
Но поскольку мы хотим что-то полезное от скрипта, то придется открывать файл, читать двойное слово и проверять на совпадение с маркером.
--- 8< ---
-- константы из bytitura.h
BTT_API_VERSION = 130 -- следите за актуальностью !
decode_engine_none = 0
decode_engine_ia32 = 1
ascii_cp_ansi = 0
ascii_cp_oem = 1
ascii_cp_utf8 = 2
-- zip константы
ZIP_MARKER_CENTRAL = 0x02014b50
ZIP_MARKER_LOCAL = 0x04034b50
ZIP_MARKER_CENTRALEND = 0x06054b50
ZIP_MARKER_DATADESCRIPTOR = 0x08074b50
function IKnowThisFile( filename )
-- Имя файла передается в utf-8.
local file = io.open( filename, "rb" )
local sign = ReadDword( file, 0 )
file:close()
if sign ~= ZIP_MARKER_LOCAL then
return -1
end
return 0, -- базовый адрес
0, -- это данные
decode_engine_none, -- кода нет
0 -- файловые флаги, например может ли пользователь менять базовый адрес
end
--- 8< ---
В Lua прочитать двойное слово оказалось еще тем квестом,
простой вариант
local dword = 12345
dword = file:read( 4 )
конечно же не работал
в интернетах я давно уже разучился искать, ничего там быстро не нашел и пошел своими огородами
--- 8< ---
function ReadDword( file, address )
file:seek( "set", address )
local byte0 = file:read( 1 )
local byte1 = file:read( 1 )
local byte2 = file:read( 1 )
local byte3 = file:read( 1 )
if byte3 == nil
then return nil
end
local dword = (string.byte(byte3)<<24)
+ (string.byte(byte2)<<16)
+ (string.byte(byte1)<<8)
+ string.byte(byte0)
return dword
end
--- 8< ---
хотя документация и говорила что-то про диапазон подстроки
string.byte (s [, i [, j]])
Returns the internal numerical codes of the characters s[i], s[i+1], ···, s[j].
The default value for i is 1; the default value for j is i.
но это совсем не про что хотелось бы
local text = file:read(4)
local dword = string.byte( text, 1, 4 )
все так же отдавал только первый байт, либо Lua умеет только байты, либо я не вижу очевидного решения прочитать не строку, а значение.
Буквально рыдал когда представлял с какой скоростью в интерпретаторе скриптового языка будет читаться каждое значение.
Ну да ладно, написал и оно даже работает как надо, теперь собственно к разбору Zip-файла.
--- 8< ---
function ParseFile( file_index, filename, base_address, codesize )
-- file_index идентифицирует файл с которым работает скрипт
-- все вызовы к хосту проходят с ним
-- готовой функции в Lua нет, пишем свою
local filesize = Filesize( filename )
-- сначала требуется определить сегмент(-ы) файла
-- будет один сегмент данных на весь файл
BttFileAddDataSegment( file_index,
0, -- виртуальный адрес
0, -- файловый адрес
filesize, -- размер в файле
0 ) -- виртуальный размер = размеру в файле
-- установим нужную кодировку для имен файлов
BttFileStringAsciizCP( file_index, ascii_cp_oem )
-- чтобы не скучно ждать интерпретатора будем показывать хотя бы количество файлов
local nfiles = 0
local address = base_address
-- установим начальный адрес для разбора файла
BttFileSetRunAddress( file_index, address )
local file = io.open( filename, "rb" )
local result = true
-- цикл пока result истинный
while result do
-- читаем маркеры и смотрим ...
local marker = ReadDword( file, address )
-- конец файла - конец цикла
if marker == nil then result = false
-- для соответствующих маркеров вызывается соответствующая структра из .sture-файла
elseif marker == ZIP_MARKER_LOCAL then result = BttFileRunStruct( file_index, "ZIP_LOCAL" )
nfiles = nfiles + 1
-- вызов функции для отображения количества насчитанных файлов
BttFileParseStage( file_index, string.format( "%i files", nfiles ) )
elseif marker == ZIP_MARKER_DATADESCRIPTOR then result = BttFileRunStruct( file_index, "ZIP_DATADESCRIPTOR" )
elseif marker == ZIP_MARKER_CENTRAL then result = BttFileRunStruct( file_index, "ZIP_CENTRAL" )
elseif marker == ZIP_MARKER_CENTRALEND then reuslt = BttFileRunStruct( file_index, "ZIP_CENTRALEND" )
-- если прилетел незнакомый маркер, расскажем об этом в log-окне
else result = false
BttFileLog( file_index, string.format( "Unknow marker %08X at address %X", marker, address ) )
end
-- получим следующий адрес после применения структуры
address = BttFileGetRunAddress( file_index )
-- цикл OFF
end
file:close()
-- в любом случае сообщаем что разбор прошел успешно
return 1
end
--- 8< ---
по сути, все делают вызовы BttFileRunStruct,
BttFileSet/GetRunAddress обвязка,
а BttFileStringAsciizCP/BttFileParseStage/BttFileLog для рюшек
вызов этого скрипта покажет байтитуру Zip-файла,
т.е. куда относится каждый байт файла
празднолюбопытствующие могут скачать набор Lua + .sture для Zip-файла здесь,
естественно ни в каких Demo оно работать не будет.
В следующий раз расскажу об уникальной возможнности, и это не дизассемблер.
На Lua не пишу, но видимо это делается так:
ОтветитьУдалитьfile = io.open(filename, "rb")
data = file:read(4)
dword = string.unpack("I4", data)
быстрее, но не в разы.
ОтветитьУдалитьожидаю комментарии от тех кто пишет на lua давно и много.