+#define _GNU_SOURCE // don't worry, GNU extensions are only used when available
+#include <dragonstd/flag.h>
#include <stdio.h>
-#include <string.h>
#include <stdlib.h>
-#include <arpa/inet.h>
+#include <string.h>
+#include <pthread.h>
#include <unistd.h>
-#include <errno.h>
-#include <netdb.h>
#include "client/client.h"
-#include "client/client_map.h"
+#include "client/client_auth.h"
+#include "client/client_inventory.h"
+#include "client/client_node.h"
#include "client/client_player.h"
+#include "client/client_terrain.h"
+#include "client/debug_menu.h"
#include "client/game.h"
#include "client/input.h"
-#include "signal_handlers.h"
-#include "util.h"
+#include "day.h"
+#include "interrupt.h"
+#include "perlin.h"
+#include "types.h"
-Client client;
+DragonnetPeer *client;
-void client_disconnect(bool send, const char *detail)
+static Flag finish;
+static Flag gfx_init;
+
+static bool on_recv(__attribute__((unused)) DragonnetPeer *peer, DragonnetTypeId type, __attribute__((unused)) void *pkt)
{
- pthread_mutex_lock(&client.mtx);
- if (client.state != CS_DISCONNECTED) {
- if (send)
- write_u32(client.fd, SC_DISCONNECT);
-
- client.state = CS_DISCONNECTED;
- printf("Disconnected %s%s%s\n", INBRACES(detail));
- close(client.fd);
+ bool allowed = false;
+ pthread_mutex_lock(&client_auth.mtx);
+
+ // this code exists to stop malicious or malfunctioning packets
+ switch (client_auth.state) {
+ // the server shouldn't send anything during auth preparation, drop it
+ case AUTH_INIT:
+ allowed = false;
+ break;
+
+ // only the auth packet is allowed before auth is finished
+ case AUTH_WAIT:
+ allowed = type == DRAGONNET_TYPE_ToClientAuth;
+ break;
+
+ // don't process auth packets when auth is already finished
+ case AUTH_SUCCESS:
+ allowed = type != DRAGONNET_TYPE_ToClientAuth;
+ break;
}
- pthread_mutex_unlock(&client.mtx);
+
+ /*
+ It is important that the auth state does not change to until the packet is
+ processed.
+
+ However, the only state change done by other threads is AUTH_INIT -> AUTH_WAIT,
+ which is not problematic since packets that are received during AUTH_INIT
+ are not processed, they are always dropped.
+
+ Therefore the mutex can be unlocked at this point.
+ */
+ pthread_mutex_unlock(&client_auth.mtx);
+ return allowed;
}
-void client_send_position(v3f pos)
+static void on_disconnect(__attribute__((unused)) DragonnetPeer *peer)
{
- pthread_mutex_lock(&client.mtx);
- (void) (write_u32(client.fd, SC_POS) && write_v3f32(client.fd, pos));
- pthread_mutex_unlock(&client.mtx);
+ flag_set(&interrupt);
+ // don't free the connection before all other client components have shut down
+ flag_slp(&finish);
}
-#include "network.c"
-
-static void *reciever_thread(__attribute__((unused)) void *unused)
+static void on_ToClientAuth(__attribute__((unused)) DragonnetPeer *peer, ToClientAuth *pkt)
{
- handle_packets(&client);
-
- if (errno != EINTR)
- client_disconnect(false, "network error");
+ pthread_mutex_lock(&client_auth.mtx);
+ if (pkt->success) {
+ client_auth.state = AUTH_SUCCESS;
+ printf("[access] authenticated successfully\n");
+ } else {
+ client_auth.state = AUTH_INIT;
+ printf("[access] authentication failed, please try again\n");
+ }
+ pthread_cond_signal(&client_auth.cv);
+ pthread_mutex_unlock(&client_auth.mtx);
- return NULL;
+ // yield the connection until the game is fully initialized
+ if (pkt->success)
+ flag_slp(&gfx_init);
}
-static bool client_name_prompt()
+static void on_ToClientInfo(__attribute__((unused)) DragonnetPeer *peer, ToClientInfo *pkt)
{
- printf("Enter name: ");
- fflush(stdout);
- char name[PLAYER_NAME_MAX];
- if (scanf("%s", name) == EOF)
- return false;
- client.name = strdup(name);
- pthread_mutex_lock(&client.mtx);
- if (write_u32(client.fd, SC_AUTH) && write(client.fd, client.name, strlen(client.name) + 1)) {
- client.state = CS_AUTH;
- printf("Authenticating...\n");
- }
- pthread_mutex_unlock(&client.mtx);
- return true;
+ client_terrain_set_load_distance(pkt->load_distance);
+ seed = pkt->seed;
}
-static bool client_authenticate()
+static void on_ToClientTimeOfDay(__attribute__((unused)) DragonnetPeer *peer, ToClientTimeOfDay *pkt)
{
- for ever {
- switch (client.state) {
- case CS_CREATED:
- if (client_name_prompt())
- break;
- else
- return false;
- case CS_AUTH:
- if (interrupted)
- return false;
- else
- sched_yield();
- break;
- case CS_ACTIVE:
- return true;
- case CS_DISCONNECTED:
- return false;
- }
- }
- return false;
+ set_time_of_day(pkt->time_of_day);
}
-static void client_start(int fd)
+static void on_ToClientMovement(__attribute__((unused)) DragonnetPeer *peer, ToClientMovement *pkt)
{
- client.fd = fd;
- pthread_mutex_init(&client.mtx, NULL);
- client.state = CS_CREATED;
- client.name = NULL;
- client.map = map_create();
-
- client_player_init(client.map);
- client_map_init(client.map);
-
- pthread_t recv_thread;
- pthread_create(&recv_thread, NULL, &reciever_thread, NULL);
-
- if (client_authenticate())
- game(&client);
+ pthread_rwlock_wrlock(&client_player.lock_movement);
+ client_player.movement = *pkt;
+ pthread_rwlock_unlock(&client_player.lock_movement);
- if (client.state != CS_DISCONNECTED)
- client_disconnect(true, NULL);
-
- if (client.name)
- free(client.name);
-
- client_map_deinit();
-
- map_delete(client.map);
-
- pthread_mutex_destroy(&client.mtx);
+ debug_menu_changed(ENTRY_FLIGHT);
+ debug_menu_changed(ENTRY_COLLISION);
}
int main(int argc, char **argv)
{
- program_name = argv[0];
-
- if (argc < 3)
- internal_error("missing address or port");
-
- struct addrinfo hints = {
- .ai_family = AF_UNSPEC,
- .ai_socktype = SOCK_STREAM,
- .ai_protocol = 0,
- .ai_flags = AI_NUMERICSERV,
- };
-
- struct addrinfo *info = NULL;
+#ifdef __GLIBC__ // check whether bloat is enabled
+ pthread_setname_np(pthread_self(), "main");
+#endif // __GLIBC__
- int gai_state = getaddrinfo(argv[1], argv[2], &hints, &info);
-
- if (gai_state != 0)
- internal_error(gai_strerror(gai_state));
-
- int fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
-
- if (fd == -1)
- syscall_error("socket");
-
- if (connect(fd, info->ai_addr, info->ai_addrlen) == -1)
- syscall_error("connect");
-
- char *addrstr = address_string((struct sockaddr_in6 *) info->ai_addr);
- printf("Connected to %s\n", addrstr);
- free(addrstr);
+ if (argc < 2) {
+ fprintf(stderr, "[error] missing address\n");
+ return EXIT_FAILURE;
+ }
- freeaddrinfo(info);
+ if (!(client = dragonnet_connect(argv[1]))) {
+ fprintf(stderr, "[error] failed to connect to server\n");
+ return EXIT_FAILURE;
+ }
- signal_handlers_init();
- client_start(fd);
+ printf("[access] connected to %s\n", client->address);
+
+ client->on_disconnect = &on_disconnect;
+ client->on_recv = (void *) &on_recv;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientAuth ] = (void *) &on_ToClientAuth;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientChunk ] = (void *) &client_terrain_receive_chunk;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientInfo ] = (void *) &on_ToClientInfo;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientTimeOfDay ] = (void *) &on_ToClientTimeOfDay;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientMovement ] = (void *) &on_ToClientMovement;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityAdd ] = (void *) &client_entity_add;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityRemove ] = (void *) &client_entity_remove;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityUpdatePosRot ] = (void *) &client_entity_update_pos_rot;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientEntityUpdateNametag] = (void *) &client_entity_update_nametag;
+ client->on_recv_type[DRAGONNET_TYPE_ToClientPlayerInventory ] = (void *) &client_inventory_update_player;
+
+ flag_ini(&finish);
+ flag_ini(&gfx_init);
+
+ interrupt_init();
+ client_terrain_init();
+ client_player_init();
+ client_entity_init();
+ dragonnet_peer_run(client);
+
+ if (!client_auth_init())
+ return EXIT_FAILURE;
+
+ if (!game(&gfx_init))
+ return EXIT_FAILURE;
+
+ dragonnet_peer_shutdown(client);
+ client_auth_deinit();
+ client_entity_deinit();
+ client_player_deinit();
+ client_terrain_deinit();
+ interrupt_deinit();
+
+ pthread_t recv_thread = client->recv_thread;
+
+ flag_set(&finish);
+ pthread_join(recv_thread, NULL);
+
+ flag_dst(&finish);
+ flag_dst(&gfx_init);
return EXIT_SUCCESS;
}