RyanHub - file viewer
filename: src/net/server.c
branch: main
back to repo
// server.c

#include "platform.h"
#include "server.h"
#include "player.h"
#include "engine.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
#include <openssl/evp.h>

extern GameState g_game_state;

static THREAD_FUNC_RETURN player_thread(void* arg) {
    int slot = *(int*)arg;
    free(arg);

    Player* p = &g_game_state.players[slot];

    printf("player %d joined\n", slot);

    unsigned char wsbuf[128];
    int len;

    while (p->connected && (len = recv(p->sock, wsbuf, sizeof(wsbuf), 0)) > 0) {
        if (len < 6) continue;

        int masked = wsbuf[1] & 0x80;
        int plen = wsbuf[1] & 0x7F;
        int mask_offset = 2;
        if (plen == 126) {
            plen = (wsbuf[2] << 8) | wsbuf[3];
            mask_offset = 4;
        }
        else if (plen == 127) {
            continue;
        }

        unsigned char mask[4] = { 0 };
        if (masked) {
            mask[0] = wsbuf[mask_offset];
            mask[1] = wsbuf[mask_offset + 1];
            mask[2] = wsbuf[mask_offset + 2];
            mask[3] = wsbuf[mask_offset + 3];
        }
        int payload_start = mask_offset + (masked ? 4 : 0);

        char msg[128] = { 0 };
        for (int i = 0; i < plen && i < (int)sizeof(msg) - 1; ++i) {
            msg[i] = wsbuf[payload_start + i] ^ (masked ? mask[i % 4] : 0);
        }

        Event e = parse_event(msg);
        queue_push(&p->queue, e);

    }

    printf("player %d disconnected\n", slot);

    CLOSESOCKET(p->sock);
    p->connected = 0;
    p->sock = 0;

    return THREAD_FUNC_RETURN_VALUE;
}

static void base64_encode(const unsigned char* in, int inlen, char* out) {
    EVP_EncodeBlock((unsigned char*)out, in, inlen);
}

static int get_websocket_accept_key(const char* buffer, char* accept_key) {
    const char* key_hdr = strstr(buffer, "Sec-WebSocket-Key: ");
    if (!key_hdr) return 0;
    char ws_key[128] = { 0 };
    sscanf_s(key_hdr, "Sec-WebSocket-Key: %127s", ws_key, (unsigned)_countof(ws_key));
    char concat[256] = { 0 };
    snprintf(concat, sizeof(concat), "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", ws_key);
    unsigned char sha1[20] = { 0 };
    SHA1((unsigned char*)concat, strlen(concat), sha1);
    base64_encode(sha1, 20, accept_key);
    return 1;
}

void server_run(int port) {

    if (PLATFORM_SOCKET_INIT() != 0) {
        fprintf(stderr, "WSAStartup failed\n");
        return;
    }

    SOCKET_TYPE server_sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(port),
        .sin_addr.s_addr = INADDR_ANY
    };

    bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
    listen(server_sock, 5);

    while (1) {
        SOCKET_TYPE client_sock = accept(server_sock, NULL, NULL);
        if (client_sock == INVALID_SOCKET_VAL) continue;

        char buffer[1024] = { 0 };
        recv(client_sock, buffer, sizeof(buffer) - 1, 0);

        //printf("%s", buffer);

        if (strstr(buffer, "Connection: Upgrade") != NULL) {
            char accept_key[128] = { 0 };
            if (get_websocket_accept_key(buffer, accept_key)) {
                char response[256];
                snprintf(response, sizeof(response),
                    "HTTP/1.1 101 Switching Protocols\r\n"
                    "Upgrade: websocket\r\n"
                    "Connection: Upgrade\r\n"
                    "Sec-WebSocket-Accept: %s\r\n"
                    "\r\n",
                    accept_key);
                send(client_sock, response, (int)strlen(response), 0);

                int slot = -1;
                for (int i = 0; i < MAX_PLAYERS; ++i) {
                    if (!g_game_state.players[i].connected) {
                        slot = i;
                        break;
                    }
                }
                if (slot == -1) {
                    CLOSESOCKET(client_sock);
                    continue;
                }

                g_game_state.players[slot].sock = client_sock;
                g_game_state.players[slot].connected = 1;

                thread_t tid;
                int* slot_ptr = malloc(sizeof(int));
                if (slot_ptr == NULL) {
                    CLOSESOCKET(client_sock);
                    continue;
                }
                *slot_ptr = slot;
                tid = THREAD_CREATE(&tid, player_thread, (void*)slot_ptr);
                if (tid != NULL) {
                    THREAD_DETACH(&tid);
                }
                else {
                    fprintf(stderr, "Failed to create player thread for slot %d\n", slot);
                    CLOSESOCKET(client_sock);
                    g_game_state.players[slot].connected = 0;
                    g_game_state.players[slot].sock = 0;
                    free(slot_ptr);
                    continue;
                }
            }
        }
        else {
            if (strncmp(buffer, "GET / ", 6) == 0 || strncmp(buffer, "GET /index.html", 15) == 0) {
                FILE* f = fopen("index.html", "rb");
                if (f) {
                    fseek(f, 0, SEEK_END);
                    long filesize = ftell(f);
                    rewind(f);

                    char* filebuf = malloc(filesize);
                    if (!filebuf) {
                        fclose(f);
                        const char* internal_error = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 21\r\n\r\nInternal Server Error";
                        send(client_sock, internal_error, (int)strlen(internal_error), 0);
                        CLOSESOCKET(client_sock);
                        return;
                    }
                    fread(filebuf, 1, filesize, f);
                    fclose(f);

                    char header[256];
                    int header_len = snprintf(
                        header, sizeof(header),
                        "HTTP/1.1 200 OK\r\n"
                        "Content-Type: text/html\r\n"
                        "Content-Length: %ld\r\n"
                        "\r\n",
                        filesize
                    );
                    send(client_sock, header, header_len, 0);
                    send(client_sock, filebuf, filesize, 0);
                    free(filebuf);

                }
                else {
                    const char* not_found = "HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\n\r\nNot Found";
                    send(client_sock, not_found, (int)strlen(not_found), 0);
                }
            }
            CLOSESOCKET(client_sock);
        }
    }
    PLATFORM_SOCKET_CLEANUP();
}