// 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();
}