Очередная статья, посвященная задротству и извращенству. Почему? Да потому что плагины для AIMP гораздо проще создаются на Дельфи, и для этого автор написал SDK (хотя SDK, конечно, громко сказано - всё ограничилось единственным файлом с исходником на дельфи). Тем не менее, в практике ассемблера этот пример будет весьма полезен и необычен.
Начну с API, используемого плеером. Имеется набор COM-интерфейсов, через которые и ведется работа с многочисленными функциями AIMP'а.
Вот, например, один из таких интерфейсов из пресловутого SDK на Delphi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
IAIMP2Controller = interface function IsUnicodeVersion: Boolean; stdcall; // Must be True // callback function AIMP_CallBack_SET(dwCBType: DWORD; Proc: Pointer; User: DWORD): Boolean; stdcall; function AIMP_CallBack_Remove(dwCBType: DWORD; Proc: Pointer): Boolean; stdcall; // Status function AIMP_Status_Get(StatusType: DWORD): DWORD; stdcall; function AIMP_Status_Set(StatusType, Value: DWORD): boolean; stdcall; // Playlist function AIMP_PLS_Clear(ID: HPLS): Boolean; stdcall; function AIMP_PLS_Delete(ID: HPLS): Boolean; stdcall; function AIMP_PLS_New(Name: pWideChar): HPLS; stdcall; function AIMP_PLS_Info(Index: Integer; out PLSInfo: TPLSInfo): Boolean; stdcall; function AIMP_PLS_Count: Word; stdcall; function AIMP_PLS_GetFiles(ID: HPLS; out Strings: IPLSStrings): Boolean; stdcall; function AIMP_PLS_GetSelFiles(ID: HPLS; out Strings: IPLSStrings): Boolean; stdcall; function AIMP_PLS_AddFiles(ID: HPLS; Strings: IPLSStrings): Boolean; stdcall; function AIMP_PLS_SetPLS(ID: HPLS): Boolean; stdcall; // system function AIMP_NewStrings(out Strings: IPLSStrings): Boolean; stdcall; function AIMP_GetCurrentTrack(AInfo: PAIMP2FileInfo): Boolean; stdcall; function AIMP_QueryInfo(Filename: pWideChar; AInfo: PAIMP2FileInfo): Boolean; stdcall; function AIMP_GetSystemVersion: DWORD; stdcall; function AIMP_CallFunction(FuncID: DWORD): Boolean; stdcall; function AIMP_GetLanguage(Str: pWideChar; ACount: Integer): Integer; stdcall; function AIMP_GetCFGPath(Str: pWideChar; ACount: Integer): Integer; stdcall; function AIMP_GetSupportExts(Flags: DWORD; Str: pWideChar; BufSize: Integer): Integer; stdcall; // menu function AIMP_Menu_CreateEx(Parent: Pointer; MenuInfo: PMenuInfo): Pointer; stdcall; function AIMP_Menu_Create(MenuID: DWORD; MenuInfo: PMenuInfo): Pointer; stdcall; function AIMP_Menu_Update(Handle: Pointer; MenuInfo: PMenuInfo): Boolean; stdcall; function AIMP_Menu_Remove(Handle: Pointer): Boolean; stdcall; // extantion function AIMP_QueryObject(ObjectID: Integer; var Obj): Boolean; stdcall; end; |
Теперь можно начинать поэтапно разбирать код простейшего плагина. Этот плагин отслеживает начало воспроизведения песни в AIMP и записывает все названия и исполнителей прослушанных композиций в файл "SONGS.txt" на диске C:.
Сначала идет, как обычно, начальная часть программы на MASM32 - инклюды, директивы. Эта тема затронута в более ранних статьях, и здесь я не буду в нее углубляться.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.486 ; create 32 bit code .model flat, stdcall ; 32 bit memory model option casemap :none ; case sensitive include \masm32\include\windows.inc include \masm32\include\masm32.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\macros\macros.asm includelib \masm32\lib\masm32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib |
Теперь - объявления необходимых констант. Для создания плагина нам потребуется набор определенных функций из COM-интерфейса плеера. Как вызвать функцию COM-интерфейса из ассемблера? Этот вызов представляет вызов функции по указателю, причем сначала нужно определить смещение этого указателя относительно начала интерфейса в памяти. Допустим, мы хотим вызвать AIMP_CallBack_SET(). В COM-интерфейсе, представленном выше, эта функция занимает вторую позицию. На первой стоит IsUnicodeVersion(). Относительно начала интерфейса указатель на AIMP_CallBack_SET имеет смещение 4 байта (IsUnicodeVersion(), соответственно, 0 байт). Но до этих явно описанных функций в COM-интерфейсе есть еще три служебные, о которых можно прочитать в статьях, посвященных COM. Поэтому абсолютное смещение от самого начала интерфейса для функции AIMP_CallBack_SET = 4*3 + 4 байта. Высчитываем смещения для всех интересующих нас функций:
1 2 3 4 |
AIMP_CallBack_Set=12+4 ;(dwCBType: DWORD; Proc: Pointer; User: DWORD): Boolean; stdcall; AIMP_CallBack_Remove=12+8 ;(dwCBType: DWORD; Proc: Pointer): Boolean; stdcall; AIMP_Status_Get=12+12 ;(StatusType: DWORD): DWORD; stdcall; AIMP_GetCurrentTrack=12+60 ; dd ? ;(AInfo: PAIMP2FileInfo): Boolean; stdcall; |
Теперь определим некоторые необходимы константы, указываемые в качестве параметров функций:
1 2 3 |
AIMP_STS_STREAM_TYPE=39 AIMP_STS_Player=4 AIMP_PLAYER_STATE=11 |
А также некоторые структуры, необходимые для работы с COM-интерфейсами AIMP'а:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
TAIMP2FileInfo STRUCT ;структура для получения расширенной информации о проигрываемом файле cbSizeOf dd ? nActive dd ? nBitRate dd ? nChannels dd ? nDuration dd ? nFileSize dd ? dd ? nRating dd ? nSampleRate dd ? nTrackID dd ? nAlbumLen dd ? nArtistLen dd ? nDateLen dd ? nFileNameLen dd ? nGenreLen dd ? nTitleLen dd ? sAlbum dd ? sArtist dd ? sDate dd ? sFileName dd ? sGenre dd ? sTitle dd ? TAIMP2FileInfo ENDS TAIMPAddonHeader STRUCT ;структура для задания различной информации о плагине Version dd ? DllInstance dd ? GetPlgName dd ? GetAuthor dd ? Init dd ? Config dd ? Free dd ? TAIMPAddonHeader ENDS |
Далее - несколько прототипов функций, которые создадим мы. Об их назначении написано подробнее ниже.
1 2 3 4 |
GetSongName PROTO :DWORD,:DWORD,:DWORD,:DWORD init2 proto :dword config proto :dword, :dword PlayFile proto :dword, :dword |
Воспользуемся структурой TAIMPAddonHeader для определения данных о плагине. Первые два поля несущественны, и их можно установить в ноль. Далее идет указатель на функцию, возвращающую указатель на строку с названием плагина, потом - на функцию, возвращающую указатель на строку с именем автора плагина. Затем идут указатели на функции инициализации, конфигурации и выхода, которые описаны ниже.
1 2 3 4 5 6 7 8 9 10 11 12 |
.data plugin2 TAIMPAddonHeader {0,0,offset get_plugin_name,offset get_author,offset init2,offset config,offset quit} plugin_name db "Test",0 plugin_author db "(c) dx",0 .data? hInstance dd ? FileInfo TAIMP2FileInfo {} ;структура для получения информации о файле AIMP dd ? fDone dd ? ;переменные для работы с файлом iFile dd ? |
Наконец, секция кода. Для начала - пустая функция инициализации dll. Впрочем, она вполне может содержать какой-то полезный код.
1 2 3 4 5 6 7 8 9 10 11 12 |
.code LibMain proc instance:DWORD,reason:DWORD,reserved:DWORD .if reason == DLL_PROCESS_ATTACH invoke GetModuleHandle,NULL mov hInstance,eax mov eax,1 .endif ret LibMain ENDP |
Теперь - описание экспортируемой функции, которая вызывается плеером при загрузке нашего плагина. Ее имя должно быть AIMP_QueryAddon, и возвращать она должна указатель на вышеописанную структуру с данными о плагине.
1 2 3 4 |
AIMP_QueryAddon PROC mov eax,offset plugin2 ret AIMP_QueryAddon ENDP |
Далее идут простые функции, возвращающие указатели на строки с именем автора плагина и названием плагина.
1 2 3 4 5 6 7 8 9 10 |
get_plugin_name PROC mov eax,offset plugin_name ret get_plugin_name ENDP get_author PROC mov eax,offset plugin_author ret get_author ENDP |
Процедуру конфигурации я оставил пустой, но здесь может быть, например, код открытия окна с параметрами плагина. Вызывается эта функция при нажатии на кнопку "Настройки" в списке плагинов плеера.
1 2 3 4 |
config PROC Handle :DWORD, Win: DWORD xor eax,eax ret config ENDP |
Мы добрались до функции инициализации плагина. Она вызывается сразу после загрузки и получения информации о плагине, и здесь можно размещать полезный код. Передается ей в качестве единственного параметра указатель на главный COM-интерфейс плеера - IAIMP2Controller. Воспользуемся этим и установим callback-функцию на воспроизведение файла.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
init2 PROC, xAIMP :DWORD m2m AIMP,xAIMP ;сохраним указатель на интерфейс, он нам еще пригодится push 0 push offset PlayFile push AIMP_PLAYER_STATE ;это - параметры функции AIMP_CallBack_Set. Мы записываем их в стек в обратном порядке. mov eax,AIMP ;это указатель на this - на сам COM-интерфейс push eax ;мы его тоже записываем в стек mov eax,[eax] ;затем переходим к списку функций интерфейса call dword ptr [eax+AIMP_CallBack_Set] ;и вызываем по указателю нужную нам xor eax,eax ret init2 ENDP |
Теперь рассмотрим функцию, которую мы поставили в качестве callback'а на воспроизведение в плеере.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
PlayFile PROC User :DWORD, cType :DWORD ;первый передаваемый в функцию параметр толком не документирован, второй - тип вызова LOCAL stitle [512] :BYTE ;переменные для хранения имени исполнителя и названия песни LOCAL sartist [512] :BYTE .if cType!=AIMP_PLAYER_STATE ;если этот callback вдруг сработал не по воспроизведению/паузе/стопу - выходим jmp ext .endif push AIMP_STS_Player ;с помощью функции из COM-интерфейса узнаем, какой статус имеет плеер - воспроизведение/пауза/стоп mov eax,AIMP push eax mov eax,[eax] call dword ptr [eax+AIMP_Status_Get] cmp eax,1 ;если это не воспроизведение - выйдем jne ext push AIMP_STS_STREAM_TYPE ;узнаем, какой тип дорожки играется плеером mov eax,AIMP push eax mov eax,[eax] call dword ptr [eax+AIMP_Status_Get] test eax,eax ;если это не аудиодорожка - выйдем jne ext invoke RtlZeroMemory,addr sartist,512 ;обнулим память для хранения имен, а также память структуры для получения информации о воспроизводимом файле invoke RtlZeroMemory,addr stitle,512 invoke RtlZeroMemory,offset FileInfo,88 ;далее - заполняем структуру необходимыми данными mov FileInfo.cbSizeOf,88 lea eax,stitle mov FileInfo.sTitle,eax mov FileInfo.nTitleLen,512 lea eax,sartist mov FileInfo.sArtist,eax mov FileInfo.nArtistLen,512 push offset FileInfo ;и вызываем через интерфейс функцию для получения информации о текущем воспроизводимом файле mov eax,AIMP push eax mov eax,[eax] call dword ptr [eax+AIMP_GetCurrentTrack] ;теперь наши переменные sartist и stitle заполнены именем исполнителя и названием песни в Unicode invoke CreateFile,chr$("C:\SONGS.txt"),GENERIC_WRITE,0,0,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,0 ;запишем их в файл SONGS.txt на диске C: cmp eax,-1 jne @F xor eax,eax ret @@: mov iFile,eax invoke SetFilePointer,iFile,0,0,FILE_END ;переместим файловый указатель в конец файла invoke lstrlenW,addr sartist add eax,eax invoke WriteFile,iFile,addr sartist,eax,offset fDone,0 ;запишем название исполнителя invoke WriteFile,iFile,chr$(32,0,45,0,32,0,0),6,offset fDone,0 ;это строка " - " в Unicode invoke lstrlenW,addr stitle add eax,eax invoke WriteFile,iFile,addr stitle,eax,offset fDone,0 ;запишем название песни invoke WriteFile,iFile,chr$(13,0,10,0,0),4,offset fDone,0 ;символ перевода строки в Unicode invoke CloseHandle,iFile ;готово, закроем файл ext: xor eax,eax ret PlayFile ENDP |
Вот, собственно, и вся основная часть. Остается сделать процедуру, которая вызывается плеером всегда перед выгрузкой плагина. У нас она пустая, но вообще ее можно использовать, например, для освобождения памяти или закрытия активных интернет-соединений плагинга.
1 2 3 4 5 6 |
quit PROC xor eax,eax ret quit ENDP end LibMain |
Сохраним код в файл с именем aimp_plugin.asm.
Остается всего лишь создать файл с описанием экспорта dll aimp_plugin.def:
1 2 |
LIBRARY aimp_plugin EXPORTS AIMP_QueryAddon |
После этого достаточно скомпилировать всё это в dll с помощью подобного bat-файла (подробнее о компиляции проектов masm32 - в более ранних статьях):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@echo off cls SET PATH=C:\Masm32\bin SET INCLUDE=C:\Masm32\INCLUDE SET LIB=C:\Masm32\LIB ML /nologo -c -coff %1.asm if errorlevel 1 goto terminate LINK /nologo %1.obj /SUBSYSTEM:WINDOWS /DLL /DEF:%1.def /FILEALIGN:512 /VERSION:4.0 /MERGE:.rdata=.text /ignore:4078 /RELEASE /BASE:0x400000 if errorLevel 1 goto terminate echo OK :terminate |
Я после компиляции с 64-байтным стабом получил dll размером в 2.5 кб. Эту dll следует копировать в папку PlugIns в каталоге с AIMP, после чего перезапустить плеер или загрузить плагин через меню "Плагины".
Прикладываю все необходимые файлы в одном архиве, в том числе и dll: ZIP
В свое время писал аимповский плагин для Last.fm (когда официального еще не было), на С++. Если кому-нибудь интересно, могу поделиться исходниками.
Делись. Интересно. Хочу написать свой плагин на C++, взяв за основу твой.
Приветствую! А можно и мне тоже посмотреть? Хочу на основе написать свой плагин тоже, для тренировки. Спасибо.
Зачем спрашивать? Просто публикуй и всё, если готов поделиться, кому надо - сами возьмут.
Интересная статья! А если чисто теоретически, можно ли написать таким образом плагин в который входила бы возможность управления LPT портом, думаю вполне реально. Просто есть мысль автоматизировать процесс моей работы, есть цветомузыка, точнее несколько их разновидностей в клубе, на данный момент они управляются ручным (т.е. включением каждой в отдельности) способом, назовём тумблерным. А хотелось бы через реле или ключевую связку на силовых деталях. Но суть не в этом, программный путь… Самое важное! Визуальная среда, т.е. трек, его спектрограмма, ниже полосами каналы LPT, выбираем необходимый канал, проводим время включения-выключения определённого прибора, в итоге имеем скрытый файл привязанный к треку, при воспроизведении которого получаем световой оформление зала, ну и автоматический режим с возможностью корректировки))))) короче мысль есть а возможности сделать очень малы!
Я писал для WinAmp'а плагин, который через ком-порт посылает данные о частотах во время воспроизведения песни, а потом это все отображается вот в таком кубике. API AIMP'а совместимо с винамповским, данные по частотам тоже можно получить, так что да, возможно конечно.
Привет
Это плагин не работает с aimp v4
Вы можете написать другую стать о том как писать плагин для aimp v4 на masm.
Спасибо