diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 61f3fa7..2ef9e52 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -4,8 +4,7 @@ "name": "Win32", "includePath": [ "${default}", - "${workspaceFolder}/**", - "C:/msys64/mingw64/include" + "${workspaceFolder}/**" ], "defines": [ "_DEBUG", diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ffe9ada --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,72 @@ +{ + "files.associations": { + "*.pyx": "python", + "*.js": "javascript", + "*.c": "c", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "netfwd": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "semaphore": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/Makefile b/Makefile index 4e52bdd..035e8a0 100644 --- a/Makefile +++ b/Makefile @@ -1,39 +1,29 @@ -# Compiler -CC = gcc +CXX := g++ +CXXFLAGS := -std=c++20 -Wall -Wextra -g -Iinclude -I/c/msys64/mingw64/include -# Paths -SRC_DIR = src -BUILD_DIR = build -OUT = main.exe +LDFLAGS := -lws2_32 -# Source files -SRC = \ - $(SRC_DIR)\main.c \ - $(SRC_DIR)\net.c \ - $(SRC_DIR)\event.c \ - $(SRC_DIR)\player.c +SRC_DIRS := src +BUILD_DIR := build -# Object files -OBJ = $(patsubst $(SRC_DIR)\%.c, $(BUILD_DIR)\%.o, $(SRC)) +# Find all source files +SRC := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.cpp)) -# Include and linker flags -INCLUDE_FLAGS = -IC:/msys64/mingw64/include -LIBS = -LC:/msys64/mingw64/lib -static -lws2_32 -lSDL2main -lSDL2 -lwinmm -limm32 -lole32 -loleaut32 -lversion -lsetupapi -lgdi32 -lshell32 -luser32 -mconsole -CFLAGS = -Wall -Wextra -g $(INCLUDE_FLAGS) -LDFLAGS = $(LIBS) +OBJ := $(patsubst %.cpp, $(BUILD_DIR)/%.o, $(SRC)) -# Default target -all: $(OUT) +TARGET := main.exe -$(OUT): $(OBJ) - $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) +all: $(TARGET) -# Compile each .c into .o -$(BUILD_DIR)\%.o: $(SRC_DIR)\%.c - @if not exist $(BUILD_DIR) mkdir $(BUILD_DIR) - $(CC) $(CFLAGS) -c $< -o $@ +$(TARGET): $(OBJ) + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +$(BUILD_DIR)/%.o: %.cpp + @mkdir "$(dir $@)" || exit 0 + $(CXX) $(CXXFLAGS) -c $< -o $@ -# Clean clean: - del /Q $(BUILD_DIR)\*.o 2>nul || exit 0 - del /Q $(OUT) 2>nul || exit 0 + del /Q /S $(subst /,\,$(BUILD_DIR)\*.o) $(TARGET) 2>nul || exit 0 + rmdir /S /Q $(BUILD_DIR) 2>nul || exit 0 + +.PHONY: all clean \ No newline at end of file diff --git a/build/src/Client.o b/build/src/Client.o new file mode 100644 index 0000000..5b28957 Binary files /dev/null and b/build/src/Client.o differ diff --git a/build/src/Packet.o b/build/src/Packet.o new file mode 100644 index 0000000..25136d3 Binary files /dev/null and b/build/src/Packet.o differ diff --git a/build/src/Server.o b/build/src/Server.o new file mode 100644 index 0000000..b09b3e7 Binary files /dev/null and b/build/src/Server.o differ diff --git a/build/src/main.o b/build/src/main.o new file mode 100644 index 0000000..4ba9914 Binary files /dev/null and b/build/src/main.o differ diff --git a/include/Client.h b/include/Client.h new file mode 100644 index 0000000..75004f5 --- /dev/null +++ b/include/Client.h @@ -0,0 +1,9 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include + +// Runs the client (with OpenGL rendering and network handling). +void runClient(const std::string& host, const std::string& portStr); + +#endif // CLIENT_H diff --git a/include/Packet.h b/include/Packet.h new file mode 100644 index 0000000..0f4a1dd --- /dev/null +++ b/include/Packet.h @@ -0,0 +1,40 @@ +#ifndef PACKET_H +#define PACKET_H + +#include +#include +#include +#include +#include + +enum PacketType : uint8_t { + HEARTBEAT = 1, + JOIN, + DISCONNECT, + MOVE, + CHAT, + PLAYER_UPDATE, + SCORE_UPDATE, + GAME_START, + GAME_END, + PING, + PONG, + AUTH_REQUEST, + AUTH_RESPONSE, + PACKET_MAX +}; + +struct Packet +{ + PacketType type; + std::string data; +}; + +uint32_t to_network_uint32(uint32_t host); +uint32_t from_network_uint32(uint32_t net); +bool send_all(SOCKET sock, const char *data, size_t len); +bool recv_all(SOCKET sock, char *buffer, size_t len); +bool sendPacket(SOCKET sock, const Packet &packet); +bool receivePacket(SOCKET sock, Packet &packet); + +#endif // PACKET_H diff --git a/include/Server.h b/include/Server.h new file mode 100644 index 0000000..5758d9e --- /dev/null +++ b/include/Server.h @@ -0,0 +1,23 @@ +#ifndef SERVER_H +#define SERVER_H + +#include +#include +#include +#include +#include "Packet.h" + +class Server { +public: + Server(unsigned short port); + ~Server(); + void start(); + void broadcast(const Packet &packet); +private: + void handleClient(SOCKET clientSock); + SOCKET listenSock; + std::vector clients; + std::mutex mutex_; +}; + +#endif // SERVER_H diff --git a/include/Utils.h b/include/Utils.h new file mode 100644 index 0000000..bb7449f --- /dev/null +++ b/include/Utils.h @@ -0,0 +1,18 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include + +// Returns the current timestamp in milliseconds since the epoch. +inline std::string getCurrentTimestamp() { + using namespace std::chrono; + auto now = system_clock::now(); + auto ms = duration_cast(now.time_since_epoch()).count(); + std::ostringstream oss; + oss << ms; + return oss.str(); +} + +#endif // UTILS_H diff --git a/main.exe b/main.exe index c005948..6964428 100644 Binary files a/main.exe and b/main.exe differ diff --git a/src/Client.cpp b/src/Client.cpp new file mode 100644 index 0000000..e3cf4e8 --- /dev/null +++ b/src/Client.cpp @@ -0,0 +1,98 @@ +// Client.cpp +#include "Client.h" +#include "Packet.h" +#include "Utils.h" +#include +#include +#include +#include +#include +#include + +void sendAuthPacket(SOCKET sock, const std::string &username, const std::string &password) { + Packet authPacket; + authPacket.type = AUTH_REQUEST; // Ensure AUTH_REQUEST is defined in your PacketType enum. + authPacket.data = username + ":" + password; + if(!sendPacket(sock, authPacket)) { + std::cerr << "Failed to send auth packet." << std::endl; + } +} + +void runClient(const std::string& host, const std::string& portStr) { + int port = std::stoi(portStr); + SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sock == INVALID_SOCKET) { + std::cerr << "Failed to create socket." << std::endl; + return; + } + + sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + inet_pton(AF_INET, host.c_str(), &serverAddr.sin_addr); + serverAddr.sin_port = htons(port); + + if(connect(sock, reinterpret_cast(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) { + std::cerr << "Connect failed." << std::endl; + closesocket(sock); + return; + } + + std::cout << "Connected to server." << std::endl; + + // Prompt for authentication details. + std::string username, password; + std::cout << "Enter username: "; + std::getline(std::cin, username); + std::cout << "Enter password: "; + std::getline(std::cin, password); + + // Send authentication packet. + sendAuthPacket(sock, username, password); + + Packet joinPacket; + joinPacket.type = JOIN; + joinPacket.data = "I have joined."; + if(!sendPacket(sock, joinPacket)) { + std::cerr << "Failed to send join packet." << std::endl; + closesocket(sock); + return; + } + + // Main loop to receive packets from the server. + Packet packet; + while(receivePacket(sock, packet)) { + switch(packet.type) { + case HEARTBEAT: { + try { + long long serverTimestamp = std::stoll(packet.data); + using namespace std::chrono; + auto now = system_clock::now(); + auto msNow = duration_cast(now.time_since_epoch()).count(); + long long ping = msNow - serverTimestamp; + std::cout << "Heartbeat: " << ping << " ms" << std::endl; + } catch(...) { + } + break; + } + case JOIN: + std::cout << "Connect: " << packet.data << std::endl; + break; + case DISCONNECT: + std::cout << "Disconnect: " << packet.data << std::endl; + break; + case AUTH_RESPONSE: + std::cout << "Auth Response: " << packet.data << std::endl; + break; + default: + std::cout << "Unknown packet type: " << static_cast(packet.type) + << " | Data: " << packet.data << std::endl; + break; + } + } + + std::cout << "Disconnected from server." << std::endl; + closesocket(sock); +} + + + diff --git a/src/Packet.cpp b/src/Packet.cpp new file mode 100644 index 0000000..1db0e2a --- /dev/null +++ b/src/Packet.cpp @@ -0,0 +1,72 @@ +#include "Packet.h" +#include +#include +#include + +uint32_t to_network_uint32(uint32_t host) { + return htonl(host); +} + +uint32_t from_network_uint32(uint32_t net) { + return ntohl(net); +} + +bool send_all(SOCKET sock, const char* data, size_t len) { + size_t totalSent = 0; + while(totalSent < len) { + int sent = send(sock, data + totalSent, static_cast(len - totalSent), 0); + if(sent <= 0) + return false; + totalSent += sent; + } + return true; +} + +bool recv_all(SOCKET sock, char* buffer, size_t len) { + size_t totalRecv = 0; + while(totalRecv < len) { + int recvd = recv(sock, buffer + totalRecv, static_cast(len - totalRecv), 0); + if(recvd <= 0) + return false; + totalRecv += recvd; + } + return true; +} + +bool sendPacket(SOCKET sock, const Packet& packet) { + // Payload length = 1 byte for type + data length. + uint32_t payloadLen = 1 + static_cast(packet.data.size()); + uint32_t netLen = to_network_uint32(payloadLen); + if(!send_all(sock, reinterpret_cast(&netLen), sizeof(netLen))) + return false; + if(!send_all(sock, reinterpret_cast(&packet.type), sizeof(packet.type))) + return false; + if(!packet.data.empty()) { + if(!send_all(sock, packet.data.data(), packet.data.size())) + return false; + } + return true; +} + +bool receivePacket(SOCKET sock, Packet &packet) { + uint32_t netLen = 0; + if(!recv_all(sock, reinterpret_cast(&netLen), sizeof(netLen))) + return false; + uint32_t len = from_network_uint32(netLen); + if(len < 1) + return false; + char typeChar; + if(!recv_all(sock, &typeChar, 1)) + return false; + packet.type = static_cast(typeChar); + size_t dataLen = len - 1; + if(dataLen > 0) { + std::vector buf(dataLen); + if(!recv_all(sock, buf.data(), dataLen)) + return false; + packet.data = std::string(buf.begin(), buf.end()); + } else { + packet.data.clear(); + } + return true; +} diff --git a/src/Server.cpp b/src/Server.cpp new file mode 100644 index 0000000..b8c2832 --- /dev/null +++ b/src/Server.cpp @@ -0,0 +1,151 @@ +#include "Server.h" +#include +#include +#include +#include +#include + +Server::Server(unsigned short port) { + listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(listenSock == INVALID_SOCKET) { + std::cerr << "Failed to create socket." << std::endl; + std::exit(EXIT_FAILURE); + } + sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + serverAddr.sin_addr.s_addr = INADDR_ANY; + serverAddr.sin_port = htons(port); + if(bind(listenSock, reinterpret_cast(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) { + std::cerr << "Bind failed." << std::endl; + closesocket(listenSock); + std::exit(EXIT_FAILURE); + } + if(listen(listenSock, SOMAXCONN) == SOCKET_ERROR) { + std::cerr << "Listen failed." << std::endl; + closesocket(listenSock); + std::exit(EXIT_FAILURE); + } +} + +Server::~Server() { + closesocket(listenSock); +} + +void Server::start() { + std::thread([this](){ + while(true) { + SOCKET clientSock = accept(listenSock, nullptr, nullptr); + if(clientSock == INVALID_SOCKET) { + std::cerr << "Accept failed." << std::endl; + continue; + } + std::cout << "Client connected." << std::endl; + { + std::lock_guard lock(mutex_); + clients.push_back(clientSock); + } + // Broadcast join packet. + Packet joinPacket; + joinPacket.type = JOIN; + joinPacket.data = "Client Connected"; + broadcast(joinPacket); + // Spawn a thread to handle the new client. + std::thread(&Server::handleClient, this, clientSock).detach(); + } + }).detach(); +} + +void Server::broadcast(const Packet &packet) { + std::lock_guard lock(mutex_); + for(auto it = clients.begin(); it != clients.end();) { + if(!sendPacket(*it, packet)) { + // Remove client if send fails. + closesocket(*it); + it = clients.erase(it); + } else { + ++it; + } + } +} + +void Server::handleClient(SOCKET clientSock) { + bool authenticated = false; + std::string sessionToken; + Packet packet; + + while(receivePacket(clientSock, packet)) { + if(packet.type == AUTH_REQUEST) { + std::string creds = packet.data; + size_t delim = creds.find(":"); + Packet authResp; + authResp.type = AUTH_RESPONSE; + if(delim != std::string::npos) { + std::string username = creds.substr(0, delim); + std::string password = creds.substr(delim + 1); + + + if(username == "testuser" && password == "pass123") { + + authenticated = true; + sessionToken = "SESSION_TOKEN_ABC123"; + authResp.data = sessionToken; + std::cout << "Client authenticated: " << username << std::endl; + + } else { + authResp.data = "AUTH_FAILED"; + std::cout << "Client failed to authenticate: " << username << std::endl; + } + + + + } else { + + authResp.data = "AUTH_FAILED"; + std::cerr << "Invalid authentication packet format." << std::endl; + + } + sendPacket(clientSock, authResp); + } + else if(!authenticated) { + + + Packet notAuth; + notAuth.type = AUTH_RESPONSE; + notAuth.data = "NOT_AUTHENTICATED"; + sendPacket(clientSock, notAuth); + + + } + else { + + switch(packet.type) { + case MOVE: + broadcast(packet); + break; + case HEARTBEAT: + break; + case JOIN: + std::cout << "Join packet received: " << packet.data << std::endl; + break; + case DISCONNECT: + std::cout << "Disconnect packet received: " << packet.data << std::endl; + break; + default: + std::cout << "Unknown packet type: " << static_cast(packet.type) + << " | Data: " << packet.data << std::endl; + break; + } + } + } + std::cout << "Client disconnected." << std::endl; + Packet disconnectPacket; + disconnectPacket.type = DISCONNECT; + disconnectPacket.data = "A client disconnected."; + broadcast(disconnectPacket); + std::lock_guard lock(mutex_); + auto it = std::find(clients.begin(), clients.end(), clientSock); + if(it != clients.end()) { + closesocket(*it); + clients.erase(it); + } +} diff --git a/src/event.c b/src/event.c deleted file mode 100644 index 2b480bc..0000000 --- a/src/event.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "event.h" - -#define MAX_HANDLERS 256 -static PacketHandler g_handlers[MAX_HANDLERS] = {0}; - -void register_handler(uint8_t type, PacketHandler handler) { - g_handlers[type] = handler; -} - -void handle_packet(Packet* pkt, SOCKET client) { - if (g_handlers[pkt->type]) { - g_handlers[pkt->type](pkt, client); - } -} diff --git a/src/event.h b/src/event.h deleted file mode 100644 index 9a51bac..0000000 --- a/src/event.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef EVENT_H -#define EVENT_H - -#include "net.h" - -// Each handler receives Packet* and the client socket -typedef void (*PacketHandler)(Packet*, SOCKET client); - -// Register a handler for a certain PacketType -void register_handler(uint8_t type, PacketHandler handler); - -// Dispatch a packet -void handle_packet(Packet* pkt, SOCKET client); - -#endif diff --git a/src/main.c b/src/main.c deleted file mode 100644 index c1c50a4..0000000 --- a/src/main.c +++ /dev/null @@ -1,336 +0,0 @@ -#define WIN32_LEAN_AND_MEAN -#define SDL_MAIN_HANDLED -#include -#include -#include -#include // Sleep() -#include "net.h" -#include "event.h" -#include "player.h" - -#include - -CRITICAL_SECTION g_playerLock; -static Player g_players[MAX_PLAYERS]; -static int g_numPlayers = 0; -static SOCKET g_clients[MAX_CLIENTS]; -static int g_clientCount = 0; - -static uint32_t g_nextID = 1; // to assign unique IDs - -// Forward declarations -DWORD WINAPI client_thread(LPVOID param); -void broadcast_packet(Packet* pkt, SOCKET except); - - -void on_player_move(Packet* pkt, SOCKET client) { - PlayerMove pm; - memcpy(&pm, pkt->data, sizeof(PlayerMove)); - - EnterCriticalSection(&g_playerLock); - for(int i = 0; i < g_numPlayers; i++) { - if(g_players[i].id == pm.id) { - g_players[i].x += pm.dx; - g_players[i].y += pm.dy; - break; - } - } - - // broadcast updated positions - int numStates = g_numPlayers; - int dataSize = sizeof(PlayerState) * numStates; - PlayerState* states = malloc(dataSize); - for(int i = 0; i < g_numPlayers; i++) { - states[i].id = g_players[i].id; - states[i].x = g_players[i].x; - states[i].y = g_players[i].y; - } - Packet* statePkt = create_packet(PACKET_PLAYER_STATE, states, dataSize); - free(states); - broadcast_packet(statePkt, INVALID_SOCKET); - destroy_packet(statePkt); - - LeaveCriticalSection(&g_playerLock); -} - - -void broadcast_packet(Packet* pkt, SOCKET except) { - // serialize - char buffer[BUFFER_SIZE]; - int len = serialize_packet(pkt, buffer); - - EnterCriticalSection(&g_playerLock); - for(int i = 0; i < g_clientCount; i++) { - if(g_clients[i] == except) continue; - send(g_clients[i], buffer, len, 0); - } - LeaveCriticalSection(&g_playerLock); -} - -DWORD WINAPI client_thread(LPVOID param) { - SOCKET client = *(SOCKET*)param; - free(param); - - // Non-blocking for easier single-thread dispatch or we do blocking in a thread - set_socket_nonblocking(client, 0); // blocking in this thread - - // Assign a new player - Player newPlayer; - newPlayer.id = g_nextID++; - newPlayer.x = 100; // start at 100,100 - newPlayer.y = 100; - - EnterCriticalSection(&g_playerLock); - g_players[g_numPlayers++] = newPlayer; - g_clients[g_clientCount++] = client; - LeaveCriticalSection(&g_playerLock); - - printf("[SERVER] Client %u connected.\n", newPlayer.id); - - // Now run receive loop - char buffer[BUFFER_SIZE]; - while(1) { - int received = recv(client, buffer, BUFFER_SIZE, 0); - if (received <= 0) { - break; // client disconnected - } - Packet* pkt = deserialize_packet(buffer, received); - if (pkt) { - // Dispatch - handle_packet(pkt, client); - destroy_packet(pkt); - } - } - - // Remove from global state - EnterCriticalSection(&g_playerLock); - // remove from g_clients - for(int i = 0; i < g_clientCount; i++) { - if(g_clients[i] == client) { - g_clients[i] = g_clients[--g_clientCount]; - break; - } - } - // remove from g_players - for(int i = 0; i < g_numPlayers; i++) { - if(g_players[i].id == newPlayer.id) { - g_players[i] = g_players[--g_numPlayers]; - break; - } - } - LeaveCriticalSection(&g_playerLock); - - closesocket(client); - printf("[SERVER] Client %u disconnected.\n", newPlayer.id); - return 0; -} - -void run_server(int port) { - net_init(); - InitializeCriticalSection(&g_playerLock); - - // Register server handlers - register_handler(PACKET_PLAYER_MOVE, on_player_move); - - SOCKET server = net_bind_and_listen(port); - if (server == INVALID_SOCKET) { - printf("Failed to bind on port %d\n", port); - return; - } - printf("[SERVER] Listening on port %d...\n", port); - - while(1) { - SOCKET s = net_accept(server); - if (s == INVALID_SOCKET) { - Sleep(10); - continue; - } - // Start a client thread - SOCKET* ptr = malloc(sizeof(SOCKET)); - *ptr = s; - CreateThread(NULL, 0, client_thread, ptr, 0, NULL); - } - - DeleteCriticalSection(&g_playerLock); - closesocket(server); - net_cleanup(); -} - - -static Player g_localPlayer = {0}; -static PlayerState g_otherPlayers[MAX_PLAYERS]; -static int g_otherCount = 0; - -// For now, store the socket globally -static SOCKET g_sock = INVALID_SOCKET; - -// Handler: PACKET_PLAYER_STATE -void on_player_state(Packet* pkt, SOCKET _) { - // We'll parse an array of PlayerStates - int num = pkt->size / sizeof(PlayerState); - if(num > MAX_PLAYERS) num = MAX_PLAYERS; - - memcpy(g_otherPlayers, pkt->data, pkt->size); - g_otherCount = num; -} - -void run_client(const char* ip, int port) { - net_init(); - - // Register client handlers - register_handler(PACKET_PLAYER_STATE, on_player_state); - - // Connect - g_sock = net_connect(ip, port); - if (g_sock == INVALID_SOCKET) { - printf("[CLIENT] Could not connect to server.\n"); - return; - } - printf("[CLIENT] Connected to server %s:%d\n", ip, port); - - // We'll assign an ID after reading from server in a real scenario, - // but for simplicity, let's guess the server will do so. - // We'll just store local ID = 999, or we wait for some handshake. - // For now, let's store a random number: - g_localPlayer.id = 9999; - g_localPlayer.x = 200; - g_localPlayer.y = 200; - - // Setup SDL - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - printf("SDL_Init failed: %s\n", SDL_GetError()); - return; - } - - SDL_Window* window = SDL_CreateWindow( - "MPGC Client", - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - 800, 600, - 0 - ); - if(!window) { - printf("SDL_CreateWindow failed: %s\n", SDL_GetError()); - SDL_Quit(); - return; - } - - SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0); - if(!renderer) { - printf("SDL_CreateRenderer failed: %s\n", SDL_GetError()); - SDL_DestroyWindow(window); - SDL_Quit(); - return; - } - - // Non-blocking so we can poll in main loop - set_socket_nonblocking(g_sock, 1); - - int running = 1; - while (running) { - // ---- Handle SDL events - SDL_Event e; - while(SDL_PollEvent(&e)) { - if(e.type == SDL_QUIT) { - running = 0; - } - } - - // ---- Keyboard input (WASD) - const Uint8* state = SDL_GetKeyboardState(NULL); - float dx = 0.f; - float dy = 0.f; - if(state[SDL_SCANCODE_W]) { dy -= 2.f; } - if(state[SDL_SCANCODE_S]) { dy += 2.f; } - if(state[SDL_SCANCODE_A]) { dx -= 2.f; } - if(state[SDL_SCANCODE_D]) { dx += 2.f; } - - if(dx != 0 || dy != 0) { - // Move local player - g_localPlayer.x += dx; - g_localPlayer.y += dy; - - // Send PACKET_PLAYER_MOVE - PlayerMove pm; - pm.id = g_localPlayer.id; - pm.dx = dx; - pm.dy = dy; - - Packet* movePkt = create_packet(PACKET_PLAYER_MOVE, &pm, sizeof(pm)); - char buffer[BUFFER_SIZE]; - int len = serialize_packet(movePkt, buffer); - send(g_sock, buffer, len, 0); - destroy_packet(movePkt); - } - - // ---- Check for incoming data - while (1) { - char netbuf[BUFFER_SIZE]; - int r = recv(g_sock, netbuf, BUFFER_SIZE, 0); - if (r <= 0) break; // no data or error - Packet* pkt = deserialize_packet(netbuf, r); - if(pkt) { - handle_packet(pkt, g_sock); - destroy_packet(pkt); - } - } - - // ---- Render scene - SDL_SetRenderDrawColor(renderer, 20, 20, 20, 255); - SDL_RenderClear(renderer); - - // Draw local player - SDL_Rect rLoc = { - (int)g_localPlayer.x, - (int)g_localPlayer.y, - 30, 30 - }; - SDL_SetRenderDrawColor(renderer, 200, 200, 50, 255); - SDL_RenderFillRect(renderer, &rLoc); - - // Draw other players - SDL_SetRenderDrawColor(renderer, 100, 200, 200, 255); - for(int i = 0; i < g_otherCount; i++) { - SDL_Rect rOth = { - (int)g_otherPlayers[i].x, - (int)g_otherPlayers[i].y, - 30, 30 - }; - SDL_RenderFillRect(renderer, &rOth); - } - SDL_RenderPresent(renderer); - } - - // Cleanup - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); - - closesocket(g_sock); - net_cleanup(); -} - -int main(int argc, char* argv[]) { - printf("[INFO] Starting...\n"); - SDL_SetMainReady(); - - if (argc < 3) { - printf("Usage:\n %s server \n %s client \n", argv[0], argv[0]); - return 1; - } - - - if(strcmp(argv[1], "server") == 0) { - int port = atoi(argv[2]); - run_server(port); - printf("[SERVER] Listening on port %d\n", port); - } else if(strcmp(argv[1], "client") == 0 && argc >= 4) { - const char* ip = argv[2]; - int port = atoi(argv[3]); - run_client(ip, port); - } else { - printf("Invalid arguments.\n"); - } - - return 0; -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..d405974 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include +#include "Server.h" +#include "Client.h" +#include "Utils.h" + +int main(int argc, char* argv[]) { + std::cout << "[Server] Starting Server on port:" << argv[2] << "" << std::endl; + + WSADATA wsaData; + if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0) { + std::cerr << "WSAStartup failed." << std::endl; + return EXIT_FAILURE; + } + + if(argc < 3) { + std::cout << "Usage:\n Server: " << argv[0] << " server \n Client: " << argv[0] << " client :\n"; + WSACleanup(); + return EXIT_FAILURE; + } + + std::string mode = argv[1]; + if(mode == "server") { + unsigned short port = static_cast(std::stoi(argv[2])); + Server server(port); + server.start(); + // Heartbeat thread: send a heartbeat with current timestamp every second. + std::thread heartbeat([&server](){ + while(true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + Packet heartbeat; + heartbeat.type = HEARTBEAT; + heartbeat.data = getCurrentTimestamp(); + server.broadcast(heartbeat); + } + }); + + + while(true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + heartbeat.join(); + } else if(mode == "client") { + + std::string ip_port = argv[2]; + size_t colonPos = ip_port.find(':'); + if(colonPos == std::string::npos) { + std::cerr << "Invalid client format. Use IP:port" << std::endl; + WSACleanup(); + return EXIT_FAILURE; + } + std::string ip = ip_port.substr(0, colonPos); + std::string port = ip_port.substr(colonPos + 1); + runClient(ip, port); + + } else { + + std::cerr << "Unknown mode: " << mode << std::endl; + WSACleanup(); + return EXIT_FAILURE; + + } + + WSACleanup(); + + return EXIT_SUCCESS; + +} diff --git a/src/net.c b/src/net.c deleted file mode 100644 index 2521139..0000000 --- a/src/net.c +++ /dev/null @@ -1,78 +0,0 @@ -#include "net.h" -#include -#include -#include -#include -#include - - -Packet* create_packet(PacketType type, const void* data, uint16_t size) { - Packet* pkt = malloc(sizeof(Packet) + size); - pkt->type = type; - pkt->size = size; - memcpy(pkt->data, data, size); - return pkt; -} - -int serialize_packet(Packet* pkt, char* buffer) { - buffer[0] = pkt->type; - buffer[1] = (pkt->size >> 8) & 0xFF; - buffer[2] = pkt->size & 0xFF; - memcpy(buffer + 3, pkt->data, pkt->size); - return 3 + pkt->size; -} - -Packet* deserialize_packet(const char* buffer, int len) { - if (len < 3) return NULL; - uint8_t type = buffer[0]; - uint16_t size = ((uint8_t)buffer[1] << 8) | (uint8_t)buffer[2]; - if (size > len - 3) return NULL; - return create_packet(type, buffer + 3, size); -} - -void destroy_packet(Packet* pkt) { - free(pkt); -} - -void net_init() { - WSADATA wsa; - WSAStartup(MAKEWORD(2, 2), &wsa); -} - -void net_cleanup() { - WSACleanup(); -} - -SOCKET net_connect(const char* ip, int port) { - SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_port = htons(port) - }; - inet_pton(AF_INET, ip, &addr.sin_addr); - connect(sock, (struct sockaddr*)&addr, sizeof(addr)); - return sock; -} - -SOCKET net_bind_and_listen(int port) { - SOCKET server = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_port = htons(port), - .sin_addr.s_addr = INADDR_ANY - }; - bind(server, (struct sockaddr*)&addr, sizeof(addr)); - listen(server, SOMAXCONN); - return server; -} - -SOCKET net_accept(SOCKET server) { - return accept(server, NULL, NULL); -} - - -void set_socket_nonblocking(SOCKET sock, int enable) -{ - u_long mode = (enable) ? 1 : 0; - ioctlsocket(sock, FIONBIO, &mode); -} diff --git a/src/net.h b/src/net.h deleted file mode 100644 index 75d51c8..0000000 --- a/src/net.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef NET_H -#define NET_H - -#include -#include - -#define MAX_CLIENTS 64 -#define BUFFER_SIZE 1024 - -typedef enum { - PACKET_PING = 1, - PACKET_TEXT, - PACKET_CLIENT_JOINED, - PACKET_CLIENT_LEFT, - PACKET_PLAYER_MOVE, - PACKET_PLAYER_STATE, - PACKET_MAX -} PacketType; - -typedef struct { - uint8_t type; - uint16_t size; - char data[]; -} Packet; - -Packet* create_packet(PacketType type, const void* data, uint16_t size); -int serialize_packet(Packet* pkt, char* buffer); -Packet* deserialize_packet(const char* buffer, int len); -void destroy_packet(Packet* pkt); - -// Networking setup -void net_init(); -void net_cleanup(); -SOCKET net_connect(const char* ip, int port); -SOCKET net_bind_and_listen(int port); -SOCKET net_accept(SOCKET server); -void set_socket_nonblocking(SOCKET sock, int enable); - - -#endif diff --git a/src/player.c b/src/player.c deleted file mode 100644 index e09c574..0000000 --- a/src/player.c +++ /dev/null @@ -1,2 +0,0 @@ -#include "player.h" - diff --git a/src/player.h b/src/player.h deleted file mode 100644 index f2bdd71..0000000 --- a/src/player.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef PLAYER_H -#define PLAYER_H - -#include - -typedef struct { - uint32_t id; - float x, y; -} Player; - -typedef struct { - uint32_t id; - float dx; - float dy; -} PlayerMove; - -typedef struct { - uint32_t id; - float x; - float y; -} PlayerState; - -#define MAX_PLAYERS 64 - -#endif diff --git a/test.py b/test.py new file mode 100644 index 0000000..0e09c7f --- /dev/null +++ b/test.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +import socket +import struct +import threading +import tkinter as tk +from tkinter import scrolledtext, messagebox +import queue +import sys + +PACKET_NAMES = { + 1: "HEARTBEAT", + 2: "JOIN", + 3: "DISCONNECT", + 4: "MOVE", + 5: "CHAT", + 6: "PLAYER_UPDATE", + 7: "SCORE_UPDATE", + 8: "GAME_START", + 9: "GAME_END", + 10: "PING", + 11: "PONG", + 12: "AUTH_REQUEST", + 13: "AUTH_RESPONSE", + 14: "PACKET_MAX" +} + + +class PacketClient: + def __init__(self, server_ip, server_port): + self.server_ip = server_ip + self.server_port = server_port + self.sock = None + self.running = False + self.recv_queue = queue.Queue() # For thread-safe packet passing + + def connect(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + self.sock.connect((self.server_ip, self.server_port)) + except Exception as e: + raise RuntimeError(f"Failed to connect to {self.server_ip}:{self.server_port}: {e}") + self.running = True + threading.Thread(target=self.receive_loop, daemon=True).start() + + def receive_loop(self): + while self.running: + try: + header = self._recv_all(4) + if header is None: + break + (payload_len,) = struct.unpack("!I", header) + payload = self._recv_all(payload_len) + if payload is None: + break + packet_type = payload[0] + data = payload[1:].decode("utf-8", errors="replace") + # Enqueue the packet for the GUI. + self.recv_queue.put((packet_type, data)) + except Exception as e: + self.recv_queue.put(("Error", str(e))) + break + self.running = False + + def _recv_all(self, n): + data = b"" + while len(data) < n: + try: + chunk = self.sock.recv(n - len(data)) + except Exception: + return None + if not chunk: + return None + data += chunk + return data + + def send_packet(self, packet_type, data): + data_bytes = data.encode("utf-8") + payload = struct.pack("!B", packet_type) + data_bytes + packet = struct.pack("!I", len(payload)) + payload + try: + self.sock.sendall(packet) + except Exception as e: + raise RuntimeError(f"Failed to send packet: {e}") + + def close(self): + self.running = False + if self.sock: + self.sock.close() + +# The GUI application. +class PacketClientGUI: + def __init__(self, master, client: PacketClient): + self.master = master + self.client = client + + master.title("Packet Client GUI") + + # Create a scrolling text widget for received packets. + self.received_text = scrolledtext.ScrolledText(master, width=80, height=20, state="disabled") + self.received_text.grid(row=0, column=0, columnspan=4, padx=10, pady=10) + + # Packet type label and entry. + tk.Label(master, text="Packet Type:").grid(row=1, column=0, sticky="e", padx=(10,2)) + self.type_entry = tk.Entry(master, width=5) + self.type_entry.grid(row=1, column=1, sticky="w") + self.type_entry.insert(0, "4") # Default to MOVE packet + + # Data label and entry. + tk.Label(master, text="Data:").grid(row=1, column=2, sticky="e", padx=(10,2)) + self.data_entry = tk.Entry(master, width=40) + self.data_entry.grid(row=1, column=3, sticky="w", padx=(0,10)) + + # Send button. + self.send_button = tk.Button(master, text="Send Packet", command=self.send_packet) + self.send_button.grid(row=2, column=0, columnspan=4, pady=(5,10)) + + # Start periodic GUI update. + self.master.after(100, self.process_recv_queue) + + def process_recv_queue(self): + while not self.client.recv_queue.empty(): + pkt_type, data = self.client.recv_queue.get() + if isinstance(pkt_type, int): + name = PACKET_NAMES.get(pkt_type, f"Unknown ({pkt_type})") + else: + name = pkt_type # For error messages + self.append_text(f"{name} > {data}\n") + self.master.after(100, self.process_recv_queue) + + def append_text(self, text): + self.received_text.config(state="normal") + self.received_text.insert(tk.END, text) + self.received_text.see(tk.END) + self.received_text.config(state="disabled") + + def send_packet(self): + try: + pkt_type = int(self.type_entry.get()) + except ValueError: + tk.messagebox.showerror("Error", "Packet type must be an integer") + return + data = self.data_entry.get() + try: + self.client.send_packet(pkt_type, data) + # Show the packet sent, using the name if available. + pkt_name = PACKET_NAMES.get(pkt_type, f"Unknown ({pkt_type})") + self.append_text(f"Sent Packet | Type: {pkt_name} | Data: {data}\n") + except Exception as e: + tk.messagebox.showerror("Error", str(e)) + +def main(): + if len(sys.argv) < 3: + print("Usage: python packet_client_gui.py ") + sys.exit(1) + server_ip = sys.argv[1] + server_port = int(sys.argv[2]) + + client = PacketClient(server_ip, server_port) + try: + client.connect() + except Exception as e: + print(e) + sys.exit(1) + + root = tk.Tk() + app = PacketClientGUI(root, client) + try: + root.mainloop() + finally: + client.close() + +if __name__ == "__main__": + main()