0cx00007b или установка драйверов из-под программы

habr.png

Вступление

Доброго времени суток. Знакомо ли вам исключение 0cx00007b? С момента перевода движка X-Ray под x64 приходило очень много репортов о проблеме 0cx00007b. В 90% случаев, это была проблема с отсутствием 64 битного драйвера OpenAL.


Идеи по решению проблемы

Первое время мы постоянно отвечали, что нужно установить драйвер, спустя пару месяцев написали FAQ по запуску и вероятным проблемам. Но подобные репорты не уходили, народ у нас в СНГ читать не особо любит, поэтому мы решили решить проблему радикально: устанавливать драйвер из-под движка, если таковой отсутствует.


Шаг 1: запуск программы, когда не хватает dll

Самый простой способ подключения библиотек друг к другу — компоновка (pragma comment), но в нашем случае так делать нельзя.

Итак, шаг 1: явная линковка или привет extern "C".
Что нам требуется: отвязать exe от библиотек движка. Делается это следующим способом:
1) Выносим функцию запуска движка в динамическую библиотеку:

extern "C" --// LoadLibrary, если мне не изменяет память, Сишное API
{
   void ENGINE_API RunApplication(LPCSTR commandLine)
   {
         ... // Your code
   }
}

2) Вызываем функцию из нашего exe:

using RunFunc = void(__cdecl*)(const char*); // Объявляем удобный для себя тип функции 
bool OpenALFound = false; // Оно нам потом пригодится

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    if(OpenALFound)
    {
        HMODULE hLib = LoadLibrary("xrEngine.dll"); // Получаем модуль с нашей функцией
        IsRunFunc RunFunc = (IsRunFunc)GetProcAddress(hLib, "RunApplication"); // Получаем функцию из модуля
        RunFunc(params); // Запускаем движок
    }

    return 0;
}


Шаг 2: Проверка драйвера

Ну, тут всё просто, получаем системный (может кто-то удивится, но OS не всегда на C:) раздел и проверяем dll в папке с драйверами:

/// R_ASSERT - внутренний макросс, проверяющий значение первого аргумента на истину, если лож - программа выведет сообщение об ошибке
... // WinMain code
TCHAR szOpenALDir[MAX_PATH] = { 0 }; // Получаем системный патч
R_ASSERT(GetSystemDirectory(szOpenALDir, MAX_PATH * sizeof(TCHAR)));

#ifndef UNICODE 
    _snprintf_s(szOpenALDir, MAX_PATH * sizeof(CHAR), "%s%s", szOpenALDir, "\\OpenAL32.dll");
#else
    _snwprintf_s(szOpenALDir, MAX_PATH * sizeof(WCHAR), L"%s%s", szOpenALDir, L"\\OpenAL32.dll");
#endif
DWORD dwOpenALInstalled = GetFileAttributes(szOpenALDir);

// Атрибуты валидные, значит драйвер на месте. Всё в порядке, запускаем.
if (dwOpenALInstalled != INVALID_FILE_ATTRIBUTES)
{
    OpenALFound = true;
}


Шаг 3: Файл не найден

Первым делом нам нужно попросить права администратора у пользователя, т.к. работать придётся с системным каталогом:

// Проверяем, запущенны ли мы от администратора
bool IsProcessWithAdminPrivilege()
{
    SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
    LPVOID pAdministratorsGroup = nullptr;
    BOOL bRet = FALSE;

    // init SID to control privileges
    AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdministratorsGroup);

    // ckeck membership
    CheckTokenMembership(nullptr, pAdministratorsGroup, &bRet);

    // clean pointer
    if (pAdministratorsGroup) 
    { 
        FreeSid(pAdministratorsGroup); 
        pAdministratorsGroup = nullptr; 
    }

    return !!bRet;
}

{
...// WinMain code
    // Если нет прав администратора, попросим их и перезапустимся 
    if (!IsProcessWithAdminPrivilege())
    {
        TCHAR szPathToLib[MAX_PATH] = { 0 };
        GetModuleFileName(nullptr, szPathToLib, ARRAYSIZE(szPathToLib));

        SHELLEXECUTEINFO shellInfo = { sizeof(SHELLEXECUTEINFO) };
        shellInfo.lpVerb = TEXT("runas");
        shellInfo.lpFile = szPathToLib;
        shellInfo.hwnd = nullptr;
        shellInfo.nShow = SW_NORMAL;

        if (ShellExecuteEx(&shellInfo)) 
            ExitProcess(GetCurrentProcessId());
    }
}

Этап второй: копируем библиотеку в систему

{
...
// WinMain code
    TCHAR szPath[MAX_PATH] = { 0 };
    // Для примера беру путь от исполняемого файла
    GetModuleFileName(GetModuleHandle(nullptr), szPath, MAX_PATH);
    PathRemoveFileSpec(szPath);

#ifndef UNICODE 
    _snprintf_s(szPath, MAX_PATH * sizeof(CHAR), "%s%s", szPath, "\\OpenAL32.dll");
#else
    _snwprintf_s(szPath, MAX_PATH * sizeof(WCHAR), L"%s%s", szPath, L"\\OpenAL32.dll");
#endif

    dwOpenALInstalled = GetFileAttributes(szPath);
    if (dwOpenALInstalled != INVALID_FILE_ATTRIBUTES) // Файл найдет, идём дальше
    {
        DWORD LibrarySize = 0;
        HANDLE hFile = CreateFile(szPath, GENERIC_READ, NULL, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
        R_ASSERT(hFile != INVALID_HANDLE_VALUE);

        FILE_STANDARD_INFO fileInfo = { 0 };
        GetFileInformationByHandleEx(hFile, FileStandardInfo, &fileInfo, sizeof(fileInfo));

        LPVOID pImage = HeapAlloc(GetProcessHeap(), 0, fileInfo.EndOfFile.QuadPart);
        ReadFile(hFile, pImage, fileInfo.EndOfFile.QuadPart, &LibrarySize, nullptr);

        CloseHandle(hFile);

        hFile = CreateFile(szOpenALDir, GENERIC_WRITE, NULL, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
        R_ASSERT(hFile != INVALID_HANDLE_VALUE);

        WriteFile(hFile, pImage, fileInfo.EndOfFile.QuadPart, &LibrarySize, nullptr);

        HeapFree(GetProcessHeap(), 0, pImage);
        CloseHandle(hFile);

        OpenALFound = true; // Говорим, что всё в порядке, можно запускать движок
    }
}


Вывод

Конечно, способ весьма забавный, но для подобных проектов подойдёт. Всем удачи!

© Habrahabr.ru