diff --git a/API b/API index 25b008f..4c80744 100755 Binary files a/API and b/API differ diff --git a/include/server_structs.h b/include/server_structs.h index e3d13b8..a22eefd 100644 --- a/include/server_structs.h +++ b/include/server_structs.h @@ -190,6 +190,8 @@ typedef struct api_packet { u_int8_t tracker_id; u_int8_t connected_players; + u_int8_t cars_colided[2]; // IDs of cars involved in a collision event IF [1] == 255, [0] is the only car involved (environment collision) + carAtributesAPI cars[64]; trackAtributesAPI track_info; @@ -197,23 +199,24 @@ typedef struct api_packet { // Telemetry packet structure (lightweight for streaming) struct telemetry_packet { - u_int8_t server_id; - u_int8_t car_count; - - struct car_telemetry { - u_int8_t carID; - char driver_name[64]; - char driver_guid[64]; - char car_model[64]; - float normalizedSplinePos; - float speed_kmh; - u_int8_t gear; - u_int16_t rpm; - u_int32_t last_lap_time; - u_int32_t best_lap_time; - u_int16_t current_lap; - u_int8_t position; - } __attribute__((packed)) cars[64]; + u_int8_t server_id; + u_int8_t car_count; + + struct car_telemetry { + u_int8_t carID; + char driver_name[64]; + char driver_guid[64]; + char car_model[64]; + postion position; + float normalizedSplinePos; + float speed_kmh; + u_int8_t gear; + u_int16_t rpm; + u_int32_t last_lap_time; + u_int32_t best_lap_time; + u_int16_t current_lap; + u_int8_t position_rank; // RENAME: position -> position_rank to avoid conflict + } __attribute__((packed)) cars[64]; } __attribute__((packed)); enum ACSP_MessageType { diff --git a/include/telemetry.cpp b/include/telemetry.cpp index bc0e59a..3c4b68b 100644 --- a/include/telemetry.cpp +++ b/include/telemetry.cpp @@ -5,115 +5,156 @@ std::vector telemetry_clients; pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER; +// Helper struct for sorting cars by position +struct CarPosition { + int index; + u_int16_t laps; + float splinePos; + + bool operator<(const CarPosition &other) const { + // First compare by laps (more laps = better position) + if (laps != other.laps) { + return laps > other.laps; + } + // If same laps, compare by spline position (further along = better) + return splinePos > other.splinePos; + } +}; + // Broadcast telemetry to all connected clients -void broadcast_telemetry(const api_packet& packet) { - telemetry_packet telem; - telem.server_id = packet.tracker_id; - telem.car_count = 0; - - // Convert api_packet to telemetry_packet - for (int i = 0; i < MAX_PLAYERS; i++) { - if (packet.cars[i].isConnected) { - auto& car = telem.cars[telem.car_count]; - - car.carID = packet.cars[i].carID; - strncpy(car.driver_name, packet.cars[i].driver_name, sizeof(car.driver_name) - 1); - car.driver_name[sizeof(car.driver_name) - 1] = '\0'; - strncpy(car.driver_guid, packet.cars[i].driver_GUID, sizeof(car.driver_guid) - 1); - car.driver_guid[sizeof(car.driver_guid) - 1] = '\0'; - strncpy(car.car_model, packet.cars[i].car_model, sizeof(car.car_model) - 1); - car.car_model[sizeof(car.car_model) - 1] = '\0'; - car.normalizedSplinePos = packet.cars[i].normalizedSplinePos; - - // Calculate speed from velocity vector (m/s to km/h) - float speed_ms = std::sqrt( - std::pow(packet.cars[i].velocity.x, 2.0f) + - std::pow(packet.cars[i].velocity.y, 2.0f) + - std::pow(packet.cars[i].velocity.z, 2.0f) - ); - car.speed_kmh = speed_ms * 3.6f; - - car.gear = packet.cars[i].carGear; - car.rpm = packet.cars[i].carRPM; - car.last_lap_time = packet.cars[i].lap_time; - car.best_lap_time = 0; // TODO: Track best lap - car.current_lap = packet.cars[i].total_laps_completed; - car.position = telem.car_count + 1; // Temporary position - - telem.car_count++; - } - } - - // Broadcast to all connected clients - pthread_mutex_lock(&clients_mutex); - - for (auto it = telemetry_clients.begin(); it != telemetry_clients.end();) { - ssize_t sent = send(*it, &telem, sizeof(telem), MSG_NOSIGNAL); - - if (sent < 0) { - // Client disconnected, remove from list - printf("[T] Telemetry client %d disconnected\n", *it); - close(*it); - it = telemetry_clients.erase(it); - } else { - ++it; - } - } - - pthread_mutex_unlock(&clients_mutex); +void broadcast_telemetry(const api_packet &packet) { + telemetry_packet telem; + telem.server_id = packet.tracker_id; + telem.car_count = 0; + + // Temporary storage for connected cars + std::vector positions; + std::vector carIndices; // Maps telem index to original packet index + + // First pass: collect all connected cars + for (int i = 0; i < MAX_PLAYERS; i++) { + if (packet.cars[i].isConnected) { + CarPosition pos; + pos.index = telem.car_count; + pos.laps = packet.cars[i].total_laps_completed; + pos.splinePos = packet.cars[i].normalizedSplinePos; + + positions.push_back(pos); + carIndices.push_back(i); + + auto &car = telem.cars[telem.car_count]; + + car.carID = packet.cars[i].carID; + strncpy(car.driver_name, packet.cars[i].driver_name, sizeof(car.driver_name) - 1); + car.driver_name[sizeof(car.driver_name) - 1] = '\0'; + strncpy(car.driver_guid, packet.cars[i].driver_GUID, sizeof(car.driver_guid) - 1); + car.driver_guid[sizeof(car.driver_guid) - 1] = '\0'; + strncpy(car.car_model, packet.cars[i].car_model, sizeof(car.car_model) - 1); + car.car_model[sizeof(car.car_model) - 1] = '\0'; + + car.position.x = packet.cars[i].position.x; + car.position.y = packet.cars[i].position.y; + car.position.z = packet.cars[i].position.z; + + car.normalizedSplinePos = packet.cars[i].normalizedSplinePos; + + // Calculate speed from velocity vector (m/s to km/h) + float speed_ms = std::sqrt(std::pow(packet.cars[i].velocity.x, 2.0f) + std::pow(packet.cars[i].velocity.y, 2.0f) + std::pow(packet.cars[i].velocity.z, 2.0f)); + car.speed_kmh = speed_ms * 3.6f; + + car.gear = packet.cars[i].carGear; + car.rpm = packet.cars[i].carRPM; + car.last_lap_time = packet.cars[i].lap_time; + car.best_lap_time = 0; // TODO: Track best lap across session + car.current_lap = packet.cars[i].total_laps_completed; + + telem.car_count++; + } + } + + // Sort cars by position + std::sort(positions.begin(), positions.end()); + + // Assign positions (1-indexed) + for (size_t i = 0; i < positions.size(); i++) { + telem.cars[positions[i].index].position_rank = i + 1; + } + + // Broadcast to all connected clients + pthread_mutex_lock(&clients_mutex); + + // Calculate actual packet size (header + only connected cars) + size_t header_size = 2; // server_id + car_count + size_t car_size = sizeof(telemetry_packet::car_telemetry); + size_t packet_size = header_size + (telem.car_count * car_size); + + for (auto it = telemetry_clients.begin(); it != telemetry_clients.end();) { + ssize_t sent = send(*it, &telem, packet_size, MSG_NOSIGNAL); + + if (sent < 0) { + // Client disconnected, remove from list + printf("[T] Telemetry client %d disconnected\n", *it); + close(*it); + it = telemetry_clients.erase(it); + } else { + ++it; + } + } + + pthread_mutex_unlock(&clients_mutex); } // Thread to accept telemetry client connections -void* telemetry_server_thread(void* arg) { - (void)arg; - - int server_fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (server_fd < 0) { - perror("[!] Telemetry socket creation failed"); - return NULL; - } - - unlink(TELEMETRY_SOCKET_PATH); - - struct sockaddr_un addr; - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, TELEMETRY_SOCKET_PATH, sizeof(addr.sun_path) - 1); - addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; - - if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - perror("[!] Telemetry socket bind failed"); - close(server_fd); - return NULL; - } - - if (listen(server_fd, 5) < 0) { - perror("[!] Telemetry socket listen failed"); - close(server_fd); - return NULL; - } - - printf("[+] Telemetry server listening on %s\n", TELEMETRY_SOCKET_PATH); - - while (1) { - int client_fd = accept(server_fd, NULL, NULL); - if (client_fd < 0) { - perror("[!] Telemetry accept failed"); - continue; - } - - pthread_mutex_lock(&clients_mutex); - - if (telemetry_clients.size() < (size_t)MAX_TELEMETRY_CLIENTS) { - telemetry_clients.push_back(client_fd); - printf("[+] Telemetry client connected (total: %zu)\n", telemetry_clients.size()); - } else { - printf("[!] Max telemetry clients reached, rejecting connection\n"); - close(client_fd); - } - - pthread_mutex_unlock(&clients_mutex); - } - - close(server_fd); - return NULL; +void *telemetry_server_thread(void *arg) { + (void)arg; + + int server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_fd < 0) { + perror("[!] Telemetry socket creation failed"); + return NULL; + } + + unlink(TELEMETRY_SOCKET_PATH); + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, TELEMETRY_SOCKET_PATH, sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + + if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("[!] Telemetry socket bind failed"); + close(server_fd); + return NULL; + } + + if (listen(server_fd, 5) < 0) { + perror("[!] Telemetry socket listen failed"); + close(server_fd); + return NULL; + } + + printf("[+] Telemetry server listening on %s\n", TELEMETRY_SOCKET_PATH); + + while (1) { + int client_fd = accept(server_fd, NULL, NULL); + if (client_fd < 0) { + perror("[!] Telemetry accept failed"); + continue; + } + + pthread_mutex_lock(&clients_mutex); + + if (telemetry_clients.size() < (size_t)MAX_TELEMETRY_CLIENTS) { + telemetry_clients.push_back(client_fd); + printf("[+] Telemetry client connected (total: %zu)\n", telemetry_clients.size()); + } else { + printf("[!] Max telemetry clients reached, rejecting connection\n"); + close(client_fd); + } + + pthread_mutex_unlock(&clients_mutex); + } + + close(server_fd); + return NULL; } diff --git a/source/main.cpp b/source/main.cpp index 3593cfc..ca3cd42 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -65,7 +65,190 @@ void *db_write_thread(void *arg) { continue; // Skip database write for handshake packets } - broadcast_telemetry(packet); + // Check if it was a crash report + if (packet.message_type == ACSP_CE_COLLISION_WITH_ENV) { + printf("[!] Crash report packet received for server \"%s\" (%d), skipping database write.\n", packet.track_info.server_name, packet.tracker_id); + + printf("[!] Single car collision detected. Car ID: %d\n", packet.cars_colided[0]); + + // Get current driver_rank for the car + const char *get_rank_query = "SELECT user_rank FROM users WHERE driver_guid = $1;"; + + const char *driver_guid = packet.cars[packet.cars_colided[0]].driver_GUID; + const char *get_rank_paramValues[1] = {driver_guid}; + + PGresult *rank_res = PQexecParams(conn, get_rank_query, 1, nullptr, get_rank_paramValues, nullptr, nullptr, 0); + if (PQresultStatus(rank_res) != PGRES_TUPLES_OK) { + printf("[!] Failed to retrieve driver rank for GUID %s: %s", driver_guid, PQerrorMessage(conn)); + } else { + + // Get driver contacts_alltime + const char *get_contacts_query = "SELECT contacts_alltime FROM users WHERE driver_guid = $1;"; + PGresult *contacts_res = PQexecParams(conn, get_contacts_query, 1, nullptr, get_rank_paramValues, nullptr, nullptr, 0); + int contacts_alltime = 0; + if (PQresultStatus(contacts_res) == PGRES_TUPLES_OK) { + if (PQntuples(contacts_res) > 0) { + char *contacts_str = PQgetvalue(contacts_res, 0, 0); + contacts_alltime = atoi(contacts_str); + printf("[*] Driver contacts_alltime for GUID %s is %d\n", driver_guid, contacts_alltime); + } + } else { + printf("[!] Failed to retrieve contacts_alltime for GUID %s: %s", driver_guid, PQerrorMessage(conn)); + } + PQclear(contacts_res); + + if (PQntuples(rank_res) > 0) { + char *rank_str = PQgetvalue(rank_res, 0, 0); + int driver_rank = atoi(rank_str); + printf("[*] Driver rank for GUID %s is %d\n", driver_guid, driver_rank); + + driver_rank -= 10; + if (driver_rank < 0) + driver_rank = 0; + + // Update driver_rank in the database + const char *update_rank_query = "UPDATE users SET user_rank = $1, contacts_alltime = $2 WHERE driver_guid = $3;"; + std::string new_rank_s = std::to_string(driver_rank); + std::string contacts_alltime_s = std::to_string(contacts_alltime + 1); + const char *update_rank_paramValues[3] = {new_rank_s.c_str(), contacts_alltime_s.c_str(), driver_guid}; + + PGresult *update_rank_res = PQexecParams(conn, update_rank_query, 3, nullptr, update_rank_paramValues, nullptr, nullptr, 0); + if (PQresultStatus(update_rank_res) != PGRES_COMMAND_OK) { + printf("[!] Failed to update driver rank for GUID %s: %s", driver_guid, PQerrorMessage(conn)); + } else { + printf("[*] Driver rank for GUID %s updated to %d\n", driver_guid, driver_rank); + } + PQclear(update_rank_res); + } else { + printf("[!] No driver found with GUID %s to retrieve rank.\n", driver_guid); + } + } + PQclear(rank_res); + continue; // Skip database write for crash report api_packet_storages + } + + // Check if it was a crash report + if (packet.message_type == ACSP_CE_COLLISION_WITH_CAR) { + printf("[!] Crash report packet received for server \"%s\" (%d), skipping database write.\n", packet.track_info.server_name, packet.tracker_id); + + printf("[!] Multi-car collision detected. Car IDs: %d and %d\n", packet.cars_colided[0], packet.cars_colided[1]); + + for (int i = 0; i < 2; i++) { + u_int8_t car_id = packet.cars_colided[i]; + const char *driver_guid = packet.cars[car_id].driver_GUID; + + // Get current driver_rank for the car + const char *get_rank_query = "SELECT user_rank FROM users WHERE driver_guid = $1;"; + + const char *get_rank_paramValues[1] = {driver_guid}; + + PGresult *rank_res = PQexecParams(conn, get_rank_query, 1, nullptr, get_rank_paramValues, nullptr, nullptr, 0); + if (PQresultStatus(rank_res) != PGRES_TUPLES_OK) { + printf("[!] Failed to retrieve driver rank for GUID %s: %s", driver_guid, PQerrorMessage(conn)); + } else { + if (PQntuples(rank_res) > 0) { + char *rank_str = PQgetvalue(rank_res, 0, 0); + int driver_rank = atoi(rank_str); + printf("[*] Driver rank for GUID %s is %d\n", driver_guid, driver_rank); + + driver_rank -= 5; + if (driver_rank < 0) + driver_rank = 0; + + // Update driver_rank in the database + const char *update_rank_query = "UPDATE user SET driver_rank = $1 WHERE driver_guid = $2;"; + std::string new_rank_s = std::to_string(driver_rank); + const char *update_rank_paramValues[2] = {new_rank_s.c_str(), driver_guid}; + + PGresult *update_rank_res = PQexecParams(conn, update_rank_query, 2, nullptr, update_rank_paramValues, nullptr, nullptr, 0); + if (PQresultStatus(update_rank_res) != PGRES_COMMAND_OK) { + printf("[!] Failed to update driver rank for GUID %s: %s", driver_guid, PQerrorMessage(conn)); + } else { + printf("[*] Driver rank for GUID %s updated to %d\n", driver_guid, driver_rank); + } + PQclear(update_rank_res); + } else { + printf("[!] No driver found with GUID %s to retrieve rank.\n", driver_guid); + } + } + PQclear(rank_res); + } + continue; // Skip database write for crash report api_packet_storages + } + + if (packet.message_type == ACSP_LAP_COMPLETED) { + printf("[+] Lap completed by (%d).\n", packet.cars_colided[0]); + + const char *get_rank_query = "UPDATE users SET user_rank = user_rank + 20 WHERE driver_guid = $1;"; + + const char *driver_guid = packet.cars[packet.cars_colided[0]].driver_GUID; + const char *get_rank_paramValues[1] = {driver_guid}; + + PGresult *rank_res = PQexecParams(conn, get_rank_query, 1, nullptr, get_rank_paramValues, nullptr, nullptr, 0); + if (PQresultStatus(rank_res) != PGRES_TUPLES_OK) { + printf("[!] Failed to UPDATE driver Rank for GUID %s: %s", driver_guid, PQerrorMessage(conn)); + } else { + printf("[+] Increase driver rank +20\n"); + } + + } + + + // INFO: Deprecated because packet.car[ID].total_laps_completed was already being updated by the parser alone + // TAG:4 Might be useful in future for other purposes + /* if (packet.message_type == ACSP_LAP_COMPLETED) { + u_int8_t car_id = packet.cars_colided[0]; // In this case, cars_colided[0] holds the carID that completed the lap + const char *driver_guid = packet.cars[car_id].driver_GUID; + + // Update laps_completed in the database + const char *update_laps_query = "UPDATE users SET laps_completed = laps_completed + 1, user_rank = user_rank + 25 WHERE driver_guid = $1;"; + const char *update_laps_paramValues[1] = {driver_guid}; + + PGresult *update_laps_res = PQexecParams(conn, update_laps_query, 1, nullptr, update_laps_paramValues, nullptr, nullptr, 0); + if (PQresultStatus(update_laps_res) != PGRES_COMMAND_OK) { + printf("[!] Failed to update laps completed for GUID %s: %s", driver_guid, PQerrorMessage(conn)); + } else { + printf("[*] Laps completed updated for GUID %s\n", driver_guid); + } + PQclear(update_laps_res); + + continue; // Skip database write for lap completed api_packet_storages + } */ + + /* + // DEBUG: OUTPUT ALL PACKET DATA + printf("[D] API Packet Data for Server \"%s\" (ID: %d):\n", packet.track_info.server_name, packet.tracker_id); + printf(" Session Type: %d\n", packet.track_info.session_type); + printf(" Session Count: %d\n", packet.track_info.session_count); + printf(" Track: %s\n", packet.track_info.track); + printf(" Config: %s\n", packet.track_info.track_config); + printf(" Weather/Graphics: %s\n", packet.track_info.weather_graphics); + printf(" Time: %d\n", packet.track_info.time); + printf(" Laps: %d\n", packet.track_info.laps); + printf(" Wait Time: %d\n", packet.track_info.wait_time); + printf(" Ambient Temp: %d\n", packet.track_info.ambient_temp); + printf(" Road Temp: %d\n", packet.track_info.road_temp); + printf(" Elapsed Time (ms): %d\n", packet.track_info.elapsed_ms); + printf(" Connected Players: %d\n", packet.connected_players); + for (int i = 0; i < MAX_PLAYERS; i++) { + if (packet.cars[i].isConnected) { + printf(" [D] Car ID: %d\n", packet.cars[i].carID); + printf(" Driver GUID: %s\n", packet.cars[i].driver_GUID); + printf(" Driver Name: %s\n", packet.cars[i].driver_name); + printf(" Driver Team: %s\n", packet.cars[i].driver_team); + printf(" Car Model: %s\n", packet.cars[i].car_model); + printf(" Car Skin: %s\n", packet.cars[i].car_skin); + printf(" Total Cuts Alltime: %d\n", packet.cars[i].total_cuts_alltime); + printf(" Total Contacts: %d\n", packet.cars[i].total_contacts); + printf(" Total Laps Completed: %d\n", packet.cars[i].total_laps_completed); + printf(" Is Connected: %d\n", packet.cars[i].isConnected); + printf(" Is Loading: %d\n", packet.cars[i].isLoading); + } + } + + */ + + broadcast_telemetry(packet); // DEBUG printf("[W] Writing packet for Server with tracker ID: %d to database.\tQueue: %u/%d\n", packet.tracker_id, (unsigned int)api_queue.size(), STACK_SIZE); @@ -193,6 +376,7 @@ void *db_write_thread(void *arg) { printf("[D] Processing user \(%d\): GUID=%s, Name=%s, Team=%s Car Model=%s, Skin=%s, Cuts=%s, Contacts=%s, Laps=%s, IsConnect=%s, IsLoading=%s, CurrentServer=%s\n", i, driver_guid, driver_name, driver_team, car_model, car_skin, cuts_alltime_s.c_str(), contacts_alltime_s.c_str(), laps_completed_s.c_str(), is_connect_s.c_str(), is_loading_s.c_str(), current_server_s.c_str()); + printf("[D] Player Position: (X: %.2f, Y: %.2f, Z: %.2f)\n", packet.cars[car_id].position.x, packet.cars[car_id].position.y, packet.cars[car_id].position.z); const char *user_paramValues[11] = {driver_guid, driver_name, @@ -219,7 +403,6 @@ void *db_write_thread(void *arg) { } else { usleep(1000); // Sleep for 1ms if no packets are available } - } PQfinish(conn); @@ -296,12 +479,12 @@ int main(void) { // detach the thread so it cleans up after itself pthread_detach(db_thread); - pthread_t telemetry_thread; - if (pthread_create(&telemetry_thread, NULL, telemetry_server_thread, NULL) != 0) { - perror("[!] Failed to create telemetry server thread"); - return EXIT_FAILURE; - } - pthread_detach(telemetry_thread); + pthread_t telemetry_thread; + if (pthread_create(&telemetry_thread, NULL, telemetry_server_thread, NULL) != 0) { + perror("[!] Failed to create telemetry server thread"); + return EXIT_FAILURE; + } + pthread_detach(telemetry_thread); printf("[+] Server listening on %s\n", SERVER_SOCKET_PATH);