Парсинг метаданных MMF-файлов на Ассемблере
Парсинг метаданных MMF-файлов на Ассемблере
SMAF (Synthetic Music Mobile Application File), он же MMF - мультимедийный формат данных, разработанный компанией Yamaha. В эпоху кнопочных сотовых телефонов этот формат использовался для создания очень компактных по размеру мелодий, хоть и невысокого качества звучания. Формат MMF может содержать не только мелодии, но и голоса, целые песни, и даже команды управления подсветкой телефона, если таковые поддерживаются аппаратом. Сейчас этот формат потерял актуальность, даже официальный сайт проекта окончательно закрылся несколько лет назад и доступен только в архиве. Описаний внутреннего формата MMF-файлов тоже практически нет, всю информацию пришлось собирать по крупицам из разных источников, в том числе и на основе анализа файлов "из дикой природы".
Файл в формате MMF состоит из глобального заголовка и последовательно расположенных секций. Заголовок представляет собой четырехсимвольную сигнатуру "MMMD", за которой следует DWORD с размером данных в формате Big-endian. Далее в статье будет подразумеваться, что все размеры данных указаны в формате Big-endian, это чтобы не повторяться. Итого на глобальный заголовок отводится 8 байт. Размер данных равняется общему размеру файла минус 8.
Дальше идут секции. Каждая секция также начинается с четырехбайтного заголовка и DWORD с размером данных секции. В качестве заголовка может быть одна из следующих строк: "CNTI", "OPDA", "MSTR", "MTRx" или "ATRx", других названий не предусмотрено. При этом в заголовках "MTRx" и "ATRx" четвертый байт означает версию формата. После заголовка следует содержимое секции. Музыкальные данные меня не интересуют, для получения необходимых данных понадобятся только секция "CNTI" (CoNTents Info) и/или "OPDA" (OPtional DAta). Данные по ним в основном подчерпнуты из упомянутой выше "дикой природы", то есть в результате анализа готовых файлов, так что их полноту и 100%-ю корректность я не гарантирую.
Начнем с секции CNTI. Согласно спецификации, она должна содержать пять обязательных значений в строго определенной последовательности: Contents Class, Contents Type, Contents Code Type, Copy Status и Copy Counts. У меня имеются две разновидности MMF-файлов, в одном из которых размер этой секции составляет ровно 5 байт, отсюда можно сделать вывод, что каждый из этих параметров имеет размерность в 1 байт. Справочники каких-либо конкретных значений по каждому из параметров я не нашел.
Вариант секции CNTI
Есть и второй вариант, в котором секция CNTI занимает гораздо больше 5 байт. По ее структуре можно предположить, что первые 5 байт аналогичны предыдущему варианту, а следующие за ними как раз содержат метаданные и другую информацию в виде блоков.
Вариант секции CNTI
Формат этих блоков следующий. Первые два байта - идентификатор данных, после него следует 1 байт ":" и сами данные, оканчивающиеся символом "," в том числе и последний блок. Если в данных, например, в оригинальном названии композиции, встречается символ ",", то в блоке он экранируется символом "\". Точного списка зарезервированных символов у меня также нет, но, как можно предположить, любой символ, следующий за "\", при получении данных надо просто копировать в результирующую строку. Списка идентификаторов я тоже нигде не нашел, опытным путем удалось выяснить, что "ST" - название композиции, "SW" и/или "AW" - название исполнителя, а остальное меня не интересует. Но стоит не забывать, что наличие ни одного из перечисленных идентификаторов не является обязательным. Строки записаны в кодировке UTF-8, это надо учитывать при загрузке строки и последующей ее конвертации.
Секция OPDA
Переходим к секции OPDA. Главное, что требуется знать, что теоретически в файле ее может и не быть. Но если секция CNTI содержит только 5 байт, то все основные данные с максимальной вероятностью содержатся в секции OPDA. Первые 8 байт содержимого секции OPDA достоверно опознать не удалось, в разных файлах они мало отличаются. После них следуют блоки в формате, очень похожем на описанные выше: два символа с идентификатором, WORD с размером данных, затем сами данные. Идентификаторы данных аналогичны секции CNTI.
Для воспроизведения рингтонов в формате MMF удобнее всего использовать фирменный плеер YAMAHA MidRadio Player. Не самый великий шедевр софтостроения, но лучше, чем ничего.
Осталось сделать универсальный парсер секций. В нем учтены все известные мне варианты загрузки строк, а так же их преобразование в кодировку windows-1251 из UTF-8. При необходимости можно легко добавить в обработку нужные вам идентификаторы данных.
Code (Assembler) : Убрать нумерацию
- ; Прочитать заголовок файла
- invoke _lread,[desc],buff,200h
- ; Проверить корректность файла
- mov esi,buff
- lodsd
- cmp eax,'MMMD'
- jne loc_close
- lodsd
- loc_scan:
- lodsd
- cmp eax,'CNTI'
- je loc_process_cnti
- cmp eax,'OPDA'
- je loc_process_opda
- jmp loc_done
- ; Обработка секции CNTI
- loc_process_cnti:
- lodsd
- bswap eax
- cmp eax,5
- jne @f
- add esi,eax
- jmp loc_scan
- @@:
- mov ebx,eax
- add ebx,esi
- add esi,5
- loc_parse_cnti:
- lodsw
- inc esi
- ; Скопировать название трека
- mov edi,title
- cmp ax,'ST'
- je loc_load_str_1
- ; Скопировать название артиста
- mov edi,artist
- cmp ax,'SW'
- je loc_load_str_1
- cmp ax,'AW'
- je loc_load_str_1
- @@:
- lodsb
- cmp al,','
- je loc_chk_bound_1
- cmp al,'\'
- jne @b
- lodsb
- jmp @b
- loc_chk_bound_1:
- cmp esi,ebx
- jb loc_parse_cnti
- jmp loc_done
- loc_load_str_1:
- lodsb
- cmp al,','
- je loc_chk_bound_1
- cmp al,'\'
- jne @f
- lodsb
- @@:
- stosb
- jmp loc_load_str_1
- ; Обработка секции OPDA
- loc_process_opda:
- lodsd
- bswap eax
- mov ebx,eax
- add ebx,esi
- add esi,8
- loc_parse_opda:
- lodsw
- ; Скопировать название трека
- mov edi,title
- cmp ax,'ST'
- je loc_load_str_2
- ; Скопировать название артиста
- mov edi,artist
- cmp ax,'SW'
- je loc_load_str_2
- cmp ax,'AW'
- je loc_load_str_2
- lodsw
- xchg al,ah
- movzx eax,ax
- add esi,eax
- loc_chk_bound_2:
- cmp esi,ebx
- jb loc_parse_opda
- jmp loc_done
- loc_load_str_2:
- lodsw
- xchg al,ah
- movzx ecx,ax
- rep movsb
- mov al,0
- stosb
- jmp loc_chk_bound_2
- loc_done:
- ; Удалить начальные и конечные пробелы
- invoke PathRemoveBlanks,artist
- invoke PathRemoveBlanks,title
- ; Получить нужную длину строки для конвертирования
- invoke MultiByteToWideChar,CP_UTF8,0,artist,-1,0,0
- ; Промежуточное конвертирование UTF-8 -> UTF-16
- invoke MultiByteToWideChar,CP_UTF8,0,artist,-1,buff,eax
- ; Получить нужную длину строки для конвертирования
- invoke WideCharToMultiByte,1251,0,buff,-1,0,0,0,0
- ; Финальное конвертирование UTF-16 -> cp1251
- invoke WideCharToMultiByte,1251,0,buff,-1,artist,eax,0,0
- ; Получить нужную длину строки для конвертирования
- invoke MultiByteToWideChar,CP_UTF8,0,title,-1,0,0
- ; Промежуточное конвертирование UTF-8 -> UTF-16
- invoke MultiByteToWideChar,CP_UTF8,0,title,-1,buff,eax
- ; Получить нужную длину строки для конвертирования
- invoke WideCharToMultiByte,1251,0,buff,-1,0,0,0,0
- ; Финальное конвертирование UTF-16 -> cp1251
- invoke WideCharToMultiByte,1251,0,buff,-1,title,eax,0,0
Просмотров: 196 | Комментариев: 1
Комментарии
Отзывы посетителей сайта о статье
Илья
(15.09.2024 в 21:48):
Спасибо за статью. Интересно, наверное вряд ли мы доживем до того момента, когда Microsoft полностью перейдет на utf8. А если и доживем, то старым программам это не понравится.
Добавить комментарий
Заполните форму для добавления комментария