TUnRar без DLL
Дмитро Мозулёв
дата публікації 28-11-2006 7:43
Добрий день. Перед вами моя перша стаття. У ній розглядаються відразу два важливих питання: "Як програмно розпаковувати * .rar архіви" і "Що зробити, щоб не тягати * .dll бібліотеки за своїм додатком". Обидві теми заслуговують окремої розмови. Почати пропоную з першої.
Пригадую, як кілька років тому був шалено радий, коли випадково в відомих "Радах по Delphi від Валентина Озерова" натрапив на розділ "Створюємо власний UnRar, використовуючи unrar.dll". Цілий день я мучився, опрацьовуючи наведений лістинг (потім до речі, де тільки не натикався на листинги "один в один"; дивіться тут ). Скінчилося все тим, що розплювався, статут коментувати "зайві" рядки, засмутившись від численних помилок доступу до пам'яті, закинув проект на кілька років ...
Ну, до суті. Програмно розпакувати * .rar архіви можна двома способами:
- викликати WinRar.exe з командного рядка з відповідними ключами
- скористатися API функціями бібліотеки "UnRar.dll"
Перший спосіб я описувати не буду (бо не знаю), мова піде про другий. Бібліотеку "unrar.dll" можна знайти в папці, в яку встановлений WinRar або в архіві, який додається до статті. Для того щоб додаток могло працювати з цією бібліотекою, dll повинна розташовуватися або в одній папці з exe, або в системній папці Windows.
Напишемо ми багато. Зведеться, правда, все до однієї процедури UnRarFile, перший аргумент якої - rar-файл, другий - результуюча директорія [яку можна і не вказувати]:
procedure UnRarFile (RarFileName: string; Directory: string = '');Відразу обмовлюся: Unrar API складніше, ніж хотілося б. Тому деякі перевірки на помилки і опису різних режимів "розархівації" я опустив навмисно.
Для більшої прозорості відбувається давайте кинемо на форму компоненти Memo і Button. Все, що відбувається в процесі розпакування вашого архіву буде фіксуватися в Memo. При натисканні кнопки відбуватиметься розархівування:
procedure TForm1.Button1Click (Sender: TObject); begin Memo1.Lines.Clear; UnRarFile ( 'C: \ Фото.rar'); end;Ну, що, готові творити свої "UnRar - разархіватори"? Тоді, в путь!
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class (TForm) Button1: TButton; Memo1: TMemo; procedure Button1Click (Sender: TObject); private public end; var Form1: TForm1; implementation procedure LOG (S: string); begin Form1.Memo1.Lines.Add (S); end; type TRAROpenArchiveData = record ArcName: PChar; OpenMode: cardinal; OpenResult: cardinal; CmtBuf: PChar; CmtBufSize: cardinal; CmtSize: cardinal; CmtState: cardinal; end; TRARHeaderData = record ArcName: array [0 .. 259] of char; FileName: array [0 .. 259] of char; Flags: cardinal; PackSize: cardinal; UnpSize: cardinal; HostOS: cardinal; FileCRC: cardinal; FileTime: cardinal; UnpVer: cardinal; Method: cardinal; FileAttr: cardinal; CmtBuf: PChar; CmtBufSize: cardinal; CmtSize: cardinal; CmtState: cardinal; end; TUnRarCallBack = function (msg: Cardinal; UserData, P1, P2: integer): integer; stdcall; function RAROpenArchive (var ArchiveData: TRAROpenArchiveData): THandle; stdcall; external 'unrar.dll' name 'RAROpenArchive'; function RARCloseArchive (hArcData: THandle): Integer; stdcall; external 'unrar.dll' name 'RARCloseArchive'; function RARReadHeader (hArcData: THandle; out HeaderData: TRARHeaderData): Integer; stdcall; external 'unrar.dll' name 'RARReadHeader'; function RARProcessFile (hArcData: THandle; Operation: Integer; DestPath: pchar; DestName: pchar = nil): Integer; stdcall; external 'unrar.dll' name 'RARProcessFile'; procedure RARSetCallback (hArcData: THandle; Callback: TUnRarCallback; UserData: longint); stdcall; external 'unrar.dll' name 'RARSetCallback'; const ERAR_END_ARCHIVE = 10; RAR_OM_EXTRACT = 1; RAR_EXTRACT = 2; RAR_SUCCESS = 0; UCM_PROCESSDATA = 1; var RARHeaderData: TRARHeaderData; ArcStruct: TRAROpenArchiveData; CmtBuffer: array [0 .. 1023] of char; function UnRarCallBack (msg: Cardinal; UserData, P1, P2: integer): integer; stdcall; begin Result: = 0; if msg = UCM_PROCESSDATA then LOG ( 'розпакувати' + IntToStr (P2) + 'байт'); end; function OpenRARArchive (FileName: string): THandle; begin ZeroMemory (@ArcStruct, sizeof (ArcStruct)); ArcStruct.OpenMode: = RAR_OM_EXTRACT; ArcStruct.ArcName: = pchar (FileName); ArcStruct.CmtBuf: = CmtBuffer; ArcStruct.CmtBufSize: = sizeof (CmtBuffer); Result: = RAROpenArchive (ArcStruct); end; procedure UnRarFile (RarFileName: string; Directory: string = ''); var hRAR: THandle; hReadHeader: integer; hProcessHeader: integer; begin UniqueString (RarFileName); RarFileName: = ExpandFileName (RarFileName); UniqueString (Directory); if length (Directory) = 0 then Directory: = ChangeFileExt (RarFileName, '') else Directory: = ExpandFileName (Directory); LOG ( 'розпаковувати архів "' + RarFileName + '" в папку "' + Directory + '" ...'); CharToOem (pchar (Directory), pchar (Directory)); hRAR: = OpenRARArchive (RarFileName); RARSetCallback (hRar, UnRarCallBack, 0); hReadHeader: = 0; hProcessHeader: = 0; REPEAT hReadHeader: = RARReadHeader (hRar, RARHeaderData); if hReadHeader = ERAR_END_ARCHIVE then Break;Перш, ніж почати розбиратися з кодом, давайте краще попрактікуемся! Перенесіть вищевказаний програмний код в свій проект, розтягніть Memo ширше, Memo.ScrollBars задайте ssBoth, в обробнику TForm1.Button1Click вкажіть реально існуючий rar-архів. Запускайте додаток, тисніть на кнопку. У мене вийшло що щось на зразок:
Розпаковую архів "C: \ фото.rar" в папку "C: \ фото" ... розпаковую файл "МОІ_ФОТКІ" ... 0 байт. Розпаковую файл "МОІ_ФОТКІ \ Ми З Любою" ... 0 байт. Розпаковую файл "МОІ_ФОТКІ \ Фотки Серьоги" ... 0 байт. Розпаковую файл "МОІ_ФОТКІ \ Ми З Любою \ DSCN1281.JPG" ... 307150 байт. Розпакував 307150 байт розпаковувати файл "МОІ_ФОТКІ \ Ми З Любою \ DSCN1282.JPG" ... 262913 байт. Розпакував 262913 байт розпаковувати файл "МОІ_ФОТКІ \ Ми З Любою \ Thumbs.db" ... 328192 байт. Розпакував 328192 байт розпаковувати файл "МОІ_ФОТКІ \ Фотки Серьоги \ PICT0391.JPG" ... 263412 байт. Розпакував 263412 байт розпаковувати файл "МОІ_ФОТКІ \ Фотки Серьоги \ PICT0392.JPG" ... 274698 байт. Розпакував 274698 байт розпаковувати файл "МОІ_ФОТКІ \ Фотки Серьоги \ Thumbs.db" ... 119296 байт. Розпакував 119296 байт розпаковувати файл "МОІ_ФОТКІ \ Фотки Серьоги \ Арія - Візьми моє сердце.mp3" ... 5066024 байт. Розпакував 4194045 байт розпакував 259 байт розпакував 871720 байт Все Розпаковано, закриваю архів.Пропоную проаналізувати отриманий текст. Перший рядок показує, який архів разархивируем і в яку папку. Так як папку явно ми не вказували, папка "вибирається автоматично".
Наступні 3 рядки показують, що при розархівації, якщо в архіві є піддиректорії, спочатку створюється дерево каталогів. Розархівування самих файлів починається тільки з п'ятого рядка. Відразу зверніть увагу на останній разархівіруемий файл (який невідомо як опинився в моєму архіві фотографій) - mp3-трек групи "Кипелов". Вихідний mp3 файл займає близько 5мб (5066024 байт), проте розархівуйте він в 3 етапи. Перевіряйте: 5066024 = 4194045 + 259 + 871 720.
Питання: "Чому деякі файли розархівуйте всього в один етап?"
Відповідь: Все залежить від розміру і фактора стисливості архівіруемого файлу. Файл розбивається на частини, якщо він великий чи його розбиття призведе до кращої компресії.
А ось тепер, коли вже більш-менш зрозуміло, як відбувається розархівування, можна почати розбирати програмний текст.
відкрити файл-архів, отримати дескриптор цикл: прочитати заголовок (визначаємо, який файл разархивируем) розпакувати файл якщо не досягли кінця архіву, то знову прочитати заголовок закрити файл-архівТепер можна дивитися реалізацію процедури UnRarFile. Сподіваюся, багато стало зрозуміло.
Перед тим, як розпакувати якийсь файл, ми отримуємо його заголовок. Тема містить безліч параметрів: ім'я разархівіруемого файлу (FileName), розмір в архіві (PackSize), реальний розмір (UnpSize), атрибути файлу (FileAttr; по ним, до речі, можна дізнатися, директорія це чи ні), ряд інших параметрів. Якщо ви бажаєте розпакувати не весь архів, а тільки його частину (тільки * .exe-файли, наприклад) і на черзі непотрібний файл, то просто не викликайте функцію RARProcessFile.
До речі кажучи, якщо потрібно, підтримується розархівування "в інший файл". Для цього в останньому аргумент функції RARProcessFile вказувати не nil, а нове ім'я файлу. Не забувайте, що в UnRar рядки в OEM кодуванні.
Якщо результат цієї функції дорівнює нулю, розархівування триває. Якщо -1, то припиняється (інші значення не пробував). Цією особливістю користуються, щоб припустимо скасувати разархивацию.
Повідомлення (msg) може приймати не тільки константу UCM_PROCESSDATA, але і UCM_NEEDPASSWORD (коли архів запаролено) і UCM_CHANGEVOLUME (не знаю, як використовується);
Проте, найголовнішим залишається UCM_PROCESSDATA. Коли ви викликаєте процедуру RARProcessFile, файл може розпакувати по частинах. При кожній розархівації такої частини, викликається Callback функція з повідомленням UCM_PROCESSDATA; P2 - розмір разархівіруемой частини.
Разархивируя файл в тому ж WinRAR, ми бачимо 2 смуги стану: верхня (яка частина архіву вже розпакована, яка ще залишилася) і нижня (все те ж саме щодо поточного файлу). Як реалізувати нижню, думаю, припускаєте: при читанні заголовка потрібно дізнатися, скільки займає розпакований файл (зберегти в змінну Total, наприклад) і занулити якусь целочисленную змінну (Cur, наприклад). У Callback функції, при отриманні повідомлення UCM_PROCESSDATA, до Cur додаєте локальний параметр P2.
Відсоток = round (Cur / Total * 100).Розрахувати відсоток розпакування всього архіву виявляється складніше. Навіть з урахуванням того, що розмір кожного файлу знаходиться елементарно (UnpSize), в структурі TRAROpenArchiveData немає ні натяку на що-небудь типу GlobalUnPackSize. Ну і як бути?
У будь-якому випадку, GlobalUnPackSize знайти все ж треба. Робиться це приблизно так (сильно спрощено):
GlobalUnPackSize: = 0; hRAR: = OpenRARArchive (RarFileName); RARSetCallback (hRar, UnRarCallBack, 0); While RARReadHeader (hRar, RARHeaderData) ERAR_END_ARCHIVE do Inc (GlobalUnPackSize, RARHeaderData.UnpSize); RARCloseArchive (hRAR); //Так, згоден, ні про яку реалізації класу або вже тим більше компонента TUnRar, мови не йшло. Проте, сподіваюся, що все-таки знайдеться людина, яка візьметься за реалізацію нормального компонента, що не потребує dll, що працює коректно і щоб інтерфейс доброзичливий був.
Єдине, що заважає перенесенню поточної реалізації в ООП, це - функція UnRarCallBack. Заковика в тому, що якщо оголосити таку процедуру в public секції класу, то компілятор не дозволить поставити її в якості параметра функції RARSetCallback (). І справа тут не в компіляторі; справа в угодах про виклик на рівні асемблера.
На щастя, обходиться це річ просто. Останній аргумент функції RARSetCallback () - UserData: longint, займає чотири байти, в нього можна помістити посилання на компонент ...
Пояснюю. Припустимо, у нас є клас TUnRar. Створимо в ньому функцію TUnRar.RARCallBack (msg, P1, P2: integer): integer, яка в кінцевому рахунку і буде приймати всі повідомлення. Припустимо, буде так само процедура TUnRar.UnRarFile (), в якій виклик RARSetCallback () трохи змінимо:
RARSetCallback (hRar, UnRarCallBack, longint (self)); тНу а UnRarCallBack буде виглядати так:
function UnRarCallBack (msg: Cardinal; UserData, P1, P2: integer): integer; stdcall; begin Result: = TUnRar (UserData) .RARCallBack (msg, P1, P2); end;Правда, здорово?
Як це не дивно, пару реалізацій таких компонентів я бачив. По-перше, це закритий компонент в пакеті ZipTV. Другий - німецький компонент (відкритий!) DFUnRar (качайте тут ). Обидва не зовсім зручні, росіянин не підтримують, вимагають unrar.dll.
Якщо скласти вихідні DFUnRar, одного ледачого ентузіаста і цю статтю, то можна зробити непоганий компонент. Ну а в мене, на жаль, часу немає.
Гаразд! Тепер позбавляємося від цієї unrar.dll !!!
Для мене, чесно кажучи, до сих пір не зрозуміло, чому ж багато програмісти прагнуть позбутися від наявності використовуваних dll в своїй директорії. Я скажу більше, я - один з таких програмістів :).
Кому-то, напевно, хочеться отримати деяку автономність свого застосування (якщо програма не знайде необхідну dll, то коректно воно працювати, м'яко кажучи, не буде). Хтось, напевно, хоче приховати використання платних (або безкоштовних) Dll-бібліотек (це і всілякі графічні, і скріптові, і звукові [BASS, MikMod, FMOD, ...], і фізичні [Newton, ODE, Tokamak, ...] движки , і комерційні dll-бібліотеки, і ...). Хтось, напевно, має безліч коду на Сі ++ і хоче використовувати його в Delphi; у кожного свої причини. Взагалі-то, використовувати Сі-шний код можна і не створюючи dll. Але це тема вже окремої статті ... (ладно, користуючись нагодою, напишу пізніше пару слів).
Мені відомі всього три способи інтеграції DLL в свій додаток:
- Включити DLL в файл ресурсів, файл ресурсів прілінкованние до свого додатком. При завантаженні, зберегти DLL в окремий (краще в папці "Temp") файл; динамічно завантажити всі функції з отриманої DLL.
- Скористатися утилітою Dll2Lib, програмувати на VisualC ++.
- Скористатися утилітами DLLTools.
Перший спосіб, сподіваюся, зрозуміла всім. Другий спосіб я поясню. Існує досить відома утиліта DLL2Lib. Вона, як можна здогадатися, конвертує DLL-бібліотеки в статичні * .lib-бібліотеки. Отриману * .lib можна вільно використовувати в додатках на Visual C ++. Builder C ++ теж використовує * .lib-бібліотеки, але спроба лінковки такої бібліотеки через несумісність форматів (про них поговоримо в кінці) закінчиться помилкою компілятора.
Поговоримо тепер про останньому способі. Зовсім недавно Vga дав мені посилання , За що йому величезне спасибі. Завантажувати там ні чого не треба, до статті вже додається істотно змінений архів DLLTools.zip. Саме його вам потрібно завантажити для подальшої роботи.
Архів містить всього 7 файлів (у справі тільки 3). Перше, що потрібно зробити, це скопіювати в якусь глобальну директорію (в $ Delphi \ Lib \, наприклад) модуль "DLLLoader.pas". Це основний модуль, його автором є Benjamin Rosseaux ( www.0ok.de ). Незважаючи на те, що цей і всі інші модулі проекту я, як уже казав, істотно змінив, справжнім автором залишається Benjamin Rosseaux.
Другий за важливістю є утиліта Dll2Pas (вона взагалі повністю переписана). Саме вона перетворює DLL в автономний * .pas файл-заготовку. Для того щоб їй скористатися, скопіюйте всі конвертовані DDL-бібліотеки в директорію DLLTools і запустіть утиліту. Якщо в директорії знаходиться тільки одна "UnRar.dll", то утиліта створить тільки один файл "UnRarLib.pas". Відкривши цей модуль в Delphi, ви побачите, що він "поділений" на кілька блоків:
- БЛОК КОНСТАНТ і ТИПІВ
- БЛОК ФУНКЦІЙ і ПРОЦЕДУР
- БЛОК ВНУТРІШНІХ ФУНКЦІЙ і ПРОЦЕДУР
- Ініціалізації ВСІХ ФУНКЦІЙ і ПРОЦЕДУР
- БЛОК деініціалізацію (якщо потрібно)
Давайте заповнимо деякі з них. У перший блок перемістіть опису всіх типів (TRAROpenArchiveData, TRARHeaderData, TUnRarCallBack) і констант (ERAR_END_ARCHIVE, RAR_OM_EXTRACT, RAR_EXTRACT, RAR_SUCCESS, UCM_PROCESSDATA), що відносяться до бібліотеки UnRar.
Другий блок поки пропустимо, в третій перенесемо допоміжну функцію OpenRARArchive (). Всі змінні (RARHeaderData, ArcStruct, CmtBuffer) перемістіть в секцію interface (тобто вище секції implimentation).
П'ятий блок нам взагалі не потрібен (видаляти його не обов'язково). Залишилося заповнити другий і четвертий. Якщо свою DLL ви завантажуєте динамічно, то вам не складе великих зусиль заповнити ці блоки. Якщо ви завантажуєте її статично (як в нашому випадку), то на допомогу приходить утиліта Static2DynDLL. Користуватися їй так:
- У файл "PROCS.txt" скопіювали свої статичні функції. Коментарі, якщо є, видаляти не обов'язково. Опис всій функції повинно бути у всю рядок: function RARCloseArchive (hArcData: THandle): Integer; stdcall; external 'unrar.dll' name 'RARCloseArchive'; . . .
- Запускаєте утиліту Static2DynDLL
- Вміст файлу "pointers.txt" копіюєте до другого блоку: RAROpenArchive: function (var ArchiveData: TRAROpenArchiveData): THandle; stdcall; RARCloseArchive: function (hArcData: THandle): Integer; stdcall; RARReadHeader: function (hArcData: THandle; out HeaderData: TRARHeaderData): Integer; stdcall; RARProcessFile: function (hArcData: THandle; Operation: Integer; DestPath: pchar; DestName: pchar = nil): Integer; stdcall; RARSetCallback: procedure (hArcData: THandle; Callback: TUnRarCallback; UserData: longint); stdcall;
- Вміст файлу "load" копіюєте в четвертий блок: RAROpenArchive: = GetProcedure ( 'RAROpenArchive'); RARCloseArchive: = GetProcedure ( 'RARCloseArchive'); RARReadHeader: = GetProcedure ( 'RARReadHeader'); RARProcessFile: = GetProcedure ( 'RARProcessFile'); RARSetCallback: = GetProcedure ( 'RARSetCallback');
От і все! Тепер ви - власники статичної бібліотеки "UnRar" для Delphi !!! Запускайте своє тестове додаток і насолоджуйтеся життям. На всякий випадок, якщо когось зацікавить, яким чином насправді відбувається завантаження DLL, читайте исходники DLLLoader.pas і статтю Завантажувач PE-файлів .
Питання: Чому отриманий модуль додає до exe в 2 рази менше, ніж вихідна DLL? Чи не тому, що видаляє з DLL непотрібні шматки коду?
Відповідь: Ні. Насправді використовується inflate-стиснення. Такий підхід економить не тільки розмір exe, але і займане місце в пам'яті.
Питання: Чому отриманий модуль в 2 рази більше, ніж вихідна DLL?
Відповідь: Для того щоб "вписати" дані всередину * .pas-файлу, використовується масив байт. На опис 1байт даних йде 4байта (символу) тексту. Оскільки вихідний файл стиснутий приблизно в 2 рази, а на запис кожного байта йде 4байта, то: результат = розмір (DLL) / 2 * 4 = 2 * розмір (DLL) ... Дані навряд чи будуть записуватися інакше!
Питання: В процесі роботи мого програми відбувається помилка доступу до пам'яті за адресою 0. Як лікувати?
Відповідь: Або якийсь із функцій немає в DLL, або статична функція була неправильно конвертована утилітою Static2DynDLL. Наприклад, якщо статична функція виглядає так:
function MessageBox (...): Integer; stdcall; external user32 name 'MessageBoxA';то утиліта перетворює в: MessageBox: = GetProcedure ( 'MessageBox'); Всі питання до Benjamin Rosseaux!
Питання: У моїй DLL "зашиті" ресурси. Як їх звідти можна завантажити?
Відповідь: Поняття не маю. Можете вивчити вихідні коди і зазначену вище статтю, щоб додати таку можливість в TDLLLoader (якщо таке можливо).
Питання: які зміни вніс ти?
відповідь:
- стиснення
- розділив отриманий файл на блоки-коментарі
- змінив конструктор і опис класу TDLLLoader
- функція GetProcedure ( '') перестала бути чутлива до регістру аргументу
- Dll2Pas заробила на порядок швидше і відразу з усіма Dll в директорії
- змінені імена файлів на вході і виході утиліти Static2DynDLL
Усе! Успіхів вам! Я тут поки кілька днів статтю і утиліти писав, зі своєю дівчиною посварився; каже, весь час за комп'ютером проводжу, їй уваги майже не приділяю. Все, бувай!
Так, мало не забув, що обіцяв розповісти про це. Гаразд. Найпростіший і надійний спосіб - перетворити С ++ код в DLL, яку без проблем можна використовувати в своєму додатку.
По-друге можна постаратися відкомпілювати код в одному з C ++ компіляторів формату OMF, а отримані * .obj файли прілінкованние до свого додатком директивою {$ L ...}. "Що за формат такий - OMF" - запитають деякі. Справа в тому, що існують два несумісних формату * .obj-файлів: OMF і COFF. Перший використовується в Delphi, C ++ Builder, Watcom C ++, Intel C ++ Compiler, ..., другий використовується в Visual C ++ і деяких інших компіляторах.
Можна, правда, скористатися утилітою Coff2Omf . Для того щоб їй скористатися, створіть файл, наприклад, "Execute.bat" такого змісту:
COFF2OMF.EXE MyObjFile.obj pauseПотім запустіть цей файл на виконання, повинен вийти файл формату OMF.
Але, якщо все ж є можливість скомпілювати його в OMF-компіляторі, то відкомпілюйте в ньому. вісь список безкоштовних Сі ++ компіляторів . можна завантажити безкоштовний Борландовскій C ++ компілятор .
Завантажено? Тепер заради інтересу спробуйте взяти якийсь C ++ модуль, відкомпілювати його і, якщо потрібно, перетворити його в OMF. Тепер спробуйте прілінкованние його до Delphi ... Помилка? Я так и думав! Ось майже дослівно те, що з даного приводу пише Kvant на форумі сайту RSDN :
Delphi розуміє OMF об'єктні файли, та й то з деякими обмеженнями. Обмежень, в основному, 3 штуки:
- Виклики функцій через імпорт в Delphi зводяться до формату "call [jmp адреса]". Якщо в об'ектніке вказано просто "call адреса", компілятор Delphi видасть помилку.
- Імена імпортованих функцій не повинні містити декорації (типу _imp__імя @ N), за винятком борландовского формату.
- Фіксапи (записи FIXUPP) повинні бути виключно 5-байтними.
Є ще обмеження на імена секцій, TLS і т.д. Проте, в більшості випадків досить мати на увазі лише ті 3 вищеописаних обмеження, щоб отримати працездатний OMF-об'ектнік без використання сторонніх утиліт.
Рекомендую глянути на документацію та вихідні утиліти OMF2D by EliCZ .
Ось тепер точно Все!
PS мене по даній темі питати нічого не треба, тому що я не прілінкованние до Delphi-додатком ще жодного об'ектніка, написаного на Сі; вся інформація отримана з Інтернету і зі слів Vga.
До матеріалу додаються файли:
[ Використання і створення DLL ] [ Архівація (алгоритми стиснення) ]
Обговорення матеріалу [02-07-2008 5:22] 39 повідомленьНу і як бути?
Чи не тому, що видаляє з DLL непотрібні шматки коду?
0. Як лікувати?
Як їх звідти можна завантажити?
Завантажено?
Помилка?