initial commit
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
CompileFlags:
|
||||||
|
CompilationDatabase: .
|
||||||
|
Remove:
|
||||||
|
- "-I/usr/include/pidgin"
|
||||||
|
- "-I/usr/include/gtk*"
|
||||||
|
- "-I/usr/include/glib*"
|
||||||
|
- "-I/usr/include/pango*"
|
||||||
|
- "-I/usr/include/libpurple"
|
||||||
|
- "-I/usr/lib/glib*"
|
||||||
|
- "-pthread"
|
||||||
|
Add:
|
||||||
|
- "--target=i686-w64-mingw32"
|
||||||
|
- "-I/usr/i686-w64-mingw32/include"
|
||||||
|
- "-I/usr/lib/gcc/i686-w64-mingw32/12/include"
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
compile_commands.json
|
||||||
Executable
BIN
Binary file not shown.
Executable
+426
@@ -0,0 +1,426 @@
|
|||||||
|
#include "chatwnd.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include "snoctrl.h"
|
||||||
|
#include "../discordtypes.h"
|
||||||
|
#include "../hmap/hashmap.h"
|
||||||
|
#include <libpng18/png.h>
|
||||||
|
#include <wingdi.h>
|
||||||
|
#include "pnghlp.h"
|
||||||
|
ChatWnd *chatwnds = NULL;
|
||||||
|
int chatwndcount = 0;
|
||||||
|
|
||||||
|
HFONT regfont;
|
||||||
|
HFONT boldfont;
|
||||||
|
HFONT h1font;
|
||||||
|
HFONT h2font;
|
||||||
|
HFONT h3font;
|
||||||
|
hmap pfp_map;
|
||||||
|
ChatWnd *GetChatControlDetails(HWND hwnd) {
|
||||||
|
for (int i = 0; i < chatwndcount; i++) {
|
||||||
|
if (chatwnds[i].hWnd == hwnd) {
|
||||||
|
return &chatwnds[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ChatView_SetUserPfp(char *path, char *user) {
|
||||||
|
HANDLE f = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
if (f == INVALID_HANDLE_VALUE) {
|
||||||
|
MessageBoxA(NULL, "Error accessing the path for the pfp set!",
|
||||||
|
"Snow's Controls - ChatView", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LARGE_INTEGER size;
|
||||||
|
GetFileSizeEx(f, &size);
|
||||||
|
|
||||||
|
size_t file_size = (size_t)size.QuadPart;
|
||||||
|
|
||||||
|
uint8_t *buf = malloc(file_size);
|
||||||
|
|
||||||
|
DWORD read = 0;
|
||||||
|
|
||||||
|
ReadFile(f, buf, file_size, &read, NULL);
|
||||||
|
|
||||||
|
png_structp png =
|
||||||
|
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
PngBuffer pngbuf = {buf, file_size, 0};
|
||||||
|
|
||||||
|
png_set_read_fn(png, &pngbuf, PngReadBufCB);
|
||||||
|
png_read_info(png, info);
|
||||||
|
|
||||||
|
BITMAPINFO bmi = {0};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = png_get_image_width(png, info);
|
||||||
|
bmi.bmiHeader.biHeight = -png_get_image_height(png, info); // top-down
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB; // THIS IS RAW - gordon ramsey
|
||||||
|
if (setjmp(png_jmpbuf(png))) {
|
||||||
|
png_destroy_read_struct(&png, &info, NULL);
|
||||||
|
// err
|
||||||
|
MessageBoxA(NULL, "zzzzzzzz", "zzzzzzzzz", 0);
|
||||||
|
}
|
||||||
|
png_set_expand(png);
|
||||||
|
png_set_strip_16(png);
|
||||||
|
png_set_filler(png, 0xFF,
|
||||||
|
PNG_FILLER_AFTER); // ensures RGBA or RGBX cause winblows
|
||||||
|
png_read_update_info(png, info);
|
||||||
|
int width = png_get_image_width(png, info);
|
||||||
|
int height = png_get_image_height(png, info);
|
||||||
|
png_bytep *rows = malloc(height * sizeof(png_bytep));
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
rows[y] = malloc(png_get_rowbytes(png, info));
|
||||||
|
// flat BGRA buffer for GDI
|
||||||
|
uint8_t *fbuf = malloc(width * height * 4);
|
||||||
|
png_read_image(png, rows);
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
png_bytep src = rows[y];
|
||||||
|
uint8_t *dst = fbuf + y * width * 4;
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
dst[x * 4 + 0] = src[x * 4 + 2]; // B
|
||||||
|
dst[x * 4 + 1] = src[x * 4 + 1]; // G
|
||||||
|
dst[x * 4 + 2] = src[x * 4 + 0]; // R
|
||||||
|
dst[x * 4 + 3] = src[x * 4 + 3]; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void *gdibuf = NULL;
|
||||||
|
HBITMAP bmp =
|
||||||
|
CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &gdibuf, NULL, 0);
|
||||||
|
// GDI allocates its own dumbahh buffer so we just memcpy to it
|
||||||
|
memcpy(gdibuf, fbuf, width * height * 4);
|
||||||
|
free(fbuf);
|
||||||
|
HDC memdc = CreateCompatibleDC(NULL);
|
||||||
|
SelectObject(memdc, bmp);
|
||||||
|
|
||||||
|
IntPfpTableItem it;
|
||||||
|
it.key = user;
|
||||||
|
it.bmp = memdc;
|
||||||
|
|
||||||
|
hashmap_set(pfp_map, &it);
|
||||||
|
|
||||||
|
CloseHandle(f);
|
||||||
|
}
|
||||||
|
void InsertChatMessage(DiscordMessage msg, HWND hwnd) {
|
||||||
|
ChatWnd *cwnd = GetChatControlDetails(hwnd);
|
||||||
|
cwnd->uimsgs =
|
||||||
|
realloc(cwnd->uimsgs, (cwnd->uimsgcnt + 1) * sizeof(GUIMessage));
|
||||||
|
cwnd->uimsgs[cwnd->uimsgcnt].msg = msg;
|
||||||
|
cwnd->uimsgs[cwnd->uimsgcnt].vw = hwnd;
|
||||||
|
cwnd->uimsgcnt++;
|
||||||
|
|
||||||
|
InvalidateRect(hwnd, NULL, FALSE);
|
||||||
|
UpdateWindow(hwnd); // forces WM_PAINT now, contentHeight is fresh
|
||||||
|
|
||||||
|
RECT clientRect;
|
||||||
|
GetClientRect(hwnd, &clientRect);
|
||||||
|
int viewHeight = clientRect.bottom - clientRect.top;
|
||||||
|
|
||||||
|
cwnd->scrollOffset = max(0, cwnd->contentHeight - viewHeight);
|
||||||
|
|
||||||
|
SCROLLINFO si = {sizeof(SCROLLINFO)};
|
||||||
|
si.fMask = SIF_POS;
|
||||||
|
si.nPos = cwnd->scrollOffset;
|
||||||
|
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
|
||||||
|
|
||||||
|
InvalidateRect(hwnd, NULL, FALSE); // repaint with correct offset
|
||||||
|
}
|
||||||
|
void ClearChatControl(HWND hwnd) {
|
||||||
|
ChatWnd *cwnd = GetChatControlDetails(hwnd);
|
||||||
|
int count = cwnd->uimsgcnt;
|
||||||
|
cwnd->uimsgcnt = 0;
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
DiscordMessage hmsg = (DiscordMessage)cwnd->uimsgs[i].msg;
|
||||||
|
free(hmsg.content);
|
||||||
|
free(hmsg.channelID);
|
||||||
|
free(hmsg.id);
|
||||||
|
free(hmsg.author.Username);
|
||||||
|
free(hmsg.author.DisplayName);
|
||||||
|
free(hmsg.author.id);
|
||||||
|
free(hmsg.author.avatar);
|
||||||
|
}
|
||||||
|
cwnd->uimsgs = realloc(cwnd->uimsgs, sizeof(GUIMessage));
|
||||||
|
}
|
||||||
|
LONG MeasureTotalMessageHeight(HWND hwnd) {}
|
||||||
|
static uint64_t pfp_hash(const void *item, uint64_t seed0, uint64_t seed1) {
|
||||||
|
const IntPfpTableItem *e = item;
|
||||||
|
return hashmap_sip(e->key, strlen(e->key), seed0, seed1);
|
||||||
|
}
|
||||||
|
static int pfp_compare(const void *a, const void *b, void *udata) {
|
||||||
|
return strcmp(((IntPfpTableItem *)a)->key, ((IntPfpTableItem *)b)->key);
|
||||||
|
}
|
||||||
|
static void pfp_free(void *pfpitem) {
|
||||||
|
IntPfpTableItem *item = pfpitem;
|
||||||
|
free(item->bmp);
|
||||||
|
free(item->key);
|
||||||
|
}
|
||||||
|
#define UsernameFieldHeight 15
|
||||||
|
#define PfpPadding 6
|
||||||
|
#define YPadding 4
|
||||||
|
#define PfpSize 40
|
||||||
|
#define MinMsgH PfpSize + 10
|
||||||
|
/*
|
||||||
|
| X
|
||||||
|
+------------+--------------------------------------------------------+
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
-X | |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+------------+
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
BOOL IsStartMsg(ChatWnd *cwnd, int i) {
|
||||||
|
if (i > 0) {
|
||||||
|
return (strcmp(cwnd->uimsgs[i].msg.author.id,
|
||||||
|
cwnd->uimsgs[i - 1].msg.author.id) == 0)
|
||||||
|
? FALSE
|
||||||
|
: TRUE;
|
||||||
|
} else {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BOOL ShouldGoUp(ChatWnd *cwnd, int i) {
|
||||||
|
if (i > 0) {
|
||||||
|
return (IsStartMsg(cwnd, i - 1) && !IsStartMsg(cwnd, i));
|
||||||
|
} else {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK ChatWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
||||||
|
LPARAM lParam) {
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_CREATE: {
|
||||||
|
chatwnds = realloc(chatwnds, (chatwndcount + 1) * sizeof(ChatWnd));
|
||||||
|
chatwnds[chatwndcount].hWnd = hwnd;
|
||||||
|
chatwnds[chatwndcount].uimsgcnt = 0;
|
||||||
|
chatwnds[chatwndcount].uimsgs = malloc(sizeof(GUIMessage));
|
||||||
|
chatwnds[chatwndcount].scrollOffset = 0;
|
||||||
|
chatwnds[chatwndcount].contentHeight = 0;
|
||||||
|
chatwnds[chatwndcount].pfpmap = pfp_map =
|
||||||
|
hashmap_new(sizeof(IntPfpTableItem), 256, 0, 0, pfp_hash,
|
||||||
|
pfp_compare, pfp_free, NULL);
|
||||||
|
chatwndcount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_PAINT: {
|
||||||
|
ChatWnd *cwnd = GetChatControlDetails(hwnd);
|
||||||
|
PAINTSTRUCT ps;
|
||||||
|
HDC hdc = BeginPaint(hwnd, &ps);
|
||||||
|
|
||||||
|
RECT clientRect;
|
||||||
|
GetClientRect(hwnd, &clientRect);
|
||||||
|
int viewHeight = clientRect.bottom - clientRect.top;
|
||||||
|
|
||||||
|
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
|
||||||
|
SetStretchBltMode(hdc, HALFTONE);
|
||||||
|
SetBrushOrgEx(hdc, 0, 0, NULL);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int TextContentHeight;
|
||||||
|
int UnpaddedMessageHeight;
|
||||||
|
int ClampedUnpaddedMessageHeight;
|
||||||
|
int UnclampedTotalMessageHeight;
|
||||||
|
int TotalMessageHeight;
|
||||||
|
int MessageHeightForUse;
|
||||||
|
BOOL StartMsg;
|
||||||
|
BOOL PushBack;
|
||||||
|
} MessageDetails;
|
||||||
|
cwnd->contentHeight = 0;
|
||||||
|
MessageDetails msgdet[cwnd->uimsgcnt];
|
||||||
|
for (int i = 0; i < cwnd->uimsgcnt; i++) {
|
||||||
|
RECT contr = {0, 0, clientRect.right - clientRect.left - 6 - 6 - 40,
|
||||||
|
0};
|
||||||
|
DrawTextA(hdc, cwnd->uimsgs[i].msg.content,
|
||||||
|
strlen(cwnd->uimsgs[i].msg.content), &contr,
|
||||||
|
DT_CALCRECT | DT_WORDBREAK);
|
||||||
|
// printf("%i | %i\n", i, contr.bottom - contr.top);
|
||||||
|
msgdet[i].TextContentHeight = contr.bottom - contr.top;
|
||||||
|
msgdet[i].StartMsg = IsStartMsg(cwnd, i);
|
||||||
|
if (msgdet[i].StartMsg) {
|
||||||
|
|
||||||
|
if (i + 1 < cwnd->uimsgcnt) {
|
||||||
|
if (!IsStartMsg(cwnd, i + 1)) {
|
||||||
|
msgdet[i].PushBack = TRUE;
|
||||||
|
} else {
|
||||||
|
msgdet[i].PushBack = FALSE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msgdet[i].PushBack = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msgdet[i].PushBack) {
|
||||||
|
|
||||||
|
msgdet[i].UnpaddedMessageHeight =
|
||||||
|
15 + msgdet[i].TextContentHeight;
|
||||||
|
|
||||||
|
msgdet[i].TotalMessageHeight =
|
||||||
|
15 + msgdet[i].TextContentHeight;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
msgdet[i].UnpaddedMessageHeight =
|
||||||
|
max(40, 15 + msgdet[i].TextContentHeight);
|
||||||
|
|
||||||
|
msgdet[i].TotalMessageHeight =
|
||||||
|
max(40, 15 + msgdet[i].TextContentHeight)+6;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
msgdet[i].TotalMessageHeight = msgdet[i].TextContentHeight ;
|
||||||
|
|
||||||
|
msgdet[i].PushBack = FALSE;
|
||||||
|
}
|
||||||
|
cwnd->contentHeight += msgdet[i].TotalMessageHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT msgrect;
|
||||||
|
msgrect = clientRect;
|
||||||
|
msgrect.top -= cwnd->scrollOffset;
|
||||||
|
for (int i = 0; i < cwnd->uimsgcnt; i++) {
|
||||||
|
|
||||||
|
if (msgrect.top + msgdet[i].TotalMessageHeight < 0) {
|
||||||
|
msgrect.top += msgdet[i].TotalMessageHeight;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (msgrect.top > clientRect.bottom) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DiscordMessage msg = cwnd->uimsgs[i].msg;
|
||||||
|
if (msgdet[i].StartMsg) {
|
||||||
|
RECT pfprect = msgrect;
|
||||||
|
pfprect.left += 6;
|
||||||
|
// pfprect.top += 6;
|
||||||
|
pfprect.bottom = pfprect.top + 40;
|
||||||
|
pfprect.right = pfprect.left + 40;
|
||||||
|
|
||||||
|
IntPfpTableItem searchfilters;
|
||||||
|
searchfilters.key = msg.author.id;
|
||||||
|
IntPfpTableItem *pfplookup =
|
||||||
|
hashmap_get(pfp_map, &searchfilters);
|
||||||
|
if (pfplookup) {
|
||||||
|
HBITMAP hBitmap =
|
||||||
|
(HBITMAP)GetCurrentObject(hdc, OBJ_BITMAP);
|
||||||
|
|
||||||
|
BITMAP bmp;
|
||||||
|
GetObject(hBitmap, sizeof(BITMAP), &bmp);
|
||||||
|
|
||||||
|
int width = bmp.bmWidth;
|
||||||
|
int height = bmp.bmHeight;
|
||||||
|
|
||||||
|
BITMAP bmpInfo;
|
||||||
|
HBITMAP hSrcBmp =
|
||||||
|
(HBITMAP)GetCurrentObject(pfplookup->bmp, OBJ_BITMAP);
|
||||||
|
GetObject(hSrcBmp, sizeof(BITMAP), &bmpInfo);
|
||||||
|
|
||||||
|
StretchBlt(hdc, pfprect.left, pfprect.top, 40, 40,
|
||||||
|
pfplookup->bmp, 0, 0, bmpInfo.bmWidth,
|
||||||
|
bmpInfo.bmHeight, SRCCOPY);
|
||||||
|
} else {
|
||||||
|
FillRect(hdc, &pfprect, (HBRUSH)BLACK_BRUSH);
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT usernamerect = msgrect;
|
||||||
|
usernamerect.left += (6 + 40 + 6);
|
||||||
|
// usernamerect.top += 6;
|
||||||
|
char *rndname = cwnd->uimsgs[i].msg.author.DisplayName
|
||||||
|
? cwnd->uimsgs[i].msg.author.DisplayName
|
||||||
|
: cwnd->uimsgs[i].msg.author.Username;
|
||||||
|
SelectObject(hdc, boldfont);
|
||||||
|
DrawTextA(hdc, rndname, strlen(rndname), &usernamerect, 0);
|
||||||
|
RECT contentrect = msgrect;
|
||||||
|
contentrect.left += (6 + 40 + 6);
|
||||||
|
contentrect.top += 15;
|
||||||
|
contentrect.bottom =
|
||||||
|
contentrect.top + msgdet[i].TextContentHeight;
|
||||||
|
SelectObject(hdc, regfont);
|
||||||
|
DrawTextA(hdc, cwnd->uimsgs[i].msg.content,
|
||||||
|
strlen(cwnd->uimsgs[i].msg.content), &contentrect,
|
||||||
|
DT_WORDBREAK);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
RECT contentrect = msgrect;
|
||||||
|
contentrect.left += (6 + 40 + 6);
|
||||||
|
// contentrect.top += 6;
|
||||||
|
contentrect.bottom =
|
||||||
|
contentrect.top + msgdet[i].TextContentHeight;
|
||||||
|
SelectObject(hdc, regfont);
|
||||||
|
DrawTextA(hdc, cwnd->uimsgs[i].msg.content,
|
||||||
|
strlen(cwnd->uimsgs[i].msg.content), &contentrect,
|
||||||
|
DT_WORDBREAK);
|
||||||
|
}
|
||||||
|
msgrect.top += msgdet[i].TotalMessageHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 4. SYNC SCROLL BAR ───────────────────────────────────────────────
|
||||||
|
*/
|
||||||
|
SCROLLINFO si = {sizeof(SCROLLINFO)};
|
||||||
|
si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
|
||||||
|
si.nMin = 0;
|
||||||
|
si.nMax = cwnd->contentHeight - 1;
|
||||||
|
si.nPage = viewHeight;
|
||||||
|
si.nPos = cwnd->scrollOffset;
|
||||||
|
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
|
||||||
|
|
||||||
|
EndPaint(hwnd, &ps);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_VSCROLL: {
|
||||||
|
ChatWnd *cwnd = GetChatControlDetails(hwnd);
|
||||||
|
RECT clientRect;
|
||||||
|
GetClientRect(hwnd, &clientRect);
|
||||||
|
int viewHeight = clientRect.bottom - clientRect.top;
|
||||||
|
int maxScroll = max(0, cwnd->contentHeight - viewHeight);
|
||||||
|
|
||||||
|
SCROLLINFO si = {sizeof(SCROLLINFO)};
|
||||||
|
si.fMask = SIF_ALL;
|
||||||
|
GetScrollInfo(hwnd, SB_VERT, &si);
|
||||||
|
|
||||||
|
switch (LOWORD(wParam)) {
|
||||||
|
case SB_LINEUP:
|
||||||
|
cwnd->scrollOffset -= 20;
|
||||||
|
break;
|
||||||
|
case SB_LINEDOWN:
|
||||||
|
cwnd->scrollOffset += 20;
|
||||||
|
break;
|
||||||
|
case SB_PAGEUP:
|
||||||
|
cwnd->scrollOffset -= viewHeight;
|
||||||
|
break;
|
||||||
|
case SB_PAGEDOWN:
|
||||||
|
cwnd->scrollOffset += viewHeight;
|
||||||
|
break;
|
||||||
|
case SB_THUMBTRACK:
|
||||||
|
cwnd->scrollOffset = si.nTrackPos;
|
||||||
|
break;
|
||||||
|
case SB_THUMBPOSITION:
|
||||||
|
cwnd->scrollOffset = si.nPos;
|
||||||
|
break;
|
||||||
|
case SB_TOP:
|
||||||
|
cwnd->scrollOffset = 0;
|
||||||
|
break;
|
||||||
|
case SB_BOTTOM:
|
||||||
|
cwnd->scrollOffset = maxScroll;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cwnd->scrollOffset = max(0, min(cwnd->scrollOffset, maxScroll));
|
||||||
|
|
||||||
|
si.fMask = SIF_POS;
|
||||||
|
si.nPos = cwnd->scrollOffset;
|
||||||
|
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
|
||||||
|
|
||||||
|
InvalidateRect(hwnd, NULL, FALSE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_DESTROY:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wingdi.h>
|
||||||
|
#include "snoctrl.h"
|
||||||
|
#include "../discordtypes.h"
|
||||||
|
#include "../hmap/hashmap.h"
|
||||||
|
typedef struct {
|
||||||
|
DiscordMessage msg;
|
||||||
|
HWND vw;
|
||||||
|
} GUIMessage;
|
||||||
|
typedef struct {
|
||||||
|
GUIMessage* uimsgs;
|
||||||
|
int uimsgcnt;
|
||||||
|
HWND hWnd;
|
||||||
|
int scrollOffset;
|
||||||
|
int contentHeight;
|
||||||
|
hmap pfpmap;
|
||||||
|
} ChatWnd;
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
char *key;
|
||||||
|
HDC bmp;
|
||||||
|
} IntPfpTableItem;
|
||||||
|
|
||||||
|
ChatWnd* GetChatControlDetails(HWND hwnd);
|
||||||
|
void InsertChatMessage(DiscordMessage msg, HWND hwnd);
|
||||||
|
void ClearChatControl(HWND hwnd);
|
||||||
|
LRESULT CALLBACK ChatWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
||||||
|
LPARAM lParam);
|
||||||
|
void ChatView_SetUserPfp(char *path, char *user);
|
||||||
Executable
+203
@@ -0,0 +1,203 @@
|
|||||||
|
#include "guildwnd.h"
|
||||||
|
#include <libpng18/png.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "pnghlp.h"
|
||||||
|
#include <windows.h>
|
||||||
|
#include <windowsx.h>
|
||||||
|
#include <wingdi.h>
|
||||||
|
int guildwndcount = 0;
|
||||||
|
GuildWnd *guildwnds = NULL;
|
||||||
|
GuildWnd *GetGuildControlDetails(HWND hwnd) {
|
||||||
|
for (int i = 0; i < guildwndcount; i++) {
|
||||||
|
if (guildwnds[i].hWnd == hwnd) {
|
||||||
|
return &guildwnds[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GuildView_InsertGuild(HWND hwnd, GUIGuild gld) {
|
||||||
|
GuildWnd *guildwnd = GetGuildControlDetails(hwnd);
|
||||||
|
guildwnd->uigldcnt++;
|
||||||
|
guildwnd->uiglds =
|
||||||
|
realloc(guildwnd->uiglds, guildwnd->uigldcnt * sizeof(GUIGuild));
|
||||||
|
GUIGuild toins = gld;
|
||||||
|
toins.title = strdup(toins.title);
|
||||||
|
toins.id = strdup(toins.id);
|
||||||
|
guildwnd->uiglds[guildwnd->uigldcnt - 1] = toins;
|
||||||
|
InvalidateRect(hwnd, NULL, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuildView_SetIcon(HWND hwnd, HDC icon, char *id) {
|
||||||
|
GuildWnd *guildwnd = GetGuildControlDetails(hwnd);
|
||||||
|
for (int i = 0; i < guildwnd->uigldcnt; i++) {
|
||||||
|
if (strcmp(guildwnd->uiglds[i].id, id) == 0) {
|
||||||
|
guildwnds->uiglds[i].icon = icon;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void GuildView_SetMentionCount(HWND hwnd, char *id, int cnt) {
|
||||||
|
GuildWnd *guildwnd = GetGuildControlDetails(hwnd);
|
||||||
|
for (int i = 0; i < guildwnd->uigldcnt; i++) {
|
||||||
|
if (strcmp(guildwnd->uiglds[i].id, id) == 0) {
|
||||||
|
guildwnds->uiglds[i].MentionCount = cnt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InvalidateRect(hwnd, NULL, FALSE);
|
||||||
|
}
|
||||||
|
void GuildView_SetDMsIcon(HWND hwnd, HDC icon) {
|
||||||
|
GuildWnd *guildwnd = GetGuildControlDetails(hwnd);
|
||||||
|
guildwnds->dmsicon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK GuildWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
||||||
|
LPARAM lParam) {
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_CREATE:
|
||||||
|
guildwndcount++;
|
||||||
|
guildwnds = realloc(guildwnds, guildwndcount * sizeof(GuildWnd));
|
||||||
|
guildwnds[guildwndcount - 1].uigldcnt = 0;
|
||||||
|
guildwnds[guildwndcount - 1].uiglds = NULL;
|
||||||
|
guildwnds[guildwndcount - 1].hWnd = hwnd;
|
||||||
|
guildwnds[guildwndcount - 1].scrollOffset = 0;
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
case WM_PAINT: {
|
||||||
|
RECT ctlrect;
|
||||||
|
GetClientRect(hwnd, &ctlrect);
|
||||||
|
GuildWnd *gwnd = GetGuildControlDetails(hwnd);
|
||||||
|
PAINTSTRUCT ps;
|
||||||
|
HDC hdc = BeginPaint(hwnd, &ps);
|
||||||
|
FillRect(hdc, &ctlrect, (HBRUSH)(COLOR_3DFACE + 1));
|
||||||
|
SetStretchBltMode(hdc, HALFTONE);
|
||||||
|
RECT rc = ctlrect;
|
||||||
|
int ctlw = ctlrect.right - ctlrect.left;
|
||||||
|
int guildsize = ctlw - 16;
|
||||||
|
|
||||||
|
rc.left += 8;
|
||||||
|
rc.right = rc.left + guildsize;
|
||||||
|
rc.top += 8;
|
||||||
|
rc.bottom = rc.top + guildsize;
|
||||||
|
|
||||||
|
if (gwnd->dmsicon) {
|
||||||
|
|
||||||
|
BITMAP bmpInfo;
|
||||||
|
HBITMAP hSrcBmp =
|
||||||
|
(HBITMAP)GetCurrentObject(gwnd->dmsicon, OBJ_BITMAP);
|
||||||
|
GetObject(hSrcBmp, sizeof(BITMAP), &bmpInfo);
|
||||||
|
|
||||||
|
StretchBlt(hdc, rc.left, rc.top, guildsize, guildsize,
|
||||||
|
gwnd->dmsicon, 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight,
|
||||||
|
SRCCOPY);
|
||||||
|
} else {
|
||||||
|
FillRect(hdc, &rc, (HBRUSH)(COLOR_3DSHADOW + 1));
|
||||||
|
}
|
||||||
|
HPEN hPenDark = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));
|
||||||
|
HPEN hPenLight =
|
||||||
|
CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNHIGHLIGHT));
|
||||||
|
HPEN hOldPen;
|
||||||
|
|
||||||
|
hOldPen = SelectObject(hdc, hPenDark);
|
||||||
|
MoveToEx(hdc, rc.left, rc.top + guildsize + 8, NULL);
|
||||||
|
LineTo(hdc, rc.right, rc.top + guildsize + 8);
|
||||||
|
|
||||||
|
SelectObject(hdc, hPenLight);
|
||||||
|
MoveToEx(hdc, rc.left, rc.top + guildsize + 1 + 8, NULL);
|
||||||
|
LineTo(hdc, rc.right, rc.top + guildsize + 1 + 8);
|
||||||
|
|
||||||
|
SelectObject(hdc, hOldPen);
|
||||||
|
DeleteObject(hPenDark);
|
||||||
|
DeleteObject(hPenLight);
|
||||||
|
|
||||||
|
for (int i = 0; i < gwnd->uigldcnt; i++) {
|
||||||
|
RECT rc = ctlrect;
|
||||||
|
rc.left += 8;
|
||||||
|
rc.right = rc.left + guildsize;
|
||||||
|
rc.top += i * (guildsize + 8) + 8 + guildsize + 10 + 8;
|
||||||
|
rc.bottom = rc.top + guildsize;
|
||||||
|
if (i == gwnd->selIndex) {
|
||||||
|
RECT rcs = rc;
|
||||||
|
rcs.left = ctlrect.left;
|
||||||
|
rcs.right = ctlrect.right;
|
||||||
|
rcs.top -= 4;
|
||||||
|
rcs.bottom += 4;
|
||||||
|
FillRect(hdc, &rcs, (HBRUSH)(COLOR_HIGHLIGHT + 1));
|
||||||
|
}
|
||||||
|
if (gwnd->uiglds[i].icon) {
|
||||||
|
|
||||||
|
BITMAP bmpInfo;
|
||||||
|
HBITMAP hSrcBmp =
|
||||||
|
(HBITMAP)GetCurrentObject(gwnd->uiglds[i].icon, OBJ_BITMAP);
|
||||||
|
GetObject(hSrcBmp, sizeof(BITMAP), &bmpInfo);
|
||||||
|
|
||||||
|
StretchBlt(hdc, rc.left, rc.top, guildsize, guildsize,
|
||||||
|
gwnd->uiglds[i].icon, 0, 0, bmpInfo.bmWidth,
|
||||||
|
bmpInfo.bmHeight, SRCCOPY);
|
||||||
|
} else {
|
||||||
|
FillRect(hdc, &rc, (HBRUSH)(COLOR_3DSHADOW + 1));
|
||||||
|
}
|
||||||
|
if (gwnd->uiglds[i].MentionCount) {
|
||||||
|
HBRUSH indicatorbrush = CreateSolidBrush((COLORREF)0x000000FF);
|
||||||
|
|
||||||
|
RECT rc = ctlrect;
|
||||||
|
rc.left += 8;
|
||||||
|
rc.right = rc.left + guildsize;
|
||||||
|
rc.top += i * (guildsize + 8) + 8 + guildsize + 10 + 8;
|
||||||
|
rc.bottom = rc.top + guildsize;
|
||||||
|
|
||||||
|
rc.top = i * (guildsize + 8) + 8 + guildsize + 10 + 8 + guildsize - 16;
|
||||||
|
rc.left = rc.left + guildsize - 16;
|
||||||
|
FillRect(hdc, &rc, indicatorbrush);
|
||||||
|
char unreadtxt[16];
|
||||||
|
wsprintf(unreadtxt,"%i",gwnd->uiglds[i].MentionCount);
|
||||||
|
DrawText(hdc,unreadtxt,strlen(unreadtxt),&rc,DT_SINGLELINE | DT_CENTER | DT_VCENTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndPaint(hwnd, &ps);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case WM_LBUTTONUP: {
|
||||||
|
GuildWnd *gwnd = GetGuildControlDetails(hwnd);
|
||||||
|
RECT ctlrect;
|
||||||
|
GetClientRect(hwnd, &ctlrect);
|
||||||
|
int ctlw = ctlrect.right - ctlrect.left;
|
||||||
|
int guildsize = ctlw - 16;
|
||||||
|
if (GET_Y_LPARAM(lParam) < guildsize + 10 + 8) {
|
||||||
|
if (gwnd->selIndex != 1) {
|
||||||
|
gwnd->selIndex = -1;
|
||||||
|
|
||||||
|
NMGUILDVIEW nm = {0};
|
||||||
|
nm.hdr.hwndFrom = hwnd;
|
||||||
|
nm.hdr.idFrom = GetDlgCtrlID(hwnd);
|
||||||
|
nm.hdr.code = GVN_ITEMCLICK;
|
||||||
|
nm.index = GUILDVIEW_DMS;
|
||||||
|
nm.guild = (GUIGuild){NULL};
|
||||||
|
nm.pt = (POINT){GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
|
||||||
|
|
||||||
|
SendMessage(GetParent(hwnd), WM_NOTIFY, (WPARAM)nm.hdr.idFrom,
|
||||||
|
(LPARAM)&nm);
|
||||||
|
InvalidateRect(hwnd, NULL, FALSE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int index = (GET_Y_LPARAM(lParam) - 8 - guildsize - 10 - 8) /
|
||||||
|
(guildsize + 8);
|
||||||
|
if (index != gwnd->selIndex) {
|
||||||
|
NMGUILDVIEW nm = {0};
|
||||||
|
nm.hdr.hwndFrom = hwnd;
|
||||||
|
nm.hdr.idFrom = GetDlgCtrlID(hwnd);
|
||||||
|
nm.hdr.code = GVN_ITEMCLICK;
|
||||||
|
nm.index = index;
|
||||||
|
nm.guild = gwnd->uiglds[index];
|
||||||
|
nm.pt = (POINT){GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
|
||||||
|
gwnd->selIndex = index;
|
||||||
|
SendMessage(GetParent(hwnd), WM_NOTIFY, (WPARAM)nm.hdr.idFrom,
|
||||||
|
(LPARAM)&nm);
|
||||||
|
InvalidateRect(hwnd, NULL, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include "../hmap/hashmap.h"
|
||||||
|
|
||||||
|
#define GVN_ITEMCLICK (0U - 1800U)
|
||||||
|
#define GUILDVIEW_MAGIC 0xACC0
|
||||||
|
#define GUILDVIEW_DMS -1
|
||||||
|
typedef struct {
|
||||||
|
char *title;
|
||||||
|
char *id;
|
||||||
|
HDC icon;
|
||||||
|
int MentionCount;
|
||||||
|
void* data;
|
||||||
|
} GUIGuild;
|
||||||
|
typedef struct {
|
||||||
|
NMHDR hdr;
|
||||||
|
int index;
|
||||||
|
GUIGuild guild;
|
||||||
|
POINT pt;
|
||||||
|
} NMGUILDVIEW;
|
||||||
|
typedef NMGUILDVIEW* LPNMGUILDVIEW;
|
||||||
|
typedef struct {
|
||||||
|
GUIGuild* uiglds;
|
||||||
|
int uigldcnt;
|
||||||
|
HWND hWnd;
|
||||||
|
int scrollOffset;
|
||||||
|
int contentHeight;
|
||||||
|
HDC dmsicon;
|
||||||
|
int selIndex;
|
||||||
|
} GuildWnd;
|
||||||
|
|
||||||
|
LRESULT CALLBACK GuildWndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
||||||
|
LPARAM lParam);
|
||||||
|
|
||||||
|
void GuildView_InsertGuild(HWND hwnd, GUIGuild gld);
|
||||||
|
void GuildView_SetIcon(HWND hwnd, HDC icon, char *id);
|
||||||
|
void GuildView_SetDMsIcon(HWND hwnd, HDC icon);
|
||||||
|
void GuildView_SetMentionCount(HWND hwnd, char *id, int cnt);
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include "snoctrl.h"
|
||||||
|
void InitSnowsControls() {
|
||||||
|
|
||||||
|
NONCLIENTMETRICS ncm = { sizeof(NONCLIENTMETRICS) };
|
||||||
|
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
|
||||||
|
regfont=CreateFontIndirect(&ncm.lfMessageFont);
|
||||||
|
ncm.lfMessageFont.lfWeight=FW_BOLD;
|
||||||
|
boldfont=CreateFontIndirect(&ncm.lfMessageFont);
|
||||||
|
ncm.lfMessageFont.lfHeight=-32;
|
||||||
|
h1font=CreateFontIndirect(&ncm.lfMessageFont);
|
||||||
|
ncm.lfMessageFont.lfHeight=-24;
|
||||||
|
h2font=CreateFontIndirect(&ncm.lfMessageFont);
|
||||||
|
h3font = boldfont;
|
||||||
|
|
||||||
|
const char CLASS_NAME_CHAT[] = "BackcordChat";
|
||||||
|
|
||||||
|
WNDCLASS wcc = {};
|
||||||
|
|
||||||
|
wcc.lpfnWndProc = ChatWndProc;
|
||||||
|
wcc.hInstance = GetModuleHandle(NULL);
|
||||||
|
wcc.lpszClassName = CLASS_NAME_CHAT;
|
||||||
|
wcc.hCursor = LoadCursorA(NULL, IDC_ARROW);
|
||||||
|
RegisterClass(&wcc);
|
||||||
|
|
||||||
|
const char CLASS_NAME_GUILD[] = "BackcordGuild";
|
||||||
|
|
||||||
|
WNDCLASS wcg = {};
|
||||||
|
|
||||||
|
wcg.lpfnWndProc = GuildWndProc;
|
||||||
|
wcg.hInstance = GetModuleHandle(NULL);
|
||||||
|
wcg.lpszClassName = CLASS_NAME_GUILD;
|
||||||
|
wcg.hCursor = LoadCursorA(NULL, IDC_ARROW);
|
||||||
|
RegisterClass(&wcg);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
#include "pnghlp.h"
|
||||||
|
#include <libloaderapi.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <windows.h>
|
||||||
|
void PngReadBufCB(png_structp png, png_bytep out, png_size_t length) {
|
||||||
|
PngBuffer *buf = png_get_io_ptr(png);
|
||||||
|
memcpy(out, buf->data + buf->pos, length);
|
||||||
|
buf->pos += length;
|
||||||
|
}
|
||||||
|
HDC LoadPNGImage(char *path) {
|
||||||
|
HANDLE f = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
if (f == INVALID_HANDLE_VALUE) {
|
||||||
|
MessageBoxA(NULL, "Error accessing the path!", "PNG Helper", 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
LARGE_INTEGER size;
|
||||||
|
GetFileSizeEx(f, &size);
|
||||||
|
|
||||||
|
size_t file_size = (size_t)size.QuadPart;
|
||||||
|
|
||||||
|
uint8_t *buf = malloc(file_size);
|
||||||
|
|
||||||
|
DWORD read = 0;
|
||||||
|
|
||||||
|
ReadFile(f, buf, file_size, &read, NULL);
|
||||||
|
|
||||||
|
png_structp png =
|
||||||
|
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
PngBuffer pngbuf = {buf, file_size, 0};
|
||||||
|
|
||||||
|
png_set_read_fn(png, &pngbuf, PngReadBufCB);
|
||||||
|
png_read_info(png, info);
|
||||||
|
|
||||||
|
BITMAPINFO bmi = {0};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = png_get_image_width(png, info);
|
||||||
|
bmi.bmiHeader.biHeight = -png_get_image_height(png, info); // top-down
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB; // THIS IS RAW - gordon ramsey
|
||||||
|
if (setjmp(png_jmpbuf(png))) {
|
||||||
|
png_destroy_read_struct(&png, &info, NULL);
|
||||||
|
// err
|
||||||
|
MessageBoxA(NULL, "zzzzzzzz", "zzzzzzzzz", 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
png_set_expand(png);
|
||||||
|
png_set_strip_16(png);
|
||||||
|
png_set_filler(png, 0xFF,
|
||||||
|
PNG_FILLER_AFTER); // ensures RGBA or RGBX cause winblows
|
||||||
|
png_read_update_info(png, info);
|
||||||
|
int width = png_get_image_width(png, info);
|
||||||
|
int height = png_get_image_height(png, info);
|
||||||
|
png_bytep *rows = malloc(height * sizeof(png_bytep));
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
rows[y] = malloc(png_get_rowbytes(png, info));
|
||||||
|
// flat BGRA buffer for GDI
|
||||||
|
uint8_t *fbuf = malloc(width * height * 4);
|
||||||
|
png_read_image(png, rows);
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
png_bytep src = rows[y];
|
||||||
|
uint8_t *dst = fbuf + y * width * 4;
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
dst[x * 4 + 0] = src[x * 4 + 2]; // B
|
||||||
|
dst[x * 4 + 1] = src[x * 4 + 1]; // G
|
||||||
|
dst[x * 4 + 2] = src[x * 4 + 0]; // R
|
||||||
|
dst[x * 4 + 3] = src[x * 4 + 3]; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void *gdibuf = NULL;
|
||||||
|
HBITMAP bmp =
|
||||||
|
CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &gdibuf, NULL, 0);
|
||||||
|
// GDI allocates its own dumbahh buffer so we just memcpy to it
|
||||||
|
memcpy(gdibuf, fbuf, width * height * 4);
|
||||||
|
free(fbuf);
|
||||||
|
HDC memdc = CreateCompatibleDC(NULL);
|
||||||
|
SelectObject(memdc, bmp);
|
||||||
|
CloseHandle(f);
|
||||||
|
return memdc;
|
||||||
|
}
|
||||||
|
HBITMAP LoadPNGBitmap(char *path) {
|
||||||
|
HANDLE f = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
if (f == INVALID_HANDLE_VALUE) {
|
||||||
|
MessageBoxA(NULL, "Error accessing the path!", "PNG Helper", 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
LARGE_INTEGER size;
|
||||||
|
GetFileSizeEx(f, &size);
|
||||||
|
|
||||||
|
size_t file_size = (size_t)size.QuadPart;
|
||||||
|
|
||||||
|
uint8_t *buf = malloc(file_size);
|
||||||
|
|
||||||
|
DWORD read = 0;
|
||||||
|
|
||||||
|
ReadFile(f, buf, file_size, &read, NULL);
|
||||||
|
|
||||||
|
png_structp png =
|
||||||
|
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
PngBuffer pngbuf = {buf, file_size, 0};
|
||||||
|
|
||||||
|
png_set_read_fn(png, &pngbuf, PngReadBufCB);
|
||||||
|
png_read_info(png, info);
|
||||||
|
|
||||||
|
BITMAPINFO bmi = {0};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = png_get_image_width(png, info);
|
||||||
|
bmi.bmiHeader.biHeight = -png_get_image_height(png, info); // top-down
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB; // THIS IS RAW - gordon ramsey
|
||||||
|
if (setjmp(png_jmpbuf(png))) {
|
||||||
|
png_destroy_read_struct(&png, &info, NULL);
|
||||||
|
// err
|
||||||
|
MessageBoxA(NULL, "zzzzzzzz", "zzzzzzzzz", 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
png_set_expand(png);
|
||||||
|
png_set_strip_16(png);
|
||||||
|
png_set_filler(png, 0xFF,
|
||||||
|
PNG_FILLER_AFTER); // ensures RGBA or RGBX cause winblows
|
||||||
|
png_read_update_info(png, info);
|
||||||
|
int width = png_get_image_width(png, info);
|
||||||
|
int height = png_get_image_height(png, info);
|
||||||
|
png_bytep *rows = malloc(height * sizeof(png_bytep));
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
rows[y] = malloc(png_get_rowbytes(png, info));
|
||||||
|
// flat BGRA buffer for GDI
|
||||||
|
uint8_t *fbuf = malloc(width * height * 4);
|
||||||
|
png_read_image(png, rows);
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
png_bytep src = rows[y];
|
||||||
|
uint8_t *dst = fbuf + y * width * 4;
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
dst[x * 4 + 0] = src[x * 4 + 2]; // B
|
||||||
|
dst[x * 4 + 1] = src[x * 4 + 1]; // G
|
||||||
|
dst[x * 4 + 2] = src[x * 4 + 0]; // R
|
||||||
|
dst[x * 4 + 3] = src[x * 4 + 3]; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void *gdibuf = NULL;
|
||||||
|
HBITMAP bmp =
|
||||||
|
CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &gdibuf, NULL, 0);
|
||||||
|
// GDI allocates its own dumbahh buffer so we just memcpy to it
|
||||||
|
memcpy(gdibuf, fbuf, width * height * 4);
|
||||||
|
free(fbuf);
|
||||||
|
return bmp;
|
||||||
|
}
|
||||||
|
HDC LoadPNGImageFromBytes(char *bytes, int buflen) {
|
||||||
|
png_structp png =
|
||||||
|
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
PngBuffer pngbuf = {bytes, buflen, 0};
|
||||||
|
|
||||||
|
png_set_read_fn(png, &pngbuf, PngReadBufCB);
|
||||||
|
png_read_info(png, info);
|
||||||
|
|
||||||
|
BITMAPINFO bmi = {0};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = png_get_image_width(png, info);
|
||||||
|
bmi.bmiHeader.biHeight = -png_get_image_height(png, info); // top-down
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB; // THIS IS RAW - gordon ramsey
|
||||||
|
if (setjmp(png_jmpbuf(png))) {
|
||||||
|
png_destroy_read_struct(&png, &info, NULL);
|
||||||
|
// err
|
||||||
|
MessageBoxA(NULL, "zzzzzzzz", "zzzzzzzzz", 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
png_set_expand(png);
|
||||||
|
png_set_strip_16(png);
|
||||||
|
png_set_filler(png, 0xFF,
|
||||||
|
PNG_FILLER_AFTER); // ensures RGBA or RGBX cause winblows
|
||||||
|
png_read_update_info(png, info);
|
||||||
|
int width = png_get_image_width(png, info);
|
||||||
|
int height = png_get_image_height(png, info);
|
||||||
|
png_bytep *rows = malloc(height * sizeof(png_bytep));
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
rows[y] = malloc(png_get_rowbytes(png, info));
|
||||||
|
// flat BGRA buffer for GDI
|
||||||
|
uint8_t *fbuf = malloc(width * height * 4);
|
||||||
|
png_read_image(png, rows);
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
png_bytep src = rows[y];
|
||||||
|
uint8_t *dst = fbuf + y * width * 4;
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
dst[x * 4 + 0] = src[x * 4 + 2]; // B
|
||||||
|
dst[x * 4 + 1] = src[x * 4 + 1]; // G
|
||||||
|
dst[x * 4 + 2] = src[x * 4 + 0]; // R
|
||||||
|
dst[x * 4 + 3] = src[x * 4 + 3]; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void *gdibuf = NULL;
|
||||||
|
HBITMAP bmp =
|
||||||
|
CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &gdibuf, NULL, 0);
|
||||||
|
// GDI allocates its own dumbahh buffer so we just memcpy to it
|
||||||
|
memcpy(gdibuf, fbuf, width * height * 4);
|
||||||
|
free(fbuf);
|
||||||
|
HDC memdc = CreateCompatibleDC(NULL);
|
||||||
|
SelectObject(memdc, bmp);
|
||||||
|
return memdc;
|
||||||
|
}
|
||||||
|
|
||||||
|
HDC LoadPNGImageFromResource(HMODULE hModule, int res) {
|
||||||
|
HRSRC resf = FindResource(hModule, MAKEINTRESOURCE(res), RT_RCDATA);
|
||||||
|
DWORD len = SizeofResource(hModule, resf);
|
||||||
|
HGLOBAL resl = LoadResource(hModule, resf);
|
||||||
|
void* resbuf = LockResource(resl);
|
||||||
|
png_structp png =
|
||||||
|
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
PngBuffer pngbuf = {resbuf, len, 0};
|
||||||
|
|
||||||
|
png_set_read_fn(png, &pngbuf, PngReadBufCB);
|
||||||
|
png_read_info(png, info);
|
||||||
|
|
||||||
|
BITMAPINFO bmi = {0};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = png_get_image_width(png, info);
|
||||||
|
bmi.bmiHeader.biHeight = -png_get_image_height(png, info); // top-down
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB; // THIS IS RAW - gordon ramsey
|
||||||
|
if (setjmp(png_jmpbuf(png))) {
|
||||||
|
png_destroy_read_struct(&png, &info, NULL);
|
||||||
|
// err
|
||||||
|
MessageBoxA(NULL, "zzzzzzzz", "zzzzzzzzz", 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
png_set_expand(png);
|
||||||
|
png_set_strip_16(png);
|
||||||
|
png_set_filler(png, 0xFF,
|
||||||
|
PNG_FILLER_AFTER); // ensures RGBA or RGBX cause winblows
|
||||||
|
png_read_update_info(png, info);
|
||||||
|
int width = png_get_image_width(png, info);
|
||||||
|
int height = png_get_image_height(png, info);
|
||||||
|
png_bytep *rows = malloc(height * sizeof(png_bytep));
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
rows[y] = malloc(png_get_rowbytes(png, info));
|
||||||
|
// flat BGRA buffer for GDI
|
||||||
|
uint8_t *fbuf = malloc(width * height * 4);
|
||||||
|
png_read_image(png, rows);
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
png_bytep src = rows[y];
|
||||||
|
uint8_t *dst = fbuf + y * width * 4;
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
dst[x * 4 + 0] = src[x * 4 + 2]; // B
|
||||||
|
dst[x * 4 + 1] = src[x * 4 + 1]; // G
|
||||||
|
dst[x * 4 + 2] = src[x * 4 + 0]; // R
|
||||||
|
dst[x * 4 + 3] = src[x * 4 + 3]; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void *gdibuf = NULL;
|
||||||
|
HBITMAP bmp =
|
||||||
|
CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &gdibuf, NULL, 0);
|
||||||
|
// GDI allocates its own dumbahh buffer so we just memcpy to it
|
||||||
|
memcpy(gdibuf, fbuf, width * height * 4);
|
||||||
|
free(fbuf);
|
||||||
|
HDC memdc = CreateCompatibleDC(NULL);
|
||||||
|
SelectObject(memdc, bmp);
|
||||||
|
return memdc;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#include <libpng18/png.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <windows.h>
|
||||||
|
typedef struct {
|
||||||
|
const uint8_t *data;
|
||||||
|
size_t size;
|
||||||
|
size_t pos;
|
||||||
|
} PngBuffer;
|
||||||
|
void PngReadBufCB(png_structp png, png_bytep out, png_size_t length);
|
||||||
|
HDC LoadPNGImage(char *path);
|
||||||
|
HDC LoadPNGImageFromBytes(char *bytes, int buflen);
|
||||||
|
HDC LoadPNGImageFromResource(HMODULE hModule, int res);
|
||||||
|
HBITMAP LoadPNGBitmap(char *path);
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "chatwnd.h"
|
||||||
|
#include "guildwnd.h"
|
||||||
|
|
||||||
|
extern HFONT regfont;
|
||||||
|
extern HFONT boldfont;
|
||||||
|
extern HFONT h1font;
|
||||||
|
extern HFONT h2font;
|
||||||
|
extern HFONT h3font;
|
||||||
|
|
||||||
|
void InitSnowsControls();
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#define user_agent \
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " \
|
||||||
|
"Chrome/142.0.0.0 Safari/537.36"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
category: id='1479059798421930076' name='Text Channels'
|
||||||
|
category: id='1450471843638284413' name='Text Channels'
|
||||||
Executable
+433
@@ -0,0 +1,433 @@
|
|||||||
|
#include "discordtypes.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winnt.h>
|
||||||
|
#include <winternl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <cjson/cJSON.h>
|
||||||
|
#include "ws.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "hmap/hashmap.h"
|
||||||
|
hmap GuildsTable = NULL;
|
||||||
|
hmap ChannelsTable = NULL;
|
||||||
|
DiscordChannel *PrivateChannels = NULL;
|
||||||
|
int PrivateChannelCount = 0;
|
||||||
|
static uint64_t GuildHash(const void *item, uint64_t seed0, uint64_t seed1) {
|
||||||
|
const DiscordGuild *e = item;
|
||||||
|
return hashmap_sip(e->id, strlen(e->id), seed0, seed1);
|
||||||
|
}
|
||||||
|
static int GuildCompare(const void *a, const void *b, void *udata) {
|
||||||
|
return strcmp(((DiscordGuild *)a)->id, ((DiscordGuild *)b)->id);
|
||||||
|
}
|
||||||
|
static void GuildFree(void *item) {}
|
||||||
|
|
||||||
|
static uint64_t ChannelHash(const void *item, uint64_t seed0, uint64_t seed1) {
|
||||||
|
const DiscordChannel *e = item;
|
||||||
|
return hashmap_sip(e->id, strlen(e->id), seed0, seed1);
|
||||||
|
}
|
||||||
|
static int ChannelCompare(const void *a, const void *b, void *udata) {
|
||||||
|
return strcmp(((DiscordChannel *)a)->id, ((DiscordChannel *)b)->id);
|
||||||
|
}
|
||||||
|
static void ChannelFree(void *item) {}
|
||||||
|
extern void onDiscordGuildLoad(DiscordGuild *guild, char *id);
|
||||||
|
extern void onDiscordUpdatedGuildReadState(DiscordGuild gld);
|
||||||
|
int DiscordSendMessage(const char *channelID, const char *content) {
|
||||||
|
cJSON *payload = cJSON_CreateObject();
|
||||||
|
cJSON *name = cJSON_CreateString(content);
|
||||||
|
cJSON_AddItemToObject(payload, "content", name);
|
||||||
|
char *endpoint = malloc(28 + strlen(channelID));
|
||||||
|
endpoint[0] = '\0';
|
||||||
|
strcat(endpoint, "/api/v9/channels/");
|
||||||
|
strcat(endpoint, channelID);
|
||||||
|
strcat(endpoint, "/messages");
|
||||||
|
char *forNothingGng;
|
||||||
|
char *payld = cJSON_Print(payload);
|
||||||
|
long resplen;
|
||||||
|
char headers[17 + strlen(token) + 1];
|
||||||
|
wsprintf(headers, "Authorization: %s\r\n", token);
|
||||||
|
SendShortHTTPReq("discord.com", "POST", endpoint, headers, user_agent,
|
||||||
|
"application/json", payld, strlen(payld), &forNothingGng,
|
||||||
|
&resplen);
|
||||||
|
free(payld);
|
||||||
|
cJSON_free(payload);
|
||||||
|
free(forNothingGng);
|
||||||
|
}
|
||||||
|
int DiscordListGuildChannels(char *guildID, DiscordChannel **out) {
|
||||||
|
DiscordGuild *gld;
|
||||||
|
DiscordGuild search;
|
||||||
|
search.id = guildID;
|
||||||
|
gld = hashmap_get(GuildsTable, &search);
|
||||||
|
DiscordChannel *arr1 = (RtlAllocateHeap(
|
||||||
|
GetProcessHeap(), 0, sizeof(DiscordChannel) * gld->ChannelCount));
|
||||||
|
memset(arr1, 0, sizeof(DiscordChannel) * gld->ChannelCount);
|
||||||
|
*out = arr1;
|
||||||
|
for (int i = 0; i < gld->ChannelCount; i++) {
|
||||||
|
(*out)[i] = gld->channels[i];
|
||||||
|
}
|
||||||
|
return gld->ChannelCount;
|
||||||
|
}
|
||||||
|
char *DiscordFetchTmpPfp(char *userID, char *hash) {
|
||||||
|
char *endpoint = malloc(80);
|
||||||
|
wsprintfA(endpoint, "/avatars/%s/%s.png?size=512", userID, hash);
|
||||||
|
char *HTTPResp;
|
||||||
|
long resplen;
|
||||||
|
char headers[17 + strlen(token) + 1];
|
||||||
|
wsprintf(headers, "Authorization: %s\r\n", token);
|
||||||
|
SendShortHTTPReq("cdn.discordapp.com", "GET", endpoint, headers, user_agent,
|
||||||
|
"application/json", NULL, 0, &HTTPResp, &resplen);
|
||||||
|
free(endpoint);
|
||||||
|
|
||||||
|
char tempPath[MAX_PATH];
|
||||||
|
|
||||||
|
DWORD len = GetTempPathA(MAX_PATH, tempPath);
|
||||||
|
char *path = malloc(MAX_PATH + strlen(hash) + 1);
|
||||||
|
wsprintfA(path, "%s%s.png", tempPath, hash);
|
||||||
|
HANDLE file = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
||||||
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
DWORD written;
|
||||||
|
if (file == INVALID_HANDLE_VALUE) {
|
||||||
|
MessageBoxA(NULL,
|
||||||
|
"oof, failed opening the file to save the tmp pfp file!",
|
||||||
|
"Backcord - DiscordFetchTmpPfp", 0);
|
||||||
|
free(path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
WriteFile(file, HTTPResp, resplen, &written, NULL);
|
||||||
|
CloseHandle(file);
|
||||||
|
free(HTTPResp);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
char *DiscordFetchTmpGuildIcon(char *guildID, char *hash) {
|
||||||
|
char *endpoint = malloc(80);
|
||||||
|
wsprintfA(endpoint, "/icons/%s/%s.png?size=512", guildID, hash);
|
||||||
|
char *HTTPResp;
|
||||||
|
long resplen;
|
||||||
|
char headers[17 + strlen(token) + 1];
|
||||||
|
wsprintf(headers, "Authorization: %s\r\n", token);
|
||||||
|
SendShortHTTPReq("cdn.discordapp.com", "GET", endpoint, headers, user_agent,
|
||||||
|
"application/json", NULL, 0, &HTTPResp, &resplen);
|
||||||
|
free(endpoint);
|
||||||
|
|
||||||
|
char tempPath[MAX_PATH];
|
||||||
|
|
||||||
|
DWORD len = GetTempPathA(MAX_PATH, tempPath);
|
||||||
|
char *path = malloc(MAX_PATH + strlen(hash) + 1);
|
||||||
|
wsprintfA(path, "%s%s.png", tempPath, hash);
|
||||||
|
HANDLE file = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
||||||
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
DWORD written;
|
||||||
|
if (file == INVALID_HANDLE_VALUE) {
|
||||||
|
MessageBoxA(NULL,
|
||||||
|
"oof, failed opening the file to save the tmp icon file!",
|
||||||
|
"Backcord - Guild Icon Retrieval", 0);
|
||||||
|
free(path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
WriteFile(file, HTTPResp, resplen, &written, NULL);
|
||||||
|
CloseHandle(file);
|
||||||
|
free(HTTPResp);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
int DiscordGetChannelHistory(const char *channelID, unsigned int amount,
|
||||||
|
DiscordMessage **msgs) {
|
||||||
|
cJSON *payload = cJSON_CreateObject();
|
||||||
|
char *endpoint = malloc(38 + strlen(channelID));
|
||||||
|
memset((endpoint), 0, (27 + strlen(channelID)));
|
||||||
|
strcat(endpoint, "/api/v9/channels/");
|
||||||
|
strcat(endpoint, channelID);
|
||||||
|
strcat(endpoint, "/messages?limit=");
|
||||||
|
char *amountstr = malloc(3 * sizeof(char));
|
||||||
|
wsprintf(amountstr, "%i", amount);
|
||||||
|
strcat(endpoint, amountstr);
|
||||||
|
free(amountstr);
|
||||||
|
char *HTTPResp;
|
||||||
|
long resplen;
|
||||||
|
|
||||||
|
char headers[17 + strlen(token) + 1];
|
||||||
|
wsprintf(headers, "Authorization: %s\r\n", token);
|
||||||
|
|
||||||
|
SendShortHTTPReq("discord.com", "GET", endpoint, headers, user_agent,
|
||||||
|
"application/json", NULL, 0, &HTTPResp, &resplen);
|
||||||
|
cJSON *resp = cJSON_Parse(HTTPResp);
|
||||||
|
if (!resp)
|
||||||
|
return 0;
|
||||||
|
if (!cJSON_IsArray(resp))
|
||||||
|
return 0;
|
||||||
|
*msgs = malloc(sizeof(DiscordMessage) * amount);
|
||||||
|
for (int i = 0; i < cJSON_GetArraySize(resp); i++) {
|
||||||
|
cJSON *item = cJSON_GetArrayItem(resp, i);
|
||||||
|
|
||||||
|
(*msgs)[i].content = cJSON_GetObjectItem(item, "content")->valuestring;
|
||||||
|
(*msgs)[i].channelID =
|
||||||
|
cJSON_GetObjectItem(item, "channel_id")->valuestring;
|
||||||
|
(*msgs)[i].id = cJSON_GetObjectItem(item, "id")->valuestring;
|
||||||
|
(*msgs)[i].type = cJSON_GetObjectItem(item, "type")->valueint;
|
||||||
|
cJSON *author = cJSON_GetObjectItem(item, "author");
|
||||||
|
cJSON *dispname = cJSON_GetObjectItem(author, "global_name");
|
||||||
|
(*msgs)[i].author.DisplayName = (dispname && cJSON_IsString(dispname))
|
||||||
|
? strdup(dispname->valuestring)
|
||||||
|
: NULL;
|
||||||
|
|
||||||
|
cJSON *av = cJSON_GetObjectItem(author, "avatar");
|
||||||
|
(*msgs)[i].author.avatar =
|
||||||
|
(av && cJSON_IsString(av)) ? strdup(av->valuestring) : NULL;
|
||||||
|
|
||||||
|
cJSON *ct = cJSON_GetObjectItem(item, "content");
|
||||||
|
(*msgs)[i].content =
|
||||||
|
(ct && cJSON_IsString(ct)) ? strdup(ct->valuestring) : strdup("");
|
||||||
|
(*msgs)[i].author.Username =
|
||||||
|
cJSON_GetObjectItem(author, "username")->valuestring;
|
||||||
|
(*msgs)[i].author.DisplayName =
|
||||||
|
cJSON_GetObjectItem(author, "global_name")->valuestring;
|
||||||
|
(*msgs)[i].author.id = cJSON_GetObjectItem(author, "id")->valuestring;
|
||||||
|
(*msgs)[i].author.avatar =
|
||||||
|
cJSON_GetObjectItem(author, "avatar")->valuestring;
|
||||||
|
}
|
||||||
|
return cJSON_GetArraySize(resp);
|
||||||
|
free(HTTPResp);
|
||||||
|
}
|
||||||
|
int ConnectGateway(const char *channelID, const char *content) {}
|
||||||
|
DiscordGuild *GetGuild(char *id) {
|
||||||
|
DiscordGuild *g;
|
||||||
|
DiscordGuild search;
|
||||||
|
search.id = id;
|
||||||
|
g = (DiscordGuild*)hashmap_get(GuildsTable, &search);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
int DiscordListPrivateChannels(DiscordChannel **out) {
|
||||||
|
*out = PrivateChannels;
|
||||||
|
return PrivateChannelCount;
|
||||||
|
}
|
||||||
|
void HandleREADY(cJSON *json) {
|
||||||
|
cJSON *data = cJSON_GetObjectItem(json, "d");
|
||||||
|
if (!cJSON_IsObject(data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
cJSON *guilds = cJSON_GetObjectItem(data, "guilds");
|
||||||
|
if (!cJSON_IsArray(guilds))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int size = cJSON_GetArraySize(guilds);
|
||||||
|
GuildsTable = hashmap_new(sizeof(DiscordGuild), 256, 0, 0, GuildHash,
|
||||||
|
GuildCompare, GuildFree, NULL);
|
||||||
|
ChannelsTable = hashmap_new(sizeof(DiscordChannel), 256, 0, 0, ChannelHash,
|
||||||
|
ChannelCompare, ChannelFree, NULL);
|
||||||
|
|
||||||
|
cJSON *privchannels = cJSON_GetObjectItem(data, "private_channels");
|
||||||
|
PrivateChannelCount = cJSON_GetArraySize(privchannels);
|
||||||
|
PrivateChannels = malloc(sizeof(DiscordChannel) * PrivateChannelCount);
|
||||||
|
for (int j = 0; j < PrivateChannelCount; j++) {
|
||||||
|
cJSON *ch = cJSON_GetArrayItem(privchannels, j);
|
||||||
|
if (!cJSON_IsObject(ch))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cJSON *chName = cJSON_GetObjectItem(ch, "name");
|
||||||
|
cJSON *chType = cJSON_GetObjectItem(ch, "type");
|
||||||
|
cJSON *chId = cJSON_GetObjectItem(ch, "id");
|
||||||
|
cJSON *chPid = cJSON_GetObjectItem(ch, "parent_id");
|
||||||
|
cJSON *chRecp = cJSON_GetObjectItem(ch, "recipients");
|
||||||
|
|
||||||
|
if (!cJSON_IsString(chId) || !cJSON_IsNumber(chType))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PrivateChannels[j].GuildID = NULL;
|
||||||
|
PrivateChannels[j].name = chName ? strdup(chName->valuestring) : NULL;
|
||||||
|
PrivateChannels[j].id = strdup(chId->valuestring);
|
||||||
|
PrivateChannels[j].type = chType->valueint;
|
||||||
|
PrivateChannels[j].parentID = (chPid && cJSON_IsString(chPid))
|
||||||
|
? strdup(chPid->valuestring)
|
||||||
|
: NULL;
|
||||||
|
PrivateChannels[j].receipents =
|
||||||
|
malloc(cJSON_GetArraySize(chRecp) * sizeof(DiscordUser));
|
||||||
|
for (int i = 0; i < cJSON_GetArraySize(chRecp); i++) {
|
||||||
|
cJSON *recep = cJSON_GetArrayItem(chRecp, i);
|
||||||
|
cJSON *recpAv = cJSON_GetObjectItem(recep, "avatar");
|
||||||
|
PrivateChannels[j].receipents[i].avatar =
|
||||||
|
(recpAv && cJSON_IsString(recpAv)) ? strdup(recpAv->valuestring)
|
||||||
|
: NULL;
|
||||||
|
|
||||||
|
cJSON *recpUn = cJSON_GetObjectItem(recep, "username");
|
||||||
|
PrivateChannels[j].receipents[i].Username =
|
||||||
|
(recpUn && cJSON_IsString(recpUn)) ? strdup(recpUn->valuestring)
|
||||||
|
: NULL;
|
||||||
|
|
||||||
|
cJSON *recpDn = cJSON_GetObjectItem(recep, "global_name");
|
||||||
|
PrivateChannels[j].receipents[i].DisplayName =
|
||||||
|
(recpDn && cJSON_IsString(recpDn)) ? strdup(recpDn->valuestring)
|
||||||
|
: NULL;
|
||||||
|
|
||||||
|
cJSON *recpId = cJSON_GetObjectItem(recep, "id");
|
||||||
|
PrivateChannels[j].receipents[i].id =
|
||||||
|
(recpId && cJSON_IsString(recpId)) ? strdup(recpId->valuestring)
|
||||||
|
: NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = size - 1; i >= 0; i--) {
|
||||||
|
cJSON *guild = cJSON_GetArrayItem(guilds, i);
|
||||||
|
if (!cJSON_IsObject(guild))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cJSON *channels = cJSON_GetObjectItem(guild, "channels");
|
||||||
|
int channelCount = cJSON_GetArraySize(channels);
|
||||||
|
DiscordChannel *gchannels =
|
||||||
|
malloc(sizeof(DiscordChannel) * channelCount);
|
||||||
|
for (int j = 0; j < channelCount; j++) {
|
||||||
|
cJSON *ch = cJSON_GetArrayItem(channels, j);
|
||||||
|
if (!cJSON_IsObject(ch))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cJSON *chName = cJSON_GetObjectItem(ch, "name");
|
||||||
|
cJSON *chType = cJSON_GetObjectItem(ch, "type");
|
||||||
|
cJSON *chId = cJSON_GetObjectItem(ch, "id");
|
||||||
|
cJSON *chPid = cJSON_GetObjectItem(ch, "parent_id");
|
||||||
|
|
||||||
|
if (!cJSON_IsString(chName) || !cJSON_IsString(chId) ||
|
||||||
|
!cJSON_IsNumber(chType))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
gchannels[j].name = strdup(chName->valuestring);
|
||||||
|
gchannels[j].id = strdup(chId->valuestring);
|
||||||
|
gchannels[j].type = chType->valueint;
|
||||||
|
gchannels[j].parentID = (chPid && cJSON_IsString(chPid))
|
||||||
|
? strdup(chPid->valuestring)
|
||||||
|
: NULL;
|
||||||
|
gchannels[j].GuildID =
|
||||||
|
strdup(cJSON_GetObjectItem(guild, "id")->valuestring);
|
||||||
|
hashmap_set(ChannelsTable,&gchannels[j]);
|
||||||
|
}
|
||||||
|
char *name = cJSON_GetObjectItem(guild, "name")->valuestring;
|
||||||
|
char *id = cJSON_GetObjectItem(guild, "id")->valuestring;
|
||||||
|
DiscordGuild *g = malloc(sizeof(DiscordGuild));
|
||||||
|
g->id = strdup(id);
|
||||||
|
g->name = strdup(name);
|
||||||
|
cJSON *icon = cJSON_GetObjectItem(guild, "icon");
|
||||||
|
g->IconHash =
|
||||||
|
(icon && cJSON_IsString(icon)) ? strdup(icon->valuestring) : NULL;
|
||||||
|
g->ObtainedIconPath = NULL;
|
||||||
|
g->channels = gchannels;
|
||||||
|
g->ChannelCount = channelCount;
|
||||||
|
g->MentionCount = 0;
|
||||||
|
hashmap_set(GuildsTable, g);
|
||||||
|
|
||||||
|
onDiscordGuildLoad(g, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *readstates = cJSON_GetObjectItem(data, "read_state");
|
||||||
|
for (int i = 0; i < cJSON_GetArraySize(readstates); i++) {
|
||||||
|
cJSON *readstate = cJSON_GetArrayItem(readstates, i);
|
||||||
|
DiscordChannel chnprop;
|
||||||
|
chnprop.id = cJSON_GetObjectItem(readstate, "id")->valuestring;
|
||||||
|
DiscordChannel *che =
|
||||||
|
(DiscordChannel *)hashmap_get(ChannelsTable, &chnprop);
|
||||||
|
if (!che) {
|
||||||
|
// bradar WHAT IS THIS
|
||||||
|
printf("NOT FOUND: Channel %s\n", chnprop.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
che->ReadState.MentionCount =
|
||||||
|
cJSON_GetObjectItem(readstate, "mention_count")
|
||||||
|
? cJSON_GetObjectItem(readstate, "mention_count")->valueint
|
||||||
|
: 0;
|
||||||
|
if (cJSON_GetObjectItem(readstate, "mention_count")
|
||||||
|
? cJSON_GetObjectItem(readstate, "mention_count")->valueint
|
||||||
|
: 0) {
|
||||||
|
if (che->GuildID) {
|
||||||
|
DiscordGuild *gld = GetGuild(che->GuildID);
|
||||||
|
MessageBoxA(NULL, gld->name, "AAAAAAAAAA", 0);
|
||||||
|
if (gld) {
|
||||||
|
gld->MentionCount += che->ReadState.MentionCount;
|
||||||
|
onDiscordUpdatedGuildReadState(*gld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extern void onDiscordReceiveMessage(DiscordMessage msg);
|
||||||
|
void HandleMessageCreate(cJSON *json) {
|
||||||
|
cJSON *d = cJSON_GetObjectItem(json, "d");
|
||||||
|
cJSON *author = cJSON_GetObjectItem(d, "author");
|
||||||
|
DiscordMessage msg;
|
||||||
|
msg.author.DisplayName =
|
||||||
|
strdup(cJSON_GetObjectItem(author, "global_name")->valuestring);
|
||||||
|
msg.author.id = strdup(cJSON_GetObjectItem(author, "id")->valuestring);
|
||||||
|
msg.channelID = strdup(cJSON_GetObjectItem(d, "channel_id")->valuestring);
|
||||||
|
msg.content = strdup(cJSON_GetObjectItem(d, "content")->valuestring);
|
||||||
|
if (cJSON_GetObjectItem(author, "avatar")) {
|
||||||
|
msg.author.avatar = cJSON_GetObjectItem(author, "avatar")->valuestring;
|
||||||
|
}
|
||||||
|
onDiscordReceiveMessage(msg);
|
||||||
|
}
|
||||||
|
void HandleDiscordDispatch(cJSON *json) {
|
||||||
|
if (!cJSON_IsObject(json))
|
||||||
|
return;
|
||||||
|
cJSON *t = cJSON_GetObjectItem(json, "t");
|
||||||
|
if (!t || !cJSON_IsString(t))
|
||||||
|
return;
|
||||||
|
if (strcmp(t->valuestring, "READY") == 0) {
|
||||||
|
HandleREADY(json);
|
||||||
|
}
|
||||||
|
if (strcmp(t->valuestring, "MESSAGE_CREATE") == 0) {
|
||||||
|
HandleMessageCreate(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CALLBACK HeartbeatProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent,
|
||||||
|
DWORD dwTime) {
|
||||||
|
SendWebSocket(GatewaySSL, "{\"op\": 1,\"d\": null}", 19, 0x81);
|
||||||
|
}
|
||||||
|
void WebSocketOnDataArrival(SSL *ssl, char *buffer, size_t length) {
|
||||||
|
cJSON *json = cJSON_Parse(buffer);
|
||||||
|
if (json == NULL) {
|
||||||
|
if (strstr(buffer, "Authentication failed") != 0) {
|
||||||
|
MessageBoxA(NULL,
|
||||||
|
"The provided token may be outdated, there was an "
|
||||||
|
"issue in authentication!",
|
||||||
|
"Backcord - Gateway Error", MB_ICONERROR);
|
||||||
|
}
|
||||||
|
const char *error = cJSON_GetErrorPtr();
|
||||||
|
if (error) {
|
||||||
|
printf("Parse error before: %s\n", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int opcode = (cJSON_GetObjectItem(json, "op"))->valueint;
|
||||||
|
cJSON *data = (cJSON_GetObjectItem(json, "d"));
|
||||||
|
|
||||||
|
if (opcode == 10) {
|
||||||
|
// received hello.. discord api wants us to send a heartbeat rn and
|
||||||
|
// identify
|
||||||
|
|
||||||
|
SendWebSocket(ssl, "{\"op\": 1,\"d\": null}", 19, FIN_LAST | WS_TEXT);
|
||||||
|
char identifybuf[650 + strlen(token)];
|
||||||
|
wsprintf(
|
||||||
|
identifybuf,
|
||||||
|
"{\"op\":2,\"d\":{\"token\":\"%s\",\"properties\":{\"os\":"
|
||||||
|
"\"Linux\",\"browser\":\"Chrome\",\"device\":\"\",\"has_client_"
|
||||||
|
"mods\":false,\"browser_user_agent\":\"Mozilla/5.0 (Linux; Android "
|
||||||
|
"6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
|
"Chrome/145.0.0.0 Mobile Safari/537.36\",\"release_channel\": "
|
||||||
|
"\"stable\"},\"presence\":{},\"compress\":false,"
|
||||||
|
"\"client_state\":{\"guild_versions\":{}}}}",
|
||||||
|
token);
|
||||||
|
SendWebSocket(ssl, identifybuf, strlen(identifybuf), 0x81);;
|
||||||
|
SetTimer(NULL, 0,
|
||||||
|
cJSON_GetObjectItem(data, "heartbeat_interval")->valueint * 4 /
|
||||||
|
5,
|
||||||
|
HeartbeatProc);
|
||||||
|
|
||||||
|
} else if (opcode == 11) {
|
||||||
|
// heartbeat ack
|
||||||
|
|
||||||
|
} else if (opcode == 1) {
|
||||||
|
// discords heart fluttered :wilted_rose:
|
||||||
|
SendWebSocket(GatewaySSL, "{\"op\": 1,\"d\": null}", 19, 0x81);
|
||||||
|
} else if (opcode == 0) {
|
||||||
|
HandleDiscordDispatch(json);
|
||||||
|
} else if (opcode == 7) {
|
||||||
|
MessageBoxA(NULL, "reconnect", "discordapi.c", 0);
|
||||||
|
}
|
||||||
|
cJSON_Delete(json);
|
||||||
|
}
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#include "globals.h"
|
||||||
|
#include "components/snoctrl.h"
|
||||||
|
int DiscordSendMessage(const char *channelID, const char* content);
|
||||||
|
int DiscordListGuildChannels(char* guildID, DiscordChannel** out);
|
||||||
|
#define GUILD_TEXT 0
|
||||||
|
#define CHANNEL_TYPE_DM 1
|
||||||
|
#define GUILD_VOICE 2
|
||||||
|
#define GROUPCHAT 3
|
||||||
|
#define GUILD_CATEGORY 4
|
||||||
|
int DiscordGetChannelHistory(const char* channelID, unsigned int amount, DiscordMessage** msgs);
|
||||||
|
char *DiscordFetchTmpPfp(char *userID, char *hash);
|
||||||
|
char *DiscordFetchTmpGuildIcon(char *guildID, char *hash);
|
||||||
|
int DiscordListPrivateChannels(DiscordChannel** out);
|
||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
typedef struct{
|
||||||
|
char* Username;
|
||||||
|
char* DisplayName;
|
||||||
|
char* id;
|
||||||
|
char *avatar;
|
||||||
|
} DiscordUser;
|
||||||
|
typedef struct {
|
||||||
|
char *channelID;
|
||||||
|
DiscordUser author;
|
||||||
|
char* content;
|
||||||
|
char* id;
|
||||||
|
int type;
|
||||||
|
} DiscordMessage;
|
||||||
|
typedef struct {
|
||||||
|
int MentionCount;
|
||||||
|
} ReadState;
|
||||||
|
typedef struct {
|
||||||
|
char *id;
|
||||||
|
char *name;
|
||||||
|
char *parentID; /* category ID this channel belongs to, NULL if none */
|
||||||
|
char *topic; /* channel topic/description, NULL if unset */
|
||||||
|
char *lastMessageID; /* snowflake of last message, NULL if none */
|
||||||
|
int type;
|
||||||
|
int position; /* sort order within the guild/category */
|
||||||
|
int flags;
|
||||||
|
int receipentCount;
|
||||||
|
DiscordUser* receipents;
|
||||||
|
ReadState ReadState;
|
||||||
|
char* GuildID;
|
||||||
|
} DiscordChannel;
|
||||||
|
typedef struct {
|
||||||
|
char *id;
|
||||||
|
char *name;
|
||||||
|
char *IconHash;
|
||||||
|
char *ObtainedIconPath;
|
||||||
|
DiscordChannel* channels;
|
||||||
|
int ChannelCount;
|
||||||
|
int MentionCount;
|
||||||
|
} DiscordGuild;
|
||||||
Executable
+2290
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <commctrl.h>
|
||||||
|
#include "discordtypes.h"
|
||||||
|
extern char* token;
|
||||||
|
extern SSL *GatewaySSL;
|
||||||
|
typedef struct{
|
||||||
|
HTREEITEM itm;
|
||||||
|
char* id;
|
||||||
|
} ChannelUIEntry;
|
||||||
Executable
+1154
File diff suppressed because it is too large
Load Diff
Executable
+63
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2020 Joshua J Baker. All rights reserved.
|
||||||
|
// Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef HASHMAP_H
|
||||||
|
#define HASHMAP_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
struct hashmap;
|
||||||
|
typedef struct hashmap* hmap;
|
||||||
|
|
||||||
|
struct hashmap *hashmap_new(size_t elsize, size_t cap, uint64_t seed0,
|
||||||
|
uint64_t seed1,
|
||||||
|
uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1),
|
||||||
|
int (*compare)(const void *a, const void *b, void *udata),
|
||||||
|
void (*elfree)(void *item),
|
||||||
|
void *udata);
|
||||||
|
|
||||||
|
struct hashmap *hashmap_new_with_allocator(void *(*malloc)(size_t),
|
||||||
|
void *(*realloc)(void *, size_t), void (*free)(void*), size_t elsize,
|
||||||
|
size_t cap, uint64_t seed0, uint64_t seed1,
|
||||||
|
uint64_t (*hash)(const void *item, uint64_t seed0, uint64_t seed1),
|
||||||
|
int (*compare)(const void *a, const void *b, void *udata),
|
||||||
|
void (*elfree)(void *item),
|
||||||
|
void *udata);
|
||||||
|
|
||||||
|
void hashmap_free(struct hashmap *map);
|
||||||
|
void hashmap_clear(struct hashmap *map, bool update_cap);
|
||||||
|
size_t hashmap_count(const struct hashmap *map);
|
||||||
|
bool hashmap_oom(struct hashmap *map);
|
||||||
|
const void *hashmap_get(const struct hashmap *map, const void *item);
|
||||||
|
const void *hashmap_set(struct hashmap *map, const void *item);
|
||||||
|
const void *hashmap_delete(struct hashmap *map, const void *item);
|
||||||
|
const void *hashmap_probe(struct hashmap *map, uint64_t position);
|
||||||
|
bool hashmap_scan(struct hashmap *map, bool (*iter)(const void *item, void *udata), void *udata);
|
||||||
|
bool hashmap_iter(struct hashmap *map, size_t *i, void **item);
|
||||||
|
|
||||||
|
uint64_t hashmap_sip(const void *data, size_t len, uint64_t seed0, uint64_t seed1);
|
||||||
|
uint64_t hashmap_murmur(const void *data, size_t len, uint64_t seed0, uint64_t seed1);
|
||||||
|
uint64_t hashmap_xxhash3(const void *data, size_t len, uint64_t seed0, uint64_t seed1);
|
||||||
|
|
||||||
|
const void *hashmap_get_with_hash(const struct hashmap *map, const void *key, uint64_t hash);
|
||||||
|
const void *hashmap_delete_with_hash(struct hashmap *map, const void *key, uint64_t hash);
|
||||||
|
const void *hashmap_set_with_hash(struct hashmap *map, const void *item, uint64_t hash);
|
||||||
|
void hashmap_set_grow_by_power(struct hashmap *map, size_t power);
|
||||||
|
void hashmap_set_load_factor(struct hashmap *map, double load_factor);
|
||||||
|
|
||||||
|
|
||||||
|
// DEPRECATED: use `hashmap_new_with_allocator`
|
||||||
|
void hashmap_set_allocator(void *(*malloc)(size_t), void (*free)(void*));
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif // HASHMAP_H
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
#include "http.h"
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
static void AppendHeader(char* buf,
|
||||||
|
const char* key, const char* value)
|
||||||
|
{
|
||||||
|
strcat(buf, key);
|
||||||
|
strcat(buf, ": ");
|
||||||
|
strcat(buf, value);
|
||||||
|
strcat(buf, "\r\n");
|
||||||
|
}
|
||||||
|
char* ProcessChunkedTransfer(char* chunked, size_t chunked_len, size_t *out_len) {
|
||||||
|
char* result = malloc(chunked_len);
|
||||||
|
size_t written = 0;
|
||||||
|
char* curpos = chunked;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
char* crlf = strstr(curpos, "\r\n");
|
||||||
|
if(!crlf) break;
|
||||||
|
|
||||||
|
int chunklen = (int)strtol(curpos, NULL, 16);
|
||||||
|
if(chunklen == 0) break;
|
||||||
|
|
||||||
|
char* data = crlf + 2;
|
||||||
|
memcpy(result + written, data, chunklen);
|
||||||
|
written += chunklen;
|
||||||
|
curpos = data + chunklen + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_len = written;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int SendShortHTTPReq(const char* hostname,const char* reqtype, const char* endpoint, const char* extra_headers, const char* user_agent, const char* content_type, void* payload, unsigned long payload_size, char** response, long* responselen)
|
||||||
|
{
|
||||||
|
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
MessageBoxA(NULL, "socket failed", "Aa", 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in server;
|
||||||
|
RtlZeroMemory(&server, sizeof(server));
|
||||||
|
server.sin_family = AF_INET;
|
||||||
|
server.sin_port = htons(443); // HTTPS brotatochippiee
|
||||||
|
server.sin_addr = **(struct in_addr**)gethostbyname(hostname)->h_addr_list;
|
||||||
|
|
||||||
|
if (connect(sock, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) {
|
||||||
|
MessageBoxA(NULL, "connect failed", "Aa", 0);
|
||||||
|
closesocket(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this debugging thing served us well yall MessageBoxA(NULL, "onto ze ctx", "Aa", 0);
|
||||||
|
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
|
||||||
|
if (!ctx) {
|
||||||
|
MessageBoxA(NULL, "SSL_CTX_new failed", "Aa", 0);
|
||||||
|
unsigned long err = ERR_get_error();
|
||||||
|
if (err == 0) {
|
||||||
|
MessageBoxA(NULL, "unknown OpenSSL error", "title", MB_ICONERROR);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[256];
|
||||||
|
ERR_error_string_n(err, buf, sizeof(buf));
|
||||||
|
|
||||||
|
MessageBoxA(NULL, buf, "title", MB_ICONERROR);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL* ssl = SSL_new(ctx);
|
||||||
|
if (!ssl) {
|
||||||
|
MessageBoxA(NULL, "SSL_new failed qwq", "Aa", 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach socket to SSL
|
||||||
|
SSL_set_fd(ssl, (int)sock);
|
||||||
|
|
||||||
|
// SNI (importante shi)
|
||||||
|
SSL_set_tlsext_host_name(ssl, hostname);
|
||||||
|
|
||||||
|
if (SSL_connect(ssl) <= 0) {
|
||||||
|
MessageBoxA(NULL, "SSL_connect failed", "Aa", 0);
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SSL_write(ssl, reqtype, (int)strlen(reqtype)); // Sends request type
|
||||||
|
SSL_write(ssl, " ", 1); // stupid fuckass space
|
||||||
|
SSL_write(ssl, endpoint, (int)strlen(endpoint)); // endpoint
|
||||||
|
SSL_write(ssl, " HTTP/1.1\r\n", 11); // the protocol thing.. ykw i will be more formal
|
||||||
|
SSL_write(ssl, "Host: ", 6); // Host beginning
|
||||||
|
SSL_write(ssl, hostname, (int)(strlen(hostname))); // Host data
|
||||||
|
SSL_write(ssl, "\r\n", 2); // Host end
|
||||||
|
SSL_write(ssl, "User-Agent: ", 12); // User Agent beginning
|
||||||
|
SSL_write(ssl, user_agent, (int)(strlen(user_agent))); // User Agent data
|
||||||
|
SSL_write(ssl, "\r\n", 2); // User Agent end
|
||||||
|
|
||||||
|
|
||||||
|
if (payload != 0) {
|
||||||
|
SSL_write(ssl, "Content-Type: ", 14); // Content Type beginning
|
||||||
|
SSL_write(ssl, content_type, (int)strlen(content_type));
|
||||||
|
SSL_write(ssl, "\r\n", 2); // Content Type end
|
||||||
|
|
||||||
|
SSL_write(ssl, "Content-Length: ", 16); // Content Length beginning
|
||||||
|
char lenstr[64];
|
||||||
|
RtlZeroMemory(lenstr,64);
|
||||||
|
wsprintfA(lenstr,"%d",payload_size);
|
||||||
|
SSL_write(ssl, lenstr, (int)(strlen(lenstr))); // Content Length data
|
||||||
|
SSL_write(ssl, "\r\n", 2); // Content Length end
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SSL_write(ssl, extra_headers, (int)(strlen(extra_headers))); // Extra Header
|
||||||
|
SSL_write(ssl, "Connection: close\r\n", 19); //Short-term connection
|
||||||
|
SSL_write(ssl, "\r\n", 2); //Ends the header
|
||||||
|
if(payload)
|
||||||
|
SSL_write(ssl, payload, (int)payload_size); //Payload
|
||||||
|
|
||||||
|
char* buffer = malloc(4096);
|
||||||
|
size_t sizeofbuf = 4096;
|
||||||
|
size_t total_bytes = 0;
|
||||||
|
int bytes_read;
|
||||||
|
|
||||||
|
while ((bytes_read = SSL_read(ssl, buffer + total_bytes, (int)(sizeofbuf - total_bytes - 1))) > 0) {
|
||||||
|
total_bytes += bytes_read;
|
||||||
|
|
||||||
|
if (total_bytes + 1 >= sizeofbuf) {
|
||||||
|
sizeofbuf += 4096;
|
||||||
|
char* newbuf = realloc(buffer, sizeofbuf);
|
||||||
|
if (!newbuf) {
|
||||||
|
//what the fuck had possibly happened on bros pc :sob:
|
||||||
|
free(buffer);
|
||||||
|
SSL_shutdown(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(sock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buffer = newbuf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer[total_bytes] = '\0';
|
||||||
|
|
||||||
|
char *endofhdr = strstr(buffer, "\r\n\r\n");
|
||||||
|
if (!endofhdr) {
|
||||||
|
free(buffer);
|
||||||
|
SSL_shutdown(ssl); SSL_free(ssl); SSL_CTX_free(ctx);
|
||||||
|
closesocket(sock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *body_start = endofhdr + 4;
|
||||||
|
size_t body_len = total_bytes - (body_start - buffer);
|
||||||
|
if (strstr(buffer, "Transfer-Encoding: chunked")) {
|
||||||
|
size_t outlen;
|
||||||
|
*response = ProcessChunkedTransfer(body_start,body_len,&outlen);
|
||||||
|
*responselen = (long)outlen;
|
||||||
|
free(buffer);
|
||||||
|
} else {
|
||||||
|
char *copy = malloc(body_len + 1);
|
||||||
|
memcpy(copy, body_start, body_len);
|
||||||
|
copy[body_len] = '\0';
|
||||||
|
*response = copy;
|
||||||
|
*responselen = (long)body_len;
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_shutdown(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(sock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int InitHTTP() {
|
||||||
|
SSL_library_init();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
WSADATA wsaData;
|
||||||
|
WSAStartup(MAKEWORD(2,2), &wsaData); //me when macros
|
||||||
|
}
|
||||||
|
int CleanupHTTP() {
|
||||||
|
WSACleanup();
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
void InitSockets();
|
||||||
|
int SendShortHTTPReq(const char *hostname, const char *reqtype,
|
||||||
|
const char *endpoint, const char *extra_headers,
|
||||||
|
const char *user_agent, const char *content_type,
|
||||||
|
void *payload, unsigned long payload_size, char** response, long* responselen);
|
||||||
|
|
||||||
|
|
||||||
|
static void AppendHeader(char *buf, const char *key, const char *value);
|
||||||
|
|
||||||
|
int InitHTTP();
|
||||||
|
int CleanupHTTP();
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
#include <libloaderapi.h>
|
||||||
|
#include <minwindef.h>
|
||||||
|
#include <windef.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wingdi.h>
|
||||||
|
int ret = 1;
|
||||||
|
HWND tokenTxtField;
|
||||||
|
WNDPROC txtWndProc;
|
||||||
|
HFONT CueFont;
|
||||||
|
HWND loginbtn;
|
||||||
|
LRESULT CALLBACK loginwndproc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
||||||
|
LPARAM lParam) {
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_CREATE:
|
||||||
|
return 0;
|
||||||
|
case WM_PAINT: {
|
||||||
|
PAINTSTRUCT lppaint;
|
||||||
|
HDC hdc = BeginPaint(hwnd, &lppaint);
|
||||||
|
RECT clientrect;
|
||||||
|
GetClientRect(hwnd, &clientrect);
|
||||||
|
FillRect(hdc, &clientrect, (HBRUSH)(COLOR_3DFACE + 1));
|
||||||
|
EndPaint(hwnd, &lppaint);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
case WM_COMMAND: {
|
||||||
|
if (lParam == (LPARAM)loginbtn) {
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case WM_DESTROY:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LRESULT CALLBACK TokenTxtWndProc(HWND hWnd, UINT msg, WPARAM wParam,
|
||||||
|
LPARAM lParam) {
|
||||||
|
if (msg == WM_PAINT) {
|
||||||
|
if (GetWindowTextLength(hWnd) == 0) {
|
||||||
|
PAINTSTRUCT lpPaint;
|
||||||
|
HDC hDC = BeginPaint(hWnd, &lpPaint);
|
||||||
|
RECT clRect;
|
||||||
|
GetClientRect(hWnd, &clRect);
|
||||||
|
FillRect(hDC, &clRect, (HBRUSH)(COLOR_WINDOW + 1));
|
||||||
|
SetTextColor(hDC, RGB(128, 128, 128));
|
||||||
|
SelectObject(hDC, CueFont);
|
||||||
|
DrawText(hDC, "Token...", 8, &clRect, 0);
|
||||||
|
EndPaint(hWnd, &lpPaint);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return txtWndProc(hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return txtWndProc(hWnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char *PromptToken() {
|
||||||
|
|
||||||
|
char *clsname = "BackcordLogin";
|
||||||
|
WNDCLASS wc = {0};
|
||||||
|
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
|
||||||
|
wc.lpszClassName = clsname;
|
||||||
|
wc.hCursor = LoadCursorA(NULL, IDC_ARROW);
|
||||||
|
wc.lpfnWndProc = loginwndproc;
|
||||||
|
RegisterClassA(&wc);
|
||||||
|
HWND hwnd = CreateWindowExA(0, clsname, "Backcord - Login", WS_OVERLAPPED,
|
||||||
|
CW_USEDEFAULT, CW_USEDEFAULT, 320, 480, NULL,
|
||||||
|
NULL, GetModuleHandleA(NULL), NULL);
|
||||||
|
RECT clientrect;
|
||||||
|
GetClientRect(hwnd, &clientrect);
|
||||||
|
HWND banner =
|
||||||
|
CreateWindowExA(0, "STATIC", "", WS_VISIBLE | WS_CHILD | SS_BITMAP, 0,
|
||||||
|
0, clientrect.right - clientrect.left, 64, hwnd, NULL,
|
||||||
|
GetModuleHandleA(NULL), NULL);
|
||||||
|
SendMessage(
|
||||||
|
banner, STM_SETIMAGE, IMAGE_BITMAP,
|
||||||
|
(LPARAM)(LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(102))));
|
||||||
|
|
||||||
|
tokenTxtField =
|
||||||
|
CreateWindowExA(WS_EX_STATICEDGE, "EDIT", "", WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL | ES_PASSWORD, 20,
|
||||||
|
64 + 20, clientrect.right - clientrect.left - 40, 20,
|
||||||
|
hwnd, NULL, GetModuleHandleA(NULL), NULL);
|
||||||
|
|
||||||
|
SendMessage(tokenTxtField, EM_SETLIMITTEXT, 0, 0);
|
||||||
|
|
||||||
|
NONCLIENTMETRICS ncm = {sizeof(NONCLIENTMETRICS)};
|
||||||
|
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
|
||||||
|
HFONT hmsgfont = CreateFontIndirect(&ncm.lfMessageFont);
|
||||||
|
SendMessage(tokenTxtField, WM_SETFONT, (WPARAM)hmsgfont, TRUE);
|
||||||
|
LOGFONTA CueFontLog = ncm.lfMessageFont;
|
||||||
|
CueFontLog.lfItalic = 1;
|
||||||
|
CueFont = CreateFontIndirect(&CueFontLog);
|
||||||
|
txtWndProc = (WNDPROC)GetWindowLong(tokenTxtField, GWL_WNDPROC);
|
||||||
|
SetWindowLong(tokenTxtField, GWL_WNDPROC, (long)TokenTxtWndProc);
|
||||||
|
|
||||||
|
loginbtn = CreateWindowExA(0, "BUTTON", "Login",
|
||||||
|
WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
|
||||||
|
clientrect.right - clientrect.left - 10 - 75,
|
||||||
|
clientrect.bottom - clientrect.top - 10 - 25, 75,
|
||||||
|
25, hwnd, NULL, GetModuleHandleA(NULL), NULL);
|
||||||
|
SendMessage(loginbtn, WM_SETFONT, (WPARAM)hmsgfont, TRUE);
|
||||||
|
|
||||||
|
ShowWindow(hwnd, SW_SHOW);
|
||||||
|
while (ret) {
|
||||||
|
MSG msg;
|
||||||
|
if (GetMessage(&msg, NULL, 0, 0)) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char *tok = malloc(GetWindowTextLength(tokenTxtField) + 1);
|
||||||
|
GetWindowText(tokenTxtField, tok, GetWindowTextLength(tokenTxtField) + 1);
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
@@ -0,0 +1,507 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <wingdi.h>
|
||||||
|
#include <commctrl.h>
|
||||||
|
#include "components/chatwnd.h"
|
||||||
|
#include "discordtypes.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "ws.h"
|
||||||
|
#include "discordapi.h"
|
||||||
|
#include <cjson/cJSON.h>
|
||||||
|
#include <winnt.h>
|
||||||
|
#include "config.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "hmap/hashmap.h"
|
||||||
|
#include "components/snoctrl.h"
|
||||||
|
#include "components/pnghlp.h"
|
||||||
|
#include "rc/res.h"
|
||||||
|
#include "login.h"
|
||||||
|
#define GUILD_PADDING 4
|
||||||
|
static DiscordMessage *PendingRndMsg = NULL;
|
||||||
|
static DiscordMessage *LastRndMsg = NULL; // rnd is render NOT random :sob:
|
||||||
|
char *curChannel;
|
||||||
|
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
||||||
|
static HWND hWnd;
|
||||||
|
static HWND hMsg;
|
||||||
|
static HWND hMsgList;
|
||||||
|
static HWND hSend;
|
||||||
|
static HWND hChTree;
|
||||||
|
static HWND hDMsList;
|
||||||
|
static HWND hGSel;
|
||||||
|
static HWND hPfpArea;
|
||||||
|
static HWND hMemList;
|
||||||
|
static HIMAGELIST hSidePfps;
|
||||||
|
HINSTANCE hInstance;
|
||||||
|
SSL *GatewaySSL;
|
||||||
|
char *token;
|
||||||
|
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, PSTR pCmdLine,
|
||||||
|
int nCmdShow) {
|
||||||
|
#ifdef test_token
|
||||||
|
token = test_token;
|
||||||
|
#else
|
||||||
|
// prompt for token
|
||||||
|
HKEY hKey;
|
||||||
|
RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\Backcord", 0, NULL,
|
||||||
|
REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hKey,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
DWORD type, size = 0;
|
||||||
|
LONG qres = RegQueryValueExA(hKey, "Token", NULL, &type, NULL, &size);
|
||||||
|
|
||||||
|
if (qres == ERROR_SUCCESS) {
|
||||||
|
token = malloc(size);
|
||||||
|
RegQueryValueExA(hKey, "Token", NULL, &type, (BYTE *)token, &size);
|
||||||
|
} else {
|
||||||
|
token = PromptToken();
|
||||||
|
RegSetValueExA(hKey, "Token", 0, REG_SZ, (const BYTE *)token,
|
||||||
|
(DWORD)(strlen(token) + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
INITCOMMONCONTROLSEX icex;
|
||||||
|
icex.dwICC = ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES | ICC_BAR_CLASSES |
|
||||||
|
ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES;
|
||||||
|
InitCommonControls();
|
||||||
|
InitSnowsControls();
|
||||||
|
hSidePfps = ImageList_Create(32, 32, ILC_COLOR32 | ILC_MASK, 12, 1);
|
||||||
|
hInstance = hinstance;
|
||||||
|
// register the window claws
|
||||||
|
const char CLASS_NAME[] = "BackcordMain";
|
||||||
|
|
||||||
|
WNDCLASS wc = {};
|
||||||
|
|
||||||
|
wc.lpfnWndProc = WindowProc;
|
||||||
|
wc.hInstance = hInstance;
|
||||||
|
wc.lpszClassName = CLASS_NAME;
|
||||||
|
wc.hCursor = LoadCursorA(NULL, IDC_ARROW);
|
||||||
|
RegisterClass(&wc);
|
||||||
|
hWnd = CreateWindowEx(0, // Optional window styles.
|
||||||
|
CLASS_NAME, // Window class
|
||||||
|
"Backcord", // Window text
|
||||||
|
WS_OVERLAPPEDWINDOW, // Window style
|
||||||
|
|
||||||
|
// Size and position
|
||||||
|
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
|
||||||
|
|
||||||
|
NULL, // Parent window
|
||||||
|
NULL, // Menu
|
||||||
|
hInstance, // Instance handle
|
||||||
|
NULL // Additional application data
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hWnd == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ShowWindow(hWnd, nCmdShow);
|
||||||
|
|
||||||
|
MSG msg = {};
|
||||||
|
while (GetMessage(&msg, NULL, 0, 0) > 0) {
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
BOOL identify = FALSE;
|
||||||
|
void onDiscordGuildLoad(DiscordGuild *guild, char *id) {
|
||||||
|
|
||||||
|
GUIGuild uigld;
|
||||||
|
uigld.icon = NULL;
|
||||||
|
uigld.id = id;
|
||||||
|
uigld.title = guild->name;
|
||||||
|
uigld.data = guild;
|
||||||
|
uigld.MentionCount = guild->MentionCount;
|
||||||
|
GuildView_InsertGuild(hGSel, uigld);
|
||||||
|
|
||||||
|
if (guild->IconHash) {
|
||||||
|
char tempPath[MAX_PATH];
|
||||||
|
DWORD len = GetTempPathA(MAX_PATH, tempPath);
|
||||||
|
char *path = malloc(MAX_PATH + strlen(guild->id) + 1);
|
||||||
|
wsprintfA(path, "%s%s.png", tempPath, guild->IconHash);
|
||||||
|
if (GetFileAttributesA(path) == INVALID_FILE_ATTRIBUTES) {
|
||||||
|
char *path = DiscordFetchTmpGuildIcon(guild->id,
|
||||||
|
guild->IconHash); // testing
|
||||||
|
}
|
||||||
|
GuildView_SetIcon(hGSel, LoadPNGImage(path), id);
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void onDiscordUpdatedGuildReadState(DiscordGuild guild) {
|
||||||
|
GuildView_SetMentionCount(hGSel, guild.id, guild.MentionCount);
|
||||||
|
}
|
||||||
|
static uint64_t channel_hash(const void *item, uint64_t seed0, uint64_t seed1) {
|
||||||
|
const ChannelUIEntry *e = item;
|
||||||
|
return hashmap_sip(e->id, strlen(e->id), seed0, seed1);
|
||||||
|
}
|
||||||
|
static int channel_compare(const void *a, const void *b, void *udata) {
|
||||||
|
return strcmp(((ChannelUIEntry *)a)->id, ((ChannelUIEntry *)b)->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDiscordReceiveMessage(DiscordMessage msg) {
|
||||||
|
if (curChannel && strcmp(msg.channelID, curChannel) == 0) {
|
||||||
|
if (msg.author.avatar) {
|
||||||
|
char tempPath[MAX_PATH];
|
||||||
|
DWORD len = GetTempPathA(MAX_PATH, tempPath);
|
||||||
|
char *path = malloc(MAX_PATH + strlen(msg.author.avatar) + 1);
|
||||||
|
wsprintfA(path, "%s%s.png", tempPath, msg.author.avatar);
|
||||||
|
if (GetFileAttributesA(path) == INVALID_FILE_ATTRIBUTES) {
|
||||||
|
char *path = DiscordFetchTmpPfp(msg.author.id,
|
||||||
|
msg.author.avatar); // testing
|
||||||
|
}
|
||||||
|
ChatView_SetUserPfp(path, msg.author.id);
|
||||||
|
}
|
||||||
|
InsertChatMessage(msg, hMsgList); // the backend provides strdup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void *msgoldwndproc;
|
||||||
|
LRESULT CALLBACK MsgBarSubclassProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||||
|
LPARAM lParam) {
|
||||||
|
if (msg == WM_KEYDOWN) {
|
||||||
|
if (wParam == VK_RETURN) {
|
||||||
|
if (!(GetKeyState(VK_SHIFT) &
|
||||||
|
0x8000)) // if no shift then do cool stuff
|
||||||
|
{
|
||||||
|
char *msgcontent = malloc(GetWindowTextLength(hMsg) + 1);
|
||||||
|
GetWindowText(hMsg, msgcontent, GetWindowTextLength(hMsg) + 1);
|
||||||
|
DiscordSendMessage(curChannel, msgcontent);
|
||||||
|
|
||||||
|
SetWindowText(hwnd, ""); // clear input
|
||||||
|
return 0; // preventing uhh newline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CallWindowProc(msgoldwndproc, hwnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
HBITMAP ResizeBitmap(HBITMAP hOrig, int w, int h) {
|
||||||
|
BITMAP bm;
|
||||||
|
GetObject(hOrig, sizeof(bm), &bm);
|
||||||
|
|
||||||
|
HDC hdcScreen = GetDC(NULL);
|
||||||
|
HDC hdcSrc = CreateCompatibleDC(hdcScreen);
|
||||||
|
HDC hdcDst = CreateCompatibleDC(hdcScreen);
|
||||||
|
HBITMAP hResized = CreateCompatibleBitmap(hdcScreen, w, h);
|
||||||
|
|
||||||
|
SelectObject(hdcSrc, hOrig);
|
||||||
|
SelectObject(hdcDst, hResized);
|
||||||
|
|
||||||
|
SetStretchBltMode(hdcDst, HALFTONE);
|
||||||
|
SetBrushOrgEx(hdcDst, 0, 0, NULL);
|
||||||
|
|
||||||
|
StretchBlt(hdcDst, 0, 0, w, h, hdcSrc, 0, 0, bm.bmWidth, bm.bmHeight,
|
||||||
|
SRCCOPY);
|
||||||
|
|
||||||
|
DeleteDC(hdcSrc);
|
||||||
|
DeleteDC(hdcDst);
|
||||||
|
ReleaseDC(NULL, hdcScreen);
|
||||||
|
DeleteObject(hOrig);
|
||||||
|
|
||||||
|
return hResized;
|
||||||
|
}
|
||||||
|
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
|
||||||
|
LPARAM lParam) {
|
||||||
|
switch (uMsg) {
|
||||||
|
case WM_CREATE:
|
||||||
|
InitHTTP();
|
||||||
|
OpenWebSocket("gateway.discord.gg", "/?encoding=json&v=9", &GatewaySSL);
|
||||||
|
|
||||||
|
RECT rc;
|
||||||
|
GetClientRect(hwnd, &rc);
|
||||||
|
hGSel = CreateWindowEx(0, // Optional window styles.
|
||||||
|
"BackcordGuild", // Window class
|
||||||
|
"", // Window text
|
||||||
|
WS_CHILD | WS_VISIBLE | LVS_LIST, // Window style
|
||||||
|
|
||||||
|
// Size and position
|
||||||
|
0, 0, 75, rc.bottom - rc.top - 80,
|
||||||
|
|
||||||
|
hwnd, // Parent window
|
||||||
|
(HMENU)102, // Menu
|
||||||
|
hInstance, // Instance handle
|
||||||
|
NULL // Additional application data
|
||||||
|
);
|
||||||
|
GuildView_SetDMsIcon(
|
||||||
|
hGSel, LoadPNGImageFromResource(hInstance, IDI_BACKCORDIC));
|
||||||
|
hChTree = CreateWindowExA(WS_EX_CLIENTEDGE, WC_TREEVIEWA, NULL,
|
||||||
|
WS_CHILD | WS_VISIBLE | TVS_HASLINES |
|
||||||
|
TVS_LINESATROOT | TVS_HASBUTTONS,
|
||||||
|
75, 0, 128, rc.bottom - rc.top - 80, hwnd,
|
||||||
|
(HMENU)103, hInstance, NULL);
|
||||||
|
hDMsList = CreateWindowExA(
|
||||||
|
WS_EX_CLIENTEDGE, WC_LISTVIEWA, NULL, WS_CHILD | WS_VISIBLE, 75, 0,
|
||||||
|
128, rc.bottom - rc.top - 80, hwnd, (HMENU)103, hInstance, NULL);
|
||||||
|
ShowWindow(hDMsList, SW_HIDE);
|
||||||
|
hMsgList = CreateWindowEx(0, // Optional window styles.
|
||||||
|
"BackcordChat", // Window class
|
||||||
|
"", // Window text
|
||||||
|
WS_CHILD | WS_VISIBLE, // Window style
|
||||||
|
|
||||||
|
// Size and position
|
||||||
|
75 + 128, 0, rc.right - 128 - 75,
|
||||||
|
rc.bottom - rc.top - 25,
|
||||||
|
|
||||||
|
hwnd, // Parent window
|
||||||
|
(HMENU)105, // Menu
|
||||||
|
hInstance, // Instance handle
|
||||||
|
NULL); // Additional application data
|
||||||
|
|
||||||
|
hPfpArea = CreateWindowEx(
|
||||||
|
0, // Optional window styles.
|
||||||
|
"BUTTON", // Window class
|
||||||
|
"User Profile", // Window text
|
||||||
|
ES_MULTILINE | WS_CHILD | WS_VISIBLE | BS_GROUPBOX, // Window style
|
||||||
|
|
||||||
|
// Size and position
|
||||||
|
0, rc.bottom - rc.top - 80, 128 + 75, 80,
|
||||||
|
|
||||||
|
hwnd, // Parent window
|
||||||
|
NULL, // Menu
|
||||||
|
hInstance, // Instance handle
|
||||||
|
NULL // Additional application data
|
||||||
|
);
|
||||||
|
SendMessage(hPfpArea, WM_SETFONT, (WPARAM)regfont, 0);
|
||||||
|
hMsg = CreateWindowEx(WS_EX_CLIENTEDGE, // Optional window styles.
|
||||||
|
"EDIT", // Window class
|
||||||
|
"", // Window text
|
||||||
|
ES_MULTILINE | ES_AUTOVSCROLL | WS_CHILD |
|
||||||
|
WS_VISIBLE, // Window style
|
||||||
|
|
||||||
|
// Size and position
|
||||||
|
128 + 75, rc.bottom - rc.top - 25,
|
||||||
|
rc.right - rc.left - 128 - 75 - 25, 25,
|
||||||
|
|
||||||
|
hwnd, // Parent window
|
||||||
|
(HMENU)104, // Menu
|
||||||
|
hInstance, // Instance handle
|
||||||
|
NULL // Additional application data
|
||||||
|
);
|
||||||
|
SendMessage(hMsg, WM_SETFONT, (WPARAM)regfont, 0);
|
||||||
|
msgoldwndproc = (void *)SetWindowLongPtr(hMsg, GWLP_WNDPROC,
|
||||||
|
(long)MsgBarSubclassProc);
|
||||||
|
hSend = CreateWindowEx(
|
||||||
|
0, // Optional window styles.
|
||||||
|
"BUTTON", // Window class
|
||||||
|
"E", // Window text
|
||||||
|
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE, // Window style
|
||||||
|
|
||||||
|
// Size and position
|
||||||
|
rc.right - 25, rc.bottom - rc.top - 25, 25, 25,
|
||||||
|
|
||||||
|
hwnd, // Parent window
|
||||||
|
(HMENU)(101), // Menu
|
||||||
|
hInstance, // Instance handle
|
||||||
|
NULL // Additional application data
|
||||||
|
);
|
||||||
|
SendMessage(hSend, WM_SETFONT, (WPARAM)regfont, 0);
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
case WM_SIZE: {
|
||||||
|
int width = LOWORD(lParam);
|
||||||
|
int height = HIWORD(lParam);
|
||||||
|
SetWindowPos(hPfpArea, NULL, 0, height - 80, 0, 0,
|
||||||
|
SWP_NOSIZE | SWP_NOZORDER);
|
||||||
|
SetWindowPos(hGSel, NULL, 0, 0, 75, height - 80, SWP_NOZORDER);
|
||||||
|
SetWindowPos(hMsg, NULL, 128 + 75, height - 25, width - 128 - 75 - 25,
|
||||||
|
25, SWP_NOZORDER);
|
||||||
|
SetWindowPos(hChTree, NULL, 75, 0, 128, height - 80, SWP_NOZORDER);
|
||||||
|
SetWindowPos(hMsgList, NULL, 128 + 75, 0, width - 128 - 75, height - 25,
|
||||||
|
SWP_NOZORDER);
|
||||||
|
SetWindowPos(hSend, NULL, width - 25, height - 25, 25, 25,
|
||||||
|
SWP_NOZORDER | SWP_NOSIZE);
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_COMMAND:
|
||||||
|
if (lParam == hSend) {
|
||||||
|
char msgcontent[GetWindowTextLength(hMsg) + 1];
|
||||||
|
GetWindowText(hMsg, msgcontent, GetWindowTextLength(hMsg) + 1);
|
||||||
|
DiscordSendMessage(curChannel, msgcontent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WM_NOTIFY:
|
||||||
|
NMHDR pNMHDR = *(NMHDR *)lParam;
|
||||||
|
LPNMLVCUSTOMDRAW lvcd = (LPNMLVCUSTOMDRAW)lParam;
|
||||||
|
if (pNMHDR.hwndFrom == hGSel) {
|
||||||
|
LPNMGUILDVIEW pnmv = (LPNMGUILDVIEW)lParam;
|
||||||
|
if (pnmv->index == GUILDVIEW_DMS) {
|
||||||
|
DiscordChannel *dms;
|
||||||
|
int cnt = DiscordListPrivateChannels(&dms);
|
||||||
|
TreeView_DeleteAllItems(hChTree);
|
||||||
|
TreeView_SetImageList(hChTree, hSidePfps, TVSIL_NORMAL);
|
||||||
|
TVINSERTSTRUCT tvInsert = {0};
|
||||||
|
HTREEITEM hParent = TVI_ROOT;
|
||||||
|
int pfpidx = 0;
|
||||||
|
for (int i = 0; i < cnt; i++) {
|
||||||
|
if (dms[i].type == CHANNEL_TYPE_DM) {
|
||||||
|
printf("%s\n", dms[i].receipents[0].Username);
|
||||||
|
|
||||||
|
if (dms[i].receipents[0].avatar) {
|
||||||
|
|
||||||
|
char tempPath[MAX_PATH];
|
||||||
|
DWORD len = GetTempPathA(MAX_PATH, tempPath);
|
||||||
|
char path[MAX_PATH +
|
||||||
|
strlen(dms[i].receipents[0].avatar) + 1];
|
||||||
|
wsprintfA(path, "%s%s.png", tempPath,
|
||||||
|
dms[i].receipents[0].avatar);
|
||||||
|
|
||||||
|
if (GetFileAttributesA(path) ==
|
||||||
|
INVALID_FILE_ATTRIBUTES) {
|
||||||
|
char* path2 = DiscordFetchTmpPfp(
|
||||||
|
dms[i].receipents[0].id,
|
||||||
|
dms[i].receipents[0].avatar); // testing
|
||||||
|
ImageList_Add(
|
||||||
|
hSidePfps,
|
||||||
|
ResizeBitmap(LoadPNGBitmap(path2), 32, 32),
|
||||||
|
NULL);
|
||||||
|
free(path2);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
ImageList_Add(
|
||||||
|
hSidePfps,
|
||||||
|
ResizeBitmap(LoadPNGBitmap(path), 32, 32),
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tvInsert.hParent = hParent; // Parent is the root item
|
||||||
|
tvInsert.hInsertAfter = TVI_LAST;
|
||||||
|
tvInsert.item.mask = TVIF_TEXT | TVIF_PARAM |
|
||||||
|
TVIS_EXPANDED | TVIF_STATE |
|
||||||
|
TVIF_IMAGE;
|
||||||
|
tvInsert.item.pszText =
|
||||||
|
dms[i].receipents[0].DisplayName
|
||||||
|
? dms[i].receipents[0].DisplayName
|
||||||
|
: dms[i].receipents[0].Username;
|
||||||
|
tvInsert.item.lParam = (LPARAM) & (dms[i]);
|
||||||
|
tvInsert.item.state = TVIS_EXPANDED;
|
||||||
|
tvInsert.item.stateMask = TVIS_EXPANDED;
|
||||||
|
tvInsert.item.iImage =
|
||||||
|
dms[i].receipents[0].avatar ? pfpidx : 0;
|
||||||
|
HTREEITEM lres = (HTREEITEM)SendMessage(
|
||||||
|
hChTree, TVM_INSERTITEM, 0, (LPARAM)&tvInsert);
|
||||||
|
if (dms[i].receipents[0].avatar) {
|
||||||
|
pfpidx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
DiscordGuild *guild;
|
||||||
|
guild = pnmv->guild.data;
|
||||||
|
|
||||||
|
DiscordChannel *Channels;
|
||||||
|
int ChannelCount =
|
||||||
|
DiscordListGuildChannels(guild->id, &Channels);
|
||||||
|
TreeView_DeleteAllItems(hChTree);
|
||||||
|
TreeView_SetImageList(hChTree, NULL, TVSIL_NORMAL);
|
||||||
|
struct hashmap *ChannelUITable =
|
||||||
|
hashmap_new(sizeof(ChannelUIEntry), 0, 0, 0, channel_hash,
|
||||||
|
channel_compare, NULL, NULL);
|
||||||
|
for (int i = 0; i < ChannelCount; i++) {
|
||||||
|
char *VisualName;
|
||||||
|
if (Channels[i].type == GUILD_TEXT) {
|
||||||
|
VisualName = malloc(strlen(Channels[i].name) + 2);
|
||||||
|
*VisualName = '#';
|
||||||
|
strcpy(VisualName + 1, Channels[i].name);
|
||||||
|
} else {
|
||||||
|
VisualName = malloc(strlen(Channels[i].name) + 1);
|
||||||
|
strcpy(VisualName, Channels[i].name);
|
||||||
|
}
|
||||||
|
TVINSERTSTRUCT tvInsert = {0};
|
||||||
|
HTREEITEM hParent = TVI_ROOT;
|
||||||
|
if (Channels[i].parentID) {
|
||||||
|
ChannelUIEntry key = {0};
|
||||||
|
key.id = Channels[i].parentID;
|
||||||
|
if (hashmap_get(ChannelUITable, &key)) {
|
||||||
|
ChannelUIEntry *chnl =
|
||||||
|
(ChannelUIEntry *)(hashmap_get(ChannelUITable,
|
||||||
|
&key));
|
||||||
|
hParent = chnl->itm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tvInsert.hParent = hParent; // Parent is the root item
|
||||||
|
tvInsert.hInsertAfter = TVI_LAST;
|
||||||
|
tvInsert.item.mask =
|
||||||
|
TVIF_TEXT | TVIF_PARAM | TVIS_EXPANDED | TVIF_STATE;
|
||||||
|
tvInsert.item.pszText = (VisualName);
|
||||||
|
tvInsert.item.lParam = (LPARAM) & (Channels[i]);
|
||||||
|
tvInsert.item.state = TVIS_EXPANDED;
|
||||||
|
tvInsert.item.stateMask = TVIS_EXPANDED;
|
||||||
|
HTREEITEM lres = (HTREEITEM)SendMessage(
|
||||||
|
hChTree, TVM_INSERTITEM, 0, (LPARAM)&tvInsert);
|
||||||
|
if (Channels[i].type == GUILD_CATEGORY) {
|
||||||
|
ChannelUIEntry *chnl = malloc(sizeof(ChannelUIEntry));
|
||||||
|
chnl->itm = lres;
|
||||||
|
chnl->id = strdup(Channels[i].id);
|
||||||
|
hashmap_set(ChannelUITable, chnl);
|
||||||
|
}
|
||||||
|
free(VisualName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (pNMHDR.hwndFrom == hChTree) {
|
||||||
|
LPNMTREEVIEW pnmv = (LPNMTREEVIEW)lParam;
|
||||||
|
if (pNMHDR.code == TVN_SELCHANGED &&
|
||||||
|
pnmv->itemNew.state & TVIS_SELECTED) {
|
||||||
|
DiscordChannel *chnl = ((DiscordChannel *)pnmv->itemNew.lParam);
|
||||||
|
if (chnl->type != GUILD_CATEGORY) {
|
||||||
|
char *title = malloc(14 + strlen(pnmv->itemNew.pszText));
|
||||||
|
wsprintf(title, "Backcord - %s", pnmv->itemNew.pszText);
|
||||||
|
SetWindowTextA(hwnd, title);
|
||||||
|
DiscordMessage *msgs;
|
||||||
|
int msgcnt = DiscordGetChannelHistory(chnl->id, 50, &msgs);
|
||||||
|
ClearChatControl(hMsgList);
|
||||||
|
ListView_DeleteAllItems(hMsgList);
|
||||||
|
if (curChannel)
|
||||||
|
free(curChannel);
|
||||||
|
|
||||||
|
curChannel = strdup(chnl->id);
|
||||||
|
if (msgcnt > 0) {
|
||||||
|
for (int i = msgcnt - 1; i >= 0; i--) {
|
||||||
|
if (msgs[i].author.avatar) {
|
||||||
|
char tempPath[MAX_PATH];
|
||||||
|
DWORD len = GetTempPathA(MAX_PATH, tempPath);
|
||||||
|
char *path =
|
||||||
|
malloc(MAX_PATH +
|
||||||
|
strlen(msgs[i].author.avatar) + 1);
|
||||||
|
wsprintfA(path, "%s%s.png", tempPath,
|
||||||
|
msgs[i].author.avatar);
|
||||||
|
if (GetFileAttributesA(path) ==
|
||||||
|
INVALID_FILE_ATTRIBUTES) {
|
||||||
|
char *path = DiscordFetchTmpPfp(
|
||||||
|
msgs[i].author.id,
|
||||||
|
msgs[i].author.avatar); // testing
|
||||||
|
}
|
||||||
|
ChatView_SetUserPfp(path, msgs[i].author.id);
|
||||||
|
}
|
||||||
|
InsertChatMessage((msgs[i]), hMsgList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListView_EnsureVisible(hMsgList, msgcnt - 1, FALSE);
|
||||||
|
free(title);
|
||||||
|
free(msgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WM_PAINT: {
|
||||||
|
PAINTSTRUCT ps;
|
||||||
|
HDC hdc = BeginPaint(hwnd, &ps);
|
||||||
|
|
||||||
|
// All painting occurs here, between BeginPaint and EndPaint.
|
||||||
|
|
||||||
|
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_BTNFACE + 1));
|
||||||
|
EndPaint(hwnd, &ps);
|
||||||
|
return 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_CLOSE:
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
PostQuitMessage(0);
|
||||||
|
case WM_DESTROY:
|
||||||
|
CleanupHTTP();
|
||||||
|
return TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
CC = i686-w64-mingw32-gcc
|
||||||
|
WINDRES = i686-w64-mingw32-windres
|
||||||
|
|
||||||
|
SRCS = *.c */*.c
|
||||||
|
RES = rc/res.o
|
||||||
|
|
||||||
|
Backcord: $(RES)
|
||||||
|
$(WINDRES) rc/res.rc -o rc/res.o
|
||||||
|
$(CC) $(SRCS) $(RES) \
|
||||||
|
-Wl,-Bstatic -lpng -lz \
|
||||||
|
-Wl,-Bdynamic -lgdi32 -lssl -lcrypto -lcrypt32 -lws2_32 \
|
||||||
|
-luser32 -lcomctl32 -lcomdlg32 -lcjson -lntdll \
|
||||||
|
-o Backcord.exe
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f rc/res.o Backcord.exe
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
#include "res.h"
|
||||||
|
|
||||||
|
IDI_BACKCORDIC RCDATA "backcord_icon.png"
|
||||||
|
IDI_BACKCORDBANNERIC BITMAP "backcordbanner.bmp"
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
BOOL WINAPI IsProcessorFeaturePresent(DWORD ProcessorFeature)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
FARPROC WINAPI DelayLoadFailureHook(_In_ LPCSTR pszDllName,
|
||||||
|
_In_ LPCSTR pszProcName) {
|
||||||
|
return (void*)(IsProcessorFeaturePresent);
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
#include "http.h"
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include "ws.h"
|
||||||
|
DWORD WINAPI WSThreadProc(LPVOID ssl);
|
||||||
|
int OpenWebSocket(const char *hostname, const char *path, SSL **out_ssl) {
|
||||||
|
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
MessageBoxA(NULL, "socket failed", "WebSocket Error", 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in server;
|
||||||
|
RtlZeroMemory(&server, sizeof(server));
|
||||||
|
server.sin_family = AF_INET;
|
||||||
|
server.sin_port = htons(443);
|
||||||
|
server.sin_addr = **(struct in_addr **)gethostbyname(hostname)->h_addr_list;
|
||||||
|
|
||||||
|
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) ==
|
||||||
|
SOCKET_ERROR) {
|
||||||
|
MessageBoxA(NULL, "connect failed", "WebSocket Error", 0);
|
||||||
|
closesocket(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_library_init();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
|
||||||
|
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
|
||||||
|
if (!ctx) {
|
||||||
|
MessageBoxA(NULL, "SSL_CTX_new failed", "WebSocket Error", 0);
|
||||||
|
closesocket(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL *ssl = SSL_new(ctx);
|
||||||
|
if (!ssl) {
|
||||||
|
MessageBoxA(NULL, "SSL_new failed", "WebSocket Error", 0);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_set_fd(ssl, (int)sock);
|
||||||
|
SSL_set_tlsext_host_name(ssl, hostname);
|
||||||
|
|
||||||
|
if (SSL_connect(ssl) <= 0) {
|
||||||
|
MessageBoxA(NULL, "SSL_connect failed", "WebSocket Error", 0);
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// anything but a Base64 lib
|
||||||
|
const char *ws_key = "tLGs4Zw47vMKJ8bT5AXjLw==";
|
||||||
|
|
||||||
|
// send WebSocket upgrade request
|
||||||
|
char request[2048];
|
||||||
|
wsprintfA(request,
|
||||||
|
"GET %s HTTP/1.1\r\n"
|
||||||
|
"Host: %s\r\n"
|
||||||
|
"Upgrade: websocket\r\n"
|
||||||
|
"Connection: Upgrade\r\n"
|
||||||
|
"Sec-WebSocket-Key: %s\r\n"
|
||||||
|
"Sec-WebSocket-Version: 13\r\n"
|
||||||
|
"\r\n",
|
||||||
|
path, hostname, ws_key);
|
||||||
|
|
||||||
|
SSL_write(ssl, request, (int)strlen(request));
|
||||||
|
|
||||||
|
// read response into epic stack arr
|
||||||
|
char buffer[4096];
|
||||||
|
int bytes = SSL_read(ssl, &buffer, sizeof(buffer) - 1);
|
||||||
|
if (bytes > 0) {
|
||||||
|
buffer[bytes] = '\0';
|
||||||
|
|
||||||
|
// check for 101 Switching Protocols otherwise it means smth fucked up
|
||||||
|
if (strstr(buffer, "101") == NULL) {
|
||||||
|
MessageBoxA(NULL, buffer, "websocket isnt websocketing", 0);
|
||||||
|
SSL_shutdown(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(sock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateThread(0, 8192, WSThreadProc, ssl, 0, 0);
|
||||||
|
// connection upgraded to WebSocket yay
|
||||||
|
*out_ssl = ssl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
DWORD WINAPI WSThreadProc(LPVOID ssl) {
|
||||||
|
char *buffer;
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
int result = ReadWebSocket(ssl, &buffer, &length);
|
||||||
|
|
||||||
|
if (length != 0 && result != 0) {
|
||||||
|
WebSocketOnDataArrival(ssl, buffer, length);
|
||||||
|
// printf("%s\n",buffer);
|
||||||
|
// LE IMPORTANT: free the buffer after processing... memleak goez
|
||||||
|
// brr otherewise
|
||||||
|
free(buffer);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
int SendWebSocket(SSL *ssl, const char *data, size_t length,
|
||||||
|
unsigned char flags) {
|
||||||
|
unsigned char frame[14];
|
||||||
|
size_t frame_size = 0;
|
||||||
|
|
||||||
|
// Flags are to our goofy ahh param
|
||||||
|
frame[0] = flags;
|
||||||
|
frame_size++;
|
||||||
|
|
||||||
|
// Mask bit set (client must mask the ahh) (stolen code)
|
||||||
|
if (length < 126) {
|
||||||
|
frame[1] = 0x80 | (unsigned char)length;
|
||||||
|
frame_size++;
|
||||||
|
} else if (length < 65536) {
|
||||||
|
frame[1] = 0x80 | 126;
|
||||||
|
frame[2] = (length >> 8) & 0xFF;
|
||||||
|
frame[3] = length & 0xFF;
|
||||||
|
frame_size += 3;
|
||||||
|
} else {
|
||||||
|
frame[1] = 0x80 | 127;
|
||||||
|
frame[2] = (length >> 56) & 0xFF;
|
||||||
|
frame[3] = (length >> 48) & 0xFF;
|
||||||
|
frame[4] = (length >> 40) & 0xFF;
|
||||||
|
frame[5] = (length >> 32) & 0xFF;
|
||||||
|
frame[6] = (length >> 24) & 0xFF;
|
||||||
|
frame[7] = (length >> 16) & 0xFF;
|
||||||
|
frame[8] = (length >> 8) & 0xFF;
|
||||||
|
frame[9] = length & 0xFF;
|
||||||
|
frame_size += 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// whats even the purpose of ts, just fake the masks
|
||||||
|
unsigned char mask[4];
|
||||||
|
for (unsigned char i = 0; i < 4; i++) {
|
||||||
|
mask[i] = (unsigned char)(i & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
RtlCopyMemory(&frame[frame_size], mask, 4); // me when i rtlcopymemory to look cool
|
||||||
|
frame_size += 4;
|
||||||
|
|
||||||
|
// Send frame header
|
||||||
|
if (SSL_write(ssl, frame, (int)frame_size) <= 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask and send payload
|
||||||
|
unsigned char *masked_data = (unsigned char *)malloc(length);
|
||||||
|
if (!masked_data) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
masked_data[i] = data[i] ^ mask[i % 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = SSL_write(ssl, masked_data, (int)length);
|
||||||
|
free(masked_data);
|
||||||
|
|
||||||
|
return (result <= 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadWebSocket(SSL *ssl, char **out_buffer, size_t *out_length) {
|
||||||
|
*out_buffer = malloc(10); // tmp buffer
|
||||||
|
unsigned char opcode;
|
||||||
|
BOOL hasSetFirstOpcode = FALSE;
|
||||||
|
unsigned long long payloadLen = 0;
|
||||||
|
while (1) {
|
||||||
|
unsigned long long curPayloadLen = 0;
|
||||||
|
unsigned char fin;
|
||||||
|
|
||||||
|
unsigned char coreHeaders;
|
||||||
|
if (SSL_read(ssl, &coreHeaders, 1) != 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fin = coreHeaders >> 7;
|
||||||
|
|
||||||
|
if (hasSetFirstOpcode != TRUE) {
|
||||||
|
opcode = (coreHeaders << 4) >> 4;
|
||||||
|
hasSetFirstOpcode = TRUE;
|
||||||
|
}
|
||||||
|
unsigned char plHeaders;
|
||||||
|
SSL_read(ssl, &plHeaders, 1);
|
||||||
|
unsigned char tlen = (plHeaders << 1) >> 1;
|
||||||
|
if (tlen == 127) {
|
||||||
|
SSL_read(ssl, &curPayloadLen, 8);
|
||||||
|
curPayloadLen = ntohll(curPayloadLen);
|
||||||
|
payloadLen += curPayloadLen;
|
||||||
|
} else if (tlen == 126) {
|
||||||
|
unsigned short templen;
|
||||||
|
SSL_read(ssl, &templen, 2);
|
||||||
|
curPayloadLen = ntohs(templen);
|
||||||
|
payloadLen += curPayloadLen;
|
||||||
|
} else {
|
||||||
|
curPayloadLen = tlen;
|
||||||
|
payloadLen += curPayloadLen;
|
||||||
|
}
|
||||||
|
if (opcode == 0x01) {
|
||||||
|
*out_buffer = realloc(*out_buffer, payloadLen + 1);
|
||||||
|
*out_length = payloadLen + 1;
|
||||||
|
} else {
|
||||||
|
*out_buffer = realloc(*out_buffer, payloadLen);
|
||||||
|
*out_length = payloadLen;
|
||||||
|
}
|
||||||
|
unsigned long long totalRead = 0;
|
||||||
|
while (totalRead < curPayloadLen) {
|
||||||
|
int bytesRead = SSL_read(
|
||||||
|
ssl, *out_buffer + (payloadLen - curPayloadLen) + totalRead,
|
||||||
|
curPayloadLen - totalRead);
|
||||||
|
if (bytesRead <= 0) {
|
||||||
|
free(*out_buffer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
totalRead += bytesRead;
|
||||||
|
}
|
||||||
|
if (fin == 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opcode == 0x01) {
|
||||||
|
(*out_buffer)[payloadLen] = '\0';
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
int OpenWebSocket(const char *hostname, const char *path, SSL **out_ssl);
|
||||||
|
int ReadWebSocket(SSL* ssl, char** out_buffer, size_t* out_length);
|
||||||
|
int SendWebSocket(SSL *ssl, const char *data, size_t length, unsigned char flags);
|
||||||
|
void WebSocketOnDataArrival(SSL *ssl, char *buffer, size_t length);
|
||||||
|
|
||||||
|
#define FIN_LAST 0x80
|
||||||
|
#define FIN_MORE 0x00
|
||||||
|
#define WS_TEXT 0x01
|
||||||
Reference in New Issue
Block a user