Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
QBLive - UDP server/client using ENet
#3
Enet .h file


QBLive_UI.h
Code: (Select All)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <commctrl.h>
#include <process.h>
#include <cstdint>
#include <string>

#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "user32.lib")

// --- UI CONSTANTS ---
#define IDC_PORT_LABEL  1000
#define IDC_PORT_EDIT    1001
#define IDC_BTN_START    1002
#define IDC_BTN_STOP    1003
#define IDC_LIST_CLIENTS 1004
#define IDC_BTN_KICK    1005
#define IDC_CHK_REUSE    1006
#define IDC_STATUS_BAR  1007

// --- GLOBAL HANDLES ---
HWND hMainDlg = NULL;
HWND hClientList = NULL;
typedef void (__stdcall *UI_EVENT_CALLBACK)(int32_t action, const char* data);
UI_EVENT_CALLBACK g_EventCallback = nullptr;

// --- DYNAMIC DIALOG TEMPLATE HELPER ---
// This builds the window structure in memory (VB6 style layout)
void BuildServerUI(HWND hwnd) {
    // Port Label and Input
    CreateWindowEx(0, "STATIC", "Server Port:", WS_CHILD | WS_VISIBLE, 10, 15, 80, 20, hwnd, (HMENU)IDC_PORT_LABEL, NULL, NULL);
    CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "12347", WS_CHILD | WS_VISIBLE | ES_NUMBER, 90, 12, 60, 22, hwnd, (HMENU)IDC_PORT_EDIT, NULL, NULL);

    // Options
    CreateWindowEx(0, "BUTTON", "Auto-Reuse Port", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 160, 15, 120, 20, hwnd, (HMENU)IDC_CHK_REUSE, NULL, NULL);
    SendMessage(GetDlgItem(hwnd, IDC_CHK_REUSE), BM_SETCHECK, BST_CHECKED, 0);

    // Action Buttons
    CreateWindowEx(0, "BUTTON", "START SERVER", WS_CHILD | WS_VISIBLE, 10, 50, 130, 35, hwnd, (HMENU)IDC_BTN_START, NULL, NULL);
    CreateWindowEx(0, "BUTTON", "STOP SERVER", WS_CHILD | WS_VISIBLE, 150, 50, 130, 35, hwnd, (HMENU)IDC_BTN_STOP, NULL, NULL);

    // Client List
    CreateWindowEx(0, "STATIC", "Active Clients:", WS_CHILD | WS_VISIBLE, 10, 100, 100, 20, hwnd, NULL, NULL, NULL);
    hClientList = CreateWindowEx(WS_EX_CLIENTEDGE, "LISTBOX", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 10, 120, 270, 150, hwnd, (HMENU)IDC_LIST_CLIENTS, NULL, NULL);

    // Management
    CreateWindowEx(0, "BUTTON", "KICK SELECTED", WS_CHILD | WS_VISIBLE, 10, 280, 270, 30, hwnd, (HMENU)IDC_BTN_KICK, NULL, NULL);

    // Status Bar
    CreateWindowEx(0, "STATIC", "STATUS: IDLE", WS_CHILD | WS_VISIBLE | SS_SUNKEN, 0, 320, 300, 20, hwnd, (HMENU)IDC_STATUS_BAR, NULL, NULL);
}

// --- WINDOW PROCEDURE ---
LRESULT CALLBACK ServerWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_CREATE:
        BuildServerUI(hwnd);
        return 0;

    case WM_COMMAND:
        if (g_EventCallback) {
            int wmId = LOWORD(wParam);
            if (wmId == IDC_BTN_START) {
                char buf[10];
                GetWindowText(GetDlgItem(hwnd, IDC_PORT_EDIT), buf, 10);
                SetWindowText(GetDlgItem(hwnd, IDC_STATUS_BAR), "STATUS: RUNNING");
                g_EventCallback(1, buf); // Action 1: Start
            }
            if (wmId == IDC_BTN_STOP) {
                SetWindowText(GetDlgItem(hwnd, IDC_STATUS_BAR), "STATUS: STOPPED");
                g_EventCallback(0, ""); // Action 0: Stop
            }
            if (wmId == IDC_BTN_KICK) {
                int sel = (int)SendMessage(hClientList, LB_GETCURSEL, 0, 0);
                if (sel != LB_ERR) {
                    char name[32];
                    SendMessage(hClientList, LB_GETTEXT, sel, (LPARAM)name);
                    g_EventCallback(2, name); // Action 2: Kick
                }
            }
        }
        return 0;

    case WM_CLOSE:
        hMainDlg = NULL;
        DestroyWindow(hwnd);
        return 0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

// --- BACKGROUND UI THREAD ---
void UI_Thread_Proc(void* param) {
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = ServerWndProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wc.lpszClassName = "QBLive_Admin_UI";
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    RegisterClass(&wc);

    hMainDlg = CreateWindowEx(WS_EX_TOPMOST, wc.lpszClassName, "QBLive S-Tier Admin Panel", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_VISIBLE,
                              CW_USEDEFAULT, CW_USEDEFAULT, 310, 380, NULL, NULL, wc.hInstance, NULL);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

// --- EXPORTED FUNCTIONS ---
extern "C" {
    __declspec(dllexport) void UI_Show(uintptr_t callback) {
        if (!hMainDlg) {
            g_EventCallback = (UI_EVENT_CALLBACK)callback;
            _beginthread(UI_Thread_Proc, 0, NULL);
        }
    }

    __declspec(dllexport) void UI_UpdateList(const char* name, int action) {
        if (!hClientList) return;
        if (action == 1) SendMessage(hClientList, LB_ADDSTRING, 0, (LPARAM)name); // 1 = Add
        else if (action == 0) { // 0 = Remove
            int idx = (int)SendMessage(hClientList, LB_FINDSTRINGEXACT, -1, (LPARAM)name);
            if (idx != LB_ERR) SendMessage(hClientList, LB_DELETESTRING, idx, 0);
        }
    }
}

QBLive_Server.h
Code: (Select All)
#ifndef QBLIVE_SERVER_H
#define QBLIVE_SERVER_H

#define ENET_IMPLEMENTATION
#include <winsock2.h>
#include <windows.h>
#include <commctrl.h>
#include <string>
#include <cstdio>
#include "enet.h"

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "winmm.lib")

// IDs for UI Controls
#define ID_BTN_START    101
#define ID_BTN_STOP    102
#define ID_BTN_KICK    104
#define IDM_EXIT        203
#define ID_STATE_TEXT  301
#define ID_EDIT_PORT    401
#define ID_LIST_CLIENTS 501

// Global Handles
static HWND g_hStateLabel = NULL;
static HWND g_hPortEdit = NULL;
static HWND g_hClientList = NULL;
static ENetHost* g_Server = NULL;
static int g_LastStartedPort = 7777;
static char g_LastPacketData[1024] = { 0 };

// Helper: Find the index of a client in the listbox by their ENetPeer pointer
static int FindClientIndex(ENetPeer* peer) {
    int count = (int)SendMessage(g_hClientList, LB_GETCOUNT, 0, 0);
    for (int i = 0; i < count; i++) {
        if ((ENetPeer*)SendMessage(g_hClientList, LB_GETITEMDATA, i, 0) == peer) return i;
    }
    return -1;
}

// Helper: Get Public IP using Curl
std:Confusedtring GetGlobalIP_Clean() {
    char buffer[128];
    std:Confusedtring result = "";
    FILE* pipe = _popen("curl -4 -s ifconfig.me", "r");
    if (!pipe) return "0.0.0.0";
    if (fgets(buffer, sizeof(buffer), pipe) != NULL) result = buffer;
    _pclose(pipe);
    if (result.length() < 7) return "127.0.0.1";
    result.erase(result.find_last_not_of(" \n\r\t") + 1);
    return result;
}

// Helper: Update the main status text
void RefreshStatusUI(const char* status, const char* ip, int port) {
    if (!g_hStateLabel) return;
    std:Confusedtring info = "Status: " + std:Confusedtring(status) +
                      "\nPublic IP: " + std:Confusedtring(ip) +
                      "\nPort: " + std::to_string(port) +
                      "\nClients: " + (g_Server ? std::to_string(g_Server->connectedPeers) : "0");
    SetWindowText(g_hStateLabel, info.c_str());
}

// Main Window Logic
static LRESULT CALLBACK MyWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_COMMAND:
            switch (LOWORD(wParam)) {
                case ID_BTN_START:
                    if (!g_Server) {
                        char portBuf[10];
                        GetWindowText(g_hPortEdit, portBuf, 10);
                        int userPort = atoi(portBuf);
                        if (userPort <= 0) { userPort = 7777; SetWindowText(g_hPortEdit, "7777"); }
                        g_LastStartedPort = userPort;

                        if (enet_initialize() == 0) {
                            ENetAddress addr;
                            addr.host = ENET_HOST_ANY;
                            addr.port = (enet_uint16)userPort;
                            g_Server = enet_host_create(&addr, 32, 2, 0, 0);
                            if (g_Server) RefreshStatusUI("Running", GetGlobalIP_Clean().c_str(), userPort);
                        }
                    }
                    break;

                case ID_BTN_STOP:
                    if (g_Server) {
                        for (size_t i = 0; i < g_Server->peerCount; ++i) {
                            if (g_Server->peers[i].state == ENET_PEER_STATE_CONNECTED) {
                                enet_peer_disconnect(&g_Server->peers[i], 500);
                            }
                        }
                        enet_host_flush(g_Server);
                        Sleep(100);
                        SendMessage(g_hClientList, LB_RESETCONTENT, 0, 0);
                        enet_host_destroy(g_Server);
                        g_Server = NULL;
                        enet_deinitialize();
                        RefreshStatusUI("Stopped", "---", 0);
                    }
                    break;

                case ID_BTN_KICK: {
                    int sel = (int)SendMessage(g_hClientList, LB_GETCURSEL, 0, 0);
                    if (sel != LB_ERR) {
                        ENetPeer* peer = (ENetPeer*)SendMessage(g_hClientList, LB_GETITEMDATA, sel, 0);
                        if (peer) enet_peer_disconnect(peer, 403);
                    }
                    break;
                }

                case IDM_EXIT:
                    PostQuitMessage(0);
                    break;
            }
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

extern "C" {
    __declspec(dllexport) void InitServerUI() {
        HINSTANCE hInst = GetModuleHandle(NULL);
        WNDCLASS wc = { 0 };
        wc.lpfnWndProc = MyWinProc; wc.hInstance = hInst; wc.lpszClassName = "QBLiveClass";
        wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        RegisterClass(&wc);

        HWND main = CreateWindowEx(0, "QBLiveClass", "QBLive Server 2025",
            WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
            NULL, NULL, hInst, NULL);

        CreateWindow("BUTTON", "Start", WS_VISIBLE | WS_CHILD, 20, 20, 80, 30, main, (HMENU)ID_BTN_START, hInst, NULL);
        CreateWindow("BUTTON", "Stop", WS_VISIBLE | WS_CHILD, 110, 20, 80, 30, main, (HMENU)ID_BTN_STOP, hInst, NULL);
        CreateWindow("BUTTON", "Kick Selected", WS_VISIBLE | WS_CHILD, 450, 20, 120, 30, main, (HMENU)ID_BTN_KICK, hInst, NULL);
        CreateWindow("STATIC", "Port:", WS_VISIBLE | WS_CHILD, 210, 25, 40, 20, main, NULL, hInst, NULL);
        g_hPortEdit = CreateWindow("EDIT", "7777", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_NUMBER, 255, 22, 60, 25, main, (HMENU)ID_EDIT_PORT, hInst, NULL);
        CreateWindow("STATIC", "Connected Clients:", WS_VISIBLE | WS_CHILD, 450, 60, 150, 20, main, NULL, hInst, NULL);
        g_hClientList = CreateWindow("LISTBOX", NULL, WS_VISIBLE | WS_CHILD | WS_BORDER | WS_VSCROLL | LBS_NOTIFY, 450, 80, 300, 400, main, (HMENU)ID_LIST_CLIENTS, hInst, NULL);
        g_hStateLabel = CreateWindow("STATIC", "Status: Ready", WS_VISIBLE | WS_CHILD, 20, 80, 400, 150, main, (HMENU)ID_STATE_TEXT, hInst, NULL);
    }

    __declspec(dllexport) void ProcessWindowEvents() {
        MSG msg;
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); }
        if (g_Server) {
            ENetEvent event;
            while (enet_host_service(g_Server, &event, 0) > 0) {
                char ip[64];
                enet_address_get_host_ip(&event.peer->address, ip, 64);
                switch (event.type) {
                    case ENET_EVENT_TYPE_CONNECT: {
                        std:Confusedtring entry = "IP: " + std:Confusedtring(ip);
                        int idx = (int)SendMessage(g_hClientList, LB_ADDSTRING, 0, (LPARAM)entry.c_str());
                        SendMessage(g_hClientList, LB_SETITEMDATA, idx, (LPARAM)event.peer);
                        RefreshStatusUI("Running", "Active", g_LastStartedPort);
                        break;
                    }
                    case ENET_EVENT_TYPE_DISCONNECT: {
                        int idx = FindClientIndex(event.peer);
                        if (idx != -1) SendMessage(g_hClientList, LB_DELETESTRING, idx, 0);
                        RefreshStatusUI("Running", "Active", g_LastStartedPort);
                        break;
                    }
                    case ENET_EVENT_TYPE_RECEIVE:
                        strncpy(g_LastPacketData, (char*)event.packet->data, 1024);
                        enet_packet_destroy(event.packet);
                        break;
                    default: break;
                }
            }
        }
    }

    __declspec(dllexport) int UDP_Has_Message() { return (g_LastPacketData[0] != '\0'); }
    __declspec(dllexport) void UDP_Get_Message(char* buffer) { strcpy(buffer, g_LastPacketData); g_LastPacketData[0] = '\0'; }
}
#endif

QBLive_Client.h
Code: (Select All)
#ifndef QBLIVE_CLIENT_H
#define QBLIVE_CLIENT_H

#define ENET_IMPLEMENTATION
#include <winsock2.h>
#include <windows.h>
#include <string>
#include "enet.h"

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "winmm.lib")

static ENetHost* g_ClientHost = NULL;
static ENetPeer* g_ServerPeer = NULL;

extern "C" {
    __declspec(dllexport) int InitClient(const char* ip, int port) {
        if (enet_initialize() != 0) return -1;
        g_ClientHost = enet_host_create(NULL, 1, 2, 0, 0);
        if (!g_ClientHost) return -2;

        ENetAddress address;
        enet_address_set_host(&address, ip);
        address.port = (enet_uint16)port;
        g_ServerPeer = enet_host_connect(g_ClientHost, &address, 2, 0);
        return (g_ServerPeer) ? 0 : -3;
    }


    // UPDATED: Now returns 1=Connect, 2=Data, 3=Normal Disconnect, 403=Kicked, 500=Server Off
    __declspec(dllexport) int UpdateClient() {
        if (!g_ClientHost) return -1;
        ENetEvent event;
        while (enet_host_service(g_ClientHost, &event, 0) > 0) {
            switch (event.type) {
                case ENET_EVENT_TYPE_CONNECT: return 1;
                case ENET_EVENT_TYPE_RECEIVE: enet_packet_destroy(event.packet); return 2;
                case ENET_EVENT_TYPE_DISCONNECT:
                    // Check the data code the server sent us
                    if (event.data == 403) return 403;
                    if (event.data == 500) return 500;
                    return 3; // Generic disconnect
                default: break;
            }
        }
        return 0;
    }


    __declspec(dllexport) void ClientSendMessage(const char* msg) {
        if (!g_ServerPeer) return;
        ENetPacket* packet = enet_packet_create(msg, strlen(msg) + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(g_ServerPeer, 0, packet);
        enet_host_flush(g_ClientHost);
    }

    __declspec(dllexport) void ShutdownClient() {
        if (g_ServerPeer) enet_peer_disconnect_now(g_ServerPeer, 0);
        if (g_ClientHost) enet_host_destroy(g_ClientHost);
        enet_deinitialize();
    }
}
#endif

Qb64 Side Server
Code: (Select All)
DECLARE LIBRARY "QBLive_Server"
  SUB InitServerUI ()
  SUB ProcessWindowEvents ()
  FUNCTION UDP_Has_Message& ()
  SUB UDP_Get_Message (buffer AS STRING)
END DECLARE

InitServerUI
DO
  ProcessWindowEvents
  IF UDP_Has_Message THEN
    DIM m AS STRING * 1024
    UDP_Get_Message m
    PRINT "Message: "; m
  END IF
  _LIMIT 60
LOOP UNTIL _EXIT

QB64 side Client
Code: (Select All)
' QB64 Client Application
DECLARE LIBRARY "QBLive_Client"
  FUNCTION InitClient& (ip AS STRING, BYVAL port AS LONG)
  FUNCTION UpdateClient& ()
  SUB ClientSendMessage (msg AS STRING)
  SUB ShutdownClient ()
END DECLARE

PRINT "Connecting to QBLive Server..."
res& = InitClient("127.0.0.1" + CHR$(0), 7777)

IF res& < 0 THEN
  PRINT "Failed to initialize client! Error:"; res&
  END
END IF

isConnected = 0
DO
  status& = UpdateClient

  SELECT CASE status&
    CASE 1: PRINT "Connected!"
    CASE 3: PRINT "Disconnected from server.": END
    CASE 403: PRINT "YOU HAVE BEEN KICKED!": END
    CASE 500: PRINT "SERVER SHUTDOWN!": END
  END SELECT

  _LIMIT 60
LOOP UNTIL _EXIT

ShutdownClient
SYSTEM

It'll be work but now hopefully you get no virus reports!

John
Reply


Messages In This Thread
RE: QBLive - UDP server/client using ENet - by Unseen Machine - 12-25-2025, 06:51 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)