- Статус
- Оффлайн
- Регистрация
- 13 Фев 2026
- Сообщения
- 429
- Реакции
- 10
Народ, кто искал способ рисовать оверлей без создания своих окон и палевных хуков на Present/SwapBuffers — ловите базу. Суть метода заключается в использовании общей секции памяти (shared memory) самого Дискорда.
Дискорд маппит свой фреймбуфер в память, и если найти этот участок, в него можно писать напрямую. Это фактически traceless overlay, так как мы не создаем новых объектов, которые мог бы спалить античит, а просто «паразитируем» на легитимном модуле.
Техническая часть
Для работы нам нужна структура хедера, который Дискорд использует для управления фреймбуфером:
Как это работает:
Пример реализации записи кадра:
Метод хорош тем, что DiscordOverlay маппит файл с именем вида DiscordOverlay_Framebuffer_Memory_<PID>. Можно подключаться через OpenFileMappingA из юзермода или лезть в секции из ядра.
Кто уже пробовал этот способ под EAC/BE, как обстоят дела с детектом самой записи в shared section?
Дискорд маппит свой фреймбуфер в память, и если найти этот участок, в него можно писать напрямую. Это фактически traceless overlay, так как мы не создаем новых объектов, которые мог бы спалить античит, а просто «паразитируем» на легитимном модуле.
Техническая часть
Для работы нам нужна структура хедера, который Дискорд использует для управления фреймбуфером:
Код:
typedef struct _Header
{
UINT Magic;
UINT FrameCount;
UINT NoClue;
UINT Width;
UINT Height;
BYTE Buffer[1];
} Header;
Как это работает:
- Находим базу модуля DiscordHook64.dll.
- Ищем указатель на замапленный файл. В актуальных билдах зацепка идет через смещение (в примере ниже это 0x130E58, но оффсеты имеют свойство тухнуть, так что реверсните свой билд).
- Пишем данные кадра (формат BGRA) прямо в буфер.
- Инкрементируем FrameCount. Это критично — внутренний модуль Дискорда чекает это значение и, если оно изменилось, копирует фреймбуфер на экран.
Пример реализации записи кадра:
Код:
typedef struct _Header
{
UINT Magic;
UINT FrameCount;
UINT NoClue;
UINT Width;
UINT Height;
BYTE Buffer[1];
} Header;
inline bool ConnectToProcess(ConnectedProcessInfo& processInfo)
{
std::string mappedFilename = "DiscordOverlay_Framebuffer_Memory_" + std::to_string(processInfo.ProcessId);
processInfo.File = OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, mappedFilename.c_str());
if (!processInfo.File || processInfo.File == INVALID_HANDLE_VALUE)
return false;
processInfo.MappedAddress = static_cast<Header*>(MapViewOfFile(processInfo.File, FILE_MAP_ALL_ACCESS, 0, 0, 0));
return processInfo.MappedAddress;
}
inline void SendFrame(ConnectedProcessInfo& processInfo, UINT width, UINT height, void* frame, UINT size)
{
// frame is in B8G8R8A8 format
// size can be nearly anything since it will get resized
// for the screen appropriately, although the maximum size is
// game window width * height * 4 (BGRA)
processInfo.MappedAddress->Width = width;
processInfo.MappedAddress->Height = height;
memcpy(processInfo.MappedAddress->Buffer, frame, size);
processInfo.MappedAddress->FrameCount++; // this will cause the internal module to copy over the framebuffer
}
Если работаете через драйвер и используете свою функцию трансляции VA (Virtual Address), имейте в виду, что при работе с 2MB страницами может возникнуть проблема с проверкой Read/Write битов. Если у вас рендерится только первая страница (4KB), а дальше всё падает — копайте в сторону логики вашей трансляции страниц.
Метод хорош тем, что DiscordOverlay маппит файл с именем вида DiscordOverlay_Framebuffer_Memory_<PID>. Можно подключаться через OpenFileMappingA из юзермода или лезть в секции из ядра.
Кто уже пробовал этот способ под EAC/BE, как обстоят дела с детектом самой записи в shared section?